Personal AI secretary over a single Git workspace, powered by the Pi agent runtime.
  • TypeScript 98.5%
  • Nix 1.4%
  • JavaScript 0.1%
Find a file
Repository files (latest commit first)
Filename Latest commit message Latest commit date
2026-10-11 14:22:49 +03:00
bin Initial commit 2026-09-02 16:02:55 +03:00
nix Simplify NixOS module import 2026-09-14 15:43:44 +03:00
src Add move_file tool for binary-safe moves 2026-10-11 14:22:49 +03:00
test Add move_file tool for binary-safe moves 2026-10-11 14:22:49 +03:00
.gitignore Add workspace locking and persistent sessions 2026-09-14 14:29:17 +03:00
config.example.json Make Matrix line breaks configurable 2026-09-10 19:08:28 +03:00
LICENSE Add GPL-3.0-only license and reference it in docs 2026-09-02 16:07:08 +03:00
package-lock.json Add workspace locking and persistent sessions 2026-09-14 14:29:17 +03:00
package.json Add workspace locking and persistent sessions 2026-09-14 14:29:17 +03:00
README.md Add move_file tool for binary-safe moves 2026-10-11 14:22:49 +03:00
tsconfig.json Initial commit 2026-09-02 16:02:55 +03:00

Personal Assistant

Персональный ИИ-секретарь с одним Git workspace. Сервер — единственный владелец сохраняемой Pi-сессии, модели, workspace и напоминаний. Межпроцессный lock не позволяет двум экземплярам сервера или initializer одновременно изменять один workspace. CLI и Matrix bridge — HTTP-клиенты.

Этот README — единственное каноническое описание проекта вне исходного кода.

Архитектурные принципы

Файлы — источник истины

Долговременные пользовательские данные хранятся в отдельном Git workspace как Markdown (.md) и Org (.org). Код не вводит обязательные классы Task, Project или CalendarEvent: структуру проектов, задач и заметок определяют текст, существующее дерево файлов и инструкции агента.

Код моделирует capabilities, lifecycle и security boundaries, а не пользовательскую предметную область.

Git обеспечивает историю, diff, откат, синхронизацию и резервное копирование. Pi остаётся внутренней implementation detail, а клиенты — тонкими интерфейсами к одному HTTP-сервису.

Поток запроса

CLI / Matrix
  → HTTP Secretary Service
  → WorkspaceCoordinator
  → интерактивная Pi AgentSession
  → sandboxed filesystem / notification / fetch / autonomous tools
  → Git commit или rollback
  → .secretary/runtime.json и HTTP response

Основные компоненты:

  • src/secretary.ts — composition root, интерактивные и автономные ходы;
  • src/server.ts — HTTP API;
  • src/workspace-coordinator.ts — внутрипроцессная очередь с приоритетом пользователя;
  • src/workspace-fs.ts — изолированные файловые операции;
  • src/git-workspace.ts — Git lifecycle;
  • src/runtime-state.ts — служебное persisted state;
  • src/autonomous.ts, src/autonomous-state.ts — scheduler, квоты и recovery;
  • src/matrix.ts — Matrix bridge;
  • src/workspace-lock.ts — межпроцессное владение workspace.

Threat model и KISS

Проект рассчитан на trusted single-user окружение: пользователь, процесс сервера и установленные Pi extensions доверенные, а вызовы tools со стороны модели и сетевое содержимое — нет. Файловые tools не выходят из workspace, сеть доступна только через allowlist, сырой shell модели не предоставляется. Инструмент move_file перемещает и переименовывает файлы и каталоги внутри workspace через rename, не читая содержимого; существующий файл назначения не перезаписывается.

Преднамеренные локальные TOCTOU-атаки, изоляция доверенных extensions и полноценный OS sandbox пока не входят в модель защиты. Без подтверждённой практической необходимости проект не добавляет отдельную БД, парсеры Markdown/Org, сложный workflow engine, unrestricted shell или неограниченные autonomous loops.

