ゼロから自作するAIコーディングエージェント:学習の旅

Dev.to / 2026/4/28

💬 オピニオンDeveloper Stack & InfrastructureIdeas & Deep AnalysisTools & Practical Usage

要点

  • 2025年初頭、著者は既存ツールに頼るのではなく、ルーティングや会話管理、差分レビュー、API障害時のフォールバックなど中核部分を理解したいと考え、AIコーディングエージェントをゼロから自作することを決めました。
  • プロジェクトはPythonでTelegramからLLMへつなぐ単純なパイプラインから始まりましたが、実リポジトリに適用すると、タスクの誤解、誤ったファイル編集、ビルド破壊といった限界がすぐに露呈しました。
  • 「LLMがファイルを編集する」ことは想像以上に単純ではなく、生成したシェルコマンド(sedなど)で実現しようとすると、エスケープ問題や信頼性の面で大きなトラブルに直面した点が重要な学びでした。
  • 完成したエージェントはまだ荒削りで本番投入には適していないものの、著者はそれぞれの仕組みや失敗の理由を深く理解できたことを強調しています。

€5のVPS。Telegramボット。成長し続けるPythonスクリプト。借りるのではなく作ることを選んで学んだことの物語です。失敗だらけで、行き止まりもあり、それでもすべて。

意思決定

2025年の初め、AIコーディングツールはいたるところにありました。エディタ内のCopilot、ファイルを書き換えるCursor、そしてDevinが仕事の一連のスプリントを丸ごと置き換えると約束する。オープンソース側も同じくらい活発で、OpenClawはGitHub上で最速ダウンロードされるエージェントになり、ローカル推論に本気の人は自宅でモデルを動かすためにMac Miniを買っていました。
私は両方の選択肢に惹かれました。ですが、ずっと自分に不快な問いを投げかけていました。もしこれらのどれかを入れるだけだとして、私は本当にその仕組みを理解しているのだろうか? モデルルーティング、会話管理、diffのレビューループ、そして午前2時にAPIが落ちたときのフォールバックチェーン。そういったことを私は理解しているのか?

していませんでした。そして、そのギャップをショートカットで埋めるのではなく、しばらくそこに座り込むことにしました。ゼロから作るのがいつも正しいわけではない(たいてい違います)というのは分かった上で、それでも「作ることで学びたい」と思ったのです。出来上がりが多少荒くても。

そこで制約をいくつか決めました。安いサーバー。インターフェースはTelegramです。常にスマホで開いているから。Pythonは、よく知っているから。そしてルールとして、難しい部分が存在しないふりはしない。数ユーロ/月のHetzner VPSを立ち上げて、TelegramのボットAPIに配線し、書き始めました。

続いたのは、プロトタイプから完成品へと滑らかに進む物語ではありませんでした。つらい発見が連続し、そのひとつひとつが、他人の解決策をダウンロードするだけでは出会えない何かを教えてくれました。エージェントはいまでも荒いところがあります。プロダクション対応ではありません。そうであるとも主張していません。でも動きます。自分のものです。そして「なぜそうなるのか」をすべて理解しています。

ひどく単純に始まった

最初のバージョンは、Pythonが約80行でした。Telegramメッセージが届くと、それをAnthropicのAPIへ転送し、返信が返ってくる。記憶なし、Git連携なし、diffレビューなし。ボットアイコン付きの、ほんの少し高価なチャット画面のようなものでした。

最初に実際のリポジトリを渡したとき、完全にめちゃくちゃになりました。ボットはタスクを誤解し、間違ったファイルを編集し、ビルドを壊すコミットをしました。修正するために費やした時間は、手作業で直す場合よりも多かったです。「AIがあなたのワークフローを乗っ取る」なんて、私が想像していた瞬間ではありません。

その週末のプロトタイプは、以降すべての骨組みになりました。ただし同時に、ほぼ即座に気づかされたのが、考えが足りなかった問いです。LLMは一体どうやってファイルを「編集」するのか?

バッシュの悪夢

答えとして素直なのはシェルコマンドです。モデルに「何を変えるか」を聞き、sed の呼び出しを出力させて、subprocess.run() に通す。私はこれを試しました。結果は災害でした。

まず問題になったのがエスケープでした。ターミナルで動くsedコマンドでも、LLMが組み立ててPythonがサブプロセスへ渡すと、ランダムに失敗します。置換文字列の中の特殊文字(バックスラッシュ、アンパサンド、引用符、改行など)は、LinuxのsedとmacOSのsed、シングルクォートとダブルクォートの文脈、インラインと-iモードの違いによって異なるエスケープ規則に従います。モデルはそれらしく正しいものを生成します:

