Metadata-Version: 2.4
Name: local-automata
Version: 0.2.3
Summary: ローカルLLM（mlx / llama.cpp / OpenAI互換）で動く、ツール・プロファイル対応の汎用エージェントのコア
Author: ToPo-ToPo-ToPo
License-Expression: Apache-2.0
Project-URL: Repository, https://github.com/ToPo-ToPo-ToPo/local-automata
Keywords: llm,agent,coding-agent,local-llm,mlx,llama.cpp,openai
Classifier: Development Status :: 3 - Alpha
Classifier: Intended Audience :: Developers
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Topic :: Software Development
Classifier: Operating System :: OS Independent
Requires-Python: >=3.11
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: openai>=1.0
Requires-Dist: mcp>=1.27
Requires-Dist: pypdf>=4.0
Requires-Dist: pyyaml>=6.0
Requires-Dist: mlx-lm>=0.31.3; sys_platform == "darwin" and platform_machine == "arm64"
Requires-Dist: mlx-vlm>=0.5.0; sys_platform == "darwin" and platform_machine == "arm64"
Requires-Dist: mlx-whisper>=0.4; sys_platform == "darwin" and platform_machine == "arm64"
Requires-Dist: twine>=6.2.0
Provides-Extra: science
Requires-Dist: matplotlib>=3.10; extra == "science"
Requires-Dist: numpy>=2.0; extra == "science"
Requires-Dist: pandas>=2.2; extra == "science"
Requires-Dist: scikit-learn>=1.5; extra == "science"
Requires-Dist: scipy>=1.13; extra == "science"
Requires-Dist: sympy>=1.13; extra == "science"
Dynamic: license-file

# local-automata

ローカルLLM（mlx / llama.cpp）で動く、最小の汎用コーディングエージェント。
LLM とツール（ファイル読み書き・編集・検索・シェル実行）を組み合わせた agent loop が土台で、
設定（`agent.toml` ＋ 指示 `AGENTS.md`）だけでコーダー以外のエージェント
（キャラクターチャット等）にも転用できる。

