Практическое руководство
Запускаем агентную систему на Go с Seshat
Первый прототип часто застревает не на модели, а на обвязке: отдельно создаются цикл сообщений, реестр инструментов, разрешения, сессии, MCP-клиент и RAG. Seshat объединяет эти части в один агентный рантайм. Ниже мы встроим его в программу на Go, дадим агенту прочитать локальный файл и увидим события выполнения.
Что мы собираем
запрос пользователя
│
▼
sdk.Session
│
├── модель
├── история сессии
├── политика разрешений
└── встроенный read_file
│
▼
локальный файл
Seshat — открытый рантайм на Go, а не отдельная модель. Он связывает провайдера LLM, цикл вызова инструментов, сессии, разрешения и события выполнения. В том же ядре предусмотрены MCP, RAG, память и другие инструменты, но для первого запуска они не нужны.
Мы намеренно используем встроенный инструмент чтения файла. Он дает наблюдаемый вызов без shell-команд, сетевых запросов и внешних MCP-серверов. Код ниже является минимальным учебным примером, а не готовой производственной конфигурацией.
1. Проверьте окружение
Понадобятся Go, Git и ключ поддерживаемого провайдера. Вместо предполагаемой версии Go сначала проверьте локальную установку и требования текущего модуля Seshat:
go version
git --version
go env GOPATH GOPROXY
Если Go отсутствует или зависимость сообщает о несовместимой версии, установите поддерживаемую версию по официальной документации Go, затем повторите команды. Не отключайте проверку модулей и не используйте неизвестный прокси только ради успешной сборки.
2. Создайте изолированный проект
Работайте в новом каталоге: так агент не получит случайный доступ к домашней папке или рабочему монорепозиторию.
mkdir seshat-go-lab
cd seshat-go-lab
go mod init example.com/seshat-go-lab
go get github.com/KPO-Tech/seshat@latest
Команда с @latest выбирает актуальную опубликованную версию на момент запуска. Для воспроизводимой командной сборки после проверки сохраните получившиеся go.mod и go.sum в системе контроля версий: они зафиксируют реально разрешенную версию зависимости.
Создайте файл, содержание которого нельзя угадать из запроса:
printf '%s\n' 'LAB_CODE=cedar-417' > fixture.txt
Здесь cedar-417 — публичное тестовое значение, а не пароль или секрет. Не помещайте настоящие ключи в файл, который агенту разрешено читать.
3. Передайте ключ модели безопасно
Пример использует Anthropic, потому что такой вариант показан в публичной документации Go SDK Seshat. Значение ключа в статье намеренно отсутствует:
read -s ANTHROPIC_API_KEY
export ANTHROPIC_API_KEY
printf '\nКлюч загружен в окружение текущей оболочки\n'
Вставьте ключ после первой команды и нажмите Enter. read -s не печатает ввод на экран. Не добавляйте ключ в исходный код, историю shell, go.mod или Git.
Для другого провайдера замените идентификатор, модель и переменную окружения по актуальному разделу Providers & Auth в документации Seshat. Имена моделей меняются; сверяйте их с провайдером перед запуском.
4. Соберите минимального агента
Сохраните следующий пример как main.go. Он проверяет конфигурацию до создания клиента, ограничивает продолжительность запроса, печатает runtime-события и различает ошибки инициализации, сессии и выполнения.
package main
import (
"context"
"errors"
"fmt"
"log"
"os"
"time"
"github.com/KPO-Tech/seshat/pkg/sdk"
)
func main() {
if err := run(); err != nil {
log.Printf("agent failed: %v", err)
os.Exit(1)
}
}
func run() error {
apiKey := os.Getenv("ANTHROPIC_API_KEY")
if apiKey == "" {
return errors.New("ANTHROPIC_API_KEY is empty")
}
workingDir, err := os.Getwd()
if err != nil {
return fmt.Errorf("resolve working directory: %w", err)
}
client, err := sdk.NewClient(&sdk.ClientConfig{
Model: sdk.ModelIdentifier{
Provider: sdk.APIProviderAnthropic,
Model: "claude-sonnet-4-20250514",
},
APIKey: apiKey,
WorkingDir: workingDir,
PermissionMode: sdk.PermissionModeOnRequest,
RuntimeEventFn: func(event sdk.RuntimeEvent) {
log.Printf("runtime event: %+v", event)
},
})
if err != nil {
return fmt.Errorf("create Seshat client: %w", err)
}
defer client.Close()
ctx, cancel := context.WithTimeout(context.Background(), 90*time.Second)
defer cancel()
session, err := client.CreateSession(ctx)
if err != nil {
return fmt.Errorf("create session: %w", err)
}
defer session.Close()
prompt := `Прочитай файл fixture.txt встроенным инструментом.
Верни только значение LAB_CODE. Не угадывай и не запускай shell-команды.`
response, err := session.SubmitMessage(ctx, prompt)
if err != nil {
if errors.Is(err, context.DeadlineExceeded) {
return errors.New("agent turn exceeded the 90-second deadline")
}
return fmt.Errorf("submit message: %w", err)
}
fmt.Printf("answer: %s\n", response.Content)
return nil
}
WorkingDir задает рабочую область, но сам по себе не является полноценной песочницей. PermissionModeOnRequest сохраняет контроль над чувствительными действиями. Для серверного приложения потребуется собственная политика подтверждений: фоновый worker не должен бесконечно ждать интерактивного ответа.
Обработчик RuntimeEventFn печатает структуру события целиком. Это диагностический прием: он не зависит от того, какие поля вы захотите вынести в метрики позднее. В рабочей системе фильтруйте события и не записывайте в журнал содержимое секретных файлов или аргументы с токенами.
5. Сначала проверьте сборку
gofmt -w main.go
go mod tidy
go build -o seshat-lab .
go vet ./...
Эти команды только форматируют и анализируют локальный учебный модуль. go build проверяет соответствие примера установленной версии API. Если публичный API Seshat изменился, компилятор покажет конкретный символ, который нужно сверить с документацией версии из go.mod.
Посмотрите, какая версия действительно выбрана:
go list -m github.com/KPO-Tech/seshat
go mod verify
6. Запустите агента и проверьте вызов
./seshat-lab
Успешная проверка состоит из двух независимых признаков:
- В stderr появились runtime-события, относящиеся к вызову встроенного файлового инструмента.
- Финальная строка содержит
answer: cedar-417.
Одного правильного ответа недостаточно: модель теоретически могла воспроизвести значение из прежнего контекста. Поэтому повторите опыт с новым значением:
printf '%s\n' 'LAB_CODE=maple-982' > fixture.txt
./seshat-lab
Теперь ожидается answer: maple-982 и новый инструментальный вызов. Если ответ остался прежним, проверьте путь, сессию и кэширование, а не исправляйте промпт вслепую.
7. Проверьте контролируемые ошибки
Нет ключа
env -u ANTHROPIC_API_KEY ./seshat-lab
printf 'exit code: %s\n' "$?"
Программа должна завершиться с ненулевым кодом и сообщением ANTHROPIC_API_KEY is empty. Запрос к провайдеру выполняться не должен.
Нет файла
mv fixture.txt fixture.txt.off
./seshat-lab
mv fixture.txt.off fixture.txt
Корректное поведение — явное сообщение о невозможности прочитать файл или ошибка хода. Агент не должен придумывать LAB_CODE. Команда восстановления вынесена отдельно, чтобы файл можно было вернуть даже после неуспешного запуска.
Недоступен провайдер
Ошибка авторизации, лимит запросов, сетевой сбой или неизвестная модель должны попасть в ветку submit message либо create Seshat client и завершить процесс с кодом 1. Не печатайте ключ при диагностике и не повторяйте запрос бесконечно.
Истек таймаут
По истечении 90 секунд контекст отменяется, а программа возвращает отдельную ошибку. Для production-сервиса добавьте ограниченное число повторов только для временных сбоев, экспоненциальную задержку и общий дедлайн задачи. Ошибки конфигурации и авторизации повторять автоматически бессмысленно.
Что именно взял на себя Seshat
В ручной реализации пришлось бы отдельно написать цикл «сообщение — ответ модели — tool call — результат инструмента — продолжение ответа», привести схемы инструментов к формату провайдера и решить, когда цикл должен остановиться. В примере эти обязанности находятся за интерфейсом sdk.Client и сессии.
Это не устраняет архитектурные решения. Приложение по-прежнему отвечает за секреты, границы рабочей директории, пользовательскую авторизацию, допустимые инструменты, лимиты, хранение журналов и реакцию на сбои.
Как расширять прототип
MCP
MCP нужен, когда инструмент живет вне процесса: например, отдельный сервер публикует операции над внутренней системой. Seshat умеет подключать MCP-серверы, выполнять handshake, обнаруживать операции и добавлять их в общий реестр инструментов.
Не подключайте произвольный MCP-сервер сразу с правами записи. Сначала зафиксируйте его команду запуска или URL, версию, список ожидаемых инструментов, таймаут и правила разрешений. После подключения сравните обнаруженный список с allowlist.
RAG
RAG полезен для поиска релевантных фрагментов в корпусе документов. Он не заменяет инструмент: поиск предоставляет контекст, а инструмент выполняет действие. Начинайте с отдельного тестового корпуса без персональных данных и проверяйте не только ответ, но и выбранные фрагменты.
Собственный инструмент
Публичный SDK предоставляет регистрацию инструментов на уровне клиента и сессии. При добавлении собственного обработчика задайте узкую JSON-схему, валидируйте аргументы повторно внутри Go-кода и возвращайте структурированную ошибку. Описание инструмента для модели не является механизмом безопасности.
Актуальные сигнатуры регистрации и управление поверхностью инструментов сверяйте с разделом Tools & Surface. Не копируйте сигнатуру из статьи для другой версии модуля без проверки go.mod.
Типовые ошибки
- Модель отвечает без вызова инструмента
- Убедитесь, что значение отсутствует в запросе, инструмент доступен сессии, а выбранная модель поддерживает tool calling. Смотрите runtime-события: красивый ответ не доказывает выполнение.
- Агент видит слишком много файлов
- Не запускайте пример из домашнего каталога или корня репозитория. Создавайте отдельную рабочую директорию и формируйте явный список разрешенных инструментов для каждой роли.
- Процесс ожидает подтверждение без интерфейса
-
Режим
OnRequestтребует продуманного канала одобрения чувствительных действий. В headless-сервисе возвращайте задачу в состояние ожидания или заранее запрещайте такие инструменты. - Код из документации не собирается
-
Сравните версию из
go list -mс документацией и исходным кодом этой версии. Seshat развивается, поэтому имена моделей и части SDK могут изменяться. - В лог попали аргументы инструмента
-
Диагностическая печать
%+vподходит только для стенда без секретов. В production сохраняйте тип, длительность, статус и идентификатор вызова, а чувствительные поля редактируйте или исключайте. - Каждая ошибка автоматически повторяется
- Разделяйте временные сетевые сбои, rate limit, неверный ключ, запрещенный инструмент и ошибочные аргументы. У каждого класса должны быть собственная политика повторов и предел попыток.
Ограничения минимальной сборки
- Пример запускает один процесс и одну сессию; он не решает многопользовательскую изоляцию.
- Рабочая директория ограничивает контекст, но не заменяет контейнер, системную песочницу и политику сетевого доступа.
- Вывод runtime-событий является диагностикой, а не полноценной трассировкой или аудитом.
- В примере нет бюджета токенов, очереди задач, повторов, circuit breaker и долговременного хранилища результатов.
- Качество выбора инструментов зависит от модели, формулировки задачи и схемы инструмента.
- MCP и RAG поддерживаются рантаймом, но намеренно не включены в минимальный запуск.
- Перед production-внедрением нужно закрепить версию зависимости и провести собственные интеграционные и негативные проверки.
Критерий готовности прототипа
Минимальный стенд можно считать рабочим, если он собирается из чистого каталога, завершает запрос в пределах дедлайна, показывает фактический вызов read_file, возвращает текущее значение из fixture.txt и предсказуемо падает при отсутствии ключа или файла.
Следующий разумный шаг — не добавлять сразу десятки инструментов, а зарегистрировать одну прикладную read-only операцию, оставить runtime-события включенными и проверить разрешенный вызов, запрещенный вызов, неправильные аргументы, таймаут и недоступность провайдера.