sed -i 's/def old_function/def new_function/g' src/api.py

そして次のどれかになります。何もせず黙って失敗する、黙ってファイルを壊す、あるいは、実際の問題と明確に結びつかない内容のエラーメッセージを投げる。

2つ目の問題は意味論的な精密さでした。sedはコード構文ではなく、テキストパターンを置換します。「関数のシグネチャを更新して」と伝えても、その文字列がファイル内のあらゆる場所(コメント、ドックストリング、そして似た名前の別の関数を含む)に現れていれば全部置換してしまうかもしれません。あるいは、モデルが「1つのスペース」を想定していたのに実際のファイルが「2つのスペース」だったため、何にも一致せず何も起きないこともあります。

awkも試しました。同じ種類の問題で、デバッグの学習コストがさらに高い。

モデルに、ファイルを開いて該当箇所の行を書き換える短いPythonスクリプトを出力させることも試しました。これは少しだけうまくいきましたが、編集スクリプト自体にバグがあると、その時点でエラーメッセージは元のタスクではなくメタコードについてのものになります。

これらすべてのアプローチに共通する、もっとも深い問題は、バッシュが「まさに間違った形で」ステートレスだという点です。コマンドは0で終了するか、しないか。内省(introspection)はありませんし、「この失敗のとき、本当はこうしようとしていた」という情報もない。エージェントが間違ったファイルにsedを実行した、2回実行した、そもそもまだ存在しないパスに実行したとしても、得られるシグナルは沈黙か破損だけです。

転機になったのは、あるセッションでした。エージェントが動いているファイルに構文エラーを混入させたのに、私が直してほしいと頼んだバグは直せず、その後「完了!」と自信ありげに返してきたのです。もっと良い仕組みが必要でした。

Aiderは、この状況の力学を完全に変えました。ソースファイルを生のテキストとして扱うシェルコマンドを生成するのではなく、Aiderはコードを構造化されたものとして理解します。ファイルを読み、適切な構成要素を見つけ、変更を適用し、書き戻します。失敗すると、破損したファイルや0の終了コードではなく、文脈付きで失敗します。代償として、Aiderはサブプロセスであり、自分なりの考え方、タイムアウト、そしてクセがあります。でも失敗の仕方が判読できる。判読できる失敗は直せます。黙って失敗するのは直せません。

このことから、プロジェクトの他のあらゆる場所にも当てはまる学びを得ました。「理解できる形で失敗する」ための正しいツールを選ぶこと。

モデルルーティングはプロダクトの問題

Aiderがファイル編集を担当するようになったので、次の問いは「どのLLMを背後に置くか」「そのLLMがダメな日を引いたとき、どうするか」でした。

最初はAnthropicに限定していましたが、APIクレジットは尽き、モデルも落ちます。そこで無料枠としてGroqを追加しました。そしてOpenRouterを見つけました。これは多数のモデルをプロキシする単一のAPIエンドポイントです。APIキーは1つ、クライアントクラスも1つ。文字列を変えるだけで、DeepSeek、Qwen、Mistral、そして他にも何十種類かに切り替えられます。予算の限られたVPSで小さなプロジェクトを動かしている身として、その柔軟性は重要でした。

とはいえ、「OpenRouterに切り替えればいい」と言ってしまうのは、本質的な問題を軽く見積もりすぎています。プロダクションのエージェントループでは、モデルは常に、そしてさまざまな形で失敗します:

  • プロバイダが過負荷になって返す5xxエラー
  • 30秒の待機の後にエラーを返すレートリミット
  • 200 OKを返すのに、壊れた(不正な形式の)出力を生成するモデル
  • ツール呼び出しの構文を受け付けるが、実際にはツールを実行しないモデル

素朴な実装では、これらをすべて致命的(fatal)として扱いがちです。より慎重な実装では、それぞれを別々に扱います。私は段階的なフォールバックを組みました:

プライマリモデル
  → 同じモデルをリトライ(5xxなら最大2回、指数バックオフ)
    → フォールバックモデル(例:DeepSeek → Claude Haiku)
      → Groqのサーキットブレーカー(レート制限ウィンドウの残り時間はバックオフ)