> 挙動の詳細な仕様は [SPEC.md](https://github.com/ToPo-ToPo-ToPo/local-automata/blob/main/SPEC.md) を参照。

## 目次

- [クイックスタート](#クイックスタート)
- [使い方](#使い方)
- [設定（agent.toml）](#設定agenttoml)
- [ツールと安全性](#ツールと安全性)
- [機能とオプション](#機能とオプション)
- [ローカルLLMサーバー](#ローカルllmサーバー)
- [サンプル](#サンプル)
- [トラブルシューティング](#トラブルシューティング)
- [ライブラリとして使う](#ライブラリとして使う)
- [構成とテスト](#構成とテスト)

## クイックスタート

[uv](https://docs.astral.sh/uv/) を使う。3ステップで動く。

```sh
# 1. 環境構築（OS を自動判定。Apple Silicon では mlx も入る）
uv sync

# 2. ローカルLLMサーバーを起動（別ターミナルで）
uv run local-automata-server

# 3. エージェントを起動
uv run local-automata          # ターミナルで対話
uv run local-automata-web      # ブラウザのチャット GUI（http://127.0.0.1:8765）
```

モデル・接続先・ツールなどの挙動はすべて **`agent.toml`（無ければ既定値）** で決まる
（→ [設定](#設定agenttoml)）。`uv run` の代わりに `.venv` を有効化すれば
`local-automata` を直接呼べる。

> `agent.toml` に `auto_start = true` を書けば**ステップ2は不要**（サーバー未起動時に自動で立ち上げ、終了時に止める）。`examples/` の各サンプルはこの方式なので、ディレクトリで `uv run local-automata` / `local-automata-web` を実行するだけで動く。

## 使い方

### ターミナル（`local-automata`）

対話形式。プロンプトを入力するたびに、エージェントがツールを使ってタスクを進める。
会話履歴はセッション内で保持される。

```sh
uv run local-automata
# > src/ のテストを実行して落ちている箇所を直して
# > さっきの修正にテストを追加して
```

- コマンド: `/help`・`/reset`（履歴クリア）・`/img`/`/file`/`/video`（添付）・`exit`（終了、Ctrl-D も可）
- 応答はストリーミング表示（既定）。不具合が出る場合は `agent.toml` で `stream = false`。
- メモリは無駄を貯めない: 古くなったファイル全文や `edit_file` で削除したコードは自動で省略し、各ファイルの**最新の内容だけ**を履歴に残す。それでも `max_context_tokens` を超えたら、古いやり取りを要約して圧縮する。

**画像・動画・ファイルの添付**（ターミナル）:

```
> /img photo.png この画像を説明して          # vision 対応モデル＋サーバーが必要。複数はカンマ区切り、URL も可
> /video clip.mp4 何が起きてる?              # ffmpeg で代表フレームを抽出して画像化（vision モデル必要）
> /file paper.pdf この論文の要点をまとめて    # PDF はテキスト抽出。CSV/TXT/JSON/MD も可
```

> 大きなデータは添付せず `workspace/` に置き、pandas 等で処理させる方が文脈を消費せず確実。

### ブラウザ GUI（`local-automata-web`）

追加依存なし（標準ライブラリのみ）の簡易チャット画面。設定は CLI と同じ `agent.toml` から読む。

```sh
uv run local-automata-web                 # http://127.0.0.1:8765 を開く
uv run local-automata-web --port 9000 --no-browser
```

- 応答を SSE でストリーミング表示する。**生成した画像・動画は、作られた「その場」にインラインで即時表示**される（最終報告を待たず、ツール実行→生成→表示→次の処理…の順序を保つ）。生成したコード等は折り畳みブロックで表示。
- **ツール呼び出しは折り畳みブロック**で表示し、見出しの ▾ をクリックで開閉できる（生成中もユーザーの開閉状態を保持）。
- 入力欄は**複数行を自動で拡大**する（改行・長文で欄ごと広がり、画面の半分を超えたときだけスクロール）。
- 入力欄の **🖼 ボタン**（またはクリップボードからの貼り付け）で**画像を添付**して送れる。
  画像入力には vision 対応モデル・サーバーが必要（[画像・動画入力（vision）](#画像・動画入力vision)を参照）。
- **🎤 ボタン**で**音声入力**ができる（`[voice]` を有効にしたときだけ表示）。[音声入力（voice）](#音声入力voice)を参照。
- **数式**は LaTeX 記法（`$...$` / `$$...$$`）を **KaTeX** できれいに表示する（同梱・オフライン動作）。
- 生成物はセッションごとの `workspace/<日時>_<タイトル>/` に保存。「＋ New chat」で別フォルダに切替。
- **GUI/CLI の表示文言（状況・エラー・ボタン等）は英語**。LLM の生成本文は会話の言語のまま（日本語で話せば日本語で返る）。
- **待受ポートは `agent.toml` の `web_port` でも指定できる**（`--port` が優先）。複数エージェントを並走させるときはエージェントごとに分ける（→ [同一PCで複数エージェントを動かす](#同一pcで複数エージェントを動かすポートの決め方)）。重いモデル読込の前にポートの空きを確認し、使用中なら分かりやすいエラーで即終了する。
- **LLM サーバーの自動起動も CLI と同じ**。`agent.toml` に `auto_start = true` を書けば、サーバー未起動時に
  agent.toml の `model`/`backend` で自動起動し、GUI 終了時に停止する（手動で `local-automata-server` を立てなくてよい）。
- フロントエンドの静的ファイルはパッケージ内 `local_automata/frontend/` に同梱（package-data）。`pip install` だけで GUI が動く。
- **変更が反映されないとき**は、ブラウザのキャッシュが残っている場合がある。**Cmd+Shift+R（ハードリロード）**で強制再読み込みする。

## 設定（agent.toml）

モデル・パラメータ・運用設定はすべて **`agent.toml`（＋既定値）** で指定する。
**CLI 引数や環境変数では指定しない**（CLI は設定・プロファイル選択用の `--config` / `--profile` のみ）。
設定はトップレベル、または `[profiles.<name>]` 配下に書く。

```toml
# agent.toml
model = "mlx-community/Qwen3.6-27B-4bit"
base_url = "http://localhost:8080/v1"
backend = "mlx-vlm"        # mlx-vlm（vision・mac既定）/ mlx（テキスト専用・軽量）/ llama-cpp / router。省略時は OS 自動判定
temperature = 0.2
max_tokens = 16384
max_steps = 50
max_context_tokens = 16384
max_context_images = 2     # 文脈に残す画像メッセージ数。古い画像は自動で外す（-1 で無効）
image_context_mode = "recent"  # recent=単純省略 / on_demand=id を振りモデルが view_image("img_N") で選んで呼び戻す
enable_thinking = false
stream = true
tool_mode = "prompt"       # prompt（既定・自作方式）/ native（function calling）
verbose = true             # 過程を全部表示（ツール引数・結果の全文）。CLI/GUI とも既定 on。false で1行要約
allow_install = false      # run_command でのパッケージ導入を許可するか
auto_start = false         # サーバーが無ければ自動起動するか
parallel = 1               # 自動起動時の同時処理スロット数（llama.cpp）
web_port = 8765            # Web GUI のポート（--port 未指定時の既定）。エージェントごとに分けると衝突しない
workdir = "workspace"      # ツールの作業ディレクトリ（'.' で現ディレクトリ）
planning = false           # 着手前に計画を立てさせるか
```

| キー | 既定 | 説明 |
| --- | --- | --- |
| `model` | `mlx-community/Qwen3.6-27B-4bit` | モデル名/パス |
| `base_url` | `http://localhost:8080/v1` | OpenAI 互換エンドポイント |
| `backend` | OS自動（macOS arm64→`mlx-vlm`、他→`llama-cpp`） | 起動するバックエンド。mac 既定は vision 対応の `mlx-vlm`（画像・動画もそのまま動く）。テキスト専用で軽くするなら `mlx` |
| `temperature` | `0.2` | サンプリング温度 |
| `max_tokens` | `16384` | 1応答の最大生成トークン |
| `max_steps` | `50` | 1タスクの最大ツール往復回数 |
| `max_context_tokens` | `16384` | これを超えると古い履歴を要約圧縮。手前で「古いファイル全文・削除コード」を自動で刈り取る |
| `max_context_images` | `2` | 文脈に残す画像メッセージ数。古い画像は自動で外しプレースホルダ化（毎ターンの画像再送を防ぐ）。`-1` で無効 |
| `image_context_mode` | `recent` | 古い画像の扱い。`recent`=単純省略 / `on_demand`=id を振り `view_image("img_N")` でモデルが必要な画像を選んで呼び戻せる |
| `enable_thinking` | `false` | 思考(thinking)モード |
| `stream` | `true` | ストリーミング表示 |
| `request_timeout` | （無制限） | LLM 応答の読み取りタイムアウト秒数（0/未指定で無制限） |
| `mcp_call_timeout` | `120` | MCP ツール呼び出しの既定タイムアウト秒数（0/負で無制限。各サーバーの `timeout` で上書き） |
| `tool_mode` | `prompt` | ツール呼び出し方式 `prompt`（自作・既定）/ `native`（function calling） |
| `verbose` | `true` | 過程を全部表示（ツール引数・結果の全文。prompt モードは生出力も）。**CLI/GUI とも既定 on**。`false` で1行要約 |
| `allow_install` | `false` | `run_command` でのパッケージ導入許可 |
| `auto_start` | `false` | サーバー自動起動（既定は接続専用） |
| `web_port` | `8765` | Web GUI（`local-automata-web`）の待受ポート。`--port` 未指定時の既定。同一PCで複数エージェントを動かすなら分ける |
| `parallel` | （なし） | 自動起動時の並列スロット数（llama.cpp） |
| `draft_model` | （なし） | MTP ドラフター（HF id/パス、または `"auto"` で本体名から自動選択。Gemma 4 / Qwen3.6 に対応）。本体の出力を変えず高速化。指定時は本体＋ドラフターの2モデルを初回自動DL。mlx-vlm のみ（→[投機的デコード](#投機的デコードspeculative-decodingmtp)） |
| `workdir` | `workspace` | ツールの作業ディレクトリ |
| `planning` | `false` | 着手前の計画 |
| `debug` | `false` | LLM 呼び出しの所要時間・サイズや各ステップを stderr に出す |

### 同一PCで複数エージェントを動かす（ポートの決め方）

同じマシンで local-automata ベースのエージェントを複数走らせるときは、**ポート＝モデルの住所**と考えて割り当てる。リソースは2系統あり、それぞれを `agent.toml` に固定しておくと衝突しない。

- **LLM サーバー（`base_url`）= モデルごとに別ポート。** メモリが許せば 2〜3 種のモデルを別ポートで並走できる。同じモデルを使うエージェント同士は同じ `base_url` を指せば**自動で相乗り**（2本目は起動せず既存サーバーに接続）。
- **Web GUI（`web_port`）= エージェントごとに別ポート。** 設定に書いておけば `--port` を毎回渡さずに済み、衝突は起動前のプリフライト検査で分かりやすく止まる。

**ルール: 既定モデルから変える（`model` を書く）なら `base_url`（ポート）も必ず書く。** 既定モデルをそのまま使うなら `model` も `base_url` も省略でき、既定（Qwen3.6 @ `:8080`）が使われる。`model` だけ書いて `base_url` を省くと**設定エラーで止まる**（別モデルが既定ポートのモデルと取り違えられる事故を防ぐため）。

サンプル4種は衝突しないポートを割り当て済み（`examples/*/agent.toml`）:

| エージェント | モデル | `base_url`（LLM） | `web_port`（GUI） |
| --- | --- | --- | --- |
| （既定） | Qwen3.6-27B-4bit + MTP | `:8080`（省略可） | `8765` |
| coder | 既定 Qwen3.6 + MTP | `:8080`（相乗り） | `8771` |
| ml | 既定 Qwen3.6 + MTP | `:8080`（相乗り） | `8772` |
| optimizer | 既定 Qwen3.6 + MTP | `:8080`（相乗り） | `8773` |
| character（ルナ） | gemma-4-26B-A4B **qat-4bit** + MTP | `:8123` | `8770` |

coder / ml / optimizer は既定モデル（Qwen3.6）を共有するので、同時起動すると LLM サーバー1本（`:8080`）に相乗りし、GUI だけ別ポートになる。character だけ別モデル（Gemma 4）なので専用ポート `:8123`。

**指示（システムプロンプト）は Markdown** で分けて書く（Codex / Claude Code と同様）。
既定で `./AGENTS.md` を読み込み、`instructions_file` でパス変更、無ければ組み込み既定を使う。
サンプルは `AGENTS.md.example` / `agent.toml.example`。

```markdown
<!-- AGENTS.md -->
あなたは読み取り専用アシスタントです。まずファイルを読んでから判断してください。
```

## ツールと安全性

利用可能なツール: `read_file` / `write_file` / `edit_file` / `list_dir` / `grep` / `glob` / `run_command`。
`agent.toml` の `tools` で絞り込める（省略＝全部、`[]`＝無し）。

- **検索（大規模コード対応）**: `grep` は `glob`（種別絞り込み）・`ignore_case`・`context`（前後行）に対応。
  **ripgrep（`rg`）があれば自動で優先**し、高速かつ `.gitignore` 尊重で検索する（無ければ純Python で代替し、
  `node_modules`/`.git`/`.venv` 等は枝刈り）。`rg` を入れておくと巨大リポジトリでも実用的。
- **サブエージェント探索 `dispatch_agent`**: 読み取りツールがあるとき自動で使える。読み取り専用の子エージェントが
  多数のファイルを調べて**結論だけ**を返すので、本体の文脈を膨らませずに「どこに何があるか」を調査できる。

**作業ディレクトリ**:

- 既定で `./workspace`（自動作成）に閉じ込められる。`workdir = "."` で現ディレクトリ（既存プロジェクト編集用）。
- 各実行は `workspace/<日時>_<タイトル>/` に分離保存され、検討ごとに混ざらない（タイトルは最初のタスクから LLM が生成）。
- ファイルツールは workspace の外を拒否（`..`・絶対パス・シンボリックリンクをブロック）。
- `run_command` は workspace を作業ディレクトリとして実行する。ただしシェルは絶対パスや `cd` で外へ出られるため、
  これは隔離ではない。**信頼できないモデル/タスクはコンテナ/VM（できればネットワーク制限付き）で実行する**こと。
- パッケージ導入は既定でブロック（`pip/uv/conda/npm/apt …` を検出して拒否）。許可は `allow_install = true`。
  導入する場合は `uv pip install`、恒久的に必要なら `pyproject.toml` に追加して `uv sync`。

## 機能とオプション

### ツール呼び出し方式（`tool_mode`）

- **prompt（既定）**: ツール仕様をプロンプトに注入し、応答中の `tool` コードブロック（JSON）を解析する
  自作方式。**モデルの出力がそのまま見える**ので過程を確認しやすく、コード生成もストリーミングされ、
  ツール非対応モデルでも動く。形式はモデルの指示追従に依存。
- **native**: OpenAI の function calling。形式は正確だが、ツール呼び出しの中身は API 経由の構造データに
  なるため過程は見えにくい。mlx ではツール呼び出しが一括生成のためコード部分はストリーミングされない。

### 過程の表示（`verbose`）

`verbose`（**既定 true、CLI/GUI 共通**）で「何をしているか」を全部表示する: ツール呼び出しの**全引数**・
ツール結果の**全文**を出し、prompt モードでは生出力（ツール呼び出しの JSON も隠さない）を流す。
簡潔な1行要約に戻したいときは `agent.toml` で `verbose = false`。

Web GUI（`local-automata-web`）では、各ツールの呼び出し＋結果が**折り畳みブロック**になっており、
`● ツール名` の見出しをクリックすると後から畳める（既定は開いた状態）。

### 思考(thinking)モード

`enable_thinking`（既定 false）。サーバー側は `local-automata-server --thinking` / `--no-thinking` で切替。
mlx_lm.server は起動時に固定（リクエスト単位の上書き不可）、vLLM 等はリクエスト単位で反映。
MLX 変換モデルの一部はテンプレートが未対応で効かないことがある。

### 計画モード・固定ワークフロー

- `planning = true` … 着手前にタスクを分解する `update_plan`（チェックリスト）ツールを追加し、
  「計画 → 実行 → 検証」の手順を促す。多段タスクの成功率が上がりやすい。
- `workflow_file = "..."` … 決まった手順を外部ファイルで渡す。拡張子で方式が決まる。
  - `*.md` … 助言型。手順をプロンプトに足すだけ（従うかは LLM 任せ）。
  - `*.yaml` … 確実型。手順を1ステップずつ順番に実行（順序・ツール制限・やり直しを管理）。

```toml
workflow_file = "workflow.yaml"   # 確実に手順を踏ませたいとき
```

書き方・GUI での試し方・注意点は **[使い方ガイド: docs/workflows.md](docs/workflows.md)** を参照。

### 長期メモリ

`[memory] enabled = true` で、セッションをまたいで記憶する `remember` / `recall` ツールが有効になる。
`path` をプロファイルごとに分ければキャラクター単位の記憶になる。

```toml
[memory]
enabled = true
path = ".coder/memory.json"
```

### MCP サーバー

`[mcp.servers.<name>]` に `command`（ローカルプロセス＝stdio）か `url`（リモート＝Streamable HTTP）を書くと、
その MCP サーバーのツールが起動時に自動で取り込まれ、`<name>_<ツール名>` で使える（追加インストール不要）。

| キー | 対象 | 説明 |
| --- | --- | --- |
| `command` / `args` / `env` | stdio | 起動コマンド・引数・環境変数 |
| `cwd` | stdio | サーバープロセスの作業ディレクトリ（リポジトリルートでの起動が必須なサーバー向け） |
| `url` | Streamable HTTP | リモート MCP サーバーのエンドポイント |
| `timeout` | 共通 | このサーバーのツール呼び出しタイムアウト秒数。**0/負で無制限**。全体既定（`mcp_call_timeout`）を上書き |

呼び出しタイムアウトの全体既定は runtime の `mcp_call_timeout`（既定 120 秒、**0/負で無制限**）で決める。
無制限でも進捗が分かるよう、サーバーが送る進捗通知（`notifications/progress`）はログに表示し、
タイムアウト・Ctrl-C のときはサーバーへキャンセル（`notifications/cancelled`）を伝える。

```toml
# 既定タイムアウト（120 秒）を無制限にしたい場合は runtime に書く
mcp_call_timeout = 0

[mcp.servers.fs]
command = "npx"
args = ["-y", "@modelcontextprotocol/server-filesystem", "/data"]

# リポジトリルートでの起動が必須なサーバー（cwd を指定）。
# 1 回の処理が長い場合は timeout = 0 で無制限にできる。
[mcp.servers.optimizer]
command = "julia"
args = ["-t", "auto", "--project=.", "main/server/mcp_server.jl"]
cwd = "/abs/path/to/Optimizer"
timeout = 0

# リモート MCP サーバー（Streamable HTTP）。url を書くと現行標準の
# Streamable HTTP トランスポートで接続する（エンドポイントは通常 /mcp）。
[mcp.servers.remote]
url = "http://localhost:3000/mcp"
# timeout = 0        # リモートの長時間ジョブを待ち切る場合
```

### プロファイル

用途ごとに「指示（Markdown）＋ツール＋モデル・パラメータ」を1セットとして `[profiles.<name>]` に定義し、
`--profile <name>` または `default_profile` で切り替える。

```toml
default_profile = "coder"

[profiles.coder]
instructions_file = "AGENTS.md"
tools = ["read_file", "write_file", "edit_file", "list_dir", "run_command"]

[profiles.luna]            # キャラクターチャットの例
instructions_file = "characters/luna.md"
tools = []                 # ツールを使わない純粋な会話
```

```sh
uv run local-automata --profile luna
```

## ローカルLLMサーバー

`local-automata-server` が mlx / mlx-vlm / llama.cpp を OpenAI 互換 API で起動する（応答可能になるまで待つ）。
`--backend` の既定は OS 自動判定で、**macOS Apple Silicon → `mlx-vlm`（vision 対応）**、他 → `llama-cpp`。
既定モデル Qwen3.6 はマルチモーダルなので、mac では**何もせず画像・動画入力までそのまま動く**。

```sh
uv run local-automata-server                                  # 既定（mac は vision 対応の mlx-vlm ＋ 既定モデル）
uv run local-automata-server --backend mlx                    # テキスト専用で軽くしたいとき（画像は不可）
uv run local-automata-server --backend llama-cpp --model /path/to/model.gguf
uv run local-automata-server --model model.gguf -- --ctx-size 8192 --jinja   # -- 以降はバックエンド固有引数
```

- `--host` / `--port`（既定 `127.0.0.1:8080`）、`--thinking` / `--no-thinking`。
- **並列処理**: llama.cpp は `--parallel N`（continuous batching。スロット数だけ KV キャッシュを消費）。
  mlx 系は逐次処理なので `--instances N` で連番ポートに N 個起動する（重みは N 倍のメモリ）。

### 投機的デコード（speculative decoding／MTP）

小さな**ドラフター**が数トークン先読みで起案し、本体が並列に一括検証することで、**出力を変えずに
体感 1.4〜3 倍**ほど高速化する。**MTP（Multi-Token Prediction）ドラフター**を使う（Gemma 4 に加え、
既定の **Qwen3.6-27B-4bit** も対応・実測 ~2倍速）。MTP は vision 対応の **mlx-vlm** が対応する
（`uv sync` で入る）。設定に `draft_model` を足すだけで効く（画像入力でも有効）。

```toml
# agent.toml（auto_start で自動起動する場合）
backend = "mlx-vlm"
# model 省略 = 既定 Qwen3.6-27B-4bit。draft_model="auto" でその MTP ドラフターを自動選択。
draft_model = "auto"
auto_start = true
```

サーバーを手動で立てるなら、ドラフターは第一級の引数で渡せる（`--` は不要）:

```sh
uv run local-automata-server --backend mlx-vlm \
  --model mlx-community/gemma-4-E4B-it-qat-4bit \
  --draft-model auto      # または対応ドラフターの HF id を明示
```

- **ドラフター使用時は本体＋ドラフターの「2モデル」が必要**。ただし**初回に mlx-vlm が両方を自動
  ダウンロード**する（手動DL不要。事前取得は `hf download`）。追加で増えるのはドラフター分だけで小さい
  （E4B で約181MB、12B/26B-A4B でも 256〜258MB 程度。本体はどのみち必要）。
- **`draft_model = "auto"`** で本体名から対応ドラフターを自動選択する（内蔵対応表。Gemma 4 の
  E4B / 12B / 26B-A4B / 31B の mlx-community QAT＋非QAT 26B-A4B 8bit、および既定 **Qwen3.6-27B-4bit** を収載）。未収載モデルはドラフターの HF id を明示する。
- 明示する場合は**本体とドラフターのペアを揃える**（サイズ・世代がズレるとロードに失敗）。本体は
  `...-it`、ドラフターは `...-it-...-assistant-...`。12B / 26B-A4B / 31B などに差し替え可。
- **mlx-vlm バックエンド専用**（テキスト専用 `mlx` では使えない）。`draft_kind` は内部で `mtp` に固定。
  他系列のドラフター（DFlash / EAGLE3 等）は今回は対象外。
- 手早く試すなら **[サンプル: examples/coder/](examples/coder/)**（既定 Qwen3.6 + MTP の汎用コーダー）。

### 画像・動画入力（vision）

mac の既定バックエンド **`mlx-vlm`** は vision 対応なので、**画像・動画入力はそのまま使える**
（CLI の `/img` / `/video`、ブラウザ GUI の画像添付、`Agent.run(..., images=/videos=)` 共通）。
既定モデルの Qwen3.6 はマルチモーダルなので、1プロセスでテキストも画像も扱える。

> テキスト専用の `mlx` / `llama.cpp` バックエンドは画像（`image_url`）を含むリクエストでエラーになる。
> `backend = "mlx"` を選んだ場合や非 mac では、画像を使うなら `mlx-vlm`（または vision 対応サーバー）に切り替える。

- **動画**: `/video <パス>` または `Agent.run(..., videos=[...])`。**ffmpeg** で動画から代表
  フレーム（既定8枚、全体から均等サンプリング）を抜き出し、複数画像として送る（動画ネイティブ
  入力ではなくフレーム画像化での代替）。ffmpeg が無い場合は導入を促すエラーになる。
- **生成画像をエージェント自身が確認（`view_image`）**: `run_command` で作った図（matplotlib の
  コンター図など）は保存されるだけでモデルには見えない。`view_image` ツールを使うと、その画像を
  vision 入力として読み込み、**見た目を確認してから次の処理に進める**（読み取りツールがあるとき
  自動で使える。vision モデル必要）。

```sh
# mac では既定が mlx-vlm なので明示不要。非 mac やテキスト既定から切り替える場合のみ:
uv run local-automata-server --backend mlx-vlm
```

既定の `tool_mode = "prompt"` は function calling 非対応の VLM でもそのまま動く（native を使いたいときだけ `agent.toml` で指定）。

**自動振り分け（`router`）**: テキストは軽い LLM、画像を含むときだけ別の VLM——のように
モデルを使い分けたい場合は `--backend router`。テキスト LLM（mlx）と vision VLM（mlx-vlm）を
**同時に起動**し、単一のエンドポイントを公開する。受信リクエストに画像が含まれれば VLM、
無ければ LLM へ**内部で自動転送**する（クライアントは `base_url` を 1 つ指定するだけ。
2 つのプロセスを起動するためメモリは増える）。

```sh
uv run local-automata-server --backend router        # 既定はテキスト・画像とも Qwen3.6
# 使い分けるなら個別指定:
uv run local-automata-server --backend router \
  --model <テキスト用モデル> --vision-model <画像用モデル>
# 公開: http://127.0.0.1:8080/v1（agent.toml の base_url はこれ 1 つ）
```

- `--model` がテキスト用、`--vision-model` が画像用（env: `CODER_MODEL` / `CODER_VISION_MODEL`。既定はどちらも Qwen3.6）。
- 内部ではテキストを `port+1`、vision を `port+2` で起動する（公開は `--port`、既定 8080）。

### 音声入力（voice）

ブラウザ GUI のマイクボタンで話した内容を、**ローカルの mlx-whisper** で文字起こしして
入力欄へ差し込む（Apple Silicon 専用、クラウド送信なし）。エージェントのコアはテキストを
受け取るだけで、音声は入力層（GUI）でテキストに変換される。

**手順**

1. **導入**: mlx-whisper は**既定の依存**なので、Apple Silicon で `uv sync`（または `uv add local-automata`）すれば自動で入る（追加の extra は不要）。
2. **有効化**: `agent.toml`（または `[profiles.<name>]` 配下）に書く。既定は無効で、有効なときだけ 🎤 が出る:
   ```toml
   [voice]
   enabled = true
   # model = "mlx-community/whisper-large-v3-mlx"   # 省略可（既定＝最高精度の full large-v3）
   # language = "ja"                                # 指定で精度・速度が安定（省略で自動判定）
   # correct = true                                 # 文字起こしを LLM で校正（漢字の誤変換を文脈で直す）
   ```
3. **モデルの取得**（次のいずれか。HF キャッシュ `~/.cache/huggingface/hub/` に保存、以降はオフライン可）:
   - **自動**: 🎤 で一度録音・送信すれば初回にダウンロードされる（その回だけ時間がかかる）。
   - **事前取得**: 本番前に落としておく。
     ```sh
     uv run hf download mlx-community/whisper-large-v3-mlx
     ```
   - **取得＋動作確認**（おすすめ）: 無音を1回流してダウンロード＋ロード＋動作を確認。
     ```sh
     uv run python -c "from local_automata.stt import Transcriber; print('OK:', repr(Transcriber().transcribe_pcm(b'\x00'*32000)))"
     ```
4. **使う**: `uv run local-automata-web` → 🎤 を押して話す → 止めると文字起こしが入力欄に入る。

**モデルの選び方**

| モデル（`model`） | 精度 | サイズ目安 | 用途 |
| --- | --- | --- | --- |
| `mlx-community/whisper-large-v3-mlx`（既定） | ★最高 | 数 GB | 精度優先（日本語含む多言語で turbo よりわずかに上） |
| `mlx-community/whisper-large-v3-turbo` | ほぼ同等 | 約 0.8GB | 速度・省メモリ優先 |
| `mlx-community/whisper-large-v3-mlx-8bit` | やや低下 | 中 | さらに省メモリ |
| `kaiinui/kotoba-whisper-v2.0-mlx` | 日本語◎ | 中 | **日本語特化・高速**（large-v3 の約6倍速）。`language = "ja"` 推奨 |

変更したら、そのモデル名で `hf download` しておくと初回待ちが無くなる。

**漢字の誤変換を減らす（`correct = true`）**

日本語の音声認識では同音異義語・漢字の誤変換がどうしても起きる。`[voice] correct = true` にすると、
**文字起こし結果を LLM で校正**してから入力欄へ入れる（文脈から自然になるよう最小限だけ直す）。
1回 LLM 呼び出しが増えるぶん少し遅くなるが、漢字の精度が上がる。失敗時は素の文字起こしを使う。
日本語特化の `kotoba-whisper` に変える（誤りを元から減らす）のと併用も可。

**補足**

- 仕組み: ブラウザが録音を **16kHz モノラル PCM** に変換して送り、サーバーが mlx-whisper で
  文字起こしして返す（GUI 経路は ffmpeg 不要）。ライブラリから音声ファイルを直接渡す場合は
  `Transcriber.transcribe_file("clip.wav")`（任意フォーマット可・要 ffmpeg。[ライブラリとして使う](#ライブラリとして使う)）。
- マイクは https か `localhost` でのみ使える（ブラウザの制約）。既定の `127.0.0.1` ならそのまま使える。
- ダウンロード自体に ffmpeg は不要（HF からの取得は `huggingface_hub` 経由）。

### 接続のしかた

`local-automata` は起動時に `base_url` のサーバーが応答するか判定する。

- **既定（接続専用）**: 既存サーバーに相乗りする（停止しない）。無ければ起動方法を案内して終了。
- **`auto_start = true`**: サーバーが無ければ自動起動し、終了時に停止する。
- **モデルの取り違え防止**: 相乗り時、設定の `model` が**そのサーバーで提供されているか**（`/v1/models` の一覧に含まれるか）を確認する。含まれていれば警告は出ない。複数モデルを束ねる**ルーター型サーバー**（多数のモデルをカタログ公開）でも、設定モデルが一覧にあれば誤警告にならない。単一モデルサーバーがそれと違うモデルを読み込んでいる場合や、設定モデルがカタログに無い場合だけ警告する。

**複数エージェントの同時実行**: 共有サーバーを1台だけ起動し（llama.cpp は `--parallel` をエージェント数以上に）、
各ターミナルで別プロファイルを接続する。接続専用が既定なので相乗りが安全。

```sh
uv run local-automata-server --backend llama-cpp --model model.gguf --parallel 2
uv run local-automata --profile analyzer   # ターミナル1
uv run local-automata --profile luna       # ターミナル2
```

## サンプル

`examples/` に用途別のエージェント一式がある。サーバーを起動した上で、各ディレクトリで実行する。

| ディレクトリ | 内容 |
| --- | --- |
| `examples/character/` | 会話のみ（ツールなし）＋長期メモリの「ルナ」 |
| `examples/coder/` | 汎用コーディングエージェント（既定 Qwen3.6 + MTP）。作成・修正 → 実行 → 検証 |
| `examples/optimizer/` | 作成 → 実行 → 評価 → やり直しを固定ワークフローで反復 |
| `examples/ml/` | CSV から前処理 → 学習 → 評価 → 保存 |

```sh
cd examples/coder
uv run local-automata                       # CLI（agent.toml を自動読込。生成物は ./workspace/）
uv run local-automata-web                    # 同じ設定をブラウザ GUI で
uv run python run_coder.py               # ライブラリとして埋め込む例
```

詳細は各ディレクトリの `README.md` を参照。

## トラブルシューティング

| 症状 | 原因と対処 |
| --- | --- |
| `OSError: [Errno 48] Address already in use` で起動に失敗 | Web GUI のポートが他プロセスに使われている。別ポートで起動する（`--port 8770`）か、`agent.toml` に `web_port` を設定する。GUI は重いモデル読込の**前**に空きを確認し、使用中なら分かりやすいエラーで即終了する。誰が使っているかは `lsof -iTCP:8765 -sTCP:LISTEN -P -n` で確認。 |
| `Warning: ... model will be used` / `does not offer the configured model` | 既存サーバーに相乗りしており、設定の `model` がそのサーバーで提供されていない。単一モデルサーバーが別モデルを読み込んでいるか、ポートが別用途のサーバーに使われている。設定モデルを使うには、そのサーバーを止める（`kill $(lsof -ti tcp:8080)`）か、`base_url` を専用ポートに分ける。なお複数モデルを束ねる**ルーター型サーバー**で、設定モデルがカタログに含まれている場合は警告は出ない。 |
| 同一PCで複数エージェントがポート衝突する | 「ポート＝モデルの住所」で割り当てる。既定モデルを変えるなら `base_url`（別ポート）も必ず書き、GUI は `web_port` を分ける。詳細は[同一PCで複数エージェントを動かす](#同一pcで複数エージェントを動かすポートの決め方)。 |
| GUI の変更（表示・挙動）が反映されない | ブラウザがキャッシュした古い静的ファイルを見ている。**Cmd+Shift+R（ハードリロード）**で強制再読み込みする。 |
| モデルの初回応答が遅い／タイムアウトする | ローカルの巨大モデルは初回（prefill）に時間がかかる。`agent.toml` の `request_timeout` を大きく（`0` で無制限）。投機的デコード（[MTP](#投機的デコードspeculative-decodingmtp)）で `draft_model = "auto"` を足すと体感が速くなる。 |
| 編集系ツールが不安定／ツール呼び出しがうまくいかない | プロンプト方式（`tool_mode = "prompt"`）でモデルがツール JSON を崩すことがある。既定の Qwen3.6 は比較的安定。改善しない場合は `tool_mode = "native"`（function calling）を試す。 |
| 画像入力でエラーになる | vision 対応のバックエンド・モデルが必要（mac 既定の `mlx-vlm` ＋ マルチモーダルモデル）。テキスト専用 `mlx` バックエンドには画像を送れない。 |

## ライブラリとして使う

PyPI から `uv` でインストールして組み込める（自分のプロジェクトの依存に追加する）。

```sh
uv add local-automata                # コア（mac は mlx、PDF 抽出の pypdf も自動で入る）
uv add 'local-automata[science]'     # 科学計算の例も動かす（numpy/scipy/pandas/...）
# 音声入力（mlx-whisper）は既定の依存。Apple Silicon なら追加指定なしで入る
```

```python
from local_automata import Agent, Config, LLMClient, build_registry

config = Config.load()  # 既定。agent.toml の設定を使うなら Config.load(load_agent_config(...).runtime)
agent = Agent(LLMClient(config), build_registry(), config)  # 既定で workspace/ を作る
print(agent.run("..."))
```

設定ファイル（`AGENTS.md` / `agent.toml`）から組み立てるなら `load_agent_config` を使う（`examples/` 参照）。
公開 API は `local_automata.__all__` を参照。

### 入力アダプタ（音声・画像・動画）と高度ツール

**エージェントのコアはテキスト入力**。音声・画像・動画は「入力層」でテキスト/画像に変換してから
`Agent.run(...)` に渡す（外部ライブラリは独自 UI で集めた入力をこの形にするだけ）。

```python
from local_automata import (
    Agent, Config, LLMClient, build_registry, attach_agentic_tools, Transcriber,
)
from local_automata.tools import Workspace

config = Config.load()
ws = Workspace.new_session("workspace")            # 生成物の保存先（任意）
agent = Agent(LLMClient(config), build_registry(None, ws), config)
attach_agentic_tools(agent, ws)                    # view_image / dispatch_agent を有効化（任意）

# 音声入力: 録音をファイルにして渡すだけ（mlx-whisper が ffmpeg でデコード。mlx-whisper は既定で入る）。
text = Transcriber(language="ja").transcribe_file("clip.wav")  # or transcribe_pcm(16k mono f32)
agent.run(text)

# 画像・動画: パス/URL/データURI をそのまま渡す（vision モデル＝mlx-vlm 必要）
agent.run("この図を説明して", images=["plot.png"])
agent.run("何が起きてる?", videos=["clip.mp4"])    # ffmpeg で代表フレーム抽出 → 複数画像
```

- `Transcriber` … 音声→テキスト（`transcribe_file(path)` が手軽。任意フォーマット可、要 ffmpeg）。
  GUI を介さずライブラリ単体で音声を使うときの入口。変換後の文字列を `agent.run(text)` に渡す。
- `attach_agentic_tools(agent, ws)` … `view_image`（生成画像の視認）と `dispatch_agent`
  （サブエージェント探索）を後付け登録。CLI/GUI は自動で付くが、手で `Agent` を組むときに 1 行で同じに。
- `build_user_content` / `video_to_image_urls` / `to_image_url` … 入力を OpenAI 互換 content に
  変換する低レベル関数（自前で組み立てたいとき）。

> mlx は環境マーカーで判定され、**Apple Silicon のときだけ自動で入る**（`uv add local-automata`・
> `uv sync` 共通。他OSでは自動スキップ）。非mac は llama.cpp（`llama-server` バイナリ）を別途用意する。
> 開発環境（`uv sync`）はさらに科学計算ライブラリと pytest が全OSで入る。Python は `.python-version`（3.11）。

## 構成とテスト

```
AGENTS.md.example    システムプロンプト（指示 Markdown）のサンプル
agent.toml.example   設定（TOML）のサンプル
backend/local_automata/  Python パッケージ本体（PyPI に公開される）
  config.py        実行時設定（agent.toml ＋ 既定値）
  settings.py      指示 Markdown と設定 TOML の読み込み
  images.py        画像・動画・文書入力（パス/URL → content パーツ、動画フレーム抽出）
  stt.py           音声→テキスト（mlx-whisper）と LLM 校正
  tokenizer.py     トークン数の計測（コンテキスト圧縮の判定用）
  agent.py         エージェントループ（ツール・刈り取り・圧縮）
  llm.py           OpenAI 互換クライアント
  workflow.py      オーケストレーション型ワークフロー（YAML）
  cli.py           CLI / 対話モード（local-automata）
  webapp.py        Web GUI サーバー（local-automata-web）
  frontend/        Web GUI の静的ファイル（index.html / style.css / app.js ＋ vendor/katex/。同梱）
  mcp_client.py    MCP サーバー連携（ツール自動取り込み）
  server.py        ローカルLLMサーバーのランチャー
  router.py        テキスト/画像でモデルを振り分けるルーター
  server_cli.py    サーバー起動 CLI（local-automata-server）
  spinner.py       進捗表示（生成中／実行中）
  tools/           ツール群（filesystem / shell / memory / plan / workspace / subagent / vision）
tests/             pytest テスト一式
scripts/           開発用スクリプト（MCP 連携の結合確認など）
```

```sh
uv run pytest
```

MCP 連携（cwd / タイムアウト / 進捗 / キャンセル）を実 MCP サーバー込みで一括確認するには：

```sh
bash scripts/verify_mcp.sh   # uv sync → pytest → 実サーバー結合確認
```
