序
- ドキュメントは書く人によって粒度も、品質もばらつく
- 詳しい人に負荷が偏り、その人の書いた文章が他のメンバーにとって読みやすいとも限らない
- 古い記事のメンテナンスがされておらず情報が古い
- 気がつくと俺しか書いてない
チームでナレッジを運用していこうと思うと、必ずこうした壁に、少なくとも私はぶつかり続けてきました。
この「組織の情報収集・知識平準化」という課題を、人間ではなく AIエージェントの群れにナレッジを育ててもらう ことで解決できないかなぁという試みを、ここ1ヶ月ぐらいしています。
元々、エージェントハーネスをバックエンドに置いて色々やらせるシステムの実装に興味があって試してみたかったので、エージェントはHermes Agentで実装することにしました。
エージェント駆動型ナレッジベース
動作にはOpenCode GoのAPIキーが必要です
先述した課題を解決するために、そのほとんどをAIエージェントに任せるエージェント駆動型ナレッジベースを手元でちくちくと育てています。
この記事ではこのシステムを「ナレッジベース」と称していますが、実はまだ「ユーザーがナレッジを投入してエージェントが記事を書く」ストーリーを実装していません。
つまり、情報源はWebのみであり、今のところ事実上はチーム専用のニュースキュレーションツールです。
これはざっくり言うと、ユーザーとのコンタクト層&BFFとしてのWebUI(React Router v7 + Hono)、裏で色々タスクをこなすエージェントとしてHermes Agent、そしてその間を橋渡しするMCPサーバーの3つの要素から成り立ってます。
作るにあたって意識した点は以下の通り。
- 人間は記事を直接編集しない:すべての記事編集はエージェントがやります。ユーザーはWebUI経由でチャットや記事ごとのコメント欄で修正や追加の情報収集を指示します
- エージェント同士は直接呼び合わない:エージェントがエージェントを呼び出すことはせず、エージェントが他のエージェントを呼び出す場合はタスクキュー(依頼)を経由します
- エージェントハーネスがタスク実行を管理:バックエンドでHermes Agentが各エージェントをcronで呼び出します
-
記事コレクションだけでなく、エージェントも成長:各エージェントはcronで呼び出されるスキルとして実装しています。そして、Hermes AgentにはSkillを自動改善する機能があります。これにより、エージェントが調査や記事執筆のノウハウを自ら
SKILL.mdに反映することで、勝手に成長していきます - 記事コレクションが事実上のステート:色々前提があってここでは説明しにくいですが、複数ターンの作業をエージェントのステートでやりたくないので、記事のコレクションを介してできるようにした、ということです
開発経緯
グランドデザイン、技術スタック、ユーザーストーリー、非機能要件だけ真面目に書いて、あとはClaude Code(Opus 4.7→Opus 4.8)で99%ぐらい書きました
システム構成
- HermesはOpenCode Goで全て動かしています。基本的には
deepseek-v4-flashが安くて優秀、ちょっと複雑なことをするエージェントはdeepseek-v4-proを使っています -
web_searchのバックエンドとしてSearXNGをセルフホストしているのですが、ローカルからのアクセスでも結構CAPTCHAにブロックされます。VPSとかでホストしようと思ったら、Firecrawlのようなサービスが必須かもしれないです - SearXNGは検索しかできないので、
web_extract用のバックエンドが別途必要なのですが、それはminakataの中にFirecrawlの/v1/scrapeの互換APIを作って使っています
エージェントの紹介
ナレッジベースの成熟がどのように進んでいくか、エージェントを紹介しつつ説明していきます。スクリーンショットに全員入っていませんが、全部で11+1のエージェントが実装されています。
だんだん楽しくなってきて増やしすぎたきらいはありますが、これで足りているとも言い切れません。このあたりは今後も試行錯誤していきたいと思っています。
dialogue / 対話エージェント
ユーザーとの対話を担当します。1分周期でユーザーからのチャット、記事へのコメントをポーリングしています。未対応のメッセージを見つけると、その内容を質問/調査依頼/編集依頼/その他雑談に分類します。質問であれば全文検索によるRAG、調査依頼の場合はresearcherにタスクを依頼、記事の編集依頼はreviserに、雑談には適当に雑談します。
ユーザーの声を聞く「耳」というわけです。
researcher / 調査エージェント
このシステムの中核であるWeb調査と記事作成を担当します。5分周期でタスクキューをポーリングし、web_searchとweb_extractコマンドを使って情報収集、情報がまとまったら記事にしていきます。
かなり稼働頻度が高く、エージェントモニターの6割がコイツということも珍しくありません。いつもありがとうな。
- このエージェントは担当範囲が広くなってしまっているので、調査エージェントと記事編集エージェントに分けた方がよさそうな気がしています
reviser / 校訂者
ユーザーからの記事に対するコメントに対応するにあたり、追加の調査をするまでもない、軽微な編集が発生することがあります。その編集を担当するのがこのエージェントです。一旦reviserが受け取ったタスクでも、「やっぱ追加調査要りそう」となった場合は、researcherに調査と記事編集を任せます。
- このエージェントは、ポーリング周期が短い割に、あんまり頻繁に依頼が来る感じでもないという、微妙な立ち位置だったりします。記事編集エージェントにしてしまって、
researcherとペアにしてしまったほうがいいかもしれないです
daily_research / 日次情報収集
このシステムではユーザーがあらかじめ「購読」したい内容を設定できるようになっており、daily_researchは毎晩その内容についてweb_searchを実行。検索結果から良さそうなキーワードを拾ってresearcherにタスクを投げます。
これ自身が検索もするのがポイントで、単に購読に「LLM」や「生成AI」と言うキーワードを設定していたとしても、検索結果から「Claude」、「GPT」のような単語を拾えれば、「Claude」を購読していなくてもその記事ができます。
夜中の3時に、夜な夜な情報を集めてくるのでヨナです。
gap_detector / ギャップ検出
色々調査が進んでいくと、「記事によく出てくるけど、それそのものを扱っている記事がない」ということが往々にしてあります。例えば、「Vue.js」と「Svelte」の記事に「Reactとの比較」が載っていて、しかし「React」の記事がない、と言うようなケースです。こうした知識の穴を見つけて、researcherに調査を依頼します。ナレッジベースの網羅性を高めるためのエージェントです。毎朝4時に稼働します。地味ですが、点の調査を面に広げてくれる、重要なエージェントだと考えています。
freshness_checker / 鮮度管理
ナレッジを運用していて一番困るのが、古い情報が古いまま残り続けることです。freshness_checkerは記事の鮮度管理を担当。6時間周期で、作成・追加調査から一定の期間(24h,72h,168h)が経過した記事について、再調査のキューやアーカイブ化の提案をします。
- 現在、アーカイブ化はユーザーによる承認プロセスを挟んでいるのですが、そもそもそれがあった方がいいのか、試しているところです
taxonomy_builder / 分類整理
記事にはタグをつけられるようになっているのですが、それらはresearcherが記事作成のたびにつけています。すると、微妙な表記揺れとか、固有名詞の書き方によって孤立したタグがたくさんできます。これらを整理してまとめるのがこのエージェントです。週一回、月曜日の朝5時に稼働します。これは、あんまり頻繁にやっても意味がないからです。
synthesizer / 記事の体系化
個別の事例について調査サイクルが回って記事が溜まってくると、それらをまとめた記事が欲しくなることがあります。たとえば、エージェントハーネスについての記事が、「Claude Code」、「Codex」、「OpenCode」、「Hermes Agent」などとあった場合に、これらを全部統合して「AIコーディングエージェントについて」という記事を書くのがこのエージェントの仕事です。このエージェントだけは、他のエージェントより性能の高いモデルを使うようにしています。毎日23時にナレッジベース全体をチェックして、類似記事を統合、元記事はアーカイブに回します。
何か不思議なパワーで記事を合体させるようです。
changelog_writer / 日報
毎朝7:00に、昨日のナレッジベースの活動を日報にまとめます。
ある程度ナレッジが溜まってくると、日々大量の記事が生まれては合成され、アーカイブされていくので、全部追っかけようと思うと大変です。このエージェントはそれを助ける狙いです。
ところでその窓どうなってる?
feedback_analyst / フィードバック分析
記事には「いいね」ボタンやコメントでフィードバックできるような仕組みを用意しています。このエージェントは、いいねがつけられた記事の傾向や、コメントでのフィードバック内容を分析して、「執筆インサイト」を設定します。
執筆インサイトは記事を執筆する各エージェントのシステムプロンプトに反映され、より「いいね」がつきやすい(=求められている)記事が書かれるようにする狙いです。
例えば、「よくいいねされる記事には、類似フレームワークとの比較表が含まれている」といった共通点を見つけ出して、次の執筆にその知見を生かしてくれるわけです。
backup_agent / バックアップ担当
内部的に記事はMarkdownテキストで保存しています。毎日1回、記事、SQLiteデータベース、Hermesが自己改善したスキルをまとめてバックアップします。現在はバックアップにGitHubリポジトリが設定でき、空のリポジトリとアクセストークンを設定すると毎日pushします。
この子に関しては担当作業が孤立しているのであえてエージェントにする必要はなかったのですが、なんとなく…。
蔵にしまうのかな。
audit_log / システム自動処理
こいつは実際にはエージェントではないのですが、MCPツールの呼び出しやWebUIからの重要イベントの監査ログを表示したら面白いかなと思って、ビジュアライズしています。大体タスクキューをいじくっています。特にタヌキとムササビが稼働したあとに怒涛のキューイングをします。フクロウさんがかわいそうでしょ。やめなよ……。
エージェント役割の分け方
エージェントの設定は色々作りながら増えた部分もありますが、いくつかの相互作用を意識して設定しています。
知識のライフサイクルとその中の相互作用
知識を
-
生やす:
daily_research(新しいトピックを拾う)、researcher(記事を書く)、gap_detector(網羅する) -
保つ/捨てる:
freshness_checker(古さを検出して再調査・見られなくなった記事はアーカイブ)、taxonomy_builder(タグの秩序を保つ),reviser(ユーザーからの依頼で記事を編集)、feedback_analyst(フィードバック分析で新たな記事の質を向上) -
まとめる:
synthesizer(断片を上位概念に統合) -
見せる:
dialogue(人との接点)、changelog_writer(活動の可視化)
というサイクルです。ナレッジが充実していくと、ユーザーも「じゃあこれはどうなんだろう」という意欲が湧いてくると思っていて、更にナレッジが充実していく。そして、古く顧みられなくなった情報はノイズになるのでアーカイブ化して隔離する。そうすれば、ナレッジベースは腐敗することなく成熟できるのでは、という仮説です。
この1つの大きな流れの中に、細かい相互作用があります。例えば、
-
researcher↔︎gap_detector: リサーチ結果からギャップ検出、その調査を起点に更にギャップ検出 -
taxonomy_builder↔︎freshness_checker: タグが整理されることでユーザーにアクセスされやすくなり、有用な記事はアーカイブ化が遅くなる一方、それでもアクセスされない記事はつまり捨てるべき知識ということ - ユーザー →
feedback_analyst⇒ 執筆エージェント: ユーザーがコメントやいいねをすると、それをもとに執筆インサイトが更新、執筆系エージェントは更に記事の質を向上する
などなど。
疎結合なエージェントチーム
このシステムでの各エージェントは、互いの存在を知りません。gap_detector は researcher のことを知らないし、synthesizer が researcher を直接呼ぶことはありません。後述のとおり、連携はすべてキューと記事を介して間接的に行われます。ここが Minakata の設計の核心です。
タスクキューによる駆動
タスクキューは @minakata/core の TaskService(SQLite の tasks テーブル)として実装されています。これが全エージェントの駆動装置です。
enqueue(投入)と poll(消化)が非同期に分離されているのがポイントです。dialogue が調査依頼を受けたとき、researcher を同期的に呼んで結果を待つ、ということはしません。enqueue_task でキューに積むだけで即座にユーザーへ「調査タスクを追加しました」と返します。researcher は自分のペース(5 分周期)でキューを引き、処理します。
このキューには、分散キューに必要な性質がひととおり実装されています。
-
優先度つき FIFO:
urgent(対話起点の即時調査)>interactive(コメント追調査)>scheduled(日次バッチ)>maintenance(gap / synthesis の橋渡し)の順。ユーザーからの依頼は定例処理より優先して処理されるので、ムササビやタヌキが大量の調査キューを突っ込んだとしても、ユーザーがチャットから依頼すれば割り込むことができます -
type ルーティング:
poll_tasks({ types: [...] })で自分が処理すべき種別だけをclaimする。researcherはresearch/refresh/daily_research/research_followupを、dialogueはnotify_chatのタスクを拾って処理します -
冪等性(dedup_key): 同じ依頼の二重投入を
dedup_keyの UNIQUE 制約で防ぎます。アクティブな同 key タスクがあれば既存行を返し、完了済みなら key を解放して再投入を許します -
指数バックオフと DLQ: 失敗時は
30 * 2^(attempts-1)秒後に再キュー。3 回超で Dead Letter Queue へ落とし、changelog_writerが日報でlist_dlqを集計します
dedup_key の設計が「冪等な日次ループ」を作る
dedup_key の付け方には、エージェントごとに思想が表れています。
-
daily_research:daily:<topic_id>:<YYYY-MM-DD>:<slug>— 日付を含める。同じトピックの同じ発見を 1 日に二度投入しないが、翌日は別キーになるので再スキャンできる -
gap_detector:gap:<slug>:<YYYY-MM-DD>— 同上。同じ穴を同日に二度掘らない -
synthesizer:synth-research:<cluster-key>:<topic-slug>— あえて日付を含めない。橋渡し調査の依頼は「まだ満たされていない恒久的な要求」なので、毎日起動しても同じ依頼を二重投入したくない
この「日付を含めるか否か」の判断が、それぞれのエージェントの時間的な性質(毎日仕切り直すのか、満たされるまで持続するのか)を表現しています。
統合(synthesizer)と調査(researcher)の関係
synthesizer は ステートレスであることをSKILL.mdによって強制しています。
このエージェントは 状態を持たない。「前回どのクラスタを処理したか」「どの調査を依頼したか」を記憶しない。状態は 記事コレクション自体 が持つ。
synthesizer の仕事は、similar_articles のコサイン類似度で意味的に近い記事のクラスタ(相互 KNN に入っている 3 件以上)を検出し、それらを束ねる上位概念の統合記事を生成することです。ところが、クラスタを束ねる中核概念の記事が欠けていると、統合しても「断片の寄せ集め」になってしまいます。
このとき synthesizer は追加で情報を集めたいわけですが、当然researcher を同期的に呼んで調査させるということはしません。
材料が不足しているクラスタを見つけたら、
delegate_task のような同期呼び出しは使わず、
橋渡しに必要なトピックを enqueue_task(type:"research") で依頼するだけ。
そのクラスタは今回スキップする。
その後、researcherが調査依頼を実行して記事を作っていれば、翌日の synthesizer起動時にクラスタへ自然に取り込まれ、統合が前進します。synthesizerは毎晩まっさらな状態で起動し、前回の続きではなく、「いま記事コレクションがどうなっているか」だけを見て判断します。
なぜ「記事がステート」だと嬉しいのか
この設計の利点は、エージェントを純粋関数に近づけられることです。
-
再入可能:
synthesizerはいつ・何回起動されても壊れません。途中でコンテナが死んで再起動しても、「記事コレクションの現在の状態」から再計算するだけなので、中断・再開の特別な処理がいりません -
疎結合:
synthesizerとresearcherはコード上も実行上も互いを知りません。両者を繋ぐのは「タスクキューに積まれた依頼」と「記事コレクションに現れた成果」だけ。researcherを別実装に差し替えても、synthesizerは「橋渡し記事が現れたかどうか」しか見ていないので影響を受けません- 疎結合が故に、エージェントを気兼ねなく増やせてしまい、気づいたら11体も居ました
「キューと記事だけで繋ぐ」パターンは synthesizer だけでなく、システム全体がそうなってます。
-
dialogue↔︎researcher: ユーザーの調査依頼はenqueue_taskで投げます。調査完了の通知は、今度は逆にresearcherがnotify_chatタスクを積むことで、dialogueが拾います。dialogueはresearcherの完了を待ち受けておらず、自分の処理開始時に完了通知が入ってきていればそれを処理するだけです -
gap_detector→researcher: 知識グラフの穴を検出してresearchタスクを積みます。穴が埋まったかは次回起動時にfulltext_searchで再確認し、埋まっていれば自然に対象から外れます。「researcherが調査したか」をgap_detectorが知る必要はないのです -
freshness_checker→researcher: 古い記事にrefreshタスクを積みます。再調査が済んだかはlast_researched_atを見れば分かるので、これも「researcherが調査したか」をfreshness_checkerが知る必要はありません -
daily_research→researcher:daily_researchはresearcherが自分の積んだタスクを処理したかどうかを一切関知しません。ただひたすら、夜な夜なタスクを積みます
やはりresearcherは働きすぎでは?
エージェント=状態遷移関数、システム=巨大なレデューサー
もうちょっと視点を離して見ると、これらのエージェントは、「記事というステートに対する遷移関数」の集まり として整理できます。
-
researcherは「調査タスク → 記事を生やす / 追記する」遷移 -
freshness_checkerは「時間経過 → 古い記事を再調査キューへ戻す」遷移 -
synthesizerは「似た記事の集合 → 統合記事 + 元記事をアーカイブ」遷移 -
gap_detectorは「記事群 → 穴を見つけて調査タスクを積む」遷移
そして Hermes がcron jobでこれらを定期的に回しているので、システム全体としては スケジュールで駆動される巨大なレデューサー関数 のように捉えることもできます。state = reduce(state, agents) を毎日延々と回しているわけです。
freshness_checker や synthesizer のように 新しい入力がなくても状態に働きかけるエージェント がいるのもポイントです。
このシステムでは誰も依頼を出さなくても、古い記事は鮮度が下がって再調査され、似た記事が増えれば勝手に統合され、言及されただけで実体のないトピックには記事が生えてくる。これが私が目指した、放っておいても育つ(そして腐る前に片付く)ナレッジベースの姿です。
所感
自分でドッグフーディングしてますが、使っていてなかなか楽しいです。
- ナレッジが勝手に育っていく感覚は、体感でかなりあります。今朝起きたら「Claude Fable 5 完全解説:Anthropic初のMythosクラス公開モデルの性能・料金・安全機構」という記事が作られていて、それでFable 5のリリースを知りました
- エージェントの相互呼び出しをやめて、記事とキューでつながる方針にしたのはよかったと思っています。エージェントの複雑な相互作用を管理するのは大変ですし、ましてやそこに「エージェントごとの記憶」とかが絡んでくると、もはや予測不可能です
-
synthesizerは結構面白くて、フレームワークの記事が複数できるとその比較記事やユースケースの向き不向きなんかをまとめた記事ができます。知らないフレームワークをgap_detectorが見つけてきていて、しれっと比較表に入ってたりします - 人間が直接書けないのは、意外と気にならなかったです。
researcherが調査と記事作成を担うことで記事の粒度が揃っており、そもそも訂正せよという指示をあんまりしないです - ランニングコストは、購読トピック5本で24h*2週間運用して、OpenCode Goの月間使用量の20%ぐらいで収まってます。2026年6月現在の状況ですが、サブスク範囲内で割と回せる感覚です
まとめ
「組織の情報収集・知識平準化」という課題に対して、AIエージェントの群れにナレッジを育ててもらうというアプローチを試してみた話でした。
設計の核心は、
- エージェント同士を直接呼び合わせない:連携はすべてタスクキューと記事コレクションを介して間接的に行う
- 記事コレクションがステート:各エージェントは「現在の記事の状態」だけを見て判断する、再入可能な遷移関数になる
- 知識のライフサイクルで役割を分ける:生やす・保つ/捨てる・まとめる・見せる、のサイクルが回り続けることで、ナレッジベースが腐敗せずに成熟していく
の3点だと思っています。実装を進めるにつれて、だんだんエージェントが関数に見えてきました。
まだ「ユーザーがナレッジを投入する」ストーリーが未実装だったり、researcherの過労問題だったり、アーカイブの承認プロセスの要否だったり、記事の同時編集問題だったり、試行錯誤の余地はたくさん残っています。とはいえ、朝起きたら知らないリリース情報の記事が生えている体験はなかなか新鮮なので、引き続きちくちく育てていこうと思います。
似たようなことを試している方がいたら、ぜひ知見を交換させてください。