早い段階で私をつまずかせた細部がひとつあります。エージェントのループが要求する「構造化された」意味で、すべてのモデルがツール呼び出しをサポートしているわけではないという点です。それを理解できないモデルにツール利用のJSONを送っても、エラーにはなりません。壊れた返信を返すだけで、黙ってタスクを破壊します。モデルレジストリはこれを明示的に追跡します:

MODELS = {
    "deepseek": {"provider": "openrouter", "id": "deepseek/deepseek-chat",            "supports_tools": True},
    "qwen":     {"provider": "openrouter", "id": "qwen/qwen-2.5-coder-32b-instruct",  "supports_tools": False},
    "haiku":    {"provider": "anthropic",  "id": "claude-haiku-4-5-20251001",          "supports_tools": True},
}

タスクでツール呼び出しが必要で、選択したモデルがそれに対応していない場合、エージェントはユーザーに気づかせることなく(あるいはユーザーが気にすることなく)、対応しているものへと自動的に切り替えます。

Groqの回路ブレーカーは、実際に痛い問題を解決したので、ぜひ別枠で触れるべきです。Groqの無料枠は太っ腹ですが、日次のトークン制限は厳格です。早い段階では、あらゆるバックグラウンド処理(メッセージ分類、会話の圧縮、ポストタスクの振り返り)が、それぞれ独立してGroqを呼び出し、日次クォータが尽きるまで止まりませんでした。午後の半ばには、エージェントは黙ってしまいました。共有の回路ブレーカーでこの問題を解決しました。呼び出し側のどれか1つがレート制限に当たると、グローバルなフラグによって、同じバックオフ期間中に他の呼び出し側が試みることが止まります。

def groq_available() -> bool:
    return time.time() >= _GROQ_BACKOFF_UNTIL

def groq_mark_rate_limited(retry_after_seconds: float) -> None:
    global _GROQ_BACKOFF_UNTIL
    _GROQ_BACKOFF_UNTIL = time.time() + retry_after_seconds

1つの失敗が、システム全体を保護する。シンプルで、それで動きます。

The Dispatcher: Knowing When Not to Code

モデルのルーティングが安定したところで、別の問題が表面化しました。エージェントは、コーディング指示と何気ないメッセージの違いを見分けられなかったのです。

「hello」と入力するとボットはAiderにルーティングされ、Aiderはコンテキストに追加するファイルを求める返信をします。「what did you change?」と入力すると新しいジョブが始まります。すべてがタスクになってしまっていたのです。

解決策は軽量なディスパッチャでした。ルーティングの判断を行う前に高速なLLM呼び出しを1回行い、厳格に2つの出力のみ許す契約にします。

ユーザーがコーディング作業をするよう求めている → 出力するのは単語1つ: TASK
ユーザーが雑談している、質問している、または確認している → 役に立つ返信を出力する
どちらも出力してはいけません。「yes」「ok」「sure」は常に会話用です。

契約はこれほど明示的でなければならなかったのです。初期のバージョンではメッセージを正しく分類できても、その横で要約を書いてしまうことがありました。たとえば「すぐに取りかかります。TASK」のようにです。返信のどこかにTASKという単語が含まれていれば、意味のない説明のジョブを発火させてしまい、問題になりました。

ディスパッチチェーンはまずAnthropic Haikuを最初に実行します(速くて安く、分類が得意)。次にバックアップとしてGroq Llama。さらに明らかなフレーズに対する正規表現のヒューリスティック(hi, thanks, what did you do, explain)を行い、それらがすべて失敗した場合にのみ、そのメッセージをタスクとして扱うようデフォルトします。各フォールバックは、前のものより安くて単純ですが、チェーン全体としては一般的なケースを確実に処理できます。

Conversation Management: The Problem Nobody Blogs About

メッセージのルーティングを正しく行えるようにすると、1つの問題が解決されると同時に、より深い問題が見えてきました。異なるジョブ間の会話が互いに汚染し合っていたのです。

前日のセッションで、AiderがコンテキストにFlaskのファイルを追加するよう求めていた内容が、翌朝にはまったく別のジョブに漏れ込んでいました。エージェントはそれらのファイルを参照し、現在のタスクと無関係なことを求め、どのジョブを処理しているのか分かっていないかのように振る舞います。バグが見えるのは、出力が明らかに間違ったときだけでした。

問題は3つ、それぞれ別の修正でした。

セッションの流出。 ジョブ間で会話履歴がクリアされていませんでした。直前のセッションでAiderが出したノイズは、次のセッションでもコンテキストに残っていたのです。修正:新しいジョブが開始するたびにconvo_clear(user_id)を呼び出します。

