AI agent プロダクトが成熟していくと、最も高くつくのは機能ではなく土台です。 この記事では、Orkas が 1.0 リリースラインで行った基盤リファクタリング、つまりモデル呼び出し、Agent loop、マルチ Agent 編成、tool ecosystem を丸ごと刷新した理由と、その判断の裏側を説明します。
なぜ土台に手を入れるのか
Orkas は local-first の desktop AI agent workspace です。すべての agent work はユーザー自身のマシン上の process 内で走り、データはローカルに置かれ、必要に応じて end-to-end の cloud sync を行います。初期バージョンでは機能追加が速く進みました。skill library、knowledge base、connectors、group-chat-style multi-agent。しかし進めるほど、ボトルネックは単一機能ではなく、三つの「土台レベル」の問題にあることがはっきりしました。
モデル呼び出し層が会話型の古い道を進むと、agent には合わない前提に縛られる。 大きなモデルを「一問一答の chat」として呼ぶと、chat には妥当でも agent には合わない default が紛れ込みます。固定された output token 上限、直列の tool call、見えない timeout、単一 provider への固定。Agent は何十 turn も連続して走り、context window に触れ、file を並列に読み、いつでもユーザーに止められる長い flow です。これらの default はすべて production で噛みついてきます。さらに悪いことに、desktop agent の最も価値ある能力、細かい file 操作、local search、shell 実行、parallel multi-worker、long-horizon task solving は、まさにこの層の前提で閉じ込められていました。
編成が「静的 plan」だった。 早期版は plan/DAG engine でした。まずモデルに task を plan graph へ分解させ、executor がその graph に沿って dispatch します。整って見えますが、agent の現実はかなり動的です。file を一つ読んだら方針が変わる、ある sub-task の結果で次に誰へ渡すかが決まる。判断を事前生成の graph に固定すると、「計画が現実に追いつかない」たびに executor 側へ patch を積むことになります。
ecosystem が閉じた catalog だった。 Skills は official marketplace からしか来られず、connectors は hard-coded catalog、ユーザーのマシンに既にある外部 agent tools は Orkas から見ると完全な black box でした。第三者 project、自分の MCP server、または既存の外部 agent から Orkas の skills や knowledge base を呼びたい。以前の architecture では、どれも通り道がありませんでした。
今回の refactor の論点は単純です。Agent の土台を自分たちの手に取り戻す。 具体的には、四つの線が絡み合っています。自前の in-process runtime、provider に依存しない model layer、動的な group-chat orchestration、そして closed catalog から open host への移行です。順番に見ていきます。
1. 完整な coding-agent 能力を desktop に持ち込む
Desktop は agent の主戦場です。ここには本物の file system、本物の shell、本物の local toolchain があります。chat しかできない assistant は、この環境を活かしきれていません。desktop の強みを本当に使うには、完整な coding-agent capability set が必要です。character range まで指定できる file read/write、cross-file search、bash と system tools の実行、複数 worker の parallel 実行、そして複雑な task を本当に終わらせるまで何十 turn も走れる long-horizon solving です。
Refactor の中心成果は、この能力群を Orkas 自身の process に native に持ち込む ことでした。独立して動的 load できる in-process agent runtime、code 上では core-agent と呼んでいるものです。これは単なる chat wrapper ではありません。Orkas が自分で control する agent engine です。
重要な architecture 判断は、これを 二つの層に分ける ことでした。
- Engine layer(独立 package): 純粋な agent machinery。tool-calling loop、streaming events、context compaction、error classification と retry、provider abstraction、sandbox、skill scanning、memory、self-evolution。Orkas の business を何も知りません。business data directory を読まず、会話 file format を知らず、IPC に触れません。
- Adapter layer(main process 内): engine を Orkas へつなぎます。session persistence、provider rotation、tool permissions、skill registry、connectors、knowledge base、各種 generation tools。engine の native events を Orkas 独自の event shape へ翻訳し、business layer からは stable interface だけが見えるようにします。
この engine/adapter の境界が、その後の柔軟性の根です。Engine は独立して test し evolve できます。Adapter は rotation、cooldown、sandbox、permissions といった Orkas 固有の複雑さを吸収できますが、その複雑さを engine に汚染させません。チームの architecture review はこれを一文でまとめました。これは earned complexity であり、無理に merge してはいけない。
具体的にどんな能力か
runtime を自分の手に持つ目的は、派手さではありません。agent が desktop 上で本当に「手を動かせる」ようにすることです。能力は大きく四つに分かれます。
- 細かい file 操作と local search。
read_fileは character range 指定に対応し、PDF / Office documents から text を自動抽出します。edit_fileは正確な「old string → new string」置換を行い、write 前に read を要求します。write_fileは artifact を保存して記録し、stat_fileは size を確認します。search_filesは name/glob で探し、grep_filesは file 内容を横断検索します。この一式により、agent は whole block を飲み込んで吐くのではなく、engineer のように実 workspace を調べて修正できます。 - Bash と system tools。 sandboxed shell executor は background execution mode を持ちます。長い task は current turn から離して log を file に落とせます。dangerous operations は risk grade に応じて gate されます。desktop agent の leverage の大きな部分は、system toolchain を直接使えることにあります。
- Parallel multi-worker。 一つの turn 内では、依存しない read-only tools が concurrent に実行されます。task level では、commander が独立 sub-task を複数 worker へ fan-out できます。安全なところを parallel にすることが、long-horizon task の wall-clock time を現実的な長さに圧縮します。
- Long-horizon reasoning と task solving。 何十 turn も走り、context を自分で管理し、error から回復し、同じ場所で空回りしない loop。これが「複雑な仕事を終える」と「質問に答える」の境目です。
Production grade にするための工程
「自分で loop を書く」と聞くと、面倒を背負うように見えます。実際、maintenance cost はあります。しかしその代わり、agent の lifecycle 全体を細かく control できます。この control は抽象論ではなく、production で各能力が持ちこたえるかどうかに直結する具体的な改善です。
実 context window + 80% で compaction。 Engine は各 model の実際の context window を読みます。million-token window の model も含みます。そして usage が 80% に達したときだけ compaction を trigger します。保守的に 60% で始めて有用な context の四割を捨てるのではありません。さらに「no-gain compaction」の guardrail があります。保持すべき tail 自体が window を埋めている場合、例えば大きな file read result が tail にある場合、compaction しても空きは増えません。そのときは warning を記録して skip し、summary call を空回りさせません。長い task が前の文脈を覚えていられるかは、ここにかかっています。
隣接 read-only tools を parallel に実行。 モデルが一つの turn で read-file、search-file、web lookup など互いに独立した read-only tools を並べたら、engine は隣接する parallelizable tools を batch 化して concurrent に実行します。write tools は自然な barrier になり、宣言順を保ちます。tool call と result は宣言順に commit されるため、concurrency は protocol を壊しません。最もよく使う read-only tools が serial から parallel になり、batch 全体の wall-clock time が目に見えて下がります。
Read-before-write + optimistic concurrency control。 file を edit する前には必ず read します。engine は読んだ file の baseline を記録し、edit 時に baseline が drift していないか確認します。parallel workers が同じ file を同時に変更した場合、敗者は明確な stale error を受け取り、互いの変更を silent overwrite しません。複数 worker が同じ workspace を触るなら、この保険は必須です。
途中 interrupt の即時 folding。 agent が走っている最中にユーザーが一言追加した場合、engine は tool-loop boundary でその queued message を現在の turn の input に折り込みます。別 turn として待たせません。これにより「走らせながら軌道修正する」ことが自然な interaction になります。
Loop detection。 同じ tool call が連続して繰り返された場合、engine は 3 回目で軽く促し、5 回目で hard stop します。signature が少しでも違えば count は reset されます。pagination や polling のような正当な変化は誤検知しません。モデルが詰まったとき、token を黙って燃やし続けることがなくなります。
main turn output の hard cap を外す。 主 turn の output は小さな固定上限に縛られなくなりました。長い report や大きな edit が silent truncation されません。compaction や reflection のような auxiliary call は、引き続き保守的な小さな上限を使います。
もう一つ、とても local な detail があります。中国語と英語が混ざる text の token estimation です。generic estimate は純中国語会話を二倍から三倍ほど undercount します。engine は character class ごとに中国語と英語を区別し、compaction threshold が現実に近くなるようにしています。こういうことは generic SDK が代わりに考えてはくれません。
これらを合わせると、「なぜ既製 SDK を使わないのか」への答えになります。desktop agent の最も強い能力は、SDK が露出しない層に隠れているからです。それを production grade にするには、loop を自分で握る必要があります。
2. モデルを常に online に保つ:Provider の多層 wrapper
Model layer の目的は一文です。ある key、ある provider、ある network に何が起きても、可能な限りユーザーのこの turn を生き残らせる。 そのため adapter layer は、engine の provider abstraction の上に rotation、cooldown、registration、external adaptation という数層の wrapper を重ねました。
最も重要な設計は、rotator を runner の下に置くことです。Engine は provider を呼ぶ前に、user message を persistent session に書き込みます。もし engine layer で retry/rotation を行うと、user message を重複 submit するか、session rollback を作る必要があります。rotator を engine の下に置けば、user message は一度だけ書かれ、「別 candidate で retry する」ことは session state から完全に透明になります。
rotator の判断も抑制的です。中心にあるのは first content event という線です。
- モデルが substantive content、つまり text や tool call を一切出す前に失敗した場合は、次の candidate へ安全に切り替えられます。
- first content event が出た後は rotation を止め、error を上へ投げます。モデルがすでに一 turn を実行している可能性があり、やり直すと副作用が重複するからです。
Error classification が「rotate する、rotate しない、retry する」を決めます。auth failure、insufficient balance、rate limiting、expired subscription のような account-level failure は cooldown を付けて rotate します。connection reset のような transient network failure は cooldown せず、その場で stateless retry を数回試します。malformed request、content policy、server 5xx は key を変えても同じように失敗するため、そのまま通します。cooldown は十分間の in-process, non-persistent な hint です。短期 signal にすぎないので失敗のたびに disk へ書く価値はなく、process restart は再探査にちょうどよいタイミングです。
Provider の roster 側では、三つの source を一つの abstraction へ平らにしました。
- Orkas-managed LLM: server-side proxy。sign in すれば使え、server が text/image model の間を route します。
- 自分のキーを持ち込む: 主要な LLM プロバイダーをユーザー自身の key で使います。
- 外部の直接接続アダプター: 直接接続や独自課金が必要な model 群を、同じ provider interface へ手作業でアダプトします。
上位層から見ると、すべては安定した (provider, model) pair に見えます。rotation、cooldown、external adaptation は adapter layer の中に隠れています。
3. Group chat が編成になる:static plan-DAG から commander-in-the-loop へ
今回の refactor で最も「考え方を入れ替える」部分がここです。
旧モデルは static planning でした。モデルがまず plan/DAG を生成し、executor が graph に沿って実行する。新モデルはこの graph を丸ごと外し、dynamic な commander-in-the-loop group-chat orchestration に置き換えました。
比喩は group-chat room です。
- Commander は room の host であり、見えない middleware ではありません。
- Agent workers は room の中の first-class members です。
- すべての interaction は asynchronous messages で、一つの message bus に入ります。parallel fan-out 用の private path はありません。
commander の dispatch は、本文に書かれた @somebody ではありません。LLM が body text に @AgentA と書くのは、training data に由来する markdown にすぎず、信頼できません。本物の dispatch signal は structured tool call です。refactor 後は、三つの semantic に明確な action に収束しました。
dispatch_to: agent に task を完了まで走らせ、result を返させ、commander が統合します。互いに独立した task は concurrent fan-out できます。run_worker: commander 自身が所有する sub-task です。result は synchronous に戻ります。anonymous worker は commander の「手」でユーザーには見えず、named worker は visible specialist です。hand_off_to: conversation を agent に手渡します。commander は退場し、その turn では agent が直接 user に答え、上から synthesis しません。
なぜ orchestrator や sub-agent tree ではなく group chat なのか
Multi-agent を group chat として作ると、従来の orchestrator / sub-agent tree では得にくい利点があります。
- Visibility slices。 各 message は「見えるべき相手」の slice にだけ追加されます。Agent worker が起動すると、自分の slice だけを replay します。別 agent の大きな output がその context を汚しません。commander はすべてを見ます。
- 状態が最小。 orchestration の core state は「今の floor を誰が持っているか」と軽量な task ledger だけです。DAG も複雑な state machine もありません。
- 自然に replay でき、sync できる。 Messages は timestamp で自然に並びます。reload も cross-device sync もその message stream にそのまま乗ります。mobile はこの stream で remote control します。すべての agent compute は desktop 側で走り、mobile は mirror 表示に徹するため、特別な orchestration protocol は不要です。
チームの architecture review もここは明確でした。group-chat bus と commander-in-the-loop こそが Orkas の multi-agent shape です。process 内にさらに parallel sub-agent dispatch path を重ねると、「group-chat dispatch path は一つだけ」という invariant を壊します。
この版で入った新要素:interactive hand-off
この線で新しく入った一番大きな piece は、interactive agent hand-off です。
痛点は具体的でした。「tutor」型 agent が一 turn 教えたあと、ユーザーは続けて質問したい。しかし system が floor を commander に戻してしまうため、ユーザーは毎回その agent を @ で呼び直さなければならない。
解決策は server-authoritative floor + model-decided recipient です。
- floor は persistent state field になり、reload をまたいで保存されます。既存の state-change event に乗って各端へ auto-sync されるため、新しい event type は要りません。
- commander が
hand_off_toで interactive agent に floor を渡した後、ユーザーの後続の「@ なし」message は、その agent に直接届きます。agent が自分で返すか、ユーザーが commander を再指名するまで続きます。 - agent は
<handback />marker で control を返します。parser は本当に match しているかを厳密に確認し、散文に偶然<handbackが出たものを hand-off と誤読しません。 - hand-back 時に unfinished task ledger があれば、commander は ledger から続きを拾います。
合わせて体験面の修正も入りました。commander loop bubbles です。commander が一 turn の中で「dispatch → result を読む → また dispatch」と回る loop は、以前は一つの bubble に潰され、reload すると順序も下に飛ぶことがありました。refactor では、一つの turn を可視 dispatch の boundary ごとに複数 segment へ切り、各 segment を timestamp が増える独立 message にしました。ユーザーは初めて、commander が「編成 loop を回している」ことを見られるようになり、reload order も正しくなります。
最後に、全体を貫く safety net が二つあります。group abort はすべての actor の唯一の stop path です。ユーザーが Stop を押した瞬間、全 worker の abort signal が切られ、anonymous sub-worker も fallback match で覆われます。もう一つは前述の interrupt-steer で、ユーザーの mid-run interjection を current turn へ折り込みます。
4. Closed catalog から open host へ
前の三本が土台を固める話なら、この一本は家の扉と窓を開く話です。Orkas を closed catalog から open host に変える。ただし security boundary は一寸も緩めません。
Refactor は、いくつかの「閉じた」choke point を systemically に外しました。
External packages。 ユーザーが repository address を渡すと、Orkas はそれを local folder にそのまま clone して hostします。normalize せず、rewrite せず、cloud sync もしません。中には third-party dependency directories が含まれるからです。独立した command-line tool が install/update/start-stop lifecycle を担当し、それが「skill-shaped」なのか「CLI-shaped」なのかを scan します。metadata は package directory の外側にある registry へ書きます。これにより後続の pull update と衝突しません。dependency install は「一度聞き、覚える」二段階 confirmation を通します。executable entries は shim として生成され、bash tool の PATH に注入されます。モデルは third-party CLI を直接呼べます。
Multi-root skill loading。 skill execution の single entry point は、二つの root だけを見る形から、custom / marketplace / external package / global の四 tier を priority 付きで resolve する形になりました。external package の script は package 内の dependency environment を優先します。ここは regression risk が最も高い choke point なので、fixture matrix を厚く置きました。
Global skill interop。 ユーザーのマシン上で他の agent tools が既に使っている global skill directories を直接読みます。一つの場所で蓄積した skill が Orkas でも使えるようになります。ユーザーがそこへ skill を置いた行為そのものが authorization なので default on ですが、master switch は残しています。第三者 skill description は untrusted prompt-injection surface なので、「open-tier」loader を通り、commander にだけ見え、構造上 agent の skill allowlist には入れません。
User-configured MCP。 connectors は hard-coded catalog ではなくなりました。ユーザーは任意の MCP server を追加できます。remote HTTP 形態は低リスク、local subprocess 形態は高リスクです。form 自体が consent surface で、ユーザーが手で入力した command は逐字表示されます。transport config、secret を含むものは encrypted storage に入り、custom instance には常に fixed prefix が付くため、official connector を偽装できません。
Reverse bridge: 外部 agent が Orkas を見られるようにする。 ここが最も面白い部分です。ユーザーのマシン上にある外部 agent tools は、以前 Orkas から見ると black box でした。今は Orkas がそれらを dispatch するときに bridge channel を注入し、外部 agent が逆向きに Orkas の skills を list/read/run し、connectors を呼び、knowledge base を検索できます。bridge は local inter-process channel で動き、network port は開きません。run ごとに独立し、run 終了と同時に破棄される one-time credential で認証します。external side effect を伴う connector call はすべて user confirmation dialog を通ります。tool name から read/write を heuristics で判断するのではなく、(agent, connector) ごとに一度確認し、optional に「always allow」を選べます。
Long-tail coding posture。 commander の decision tree に branch が増えました。matching agent/skill/connector がない場合、bash + short script で直接解けるか評価し、この turn で解き、output を verify し、必要なら custom skill として crystallize する提案をします。background bash execution と user-granted directories もこれに伴います。
Open だが、手放さない
扉を開くときに最も怖いのは、境界が漏れることです。今回の discipline は明確です。dangerous actions の spawn choke point は一つも動かさない。 MCP は一か所からだけ起動し、skill execution は一つの runner だけを通り、bash は一つの sandbox executor だけを通ります。その上に、何層もの defense を重ねます。
- File operations は必ず path sandbox を通ります。workspace、current attachments、ユーザーが明示的に grant した directories が対象です。credential directories、system directories、Orkas 自身の directories はgrant できません。
- Dangerous bash、つまり exfiltration、destructive deletion、privilege escalation、sensitive paths は permission confirmation を trigger します。decision は「just this once / for this run / deny」に分かれ、log は category と length だけを記録し、command text は記録しません。
- External-package install は symlink members を持つ package を fail-closed で拒否します。symlink で sandbox 外の sensitive files を scope に読み込ませるのを防ぐためです。clone source は protocol allowlist で制限します。
- credential を含む transport/secrets はすべて encrypted at rest になり、bridge credentials は run ごとに隔離されます。
- open-source / hosted distribution では、host-exclusive capabilities が trimming rule によって取り除かれます。
一文で言えば、ユーザーの明示的な行為、install / grant / form submit / confirm click が consent の credential であり、その consent は必ず相応の boundary に閉じ込められます。
5. Session をまたいで賢くなる:memory と self-evolution
Foundation overhaul に合わせて、「使うほど agent が賢くなる」二つの subsystem も作り直しました。どちらも同じ engineering discipline に従います。default off、bounded、observable です。
Cross-session memory は hybrid retrieval を使います。vector semantic search と keyword search(BM25)を行い、RRF(reciprocal rank fusion)で merge して、単一 channel の失敗を避けます。storage は local にあり、full-text index を持ちます。Memory は二種類です。agent 自身の notes と user-preference profile。どちらも character cap があり、write 前に injection threat scan を通し、各 turn 開始時に system prompt へ frozen-injected されます。この memory system は現在のユーザーを agent がより理解するためだけにあり、data は常に local にあります。ユーザーは settings から view、edit、export できます。
Self-evolution は agent-private skill library、platform shared skill library とは別のもの、と metacognitive reflection の layer です。Engine は weighted signals で reflection すべきか判断します。user correction が最も高い weight を持ち、non-trivial error からの recovery、task complexity、known weakness の trigger または overcome、skill ineffectiveness なども signal になります。weighted signals が threshold を超えたときだけ reflection します。reflection 自体は background periodic task です。約 12 時間ごとの cycle、数時間の cooldown、多日 fallback を持ち、cheap small model が recent activity summary を読み、skill を create/patch するか、agent の competence profile を更新するかを決めます。
最も重要な safety point は、self-evolution は agent が明示的に bind された session でだけ有効 ということです。default commander session は evolve しません。reflection は token の二重上限、件数と総量、を持ち、一つの agent の失敗が他を止めることもありません。single run cost は極小に押さえられます。agent を賢くする。ただし暴走させない。
Engineering philosophy: earned complexity を無理に単純化しない
Refactor 中、チームは何度も architecture review を行いました。その中で繰り返し出た結論があります。organizational hypertrophy と earned complexity を区別し、前者だけを触る。
- sync engine の複数 merge strategy、自前の agent loop、provider の multi-layer wrapper、mobile remote control の boundary。これらは複雑に見えますが、各 layer が自分の価値を稼いでいます。multi-device eventual consistency、deep integration、multi-key rotation、product が決めた end boundary。無理に「simplify」すれば、data を失い、layering を濁らせます。
- 本当に触るべきなのは god modules と local duplication です。膨らみすぎた group-chat bus から stateless pure functions、prompt assembly、commander tools、CLI turn を切り出し、何度も重複していた confirmation dialog pattern を shared component にまとめる。
この判断力を支えるのは、project constraints document に書かれた hard disciplines です。boundary、single process、IPC が唯一の通路、runtime は dynamic load のみ。layering、各 layer の dependency direction。single source of truth、categories、telemetry taxonomy、domains。prompt-facing commit には prompt audit が必要。土台を作り替えても崩れないのは、どこか一つの clever design のおかげではなく、これらの invariant が継続して守られているからです。
まとめ
四つの線をつなげて見ると、この ground-up refactor が Orkas に入れ替えたのは、self-controlled、provider-agnostic、dynamically orchestrated、外部に open、そして self-evolve できる agent foundation です。
- engine/adapter の二層 in-process runtime が、file operations、local search、system tools、parallel multi-worker、long-horizon solving という coding-agent の強みを desktop に native に持ち込み、それぞれを production grade にした。
- multi-layered model layer が、key/provider/network の揺れの中でも conversation をできるだけ生かす。
- group-chat-style、commander-in-the-loop の multi-agent orchestration が、「static planning」を「dynamic decision」に置き換え、agent 間 hand-off を初めて自然なものにした。
- closed catalog から open host へ移る ecosystem が、external packages、global skills、custom MCP、reverse bridge をすべて開いた。一方で spawn choke point は一寸も動かさなかった。
- memory と self-evolution は default off、bounded、observable に保たれている。
機能は一つずつ追加できます。しかし foundation は、一度だけ真剣に作り替える価値があります。作り替えた後は、その上に何を載せても速くなります。今回の refactor が目指した結果は、まさにそれです。