Как создавать AI-агентов в 2026 году (полный курс)
Полный русский перевод статьи Avid о том, как строить production-ready AI-агентов в 2026 году на agentic-harness.
Оригинал: пост на X. Автор: Avid.
вот правда, которую никто не рассказывает создателям ai.
большинство из них создают демо
всё, что вам нужно построить, — это
ai-агент production-level
TLDR; если не хотите читать, дайте эту ссылку своему агенту и задавайте ему вопросы: ➡️https://github.com/codejunkie99/agentic-harness
вот твит, с которого всё началось
проблема в том, что у большинства AI-инженеров нет ясного представления, что именно строить, когда они решают всерьёз заняться агентами.
кто-то хватается за LangChain, потому что multi-agent демо аккуратно выглядят на YouTube, и следующие две недели воюет с Python interop и несовпадениями async runtime, прежде чем выбросить всё целиком.
кто-то пытается с нуля построить собственный orchestration layer: loop, session store, context assembler — и так и не доделывает самого агента, потому что инфраструктура съела весь таймлайн.
другие копируют hello-world пример с webhook, получают JSON-ответ, решают, что поняли систему, и выкатывают то, что ломается при первом же случае, когда сессия длится больше десяти минут, remote sandbox падает посреди задачи или context window заполняется без настроенной compaction.
результат обычно один и тот же: много plumbing, никаких production-агентов и никакой ментальной модели того, как на самом деле выглядит production agent runtime.
если ваша цель — создавать и выпускать настоящих агентов в 2026 году, вам не нужно изучать шесть фреймворков.
вам нужно понять один runtime достаточно глубоко, чтобы владеть production-агентом от handler до deployment.
это значит научиться:
- связывать three-layer architecture так, чтобы ваша handler logic переживала замены provider и target без изменений agent code
- правильно использовать sessions и tasks, чтобы долгие jobs не загрязняли собственный context
- писать roles и skills, которые формируют поведение модели без перекомпиляции
- настраивать compaction так, чтобы sessions, работающие два часа, не начинали галлюцинировать уже на первом часу
- направлять HttpSessionEnv на remote sandboxes, чтобы binary запускался локально, а execution выполнялся на Linux
- выбирать правильный build target: native, node или Cloudflare — без переписывания agent logic между ними
- генерировать connectors вместо ручного написания adapters и понимать, почему это различие важно под реальной нагрузкой
этот гайд — полноценный технический walkthrough, построенный на реальной codebase agentic-harness, шести неделях создания и ломания настоящих агентов на нём и failure modes, отладка которых отнимает больше всего времени.
материал на 4,000+ WORDS и напрямую опирается на repo и docs — не на пересказы из вторых рук и не на demo-level примеры.
но его настоящая ценность в том, что в каждом разделе есть рабочий code snippet, понятное объяснение, почему было принято такое решение, и точный failure mode, с которым вы столкнётесь, если это пропустите.
так что к моменту, когда вы дочитаете, вы сможете владеть production-агентом end to end — от первого handler до sandbox и CI job, которая запускает его без присмотра.
на выстраивание этого понимания ушло больше 6 WEEKS ежедневной работы с codebase, в основном на отладку вещей, которые выглядели правильно, пока не ломались в реальных условиях.
теперь давайте к делу. ⬇️
Форма проекта
две crates. один binary. каждый execution target — это выбор config, а не rewrite.
- SDK — это library, которую вы подключаете в любой rust project. CLI оборачивает её. ваш agent — это rust binary, который начинается с use agentic_harness::prelude::*.
- cargo build — это весь pipeline. без bundler. без transpile step. без language runtime на target machine. один self-contained executable плюс manifest.json.
constraint дизайна, который определил всё: один и тот же agent binary должен запускаться на вашем laptop в interactive mode, в GitHub Actions job, клонирующей свежий repo, против remote E2B sandbox по HTTP и на границе Cloudflare Worker — без изменения ни одной строки agent logic между ними.
каждое решение в этой codebase существует, чтобы соблюсти этот constraint.
3 слоя и зачем каждый существует
ментальная модель — три концентрических кольца. понимание того, где проходит каждая граница, сэкономит вам больше времени на отладке, чем что-либо ещё в этом гайде.
ваш rust code — внешнее кольцо.
- вы пишете handlers. handlers получают AgentContext. они вызывают sessions. sessions вызывают модель, читают files, пишут files, запускают shell commands, порождают tasks, подключаются к MCP servers.
- вы никогда напрямую не трогаете HTTP client. вы никогда напрямую не парсите model response. SDK обрабатывает и то и другое.
harness — среднее кольцо.
- он управляет agent registry, маршрутизирует identity по URL path, обрабатывает session persistence между calls, context compaction при росте sessions, discovery roles и skills, precedence выбора model и provider-neutral trait ModelClient.
- это позволяет заменить Anthropic на OpenAI или локальный Ollama instance без изменения handler code.
- harness делает вашу agent logic переиспользуемой между providers и targets.
- именно там обрабатывается всё, что ломается в production: session state, context overflow, provider failures, concurrent request ordering.
execution targets — внутреннее кольцо.
- local filesystem. CI checkout. HttpSessionEnv, указывающий на Daytona или E2B. граница Cloudflare Worker.
- harness не волнует, что именно вы используете. ваши handlers — тоже. они вызывают session.shell() и session.write(), а harness переводит это в то, что нужно underlying target.
- в этом разделении и есть весь смысл. когда E2B выпускает новую API version, вы обновляете connector, а не agent logic.
- когда Anthropic выпускает claude-opus-4-7, вы обновляете runtime.json, а не handlers. внешнее кольцо остаётся чистым, потому что среднее кольцо поглощает весь churn.
runtime config: файл, который управляет model layer
прежде чем написать хотя бы один handler, вам нужен runtime.json в workspace.
положите его в .agentic-harness/config.json или в корень workspace как agentic-harness.json. load_workspace_context() подхватит его автоматически.
выбор model во время runtime следует такому precedence:
- PromptOptions::model(...) : per-call override
- metadata model выбранной role : per-role default
- defaultModel из runtime config : workspace default
главное понять: model ID должен быть зарегистрирован, прежде чем вы сможете его использовать. openaiCompatibleModels — это список, который harness использует, чтобы подключить встроенный chat-completions client. если вашей model нет в этом списке, вы получите понятную ошибку при startup вместо запутанного сбоя посреди session.
для OpenAI-compatible gateways config выглядит так же. направьте baseUrl на ваш gateway:
- никогда не записывайте literal API key в runtime.json. используйте apiKeyEnv и храните настоящий key в environment.
- harness читает env var во время request, а не при startup, то есть вы можете ротировать keys без перезапуска server.
agent identity — это URL path, а не registry lookup
это было первое design decision, которое меня удивило. теперь я думаю, что оно правильное.
нет никакой системы agent ID. нет registry key. нет UUID, который вы генерируете сами. identity вашего agent — это POST /agents/<name>/<id>.
- harness берёт на себя всё bookkeeping session state за этим URL.
- причина, почему это работает: каждый caller в каждой системе уже умеет строить осмысленный ID из context. PR number. run ID. timestamp вместе с task name. user handle.
- вам не нужен session creation endpoint. вам не нужно отдельно хранить session IDs. URL и есть session.
agent handler на стороне rust вызывает ctx.id(), чтобы получить тот ID, который предоставил caller:
sessions: stateful execution context
session — это больше, чем conversation thread. это полный execution context для agent invocation.
он содержит:
- message history с model
- workspace file access (read, write, edit, grep, glob, stat, readdir)
- shell execution с управлением cwd и env
- tool registrations (MCP servers, custom tools)
- назначенную role и её system prompt overlay
- compaction budget и history watermark
вы получаете session, вызывая ctx.session_with_id() с тем ID, который имеет смысл:
- sessions сохраняются между HTTP calls, когда вы используете один и тот же ID. вызовите один и тот же agent endpoint три раза с одним session ID — model увидит все три обмена как один непрерывный conversation.
- history накапливается автоматически. вы не управляете ею.
- именно это делает multi-step workflows возможными без самостоятельного state management. вы продолжаете вызывать session.prompt(), а harness делает всё остальное.
когда нужно передать большие объёмы context вместе с prompt, прочитайте file и отформатируйте его inline:
session управляет token counting, чтобы вы случайно не переполнили context window посреди conversation. когда вы близко к budget, срабатывает compaction. подробнее об этом в следующем разделе.
tasks: focused child sessions, которые сохраняют parent чистым
- это primitive, который я хотел бы понять в первый день. это разница между агентами, которые сохраняют связность на долгих jobs, и агентами, которые начинают галлюцинировать на полпути.
- task — это one-shot child session. свежая history. общий workspace. возвращает result parent. history parent никогда не видит никакого промежуточного reasoning task.
- research task запускается в isolation. вся его reasoning chain.
- каждое промежуточное observation, которое model сделала о code, каждое «подожди, дай проверю ещё этот file», остаётся внутри task.
- parent session получает один чистый summary. это всё, что она когда-либо видит.почему это важно на практике: когда вы запускаете exploratory analysis прямо внутри long-running session, history заполняется промежуточными tool calls, partial answers и model reasoning о вещах, которые уже не важны.
- model цепляется за этот noise, когда не должна. compaction в итоге срабатывает и теряет context, который действительно был нужен. tasks — хирургическое исправление.
правило: если у sub-problem есть ясный deliverable и для завершения ей не нужна conversation history parent, сделайте её task. порог для «сделать task» ниже, чем вы думаете.
для parallel analysis по codebase: cartographer pattern — разветвляйте tasks и собирайте results:
каждый task чистый. каждый task сфокусирован ровно на одной directory. parent session собирает results и пишет финальный document.
если у вас 12 modules, вы запускаете 12 сфокусированных tasks, каждый начинает с нулевого baggage от остальных.
roles and skills: shaping behavior без recompiling
- roles живут в .agentic-harness/roles/. skills живут в .agents/skills/. и те и другие auto-discovered при startup harness.
- roles — это system-prompt overlays, scoped to a call. применяются во время call и затем отбрасываются. они не сохраняются в message history. они не накапливаются между calls.
precedence chain: call role > session role > agent role > no role.
- model frontmatter необязателен, но полезен. он позволяет направлять конкретные roles на конкретные models.
- ваша explainer role работает на claude-sonnet-4-6 ради скорости и стоимости. ваша security-auditor работает на claude-opus-4-7 ради глубины. вы настраиваете это один раз в role file и больше об этом не думаете.
- skills — это behavior descriptor files, которые model читает в начале session.
- это markdown files в .agents/skills/. harness находит их автоматически. вы нигде их не регистрируете.
практическое применение: skills library рядом с вашей codebase описывает, как вы работаете. commit message format, preferred libraries, migration naming conventions, API design patterns, testing requirements.
model читает это перед каждой session. вы редактируете markdown. behavior обновляется при следующем run. без recompile.
model читает это. она пишет commits, соответствующие вашему convention. вы не напоминаете ей об этом каждую session. вы поддерживаете один file.
coding agent loop во всех деталях
coding agent loop — основной use case, под который был построен CLI. и именно здесь больше всего всего может пойти не так при misconfiguration.
полная command со всеми важными options:
что делает каждый flag и почему это важно:
- --workspace . задаёт root. все file operations sandboxed здесь. agent не может читать или писать за пределами этого path, это enforced на уровне harness — а не через доверие model к самоограничению.
- --llm auto выбирает model из defaultModel в вашем runtime config. используйте --llm anthropic/claude-opus-4-7 для complex tasks, которым нужно deep reasoning, или --llm anthropic/claude-sonnet-4-6 для более быстрой iteration.
- --deny-path — жёсткий block. он работает prefix-style, так что --deny-path config/ покрывает всё под config/. проведите audit workspace перед первым run и перечислите каждый path, где лежат secrets или production config — не только .env.
- --approve-dependencies позволяет изменения Cargo.toml без human approval step. уберите это, если хотите review каждого нового crate до добавления.
- --commit auto-stages все changes и commits их в конце успешного run с указанным message. без этого flag changes окажутся unstaged modifications для вашего review.
- --pr открывает pull request из commit. требует clean git state перед run и настоящую branch, а не detached HEAD.
сам loop: Inspect → Brief → LLM + Tools → Edit + Test → Commit · PR.
- inspect: читает structure workspace, загружает skills и roles, определяет files, наиболее вероятно относящиеся к prompt.
- записывает своё понимание в coding-brief.md перед тем, как трогать code.
- brief: model фиксирует plan. вы можете прочитать .agentic-harness/runs/<id>/coding-brief.md посреди run, чтобы увидеть, что она решила.
- если brief выглядит неверно, kill the run. дешевле перезапустить с более ясным prompt, чем дать agent выполнить плохой plan.
- LLM + tools: edit-test loop. model вносит changes, запускает test suite, читает output, вносит новые changes. iterates, пока tests не пройдут, не будет достигнут iteration limit или она не решит, что task complete.
commit · PR: stages, commits, pushes, открывает PR с приложенным diff.
каждый run записывает шесть artifacts в .agentic-harness/runs/<id>/:
- coding-brief.md : plan, которому agent committed before writing any code
- summary.md : human-readable account того, что было сделано, что пробовали и почему
- run.json :structured metadata: использованная model, total duration, input/output token counts, iteration count, final exit status
- events.jsonl : каждый tool call по порядку с полными inputs и outputs, для debugging того, что пошло не так
- diff.patch : полный diff всех file changes
- checks.json : финальные test и lint results, которые определили success или failure
Советы, которые стоит помнить
- относитесь к ним как к structured logs, а не ephemeral output. я commit run artifacts в repo для любой task, которую мне нужно уметь reproduce.
- один только run.json — 2KB — говорит вам model, token costs и whether it succeeded. events.jsonlговорит вам exactly what the agent did and in what order, когда нужно debug плохой run.
для CI pattern такой:
HttpSessionEnv: запуск binary локально, execution удалённо
- это capability, которую я дольше всего полностью понимал. теперь я использую её почти на каждой task, которая касается infrastructure.
- agent binary запускается на вашей машине или в CI. filesystem и shell operations выполняются внутри remote sandbox.
- agent не знает и не заботится, в какой environment он находится.
use agentic_harness::HttpSessionEnv;
wire protocol — JSON over HTTP. каждая operation:
- exec
- read
- write
- edit
- grep
- glob
- stat
- readdir
- mkdir
- rm имеет определённую request/response shape.
любой sandbox, который реализует этот protocol, работает как HttpSessionEnv target.
чтобы подключить named sandbox:
built-in connectors обрабатывают auth и lifecycle boilerplate для Vercel Sandbox, Daytona и E2B:
- concrete use case, для которого я использую это чаще всего: воспроизведение CI failures в clean Linux environment.
- agent клонирует repo на exact failing commit hash, запускает exact failing test command, читает полный output, диагностирует failure и пишет report.
- я читаю report. я никогда не трогал свою local machine. sandbox discarded, когда session ends.
performance-вещь, о которой никто не предупреждает: каждый shell call через HttpSessionEnv — это network round trip. tight loops: edit, test, check output, edit — быстро накапливают latency.
40-iteration loop, который локально занимает 5 seconds, занимает several minutes против remote sandbox, если каждая iteration делает три отдельных shell calls.
исправление: batch shell work into scripts.
один call per iteration вместо трёх. напишите script один раз, запускайте его repeatedly. разница latency на 40-iteration loop реальна.
build targets: одна codebase, три deployment shapes
native — default. один binary. один manifest. больше ничего на target machine. запускается везде, где можно выполнить native Linux binary.
node — для hosting platforms, которым нужен Node entrypoint. build генерирует server.mjs, который запускает native rust binary как child process и proxies HTTP к нему. agent logic всё ещё runs as rust. Node layer — это 30-line HTTP shim.
Cloudflare — для edge deployment.
- build генерирует Worker boundary file и links Worker-compatible app adapter.
- handlers compile to WASM через WASM JSON ABI.
- Durable Object bindings обрабатывают session persistence через Cloudflare KV.
важный constraint про Cloudflare: Workers не поддерживают long-running shell commands. у них нет настоящего filesystem.
они не поддерживают cargo или build tooling. --target cloudflare — для webhook handling, route metadata, small control endpoints и Durable Object routing, а не для coding work.
для всего, что требует запускать cargo test, delegate to native process или remote sandbox.
практическая decision matrix:
- shipping an agent as an API that other services call → native behind nginx or a managed platform
- hosting on Railway, Render, or a platform that expects Node → node
- webhook ingestion, lightweight routing, Durable Object state management → cloudflare
- everything else → native
schema-guided output: typed rust structs from model responses
просить model вернуть JSON и надеяться, что она это сделает, — половина решения.
когда harness извлекает, validates и deserializes это в ваш rust struct — это полное решение.
model может вернуть reasoning prose вместе с typed payload в одном response. harness извлекает result block между
- --RESULT_START--- и ---RESULT_END--- markers. вы получаете rust struct. compile-time type safety от model output до вашей handler logic.
- schema делает две вещи: говорит model, какую shape производить, и даёт harness то, against which можно validate before deserialization.
- если model возвращает что-то, что не соответствует schema, вы получаете PromptError::SchemaValidationFailed вместо panic через три call sites, когда обращаетесь к missing field.
MCP tools: выход за пределы sandbox
когда agent нужны capabilities beyond file and shell, connect_mcp — escape hatch.
agent получает полный tool set MCP server. не нужно писать tool definitions. descriptions приходят от server. model решает, когда какой tool вызвать, на основе этих descriptions.
можно подключить multiple MCP servers к одной session:
- model вызывает tools на основе их descriptions. vague description вроде «search sentry» вызывается inconsistently.
- description, где сказано «call this before responding to any question about errors, incidents, or production issues», вызывается reliably.
- если вы контролируете MCP server, пишите prescriptive descriptions: говорите model, когда вызывать, а не только что возвращается.
connectors: generating adapters instead of writing them
вместо ручного написания adapter code против незнакомого API, pipe connector recipe вашему coding agent:
- connector recipe — это structured description sandbox API и SessionEnv contract, который ему нужно удовлетворить.
- coding agent читает его, пишет rust adapter module, handles authentication, wraps provider lifecycle и exposes it as an HttpSessionEnv.
- вы review diff. вы merge it. adapter теперь живёт в вашем project. это теперь ваш code.
я подключил Daytona таким способом примерно за 20 минут, включая полный review cycle. agent с первого раза правильно понял auth header format.
написание adapter с нуля по Daytona docs заняло бы большую часть afternoon и минимум два wrong assumptions о refresh token flow.
once the connector is generated:
automatic compaction: handling long sessions without losing context
long-running sessions накапливают history.
в итоге они переполняют context window model.
harness обрабатывает это автоматически, но нужно правильно настроить, иначе вы потеряете context ровно в самый неподходящий момент.
context_window_tokens — общий budget для session.
- reserve_tokens — то, что вы резервируете для response model. effective limit for history — context_window_tokens - reserve_tokens.
- keep_recent_messages — количество messages в tail, которые всегда сохраняются verbatim regardless of compaction.
когда history превышает budget, harness просит model summarize всё между system prompt и kept tail.
этот summary заменяет middle section. tail messages остаются intact. compacted session становится меньше, и следующий call помещается в budget.
tradeoff реален: summaries теряют precision. конкретное решение, принятое 50 messages назад: «we chose authlib because it's the only library with PKCE support that works with axum's middleware model» — может выжить как «we chose authlib for auth» в summary.
если эта precision load-bearing для последующих decisions в session, сохраните её явно:
- write decisions to files. files survive compaction. model может перечитать их on demand. history не обязана нести всё, если workspace это делает.
- run agentic-harness doctor, чтобы увидеть actual reported context window вашей model. задайте context_window_tokens на 80-90% этого значения.
- token counter не идеально accurate на стороне model, и single large file read может вытолкнуть вас over, если вы сидите на 99%.
На что обратить внимание
- session history contamination
- problem: exploratory analysis внутри long session отравляет later prompts шумом из exploration phase
- fix: используйте tasks. task history никогда не касается parent. порог для «make it a task» ниже, чем вы думаете
- role precedence surprises
- problem: call-level role shadows session role. model ведёт себя differently than expected, и вы не понимаете почему
- fix: session role sets identity. call role narrows focus. они layer — call role добавляет, а не должна cancel
- --deny-path gaps
- problem: вы deny .env. ваши secrets также лежат в .env.local и config/staging.yaml. agent читает один из них
- fix: deny prefixes, not filenames. --deny-path config/ covers everything under it
- detached HEAD in CI
- problem: agent edits, tests pass, commit fails — потому что нет branch, куда commit
- fix: git checkout -b agent-run-$RUN_ID перед invoking harness
- HttpSessionEnv latency in tight loops
- problem: 40 iterations по три shell calls каждая — это minutes pure network latency
- fix: напишите agent-check.sh, который делает всё в one invocation. one call per iteration
- context budget underestimation
- problem: compaction fires mid-task. model loses its plan and starts improvising from the summary
- fix: run agentic-harness doctor, чтобы получить actual window. set budget to 80-90% of that
- runtime config loaded after handler registration
- problem: handler runs before load_workspace_context(). no model registered. error looks nothing like a config problem
- fix: always call load_workspace_context() in app() before wiring any agents
- --llm auto changing between runs
- problem: defaultModel gets updated. two runs six months apart aren't comparable. you can't reproduce the first one
- fix: pin the model in runtime.json for anything that needs reproducibility
- deleting run artifacts
- problem: вы clean runs/ через gitignore rule. three weeks later you need to reproduce a regression and everything's gone
- fix: commit run artifacts for any task you need to reproduce. run.json is 2KB. keep it
что я сделал бы иначе
- run agentic-harness guide before touching anything.
- write session-level tests before writing handler logic.
- use tasks for everything that has a sub-deliverable.
- pin the model from the first serious run.
- store decisions in files, not in session history.
- batch shell operations from the start when using remote sandboxes.
bottom line
большинство agent frameworks — это wrappers around an API call. это runtime.
wrapper решает «make the model respond». runtime решает «ship an agent to production and keep it working after the model changes, after the sandbox changes, after the codebase changes, after the session runs for two hours and overflows the context window.»
the 3-layer architecture
- your code
- the harness
- the execution target
— вот что делает это возможным. вы пишете handlers. harness absorbs all the operational complexity. execution target — это config choice.
things that don't change: handler logic, session structure, task patterns, role definitions, skill files. things that do change: models, providers, sandbox vendors, deploy targets.
architecture designed so the things that change never touch the things that don't.
that's the bet. it's the right bet.
надеюсь, вам понравилось читать это и изучать, как я создаю для agents и вообще ❣️
Disclaimers
Эта статья была researched and written by the author, edited by Minimax-M2.7 The thumbnail was taken off Pinterest.
Harrison Chase "memory should be open!" —
https://x.com/hwchase17/status/2046308913939919232Harrison
Chase :"Your Harness, Your Memory" —
https://www.langchain.com/blog/your-harness-your-memory
Vivek Trivedi :"The Anatomy of an Agent Harness" —
https://www.langchain.com/blog/the-anatomy-of-an-agent-harness