メインコンテンツへ

Go agent — NAS 同期エージェント

agent/ は顧客の NAS・PC 上で常駐する Go 製バイナリ(astar-agent)です。ファイルをクラウドにコピーせず、Astar の Web UI から閲覧・検索・AI 抽出できるようにするのが目的です。ファイル本体は顧客のストレージに置かれたまま、agent がメタデータ(と要求時のみコンテンツ)を backend に流します。

動き方の要点

  1. agent は backend にアウトバウンドの WSS 接続を張る(backend が agent にダイヤルインすることはない)。認証は WS 接続メッセージ内で行う in-band 方式で、HTTP ヘッダ認証ではない。
  2. 監視対象ディレクトリ(root)をスキャン・監視し、ファイルの追加・変更・削除を検知してイベントを backend に送る。
  3. backend からのコマンド(ファイル読み取り・書き込み・削除・検索)を WebSocket 経由で受け取り、ローカルファイルシステムに対して実行する。

実パッケージ一覧

ls agent/cmd/ ls agent/internal/ で確認できる実際の構成です。存在しないパッケージ名(repositories/pkg/internal/handler)を前提にしないでください。

agent/cmd/ — 補助バイナリ

パッケージ役割
fakecliローカル/dev テスト用の偽 CLI プロバイダ。claude -p の stream-json 出力を再現する
generate-messagesgo:generate codegen。asyncapi.yaml から Go の struct を生成する
mcpbridgeastar-agent mcp-bridge サブコマンドの実体。Claude Code が agent を stdio MCP サーバーとして呼ぶときの橋渡し
pion-browserWebRTC のデバッグ・ブラウザ確認用ヘルパーバイナリ

agent/internal/ — 主要パッケージ

パッケージ責務
websocketbackend との永続 WS 接続(Client)とコマンドディスパッチ(HandlerRegistry、~30 個のハンドラファイル)。1 コマンド = 1 ハンドラファイルの規約
watcherscanner.go(初期フルスキャン)→ fsnotify/fanotify/polling バックエンド(ライブ変更検知)→ file_event_debouncer.go(バースト集約)→ notifier.go(有限キュー、溢れたら古いものを破棄)のパイプライン
fileioファイルの読み取り・書き込み・削除の primitive、アップロードセッション、glob 展開
securitypath_validator.go — backend から渡されたパスをシンボリックリンク解決込みで検証し、.. トラバーサル・null byte を拒否する
webrtcNAS ファイルの直接ダウンロード機能(P2P 転送)の agent 側実装
messagesWebSocket プロトコルのメッセージ型。BaseMessage(手書き)+ messages_generated.go(自動生成、後述)
config設定の読み込み・優先順位解決、root(監視対象ディレクトリ)の定義、デバイス認証情報の保存
cliprovidersローカル CLI プロバイダ(Claude Code / Codex)のサブプロセス管理と、承認・ツール呼び出し用の stdio MCP bridge
aiprovisionerAI Member(display_name を持ち isolation_mode != NONE の ServiceAccount)のために Claude Code / Codex サブプロセスを起動する Spawner 抽象化
containerctlAI Member サブプロセスの分離実行に使うコンテナランタイム(docker / podman / containerd)を抽象化
mountrclone + FUSE で WebDAV 経由の NAS をローカルにマウントする(astar mount の実処理)
updater自己更新(version.json ポーリング、バイナリ検証、置換、Docker 環境向け更新)
docx / pdfOffice / PDF ファイルからのローカルテキスト抽出
errorsユーザー向けエラー分類(UserError — 日本語の actionable メッセージ + Unwrap() で生エラーを保持)
webuiagent 起動時にホストされるローカルセットアップ UI(登録コード入力)
logger / format / mimeutil / pathenv / parentwatch / systemdロギング・フォーマット・MIME判定・PATH補正・親プロセス監視・systemd連携の小さなインフラユーティリティ

watcher の auto モードは Linux/macOS で fsnotify に解決してはいけない。 inotify/kqueue はディレクトリ単位で watch を張るため、fs.inotify.max_user_watches の上限に達すると新規ファイルのイベントを静かに落とす。auto モードは fanotify かポーリングを選ぶ設計になっている(ASTAR_AGENT_ENABLE_FANOTIFY=0 で fanotify を強制無効化できる)。Windows の fsnotify はこの制約の対象外。

AsyncAPI プロトコル契約

WebSocket プロトコルの SSoT は Kotlin 側の AgentMessages.kt。手で agent/asyncapi.yamlmessages_generated.go を編集してはいけません。

Kotlin AgentMessages.kt (SSoT)
    │ ./gradlew generateAgentMessages
    ▼
agent/asyncapi.yaml           (自動生成)
    │ go generate ./internal/messages/...
    ▼
messages_generated.go          (自動生成)

CI がこのチェーンの再生成 diff をチェックしており、生成物が古いままだとビルドが落ちます。

multi-root 対応

agent は複数の監視対象ディレクトリ(root)を同時に扱えるよう multi-root 化されています。config.Root が 1 つの root を表し、internal/watcher/multiroot_manager.goMultiRootManager が root ごとに watcher + scanner のペアをファンアウトします。websocket.Client 側の呼び出し規約(SetFileWatcher/SetScanner/performInitialScan など)は単一インスタンス時代のまま変わっておらず、MultiRootManager がその単一インスタンスであるかのように振る舞う設計です。

パスの扱いには注意が必要です。agent 側の RelativePathbasePath 相対、backend 側の DocumentNode.relativePathworkspace 相対で、両者は直接比較できません(deviceRelativePath を介して変換する)。

開発時の起動・テスト

cd agent && go test ./...   # watcher/websocket の goleak リークテストを green に保つ
  • クロスコンパイルは agent/Makefile 経由。
  • Tauri サイドカービルドは CGO_ENABLED=0 のバイナリを frontend/src-tauri/binaries/ に生成する。
  • dev-lifecycle skill(lifecycle.sh start agent)を使うと、ソースからビルドして worktree ごとのランタイムディレクトリに配置される(Version="dev" により自己更新は無効化される)。設定ディレクトリは ~/.astar-agent-dev/<port>/(worktree slot で名前空間分離)。
  • 手動でローカル起動する場合は --config-dir を明示すること。--config-dir を省略した複数 agent は本番と同じ ~/.astar-agent/ を共有し、device-auth.json を後勝ちで上書きし合う既知のハザードがある。

ローカル Web UI のポートは本番が 31415 固定、dev/worktree インスタンスは本番 agent と衝突しないよう 31500 + slot を使う。

参照ソース

  • agent/CLAUDE.md(2026-07-03 時点の規約・構造)
  • agent/README.md(インストール手順・配布経路)
  • agent/internal/ 実コード

agent/REFACTORING.md(2025-12)は当時のリファクタ計画の歴史文書で、現在の構造とは一致しない箇所があるため参照しないでください。