OpenHandsのコードリーディング

OpenHandsは旧名称(OpenDevin)から分かるように、Devinのオープンソース版。
最近流行っているDevinやClineなど、ソフトウェア開発に特化した自律型AIエージェントの作りが気になるので、実際のユーザーの操作に合わせてどのコードが動いているのか読み解いてみる。
レポジトリ
アーキテクチャ概要

バックエンド起動
make-backend
でバックエンドを起動してみる。
バックエンドだけ起動するのかなと思いきや、普通にフロントエンドも起動していた。http://127.0.0.1:3000
でアクセスできる。
ただ、動かそうとするとmemory/condenser
周りでエラーが起きた。
memory/condenser/__init__.py
を下記のように修正すると、動くようになった。
from openhands.memory.condenser.condenser import Condenser, get_condensation_metadata
from openhands.memory.condenser.impl import (
AmortizedForgettingCondenser,
LLMAttentionCondenser,
ImportantEventSelection,
LLMSummarizingCondenser,
NoOpCondenser,
ObservationMaskingCondenser,
RecentEventsCondenser,
)
__all__ = [
'Condenser',
'get_condensation_metadata',
'AmortizedForgettingCondenser',
'LLMAttentionCondenser',
'ImportantEventSelection',
'LLMSummarizingCondenser',
'NoOpCondenser',
'ObservationMaskingCondenser',
'RecentEventsCondenser',
]
フロントエンド起動
make-frontend
でフロントエンドを起動してみる。
ssr周りの設定でエラーが起きる。
vite.config.ts
のssrの部分を下記のように書き換えれば、起動できた。
{
// ...
ssr: {
noExternal: [
"react-syntax-highlighter",
"react-textarea-autosize",
"use-composed-ref",
],
// ...
}
UIをガチャガチャしたいわけではないので、make-backend
で起動するだけで良さそう。

タスク開始(会話開始)
/api/conversations
リクエスト:
-
_update_timestamp_for_conversation
は会話データをConversationStore
に引渡し、同期させている。
conversation_manager.maybe_start_agent_loop
:
-
Session
がAgentSession
を管理している。 -
AgentSession
がEventStream
を管理している。 -
Session.on_event
はWebsocketでイベントをFEに引渡し、UI制御を行っている。
session.initialize_agent
:
AgentStateChangedObservation
:
agent_session.event_stream.add_event(
AgentStateChangedObservation('', AgentState.LOADING),
EventSource.ENVIRONMENT,
)
agent_session.start
:
-
AgentSession
がRuntime
,AgentController
を管理している。 -
AgentController
がCodeActAgent
を管理している。 -
Runtime.on_event
はAction
を受け取り、適切なObservation
を発行する。 -
AgentController.on_event
は受け取ったイベントをもとに、CodeActAgent
での処理を制御している。
MessageAction
:
event_stream.add_event(
MessageAction(content=initial_user_msg),
EventSource.USER
)
ChangeAgentStateAction
:
event_stream.add_event(
ChangeAgentStateAction(AgentState.RUNNING),
EventSource.ENVIRONMENT
)
MessageAction
でAgentController
のon_event
発火🔥:
- 「
Action
発行」はagent.step
の結果によって、何のAction
なのか動的に決まる。ここでは、コマンド実行のAction
が発行されたとして整理している。
AgentStateChangedObservation
*1:
event_stream.add_event(
AgentStateChangedObservation('', AgentState.RUNNING),
EventSource.ENVIRONMENT,
)
AgentStateChangedObservation
*2:
self.event_stream.add_event(
AgentStateChangedObservation('', AgentState.AWAITING_USER_CONFIRMATION),
EventSource.ENVIRONMENT,
)
CmdRunAction
:
event_stream.add_event(
CmdRunAction(command=arguments['command'], is_input=is_input, confirmation_state=ActionConfirmationStatus.AWAITING_CONFIRMATION),
EventSource.ENVIRONMENT
)
agent.step
:
- 「toolsの登録」は、ミニマム構成だと、
CmdRunTool
,FinishTool
,StrReplaceEditorTool
を登録する。 -
codeact_function_calling.response_to_actions
は、llmのレスポンスのtoolsをAction
に変換する。
llmのmessages:
[
{
"role": "system",
"content": [
{
"type": "text",
"text": "{{agenthub/codeact_agent/system_prompt.j2}}"
}
]
},
{
"role": "user",
"content": [
{
"type": "text",
"text": "{{MessageAction.content}}"
},
{
"type": "text",
"text": "{{microagents/knowledge/*.md}}"
}
]
}
]

タスク承認(USER_CONFIRMED)
タスク承認:
-
frontend/src/components/shared/buttons/confirmation-buttons.tsx
からAction
を発行している。
send({
action: ActionType.CHANGE_AGENT_STATE,
args: { agent_state: AgentState.USER_CONFIRMED },
})
AgentStateChangedAction
でAgentController
のon_event
発火🔥:
pending_action
:
_pending_action.confirmation_state = ActionConfirmationStatus.CONFIRMED
_pending_action._id = None
event_stream.add_event(_pending_action, EventSource.AGENT)
AgentStateChangedObservation
:
event_stream.add_event(
AgentStateChangedObservation('', AgentState.USER_CONFIRMED),
EventSource.ENVIRONMENT,
)
pending_action(CmdRunAction)
でRuntime
のon_event
発火🔥:
TBD...
-
CmdRunAction
の場合、observation = getattr(self, action_type)(action)
でrun
を呼び出すことになる。 -
/execute_action
リクエストでActionExecutor.execute_action
を呼び出している。