Запуск

npm install
cp config.example.json config.json
npm run init-workspace -- config.json
SECRETARY_API_KEY='…' npm run serve -- config.json

workspaceRoot можно указать абсолютным путём или относительным путём от каталога конфигурационного файла. Если его не указать, workspace — каталог, содержащий конфиг; конфиг разрешено хранить в самом workspace. Workspace не должен пересекаться с репозиторием сервиса. Ключ модели можно задать как model.apiKey в JSON или через SECRETARY_API_KEY; переменная окружения имеет приоритет. Опциональный SECRETARY_BEARER_TOKEN защищает все API-запросы. SECRETARY_LOG_TOOL_CALLS=1 выводит в stderr имя, ID, аргументы и результат каждого вызова tool. Журнал может содержать приватные данные и секреты. По умолчанию сервер слушает 127.0.0.1:3000.

Пример конфига включает webFetch.allowedOriginsFile — файл allowlist для fetch_url и fetch_feed (относительный путь — от workspaceRoot) должен существовать и быть читаемым до старта сервера, иначе запуск завершится ошибкой. Inline-список allowedOrigins можно оставить пустым, задать в нём постоянные origins или удалить вместе с allowedOriginsFile. Если fetch не нужен, удалите блок webFetch целиком.

В отдельном терминале запускаются тонкие клиенты:

npm start -- --config config.json
MATRIX_ACCESS_TOKEN='…' npm run matrix -- config.json

CLI и Matrix bridge используют workspaceRoot только для локального служебного состояния; им не нужны ключ модели и её настройки.