コンテキスト爆発。 会話履歴はターンごとに増えていきます。40ターン後には、何千ものトークン分の古いコンテキストを新しいリクエストごとに投入してしまい、出力を悪化させるだけのトークン代を払うことになります。修正:古いターンを圧縮します。履歴が30メッセージを超えたら、古い半分を軽量なGroq呼び出しで1つの箇条書きブロックに要約します。エージェントは、無造作な生テキストの壁ではなく、過去コンテキストの要点を見ることになります。

async def _compact_history(user_id: int) -> None:
    hist      = _conversations.get(user_id, [])
    cutoff    = len(hist) - _COMPACT_AFTER
    to_squash = hist[:cutoff]
    keep      = hist[cutoff:]
    summary   = await _call_summary_llm(to_squash)
    if summary:
        _conversations[user_id] = [
            {"role": "assistant", "content": f"[Earlier conversation summary]
{summary}"}
        ] + keep

履歴の汚染。 Aider の生の出力は冗長で、ツール呼び出しのアーティファクトでいっぱいです。これをそのまま保存すると、将来のプロンプトで“信号”ではなく“ノイズ”が引き継がれてしまいます。対策: ジョブが完了したら、その結果の最初の1行だけを保存します。行は「何をしたか」を1文で要約したものにします。

この3つすべてが、エージェントが自分自身の過去と、いまあなたがやらせたいことを混同するのを止めるのに役立ちました。

Git Operations Needed Their Own Layer

コーディングタスクが確実に動くようになった次は、git 操作が壊れる番でした。チェリーピック、ブランチ比較、選択的なファイルのチェックアウト――どれも同じ壁にぶつかりました。

実行環境は意図的にシェル合成演算子をブロックします: $(), &&, |, ;。リモートサーバー上で任意のシェルパイプラインを実行するのは危険であり、制御された環境にする価値があるため、この制限はやむを得ません。問題は、あらゆる LLM の「ファイルのリストを反復する」という最初の直感が、だいたい次の形に見えることです:

for file in $(git diff --name-only origin/branch); do
    git checkout origin/branch -- "$file"
done

モデルはこれを生成し、実行環境はそれを拒否し、タスクは役に立たないエラーメッセージとともに失敗してしまいます。

これを修正するには3つの変更が必要で、そして3つとも必要でした。

まず、承認されたプランが主に git 操作であることを検出する _is_git_task() 関数を用意し、それ以外の経路へルーティングします。Aider は git checkout を扱いません。チャットセッションを開いて、追加するファイルを尋ねようとします。

次に、システムプロンプトに明示的な CRITICAL セクションを伴う git 専用のスキルブロックを注入します:

CRITICAL. No shell operators permitted.
  WRONG: git checkout $(git diff --name-only)
  WRONG: for file in $(...); do ...
  RIGHT: run "git diff --name-only" first. Read the output. Then run
         "git checkout origin/<branch> -- <file>" once per file as a separate step.

3つ目に、_inject_plan_steps() が承認されたプラン手順を受け取り、それらをタスクメッセージに番号付きの指示として直接埋め込みます。エージェントは、曖昧な説明から何をすべきかを推測する必要がありません。明確で順序立てられたコマンドが渡されます。

これらの仕組みのどれか1つ、または2つだけでは十分ではありませんでした。3つを一緒に機能させることで、git タスクが実際に確実になったのです。

The Review Loop: Staying in Control

ここまでのすべてのインフラは、1つのことを支えるために存在しています。何かがコミットされる前に、あなたがコントロールできるよう維持する“差分レビューのループ”です。

エージェントがジョブを終えたとき、すぐにコミットするのではなく、構造化されたレビューとして変更を提示します:

  • 何がどう変わったのか、そしてなぜ変わったのかの要約
  • 気づいたリスクや意図しない副作用
  • type、scope、subject、body、正当化を含む従来のコミットメッセージ

ここから、承認したり、元に戻したり、さらに話し続けたりできます。「なぜこの行を変えたの?」と聞けば、エージェントは実際の差分を文脈として使って答えます。追加の指示を出せば、別の差分を見せる前に同じブランチでさらに変更が適用されます。明示的な approve <job_id> がコミットをトリガーします。

You:  fix the auth middleware
Bot:   Plan #42 [5 steps] - reply "approve 42" to proceed
You:  approve 42
Bot:  Done. Here's what changed:
        Modified: src/auth/middleware.py, tests/test_auth.py
        feat(auth): replace session token storage to meet compliance requirements
        Risk: existing sessions will be invalidated on deploy
        Reply "approve 42" to commit, "revert 42" to cancel, or describe further changes.
You:  why did you touch line 47 in middleware.py?
Bot:  Line 47 contained the old token serialisation logic. The new approach signs
      the token with HMAC-SHA256 before storing it, which is what the compliance
      requirement specifies. I moved the signing step here so it applies to all
      token creation paths, not just the login endpoint.
You:  approve 42
Bot:  Committed. Reply "push 42" to push to origin.

これは重要です。エージェントはミスをします。必要以上に変更したり、正しいものを間違った場所に置いたりします。コミット前に差分について構造化された会話をすることで、それらのミスを“リポジトリに着地した後”ではなく“着地する前”に捕まえられます。

Memory: Not Repeating the Same Mistakes

時間が経つにつれて、悩ましいパターンに気づきました。エージェントは、すでに直されたエラーを繰り返していました。pytest を、pytest -x --no-header が必要なプロジェクトで使おうとしました。先にファイルを読まずに書き込もうとしました。自分がすでにデバッグして直したものが、次のセッションで何も起きていないかのように再び現れました。

解決策は永続的なメモリで、このプロジェクトではそれが SQLite でした。

説明するにはほとんど退屈なくらいです。SQLite はすでにサーバーに入っていて、セットアップは不要で、落ちることがなく、コストもかかりません。私は一瞬だけ代替案を見ました。Postgres は、€5 の VPS 上で動く単一ユーザーのエージェントには過剰です。Supabase のようなホスト型データベースは、依存関係を増やし、管理すべき認証情報を追加し、リモートに置くべきではないデータに対する継続的な費用が発生します。Redis なら、各タスクで数回クエリするデータに対してサブミリ秒の読み取りを提供してくれます。でも、存在しない問題を解決することになります。

SQLite は、エージェントのコードの隣に置かれたただのファイルです。バックアップは cp。検査は sqlite3 memory.db。ここで必要な複雑さのレベルはまさにこれです。

テーブルは2つ、ジョブは2つ:

lessons は時間とともに知識を蓄積します。リポジトリ固有のクセ、グローバルなパターン、何がうまくいかなかったかと、それがどう修正されたか。hit_count 列が、各レッスンがどれくらいの頻度でプロンプトに注入されるかを追跡します。役に立つレッスンは自然に上位へ上がっていきます。古くなったものは手作業によるキュレーションなしで自然に薄れていきます。各ジョブの開始時には、最も関連性の高いレッスンがそのままシステムプロンプトに投入されます。

history は直近100タスクのローリングログです。何が依頼されたか、結果はどうだったか、どのモデルが処理したかを記録します。プロセスが再起動すれば消えてしまうインメモリ状態に頼らずに、エージェントへ最近の文脈を提供します。

各タスクが完了するたびに、Groq のリフレクション呼び出しが結果からレッスンを取り出します:

REFLECT_PROMPT = """
このタスクの結果から、簡潔で実行可能なレッスンを抽出してください。以下に注目してください:
- 起きたミスと、それがどう修正されたか
- リポジトリ固有の慣習:テストコマンド、ビルドツール、ファイル構成
- うまくいったパターンで、繰り返すべきもの
有効な JSON のみを返してください: {"global_lessons": [...], "repo_lessons": [...]} 
"""

エージェントはタスク結果だけでなく、会話内容についても振り返ります。各やり取りの後、別のパスで「ユーザーがどうやって作業するのが好きか」に関する事実を抽出します。コミュニケーションのスタイル、技術レベル、好みのツールです。これらがディスパッチャの応答方法や、エージェントが時間をかけてどう伝えるかを形作ります。

Telegram を使ったままデプロイする

エージェントが十分に役立つようになり、定期的に変更を反映するようになると、小さな摩擦ポイントがじわじわと積み上がっていきました。プッシュのたびに SSH してサーバーへ入り、git pull を実行し、プロセスを再起動する必要がありました。些細に聞こえます。でも、その晩に6回目くらいからは些細ではなくなりました。

明らかな解決策は GitHub Actions です。プッシュのたびにサーバーへ SSH してサービスを再起動するワークフローを作ります。しかし、その場合はサーバーの認証情報を GitHub Secrets に保存する必要があり、Actions の実行時間も消費し、さらに個人用のツールに CI という丸ごとの層を追加することになります。このツールは計算資源として €5 分しか使わないのに。割に合いません。

代わりに、ボットに直接 /update コマンドを追加しました。

You: /update
Bot: 最新のコードを取得しています...
     git pull: 3 files changed, 47 insertions(+), 12 deletions(-)
     エージェントを再起動します...
     エージェントを再起動しました。現在実行中:bf7e8f4

裏側では、サーバー上のリポジトリで git pull を実行し、その後 systemctl restart agent を呼びます。Python で約15行です。パイプラインも、サードパーティの認証情報も、課金もありません。

ボットはすでに認証の境界になっています。どんな /update リクエストも、approverevert、それ以外のすべてと同じチェックを通過したものです。すでに用意されていて完璧に機能するチャネルがあるのに、並行するデプロイ用の経路を作る理由はありません。

ワークフローは今こうです。GitHub にプッシュして、Telegram で /update を入力して完了。サーバーは数秒で取得して再起動します。SSH も、CI も、VPS 以外のコストも不要です。

これを作って学んだこと

振り返ると、ダウンロードして済ませずに作ることを選んだことで、他の方法では得られない種類の理解が生まれました。

モデルルーティングはプロダクトの意思決定です。 各タスクに対して適切なモデルを選ぶ(分類は安くて速い、コード生成はできる、背景のリフレクションは無料枠)ことは、実際のエンジニアリング課題であり、実際のコスト影響があります。全部が自明ではありません。なぜなら、ループが3通りのやり方で壊れるのを見ない限り、気づけないからです。

会話の状態こそが、エージェントが実際に崩れる場所です。 エージェントに関する記事はどれもプロンプトの話をします。ほとんどのものは、40ターン後の会話履歴に何が起きるのか、あるいは2つのセッションが互いに滲んだときにどうなるのか、という話をしません。信頼性が生きているのはまさにそこで、うまく動いているときは完全に見えません。

レビューのループが本質であって、回避策ではありません。 コミットする前に差分(diff)について行き来しながら確認することは、エージェントに直接コミットさせるだけよりも有用です。それはミスを生みます。そして会話がそのミスを捕まえます。

安価なインフラは、思っている以上に効きます。 €5 の VPS、無料枠 Groq、SQLite、OpenRouter の「トークン課金」は、Claude Pro のサブスクリプション1つよりも月あたりの支払いが安いです。抽象化レイヤーのために払うのをやめた瞬間、経済性は驚くほど良くなりました。

学びをショートカットすることはできません。 今では、モデルのフォールバックチェーン、会話ライフサイクルの管理、ツール呼び出しのプロトコル、そして diff のレビュー・ループに関する、実際に動く理解があります。記事を読んだからではありません。まさにその場所で壊れるコードを書いて、直さなければならなかったからです。まだ学び続けています。でも、最初にいた場所と今の自分の間にあるギャップは、「作ることを選んだ」からこそ存在しています。

次にやること

まだ解決すべきことはたくさんあります。エージェントは十分にうまく動いているので定期的に使っていますが、完成したという幻想はありません。むしろ、作ってみたことで、答えられなかったよりも多くの疑問が浮かび上がりました。

現時点では、タスクがどのリポジトリに属するのかを明示してやる必要があります。常に伝えるのではなく、文脈からそれを拾ってほしいです。テスト統合も別のギャップです。つまり、変更後にテストスイートを実行して、その結果を承認を求める前に diff レビューへ含められるようにできれば、ループ全体の信頼性はもっと高まります。また、Webhook のトリガーも探ってみたいです。そうすれば GitHub へのプッシュが、私が Telegram で何かを打つことなく、直接エージェントを起動できます。

正直に言うと、使うほどにリストは増え続けています。タスクを実行するたびに、もっと良くできる何かに気づきます。それは良い兆候だと思います。

コードはところどころ雑です。一部のフォールバックロジックは、パターンマッチングと頑固さでつながっています。プロダクションレディではありませんし、そのつもりであることを装うつもりもありません。でも、これは自分のものです。理解しています。そして、それが最初に作ることを決めた理由のすべてでした。

スタック:Hetzner VPS(€4.51/月)· python-telegram-bot · Anthropic API · OpenRouter · Groq(無料枠)· Aider-chat · SQLite
コード:github.com/Teegold007/my-agents
これと似たものを作っている場合や、ここでの判断のどれかについて質問がある場合は、お気軽に連絡してください。