Конфигурация

  • workspaceRoot — путь к Git workspace с пользовательскими данными. Абсолютный путь используется как есть, относительный разрешается от каталога config-файла. По умолчанию — каталог config-файла. Не может пересекаться с репозиторием сервиса; config-файл может находиться внутри workspace.
  • assistantDirectory — относительный путь к каталогу автоматически загружаемого долгосрочного контекста (assistant по умолчанию). Рекурсивно загружаются все его несокрытые Markdown- и Org-файлы без специальных исключений.
  • memoryPath — относительный путь к файлу фактов и предпочтений; по умолчанию <assistantDirectory>/memory.md.
  • agendaPath — относительный путь к индексу датированных задач; по умолчанию <assistantDirectory>/agenda.md.
  • autonomous.memoryPath — относительный путь к автономной рабочей памяти (autonomous/memory.md по умолчанию). Она отдельно загружается в автономных сессиях и подчиняется autonomous.maxMemoryBytes.
  • piResourcesRoot — необязательный путь к существующему каталогу ресурсов Pi, принадлежащему владельцу сервиса. Абсолютный путь используется как есть, относительный разрешается от каталога config-файла. По умолчанию используется <workspaceRoot>/.pi; если этого каталога нет, ресурсы Pi не загружаются. Он не должен пересекаться с репозиторием сервиса.
  • timezone — IANA timezone для дат и времени, возвращаемых tool get_current_time, и для локального времени ежедневных автономных заданий; по умолчанию timezone процесса.
  • model — блок всех параметров модели:
    • apiBaseUrl — HTTP(S) endpoint OpenAI-совместимого API модели.
    • id — идентификатор модели у этого endpoint.
    • apiKey — ключ API модели; пустая строка и отсутствие поля равнозначны, предпочтительнее задавать через SECRETARY_API_KEY, который имеет приоритет.
    • reasoning — поддерживает ли модель расширенное рассуждение (reasoning).
    • supportsReasoningEffort — принимает ли endpoint параметр reasoning_effort; по умолчанию равен reasoning. Модель может уметь reasoning, но не принимать effort — но не наоборот: true требует reasoning: true. При false уровень thinking в запросе не участвует, модель рассуждает своими средствами.
    • thinkingLevel — уровень расширенного рассуждения: off (по умолчанию), minimal, low, medium, high, xhigh или max. Уровень выше off требует reasoning: true. Уровень, отключённый в thinkingLevelMap (значение null), а также xhigh/max без явной записи в карте — ошибка конфигурации, а не молчаливое понижение.
    • thinkingLevelMap — необязательный маппинг уровней в значения провайдера (reasoning_effort); null отключает уровень, xhigh/max доступны только с явной записью. Пример для Qwen3.8: { "off": "none", "minimal": null, "low": "low", "medium": "medium", "high": null, "xhigh": "xhigh", "max": null }. Маппинг "off": "none" при reasoning: true явно выключает thinking запросом reasoning_effort: "none".
    • contextWindow — размер контекстного окна модели в токенах.
    • maxTokens — верхняя граница токенов ответа (max_tokens), 4096 по умолчанию. У многих OpenAI-совместимых серверов reasoning-токены считаются в этом бюджете — при включённом reasoning лимит стоит увеличить.
    • supportsImages — может ли модель принимать изображения во входе.
  • serverHost, serverPort — интерфейс и порт, на которых слушает сам Secretary (127.0.0.1:3000 по умолчанию).
  • secretaryApiUrl — публичный URL Secretary для CLI и Matrix bridge. По умолчанию составляется из serverHost и serverPort; задаётся отдельно при reverse proxy.
  • terminalTurnRecordLimit — положительный лимит сохранённых завершённых идемпотентных запросов.
  • webFetch.allowedOrigins — точные HTTP(S) origins, которым fetch_url и fetch_feed могут передавать GET в интерактивных и автономных сессиях. Пустой список или отсутствие блока отключает tool; "*" разрешает любые HTTP(S) origins.
  • webFetch.allowedOriginsFile — путь к файлу с origins в дополнение к allowedOrigins; обе формы можно задать одновременно. Относительный путь разрешается от workspaceRoot (удобно для скрытых каталогов workspace — например, .secretary/fetch-origins.txt), абсолютный используется как есть. Одна строка — один origin; пустые строки и #-комментарии игнорируются. Действующие origins — объединение inline-списка и файла; "*" в любом из них разрешает всё. Оба проверяются при старте; файл перечитывается при каждом вызове fetch_url, поэтому правки применяются без рестарта сервера.
  • autonomous — необязательный server-only блок настроек отложенных самосообщений модели (см. «Автономные самосообщения»). По умолчанию выключен.
  • response.suppressToolNarration — не просить модель описывать промежуточное использование tools.
  • response.finalMessageOnly — возвращать клиенту только последнее сообщение assistant в turn.
  • response.showDiff — добавлять diff к успешному chat-ответу.
  • matrix содержит homeserver, userId, accessToken, roomId, необязательный secretaryApiUrl, markdown (булево, по умолчанию true) и markdownLineBreaks (булево, по умолчанию false). При markdown: true ответы модели преобразуются из Markdown в безопасный Matrix HTML (formatted_body с исходным Markdown в fallback body); markdownLineBreaks: true превращает одиночные переводы строк в HTML <br>. При markdown: false сообщения отправляются обычным текстом без format. MATRIX_ACCESS_TOKEN переопределяет matrix.accessToken. Входящие сообщения с вложениями (m.file, m.image, m.audio, m.video) мост скачивает через авторизованный media endpoint homeserver (незашифрованные события с mxc://-адресом, до 7 MiB — лимит увязан с 10 MiB JSON-лимитом POST /files с учётом base64), загружает на сервер через POST /files, после чего файл лежит в attachments/ и зафиксирован в Git, и передаёт агенту сообщение с именем, размером, MIME-типом, путём attachments/<id>-<имя> в workspace и подписью пользователя как его сообщением. Зашифрованные комнаты и вложения не поддерживаются. Ошибки скачивания или загрузки мост отправляет в комнату текстом Request failed: … без обращения к агенту; сбой моста между загрузкой вложения и фиксацией события может при повторной обработке создать в attachments/ дубликат файла. Внутри workspace агент перемещает бинарные вложения инструментом move_file, не читая содержимое.

Пути layout задаются относительно workspaceRoot, не могут содержать hidden/parent-компоненты или symlink и должны обозначать разные файлы. При изменении путей сервис не перемещает старые данные и не переписывает существующий AGENTS.md: миграцию и Git-коммит владелец workspace выполняет вручную до перезапуска. Если workspace уже инициализирован (AGENTS.md существует), initializer откажется запускаться при отсутствии любого configured path вместо создания и коммита пустых замен.

Переменные окружения

Переменная Назначение
SECRETARY_API_KEY Ключ API модели для сервера. Имеет приоритет над model.apiKey из конфигурации.
SECRETARY_BEARER_TOKEN Токен защиты HTTP API. При задании клиенты CLI и Matrix передают его в Authorization: Bearer …; сервер требует этот заголовок для всех endpoints.
MATRIX_ACCESS_TOKEN Токен Matrix-клиента. Имеет приоритет над matrix.accessToken в конфигурации.
SECRETARY_LOG_TOOL_CALLS=1 Включает запись в stderr имени, ID, аргументов и результата каждого tool-вызова. Лог может содержать приватные данные и секреты.
RIPGREP_CONFIG_PATH Служебная переменная: при запуске сервис устанавливает её сам на файл .secretary/.ripgreprc workspace, чтобы grep не видел скрытые файлы. Вручную задавать её не требуется.

В piResourcesRoot сервис автоматически загружает только существующие подкаталоги extensions/ и skills/; их создавать не нужно. В extensions/ допустимы обычные Pi entry points (name.ts, name.js, name/index.ts или name/index.js) и Pi packages: пакет помещается непосредственным дочерним каталогом extensions/ и объявляет entry points в package.json через pi.extensions и pi.skills. Его зависимости должны быть установлены в самом пакете. Skills имеют вид skills/<name>/SKILL.md: в prompt попадают только имя и описание, а полный текст агент получает инструментом load_skill. Вспомогательные файлы skills не получают доступа вне workspace. Обычный discovery ресурсов workspace (.agents/, package-файлы и другие пути) отключён: загружается только явно заданный piResourcesRoot, в том числе когда это <workspaceRoot>/.pi.

Extensions — доверенный код владельца сервиса с полными правами процесса, поэтому piResourcesRoot не является защитной границей от вредоносного extension (в MVP также не проверяется дерево symlink extensions). Их зарегистрированные tools и обычные agent/tool hooks доступны автоматически. Extension commands, UI API, session_start и resources_discover не поддерживаются. Изменения ресурсов владельцем вступают в силу при reload на следующем turn.

Получение страниц и файлов

Tools fetch_url и fetch_feed включаются непустым webFetch.allowedOrigins и доступны как в обычном диалоге, так и в автономных сессиях; в автономной сессии правило о недоверенных данных действует так же, как в интерактивной. fetch_url без path возвращает модели текстовый HTTP(S)-ответ размером до 256 KiB. С относительным path он сохраняет до 10 MiB сырых байтов в workspace, создаёт родительские каталоги и не перезаписывает существующий файл. fetch_feed принимает RSS 2.0 и Atom, сортирует элементы по убыванию даты и возвращает не более 20 элементов по умолчанию (до 100); since включает элементы с указанной даты включительно, а тело каждого элемента очищается от HTML и ограничено 4 096 символами по умолчанию (до 16 384). Оба инструмента используют только GET, не более пяти redirect и общий timeout 30 секунд; каждый redirect снова проверяется по allowlist. Бинарный ответ fetch_url без path отклоняется. Загруженный файл участвует в обычном Git lifecycle успешного turn.

Значения allowlist имеют вид https://example.org и включают protocol, hostname и port. "*" разрешает все HTTP(S) origins, в том числе localhost, локальную сеть и metadata endpoints, и тем самым открывает модели широкий сетевой и потенциальный exfiltration-канал. Используйте wildcard только осознанно либо ограничивайте исходящий трафик сервиса системным firewall/proxy. Tool не передаёт пользовательские headers, cookies, credentials или request body; промпт в обеих модах требует относиться к полученному содержимому как к недоверенным данным, а не инструкциям.

Allowlist можно держать и в файле: webFetch.allowedOriginsFile комбинируется с inline-списком, действующие origins — их объединение (wildcard в любом источнике разрешает всё). Храните файл в скрытом (начинающемся с точки) каталоге — например, в workspace рядом с .secretary/runtime.json: агент не может читать и изменять dot-файлы, поэтому не сможет расширить собственный allowlist. Tool перечитывает файл при каждом вызове, а изменения, нарушающие валидность (недоступный файл или невалидная строка), отклоняют конкретный вызов, а не весь сервер. Файл читается процессом сервиса напрямую, поэтому он должен быть доступен на чтение его пользователю.

Интерактивная история Pi сохраняется как нативная JSONL-сессия в <workspaceRoot>/.secretary/sessions/ и продолжается после рестарта сервера. Эти файлы находятся вне Git и недоступны файловым tools агента, но содержат полный приватный transcript, tool calls и результаты; права на .secretary и резервные копии должны ограничиваться пользователем сервиса. Pi автоматически компактизирует длинную историю. Команда /new создаёт новую сохраняемую сессию и отбрасывает ожидающий runtime inbox, не удаляя старые JSONL-файлы. Команда /commit фиксирует все незакоммиченные изменения workspace, включая новые файлы; если изменений нет, она ничего не делает. Команда /usage возвращает оценку занятого и свободного context window в токенах и процентах. Команда /compact [инструкции] вручную сворачивает историю диалога; необязательные инструкции передаются модели для формирования summary. Сразу после compaction оценка недоступна до следующего ответа модели. Долговременный контекст автоматически загружается только из assistantDirectory; расположенные вне него memoryPath и agendaPath модель читает обычными файловыми tools по необходимости.

HTTP API

Если задан SECRETARY_BEARER_TOKEN, каждый endpoint требует Authorization: Bearer <token>.

  • POST /chat: тело { "message": "..." }, обязательный заголовок Idempotency-Key (1–200 печатных ASCII-символов без пробелов). Ответ: { "text", "commit" }, а при showDiff также diff.
  • POST /files: { "name", "contentBase64" } создаёт вложение в workspace.
  • GET /files/:id скачивает вложение.
  • GET /events?after=<id> возвращает упорядоченные notifications и автономные события.
  • GET /autonomous — состояние автономных заданий: включение, пауза, блокировки, остатки бюджетов, активные задания и ограниченная история завершённых заданий (включая неуспешные, с кодом и кратким описанием ошибки). Ошибки заданий также пишутся в журнал сервера.
  • POST /autonomous/pause — сохраняемая общая пауза исполнения; не прерывает уже идущий ход.
  • POST /autonomous/resume — снятие паузы и блокировок; при «грязном» workspace возвращает 409.
  • POST /autonomous/jobs/:id/cancel — идемпотентная отмена ещё не запущенного задания (повторная отмена уже отменённого безопасна); для running и завершённых (completed/failed/interrupted) — 409 с фактическим статусом.

Повтор POST /chat с тем же ключом и тем же сообщением возвращает сохранённый HTTP-результат. Тот же ключ с иным сообщением возвращает 409.

Runtime state и уведомления

Служебное состояние хранится отдельно от пользовательских данных в <workspaceRoot>/.secretary/: runtime.json содержит идемпотентные turn records, напоминания, автономные задания и журнал событий; sessions/ — нативные интерактивные Pi-сессии; CLI и Matrix bridge сохраняют там свои cursors. Запись runtime выполняется atomic replace. workspace.lock — advisory lock, удерживаемый сервером через flock; второй сервер завершается сразу с ошибкой. Каталог добавляется в .gitignore при инициализации workspace. Агенту недоступны все файлы и каталоги workspace, чьи имена начинаются с точки.

Инструменты schedule_notification, list_notifications, cancel_notification работают с общим набором напоминаний. При срабатывании появляется event; CLI выводит накопленные события только до и после пользовательского запроса, Matrix bridge опрашивает их в своём последовательном sync loop. Перед следующим обычным интерактивным ходом Secretary добавляет в контекст свежий хвост ещё не переданных модели событий (уведомлений и публикаций автономных сессий), затем помечает весь этот inbox переданным независимо от успеха модели. /new отбрасывает ожидающий inbox; журнал /events при этом не меняется.

Автономные самосообщения

При включённом autonomous.enabled модель может планировать отложенные запуски самой себя. Это отдельный механизм от уведомлений: уведомление доставляет готовый текст пользователю, самосообщение запускает новый ход модели.

При включённом autonomous.enabled в интерактивном ходе также доступен run_async_task с формой { task }: он создаёт обычное одноразовое задание на немедленный запуск после текущего успешного хода и сразу возвращает его ID. Задача выполняется в отдельной автономной сессии и должна передать пользователю краткий итог через publish_user_message; до этого интерактивный ход не считает её завершённой.

Остальные доступные инструменты: schedule_self_message, list_self_messages, cancel_self_message; в автономной сессии также publish_user_message — явная публикация одного сообщения пользователю. schedule_self_message принимает обычную одноразовую форму { at, message } и, только в интерактивном ходе, календарную форму { at, message, repeat: "daily", until?, maxRuns? }. Для ежедневной формы обязателен хотя бы один из лимитов until и maxRuns; локальное время первого at интерпретируется в настроенной timezone. Автономный ход может создавать только одноразовых потомков. Инструменты уведомлений (schedule_notification, cancel_notification, list_notifications) намеренно доступны только в обычном диалоге: в автономной сессии их нет, чтобы доставку пользователю нельзя было обойти мимо явной публикации. Настроенные fetch_url и fetch_feed, напротив, доступны и в автономных сессиях: allowlist webFetch.allowedOrigins — единая политика доступа для обеих мод, а автономный промпт получает то же правило о недоверенных данных. Принятое задание сначала получает статус staged и активируется только после успешного завершения создающего хода. «Немедленный» run_async_task всё равно может стартовать позже из-за существующих ограничений очереди, в частности minRunIntervalSeconds и лимита запусков за 24 часа. Финальный текст модели сам по себе пользователю не отправляется.

Каждое задание исполняется в новой изолированной Pi-сессии без истории диалога. Преемственность обеспечивается файлом autonomous.memoryPath: он явно загружается в автономные сессии с лимитом autonomous.maxMemoryBytes (при превышении — явная ошибка без обрезания). В интерактивном диалоге он загружается автоматически только тогда, когда является Markdown/Org-файлом внутри assistantDirectory; иначе он остаётся доступен обычным файловым tools. Пути memoryPath и agendaPath также автоматически загружаются только при нахождении внутри assistantDirectory.

Ограничения назначает и хранит сервер в .secretary/runtime.json, они переживают рестарт: maxPending, maxCreatedPerTurn, minDelaySeconds, minRunIntervalSeconds, maxRunsPer24Hours (скользящее окно 24 часов), maxChainDepth, maxJobsPerChain (кумулятивный бюджет цепочки, не возвращается отменами и ошибками), maxRunSeconds и maxModelStepsPerRun (ограничения одного хода), maxMessageBytes, terminalJobRecordLimit. Потомки создаются только от успешного хода; автономный ход не может создавать новые корни цепочки.

Ограничение остановки хода: maxRunSeconds и maxModelStepsPerRun реализованы через кооперативный abort Pi. При срабатывании лимита журнал и terminal history получают соответственно max_run_seconds или max_model_steps, а не общий aborted. Все встроенные инструменты завершаются по сигналу abort, но доверенное extension, игнорирующее сигнал и зависшее в середине операции, может задержать ход дольше лимита и держать очередь workspace занятой — сервер не завершает такой ход принудительно. Держите TimeoutStopSec службы с запасом выше maxRunSeconds (см. Nix-документацию).

Ежедневное задание остаётся одной job с неизменными ID, root и chain. Первый запуск задаёт at, следующие сохраняют то же локальное время в настроенной IANA timezone, поэтому переход DST не сдвигает пользовательское время. Для несуществующего времени весеннего перехода применяется Temporal compatible: оно сдвигается вперёд на величину разрыва; при осеннем повторе выбирается первое вхождение. На следующий обычный день серия возвращается к исходному локальному времени. maxRuns считает начатые (claimed) запуски, включая failed и interrupted. until — абсолютный последний допустимый момент начала (now <= until). После простоя выполняется не более одного catch-up; пропущенные дни не материализуются. Ошибка отдельного запуска не повторяется, но серия продолжается, пока остаётся лимит. Отдельная история occurrences не хранится; активное состояние и итог серии видны через существующие list/control API.

Управление без модели — через HTTP API (/autonomous). Пауза и отмена не прерывают уже идущий ход. Если при старте задания workspace «грязный», ручные изменения сохраняются, задание остаётся ожидать, а исполнитель блокируется до POST /autonomous/resume после исправления. После рестарта прерванный одноразовый ход помечается interrupted и автоматически не повторяется; прерванный запуск периодической job расходует один maxRuns и серия продолжается со следующего будущего интервала, если лимиты не исчерпаны. staged-потомки прерванного хода отменяются.

Опубликованные сообщения появляются в /events с kind: "agent"; Matrix доставляет их как обычные события, CLI показывает накопленные события при следующем взаимодействии. Сбой между Git-коммитом и записью runtime не повторяется автоматически: такой запуск считается прерванным с неопределённым результатом, решение принимает пользователь.

Git lifecycle

Перед turn workspace должен быть чистым. Успешные изменения коммитятся как Assistant update. Ошибочный agent turn очищается до HEAD командой git reset --hard HEAD и git clean -fd; игнорируемые пользовательские файлы не удаляются. Сервер захватывает fail-fast advisory lock <workspaceRoot>/.secretary/workspace.lock до чтения runtime и удерживает его до shutdown. init-workspace использует тот же lock. Для запуска требуется утилита flock (в Nix package она добавлена в runtime PATH).

Текущее состояние и дальнейшие направления

Реализованы sandboxed файловые tools, Git lifecycle, HTTP/CLI/Matrix, persisted события и уведомления, allowlisted web/RSS fetch, вложения, ограниченные автономные задания, timezone-aware ежедневное расписание, crash recovery, workspace lock и persisted native Pi sessions.

Возможный дальнейший backlog, только при подтверждённой необходимости:

  1. CI с TypeScript, test, format и coverage gates.
  2. Retention для runtime events и Pi session JSONL.
  3. Lifecycle либо отдельное blob-хранилище для вложений.
  4. Более строгая SSRF-защита и сетевой sandbox.
  5. Интеграция с внешним календарём с заранее выбранным source of truth.
  6. Ограниченный script execution в отдельном sandbox.
  7. Web search поверх отдельной политики доступа.

Система считается успешной, если пользователь может естественным языком вести планы, проекты и устойчивые предпочтения, а агент использует существующую структуру Markdown/Org, не выходит за workspace, оставляет понятный Git diff, продолжает сессию после рестарта и безопасно сериализует foreground/background работу — без обязательной предметной схемы проектов и задач.

Проверки

npm run check
npm test

Лицензия

Проект распространяется по лицензии GPL-3.0-only.