ข้ามไปที่เนื้อหา

Agents

เอเจนต์คือชั้น logic เชิงตัวแทน (agentic) ของ OpenJarvis มันกำหนดว่าคำถาม จะถูกประมวลผลอย่างไร -- ไม่ว่าจะไปที่โมเดลโดยตรง, ผ่านลูปการเรียกใช้ tool, ผ่านการให้เหตุผลแบบ ReAct, การรันโค้ดแบบ CodeAct, การแตกย่อยแบบ recursive หรือ runtime ของเอเจนต์ภายนอก เอเจนต์ทั้งหมด implement ABC BaseAgent และ ลงทะเบียนผ่าน AgentRegistry

ภาพรวม

เอเจนต์ Registry Key accepts_tools หลายรอบ คำอธิบาย
SimpleAgent simple ไม่ ไม่ คำถาม-คำตอบแบบรอบเดียว
OrchestratorAgent orchestrator ใช่ ใช่ ลูปการเรียกใช้ tool แบบหลายรอบ (function_calling + structured)
NativeReActAgent native_react ใช่ ใช่ ลูป Thought-Action-Observation
NativeOpenHandsAgent native_openhands ใช่ ใช่ การรันโค้ดแบบ CodeAct + การเรียก tool
RLMAgent rlm ใช่ ใช่ Recursive LM พร้อม REPL แบบต่อเนื่อง
OpenHandsAgent openhands ไม่ ใช่ ห่อ openhands-sdk จริง
ClaudeCodeAgent claude_code ไม่ ใช่ Claude Agent SDK ผ่าน Node.js subprocess
OpenCodeAgent opencode ไม่ ใช่ เอเจนต์เขียนโค้ด opencode บน engine ในเครื่องของคุณ
OperativeAgent operative ใช่ ใช่ เอเจนต์ตั้งเวลาแบบต่อเนื่อง พร้อมจัดการ state
MonitorOperativeAgent monitor_operative ใช่ ใช่ เอเจนต์ระยะยาว พร้อม 4 แกนกลยุทธ์ที่ตั้งค่าได้

Persona แบบถาวร: SOUL.md, MEMORY.md, USER.md

System prompt ของเอเจนต์ทุกตัวถูกประกอบขึ้นตอนเริ่มบทสนทนาโดย SystemPromptBuilder ซึ่งแทรกไฟล์ Markdown เสริมได้สูงสุดสามไฟล์ -- persona แบบถาวร ไฟล์เหล่านี้เป็นข้อความล้วนที่คุณเป็นเจ้าของและแก้ไขได้ ถูกโหลด ตอนเริ่มบทสนทนาแต่ละครั้ง ไม่มี vector database หรือ embedding cache เบื้องหลัง

ไฟล์ สิ่งที่เก็บไว้ ตัวอย่างบรรทัด
SOUL.md เอเจนต์ควรมีพฤติกรรมอย่างไร -- โทน, ความยาว, สิ่งที่ควรทักท้วง Be concise. Challenge weak assumptions.
MEMORY.md ข้อเท็จจริงเกี่ยวกับคุณ, โปรเจกต์ของคุณ, ความชอบของคุณ I deploy to Postgres, never MySQL.
USER.md คุณคือใคร -- role, ทีม, บริบท Backend engineer at Acme, on the payments team.

Persona นี้แตกต่างจาก memory backend แบบ retrieval: persona คือบริบท Markdown ที่เปิดใช้งานตลอดเวลา ถูกโหลดเข้า prompt ในขณะที่ memory backend คือที่เก็บระยะยาวที่ค้นหาได้ ซึ่งเอเจนต์ query ตามความต้องการ

อยู่ที่ไหน

โดยค่าเริ่มต้น ไฟล์เหล่านี้ถูกอ่านจากไดเรกทอรี config:

~/.openjarvis/SOUL.md
~/.openjarvis/MEMORY.md
~/.openjarvis/USER.md

(ไดเรกทอรี config เคารพ $OPENJARVIS_HOME / $XDG_DATA_HOME เมื่อตั้งค่าไว้) Path เหล่านี้ตั้งค่าได้ใต้ [memory_files]:

[memory_files]
soul_path    = "~/.openjarvis/SOUL.md"
memory_path  = "~/.openjarvis/MEMORY.md"
user_path    = "~/.openjarvis/USER.md"
persona_name = ""    # optional named persona -- see below

โหลดอย่างไร

ตอนเริ่มบทสนทนาแต่ละครั้ง SystemPromptBuilder จะอ่านแต่ละไฟล์เป็น UTF-8 และเพิ่มเนื้อหาเป็นส่วนหนึ่งของ system prompt หลังจาก template ของเอเจนต์ และก่อนแคตตาล็อก skill:

  • ทั้งสามไฟล์เป็นตัวเลือกเสริม ไฟล์ที่ไม่มีหรือว่างเปล่าจะถูกข้าม ดังนั้น subset ใดก็ใช้งานได้ และการติดตั้งที่ไม่มีไฟล์ persona เลยจะทำงานเหมือนเดิมทุกประการ
  • การแก้ไขมีผลกับบทสนทนาถัดไป ไฟล์ถูกอ่านครั้งเดียวตอนสร้าง prompt ของบทสนทนา จึงไม่ต้อง restart หรือ re-index -- แก้หรือลบบรรทัดแล้วมีผลทันทีในบทสนทนาถัดไป
  • แต่ละส่วนมีการจำกัดความยาว ไฟล์จะถูกตัดให้อยู่ในงบตัวอักษรต่อส่วน เพื่อไม่ให้ MEMORY.md ขนาดใหญ่แย่งพื้นที่ prompt ส่วนที่เหลือ

Persona แบบมีชื่อ

การติดตั้งเดียวตอบเป็น persona ที่ต่างกันได้โดยไม่ต้องเปลี่ยน config ทั้งระบบ Persona แบบมีชื่ออยู่ในไดเรกทอรีของตัวเอง:

~/.openjarvis/personas/<name>/SOUL.md
~/.openjarvis/personas/<name>/MEMORY.md
~/.openjarvis/personas/<name>/USER.md

เลือกได้ต่อการเรียกใช้ หรือปิดใช้งานทั้งหมดก็ได้:

jarvis ask --persona work  "summarize my open PRs"
jarvis ask --persona none  "what is 2 + 2?"     # inject no persona

ตั้งค่า persona_name ใต้ [memory_files] เพื่อให้ persona แบบมีชื่อเป็นค่า เริ่มต้น persona_name = "none" (เทียบเท่ากับ --persona none) ปิดการแทรก persona สำหรับการรันนั้น

แก้ไขไฟล์เหล่านี้

SOUL.md, MEMORY.md และ USER.md เป็น Markdown ล้วน -- เปิดด้วย editor ใดก็ได้ MEMORY.md และ USER.md ยังอัปเดตได้โดยเอเจนต์เองผ่าน tool memory_manage และ user_profile_manage เมื่อเปิดใช้งาน tool เหล่านั้น ทำให้เอเจนต์บันทึกข้อเท็จจริงใหม่ระหว่างบทสนทนาได้ tool เหล่านี้เล็งเป้าไป ที่ MEMORY.md และ USER.md เริ่มต้นเสมอ (ใต้ ~/.openjarvis/) ไม่ใช่สำเนา ของ persona แบบมีชื่อ -- แก้ไขไฟล์เหล่านั้นด้วยตนเอง


BaseAgent ABC

เอเจนต์ทั้งหมด extend คลาส abstract BaseAgent

from abc import ABC, abstractmethod
from openjarvis.agents._stubs import AgentContext, AgentResult

class BaseAgent(ABC):
    agent_id: str
    accepts_tools: bool = False

    def __init__(
        self,
        engine: InferenceEngine,
        model: str,
        *,
        bus: Optional[EventBus] = None,
        temperature: float = 0.7,
        max_tokens: int = 1024,
    ) -> None: ...

    @abstractmethod
    def run(
        self,
        input: str,
        context: AgentContext | None = None,
        **kwargs,
    ) -> AgentResult:
        """Execute the agent on the given input."""

Class attribute accepts_tools ควบคุมว่าเอเจนต์รับ tool ผ่าน --tools บน CLI หรือ tools= ใน SDK ได้หรือไม่ เอเจนต์ที่มี accepts_tools = False จะไม่สนใจ argument ของ tool

BaseAgent ยังมี helper method ที่ implement แล้ว (_emit_turn_start, _emit_turn_end, _build_messages, _generate, _max_turns_result, _strip_think_tags) ที่ subclass ใช้เพื่อไม่ต้องเขียน logic ที่ซ้ำกัน ดู เอกสารสถาปัตยกรรม สำหรับรายละเอียด

ToolUsingAgent คือคลาส base ระดับกลาง (extend BaseAgent) ที่ตั้งค่า accepts_tools = True และเพิ่ม ToolExecutor กับขีดจำกัดลูป max_turns เอเจนต์ที่ใช้ tool ทั้งหมด extend คลาสนี้

AgentContext

Context ตอน runtime ที่ส่งให้เอเจนต์ในแต่ละการเรียก

ฟิลด์ ชนิด คำอธิบาย
conversation Conversation ประวัติข้อความ (เติมล่วงหน้าด้วยบริบทหากการแทรก memory ทำงานอยู่)
tools list[str] ชื่อ tool ที่เอเจนต์ใช้ได้
memory_results list[Any] ผลลัพธ์การดึงข้อมูล memory ที่ดึงมาล่วงหน้า
metadata dict[str, Any] Metadata ใดๆ สำหรับการรัน

AgentResult

ผลลัพธ์ที่คืนมาหลังจากเอเจนต์รันเสร็จ

ฟิลด์ ชนิด คำอธิบาย
content str ข้อความคำตอบสุดท้าย
tool_results list[ToolResult] ผลลัพธ์จากการรัน tool ระหว่างการรัน
turns int จำนวนรอบ (การเรียก inference) ที่ใช้
metadata dict[str, Any] Metadata ใดๆ เกี่ยวกับการรัน

SimpleAgent

SimpleAgent คือเอเจนต์แบบรอบเดียวที่ส่งคำถามตรงไปยัง inference engine และ คืนคำตอบ ไม่รองรับการเรียกใช้ tool

ทำงานอย่างไร:

  1. สร้างรายการข้อความจากบริบทของบทสนทนา (ถ้ามี) รวมกับคำถามของผู้ใช้
  2. เรียก inference engine ผ่าน _generate()
  3. คืนคำตอบเป็น AgentResult พร้อม turns=1

พารามิเตอร์ของ Constructor:

พารามิเตอร์ ชนิด ค่าเริ่มต้น คำอธิบาย
engine InferenceEngine -- Inference engine ที่จะใช้
model str -- Identifier ของโมเดล
bus EventBus None Event bus สำหรับ telemetry
temperature float 0.7 Temperature ของ sampling
max_tokens int 1024 จำนวน token สูงสุดที่จะสร้าง

เมื่อไหร่ควรใช้: สำหรับคำถาม-คำตอบแบบตรงไปตรงมา ที่ไม่ต้องเรียก tool หรือ ให้เหตุผลแบบหลายรอบ


OrchestratorAgent

OrchestratorAgent คือเอเจนต์แบบหลายรอบที่ implement ลูปการเรียกใช้ tool เป็นเอเจนต์หลักสำหรับคำถามที่ต้องการการคำนวณ, การดึงความรู้ หรือการให้เหตุผล แบบมีโครงสร้าง Extend ToolUsingAgent

ทำงานอย่างไร:

  1. สร้างรายการข้อความเริ่มต้นจากบริบทและคำถามของผู้ใช้
  2. ส่งข้อความพร้อมนิยาม tool (รูปแบบ function-calling ของ OpenAI) ไปยัง engine
  3. หาก engine ตอบกลับด้วย tool_calls ToolExecutor จะ dispatch แต่ละการเรียก
  4. ผลลัพธ์ tool จะถูกต่อไว้เป็นข้อความ TOOL และลูปจะทำงานต่อ
  5. หากไม่มี tool_calls คืนมา คำตอบจะถือว่าเป็นคำตอบสุดท้าย
  6. ลูปจะหยุดหลังจากผ่านไป max_turns รอบ (ค่าเริ่มต้น: 10) โดยคืนเนื้อหาที่มีอยู่พร้อม metadata flag max_turns_exceeded

พารามิเตอร์ของ Constructor:

พารามิเตอร์ ชนิด ค่าเริ่มต้น คำอธิบาย
engine InferenceEngine -- Inference engine ที่จะใช้
model str -- Identifier ของโมเดล
tools list[BaseTool] [] Instance ของ tool ที่จะเปิดให้ใช้
bus EventBus None Event bus สำหรับ telemetry
max_turns int 10 จำนวนรอบการเรียกใช้ tool สูงสุด
temperature float 0.7 Temperature ของ sampling
max_tokens int 1024 จำนวน token สูงสุดที่จะสร้าง
mode str "function_calling" โหมดการเรียก tool (function_calling หรือ structured)
system_prompt str None System prompt กำหนดเอง

เมื่อไหร่ควรใช้: สำหรับคำถามที่ต้องการการคำนวณ, ค้นหา memory, เรียกโมเดล ย่อย, อ่านไฟล์ หรือการให้เหตุผลแบบหลายขั้นตอน

ลูปการเรียกใช้ Tool

orchestrator ทำตามข้อตกลง function-calling ของ OpenAI engine ต้องรองรับการคืน tool_calls ในผลลัพธ์ ลูปจึงจะทำงาน หากส่ง tool ไปแล้วแต่ engine ไม่คืน tool call ใดๆ เอเจนต์จะทำงานเหมือนเอเจนต์แบบรอบเดียว


NativeReActAgent

NativeReActAgent implement ลูป Thought-Action-Observation ตามรูปแบบ ReAct มัน prompt LLM ให้สร้างผลลัพธ์แบบมีโครงสร้าง (Thought:, Action:, Action Input:, Final Answer:) และ parse คำตอบเพื่อขับเคลื่อนการรัน tool Extend ToolUsingAgent

ทำงานอย่างไร:

  1. สร้าง system prompt พร้อมคำอธิบาย tool แบบเสริมข้อมูล (ชื่อ, parameter schema, หมวดหมู่) ผ่าน build_tool_descriptions() การ parse ไม่สนใจตัวพิมพ์เล็ก/ใหญ่
  2. สร้างคำตอบและ parse ผลลัพธ์ที่มีโครงสร้างแบบ ReAct
  3. หากพบ Final Answer: คืนค่านั้น
  4. หากพบ Action: รัน tool และป้อนผลลัพธ์กลับเป็น Observation:
  5. วนซ้ำจนกว่าจะได้คำตอบสุดท้าย หรือเกิน max_turns

พารามิเตอร์ของ Constructor:

พารามิเตอร์ ชนิด ค่าเริ่มต้น คำอธิบาย
engine InferenceEngine -- Inference engine ที่จะใช้
model str -- Identifier ของโมเดล
tools list[BaseTool] [] Instance ของ tool ที่จะเปิดให้ใช้
bus EventBus None Event bus สำหรับ telemetry
max_turns int 10 จำนวนรอบการให้เหตุผลสูงสุด
temperature float 0.7 Temperature ของ sampling
max_tokens int 1024 จำนวน token สูงสุดที่จะสร้าง

เมื่อไหร่ควรใช้: สำหรับคำถามที่ได้ประโยชน์จากการให้เหตุผลทีละขั้นตอนแบบ ชัดเจนร่วมกับการใช้ tool ที่คุณต้องการเห็นกระบวนการคิดของเอเจนต์

ความเข้ากันได้ย้อนหลัง

Registry alias "react" ชี้ไปยัง NativeReActAgent การ import แบบเดิม from openjarvis.agents.react import ReActAgent ก็ยังใช้งานได้เช่นกัน


NativeOpenHandsAgent

NativeOpenHandsAgent คือเอเจนต์แบบ CodeAct ที่สร้างและรันโค้ด Python ควบคู่กับการเรียก tool แบบมีโครงสร้าง มันดึงเนื้อหา URL จาก input ของผู้ใช้ ล่วงหน้าเพื่อให้บริบทตรงกับ LLM ได้ด้วย Extend ToolUsingAgent

ทำงานอย่างไร:

  1. สร้าง system prompt แบบละเอียดพร้อมคำอธิบาย tool แบบเสริมข้อมูล (ผ่าน builder build_tool_descriptions() ที่ใช้ร่วมกัน) และคำสั่งการรันโค้ด
  2. ดึงเนื้อหา URL ใดๆ ใน input ของผู้ใช้ล่วงหน้า แล้วแทรกเนื้อหาเข้าไปโดยตรง
  3. ในแต่ละรอบ สร้างคำตอบและพยายามดึง code block หรือ tool call ออกมา
  4. โค้ดถูกรันผ่าน code_interpreter; tool call ถูก dispatch ผ่าน ToolExecutor
  5. หากไม่พบทั้งคู่ คืนเนื้อหาเป็นคำตอบสุดท้าย

พารามิเตอร์ของ Constructor:

พารามิเตอร์ ชนิด ค่าเริ่มต้น คำอธิบาย
engine InferenceEngine -- Inference engine ที่จะใช้
model str -- Identifier ของโมเดล
tools list[BaseTool] [] Instance ของ tool ที่จะเปิดให้ใช้
bus EventBus None Event bus สำหรับ telemetry
max_turns int 3 จำนวนรอบสูงสุด
temperature float 0.7 Temperature ของ sampling
max_tokens int 2048 จำนวน token สูงสุดที่จะสร้าง

เมื่อไหร่ควรใช้: สำหรับคำถามที่เกี่ยวกับเนื้อหา URL, การรันโค้ด หรืองาน ที่ LLM เขียนและรัน Python เพื่อแก้ปัญหาได้


RLMAgent

RLMAgent implement การแตกย่อยแบบ recursive ผ่าน REPL แบบต่อเนื่อง อิงจาก paper RLM บริบทถูกเก็บเป็นตัวแปร Python แทนที่จะถูกแทรกเข้า prompt ทำให้ ประมวลผล input ยาวเท่าใดก็ได้ผ่านการเรียก sub-LM แบบ recursive Extend ToolUsingAgent

ทำงานอย่างไร:

  1. สร้าง REPL แบบต่อเนื่องพร้อม callback llm_query() และ llm_batch()
  2. แทรกบริบทจาก AgentContext เข้า REPL เป็นตัวแปร
  3. สร้างโค้ดและรันใน REPL
  4. หากมีการเรียก FINAL(value) คืนค่านั้นเป็นคำตอบสุดท้าย
  5. หากไม่พบ code block ถือเนื้อหาเป็นคำตอบข้อความโดยตรง

พารามิเตอร์ของ Constructor:

พารามิเตอร์ ชนิด ค่าเริ่มต้น คำอธิบาย
engine InferenceEngine -- Inference engine ที่จะใช้
model str -- Identifier ของโมเดล
tools list[BaseTool] [] Instance ของ tool (เสริม)
bus EventBus None Event bus สำหรับ telemetry
max_turns int 10 จำนวนรอบการรันโค้ดสูงสุด
temperature float 0.7 Temperature ของ sampling
max_tokens int 2048 จำนวน token สูงสุดที่จะสร้าง
sub_model str เหมือน model โมเดลสำหรับการเรียก sub-LM
sub_temperature float 0.3 Temperature สำหรับการเรียก sub-LM
sub_max_tokens int 1024 Token สูงสุดสำหรับการเรียก sub-LM
max_output_chars int 10000 จำนวนตัวอักษรผลลัพธ์ REPL สูงสุด
system_prompt str RLM_SYSTEM_PROMPT Override system prompt

เมื่อไหร่ควรใช้: สำหรับงานบริบทยาวที่ได้ประโยชน์จากการแตกย่อยแบบ recursive เช่น การสรุปเอกสารขนาดใหญ่, การประมวลผลข้อมูลมีโครงสร้าง หรืองาน ที่ต้องจัดการบริบทเชิงโปรแกรม


OpenHandsAgent (SDK)

OpenHandsAgent ห่อแพ็กเกจ openhands-sdk จริง สำหรับการพัฒนาซอฟต์แวร์ที่ ขับเคลื่อนด้วย AI Extend BaseAgent โดยตรง (การจัดการ tool ถูกจัดการโดย SDK ภายใน)

ทำงานอย่างไร:

  1. Import openhands.sdk ตอน runtime
  2. สร้าง LLM, Agent และ Conversation จาก SDK
  3. ส่ง input และรันบทสนทนา
  4. คืนเนื้อหาข้อความสุดท้าย

พารามิเตอร์ของ Constructor:

พารามิเตอร์ ชนิด ค่าเริ่มต้น คำอธิบาย
engine InferenceEngine -- Inference engine (fallback)
model str -- Identifier ของโมเดล
bus EventBus None Event bus สำหรับ telemetry
temperature float 0.7 Temperature ของ sampling
max_tokens int 1024 จำนวน token สูงสุดที่จะสร้าง
workspace str os.getcwd() Working directory ของเอเจนต์
api_key str $LLM_API_KEY API key สำหรับผู้ให้บริการ LLM

เมื่อไหร่ควรใช้: สำหรับงานพัฒนาซอฟต์แวร์ (ดีบัก, แก้ไขโค้ด, แก้เทสต์) ที่ OpenHands SDK มี runtime เอเจนต์พัฒนาซอฟต์แวร์แบบเต็ม

Dependency เสริม

ต้องมี openhands-sdk (uv sync --extra openhands) และ Python 3.12+


การใช้เอเจนต์

ผ่าน CLI

# Simple agent
jarvis ask --agent simple "What is the capital of France?"

# Orchestrator with tools
jarvis ask --agent orchestrator --tools calculator,think "What is sqrt(256)?"

# NativeReActAgent
jarvis ask --agent native_react --tools calculator "What is 2+2?"

# ReAct alias (same as native_react)
jarvis ask --agent react --tools calculator,think "Solve step by step: 15% of 340"

# NativeOpenHandsAgent
jarvis ask --agent native_openhands --tools calculator,web_search "Summarize example.com"

# RLMAgent
jarvis ask --agent rlm "Summarize this long document"

# OpenHands SDK agent
jarvis ask --agent openhands "Fix the bug in test_utils.py"

ผ่าน Python SDK

from openjarvis import Jarvis

j = Jarvis()

# Simple agent
response = j.ask("Hello", agent="simple")

# Orchestrator with tools
response = j.ask(
    "Calculate 15% of 340",
    agent="orchestrator",
    tools=["calculator"],
)

# NativeReActAgent with tools
response = j.ask(
    "What is sqrt(256)?",
    agent="native_react",
    tools=["calculator", "think"],
)

# Full result with tool details
result = j.ask_full(
    "What is the square root of 144?",
    agent="orchestrator",
    tools=["calculator", "think"],
)
print(result["content"])
print(result["turns"])
print(result["tool_results"])

j.close()

ClaudeCodeAgent

ClaudeCodeAgent ห่อ SDK @anthropic-ai/claude-code ผ่าน Node.js subprocess bridge ที่มาพร้อม ต่างจากเอเจนต์อื่นตรงที่การอนุมานถูกจัดการทั้งหมด โดย Claude Agent SDK -- พารามิเตอร์ engine ถูกรับไว้เพื่อให้สอดคล้องกับ interface ของ BaseAgent เท่านั้น ไม่ได้ถูกใช้จริง

สิ่งที่ต้องมี

ต้องมี Node.js 22+ อยู่ใน PATH และ environment variable ANTHROPIC_API_KEY (หรือส่ง api_key= โดยตรง) ตัวรันที่มาพร้อมจะถูกติดตั้งอัตโนมัติที่ ~/.openjarvis/claude_code_runner/ ตอนใช้งานครั้งแรกผ่าน npm install

ทำงานอย่างไร:

  1. ในการเรียกครั้งแรก คัดลอก claude_code_runner/ ที่มาพร้อม ไปยัง ~/.openjarvis/claude_code_runner/ และรัน npm install --production หากไม่มี node_modules
  2. สร้าง JSON request payload (prompt, API key, workspace, allowed tools, system prompt, session ID) แล้วส่งไปยัง stdin ของ subprocess node dist/index.js
  3. ตัวรัน Node.js เรียก Claude Agent SDK และเขียน JSON ที่คั่นด้วย sentinel ลง stdout
  4. ฝั่ง Python parse ผลลัพธ์ระหว่าง marker ---OPENJARVIS_OUTPUT_START--- และ ---OPENJARVIS_OUTPUT_END--- ดึงเนื้อหา, ผลลัพธ์ tool และ metadata ออกมา
  5. คืน AgentResult พร้อม turns=1

พารามิเตอร์ของ Constructor:

พารามิเตอร์ ชนิด ค่าเริ่มต้น คำอธิบาย
engine InferenceEngine -- รับไว้เพื่อความสอดคล้องของ interface; ไม่ได้ใช้
model str -- รับไว้เพื่อความสอดคล้องของ interface; ไม่ได้ใช้
bus EventBus None Event bus สำหรับ telemetry
temperature float 0.7 รับไว้เพื่อความสอดคล้องของ interface; ไม่ได้ใช้
max_tokens int 1024 รับไว้เพื่อความสอดคล้องของ interface; ไม่ได้ใช้
api_key str $ANTHROPIC_API_KEY API key ของ Anthropic
workspace str os.getcwd() Working directory ของเอเจนต์ Claude
session_id str "" Session ID เสริม สำหรับความต่อเนื่องของบทสนทนา
allowed_tools list[str] None (ทั้งหมด) ชื่อ tool ของ Claude Code ที่อนุญาต
system_prompt str "" System prompt เพิ่มเติมสำหรับเอเจนต์
timeout int 300 Timeout ของ subprocess เป็นวินาที

เมื่อไหร่ควรใช้: สำหรับงานวิศวกรรมซอฟต์แวร์ที่ tool ในตัวของ Claude Agent SDK (แก้ไขโค้ด, รัน bash, จัดการไฟล์) มีความสามารถเหนือกว่าเอเจนต์แบบ เรียก tool ของ OpenJarvis

from openjarvis.agents.claude_code import ClaudeCodeAgent

agent = ClaudeCodeAgent(
    engine=None,          # not used
    model="",             # not used
    workspace="/path/to/project",
    allowed_tools=["Read", "Write", "Bash"],
    timeout=120,
)
result = agent.run("Add type hints to all functions in utils.py")
print(result.content)
# Via CLI
jarvis ask --agent claude_code "Refactor the tests to use pytest fixtures"

accepts_tools = False

ClaudeCodeAgent ไม่รับ tool ของ OpenJarvis ผ่าน --tools การเข้าถึง tool ของเอเจนต์ Claude ตั้งค่าแยกต่างหากผ่านพารามิเตอร์ constructor allowed_tools ซึ่งส่งชื่อ tool ที่ Claude Agent SDK เข้าใจเอง


OpenCodeAgent

OpenCodeAgent มอบหมายงานเขียนโค้ดให้ opencode เอเจนต์เขียนโค้ด open-source โดยรันบน engine ในเครื่องของคุณ opencode จัดการลูปเชิงตัวแทน, การแก้ไขไฟล์ และการใช้ tool; OpenJarvis จัดหาโมเดล -- รักษางานเอเจนต์เขียนโค้ดให้เป็น local-first

สิ่งที่ต้องมี

ต้องมี binary opencode อยู่ใน PATH (npm i -g opencode-ai หรือ brew install anomalyco/tap/opencode) มัน**ไม่**ได้มาให้ในตัว; run() จะคืน error ที่ชัดเจนหากไม่พบ ไม่ต้องมี ANTHROPIC_API_KEY -- การอนุมานไปผ่าน engine ของ OpenJarvis ของคุณ

ทำงานอย่างไร:

  1. หา base URL ที่เข้ากันได้กับ OpenAI จาก engine (เช่น Ollama/vLLM/llama.cpp ที่ <host>/v1) และเขียน opencode.json ใน workspace ลงทะเบียนเป็น provider @ai-sdk/openai-compatible (openjarvis/<model>)
  2. เปิด opencode serve แบบ headless (loopback, port สุ่ม) และรอ /global/health
  3. สร้าง session (POST /session) และส่งงาน (POST /session/{id}/message) พร้อม model={providerID, modelID} และ agent ที่เลือก (build หรือ plan)
  4. Parse parts ของข้อความที่คืนมา — ส่วนข้อความ → content, ส่วน tool → tool_results — เป็น AgentResult
  5. close() จะปิด session/server

พารามิเตอร์ของ Constructor (คัดมา):

พารามิเตอร์ ชนิด ค่าเริ่มต้น คำอธิบาย
engine InferenceEngine -- ใช้หา URL ของ provider ที่เข้ากันได้กับ OpenAI ในเครื่อง
model str -- id ของโมเดลที่ให้บริการที่ provider (เช่น qwen3:8b)
workspace str os.getcwd() ไดเรกทอรีที่ opencode ทำงานอยู่
agent str "build" เอเจนต์ opencode: build (เข้าถึงเต็ม) หรือ plan (อ่านอย่างเดียว)
provider_base_url str อิงจาก engine Override URL ฐานของ OpenAI ที่อิงจาก engine
provider_id str "openjarvis" id ของ provider opencode ที่จะลงทะเบียน/ใช้
model_id str model id ของโมเดลภายใน provider
server_password str $OPENCODE_SERVER_PASSWORD basic-auth เสริมสำหรับเซิร์ฟเวอร์ opencode
timeout int 600 Timeout ของ HTTP เป็นวินาที
from openjarvis.agents.opencode import OpenCodeAgent

agent = OpenCodeAgent(engine, "qwen3:8b", workspace="/path/to/project", agent="build")
result = agent.run("Add type hints to utils.py and run the tests")
print(result.content)
agent.close()
# Via CLI (opencode must be installed)
jarvis ask --agent opencode "Refactor the parser to use a state machine"

Provider แบบ Pass-Through

หาก engine ไม่มี base URL ที่หาได้ ส่ง model เป็น provider/model (เช่น ollama/llama3) แล้ว opencode จะ resolve จาก configuration ของตัวเอง -- ไม่มีการเขียน opencode.json

ความสามารถของโมเดลสำคัญ

ลูปเชิงตัวแทนของ opencode (การวางแผน + การเรียก tool ที่ถูกต้อง + การทำงานต่อเนื่องหลายขั้นตอน) ต้องการโมเดลที่มีความสามารถพอสมควร ในการทดสอบ โมเดลในเครื่องขนาด 27B (Qwen3.5-27B ให้บริการผ่าน vLLM) แก้ชุดงานเขียนโค้ด 7 งานได้อย่างเรียบร้อย (สร้าง / แก้ไข / แก้บั๊ก / implement-ให้ผ่านเทสต์ / หลายไฟล์ ยืนยันด้วยการรันโค้ดและ เทสต์จริง) โมเดล 8B (qwen3:8b) ไม่น่าเชื่อถือ — เรียก tool ผิด รูปแบบ, โค้ดผิด syntax และงานทำไม่เสร็จครึ่งๆ กลางๆ เลือกใช้โมเดล ในเครื่องที่มีความสามารถ (หรือโมเดล cloud) สำหรับงานเขียนโค้ดจริง


OperativeAgent

OperativeAgent คือเอเจนต์อัตโนมัติแบบตั้งเวลาและต่อเนื่อง พร้อม session persistence และการเรียกคืน state ในตัว ออกแบบมาสำหรับ "Operators" -- เอเจนต์อัตโนมัติที่รันตามตารางเวลา พร้อมการจัดการ state อัตโนมัติระหว่าง tick Extend ToolUsingAgent

ทำงานอย่างไร:

  1. โหลด Session -- กู้คืนประวัติบทสนทนาจาก tick ก่อนหน้าผ่าน session store
  2. เรียกคืน State -- ดึง state JSON ก่อนหน้าจาก memory backend
  3. แทรก System Prompt -- แทรกคำสั่ง protocol ของ operator
  4. ลูป Tool -- ลูป function-calling มาตรฐาน (เหมือน OrchestratorAgent)
  5. บันทึก Session -- บันทึก prompt และคำตอบของ tick ลง session store
  6. การคงอยู่ของ State -- บันทึก state อัตโนมัติ หากเอเจนต์ไม่ได้เก็บมันเองผ่าน tool memory_store

พารามิเตอร์ของ Constructor:

พารามิเตอร์ ชนิด ค่าเริ่มต้น คำอธิบาย
engine InferenceEngine -- Inference engine ที่จะใช้
model str -- Identifier ของโมเดล
tools list[BaseTool] [] Instance ของ tool ที่จะเปิดให้ใช้
bus EventBus None Event bus สำหรับ telemetry
max_turns int 20 จำนวนรอบการเรียกใช้ tool สูงสุด
temperature float 0.3 Temperature ของ sampling
max_tokens int 2048 จำนวน token สูงสุดที่จะสร้าง
system_prompt str None System prompt กำหนดเองสำหรับ operator
operator_id str None ID เฉพาะสำหรับ session และการคงอยู่ของ state
session_store Any None Session store backend สำหรับประวัติบทสนทนา
memory_backend Any None Memory backend สำหรับเรียกคืนและคงอยู่ของ state

เมื่อไหร่ควรใช้: สำหรับเอเจนต์อัตโนมัติที่รันตามตารางเวลา (เช่น ผ่าน TaskScheduler) และต้องคง state ไว้ข้ามการเรียกใช้ เอเจนต์จะจัดการประวัติ session และการคงอยู่ของ state ข้าม tick โดยอัตโนมัติ

from openjarvis.agents.operative import OperativeAgent

agent = OperativeAgent(
    engine,
    model="qwen3:8b",
    tools=[...],
    operator_id="daily-report",
    session_store=session_store,
    memory_backend=memory_backend,
    system_prompt="You are a daily report agent. Gather and summarize news.",
)
result = agent.run("Generate today's report")
# Via CLI
jarvis ask --agent operative "Check system status"

MonitorOperativeAgent

MonitorOperativeAgent คือเอเจนต์ระยะยาวที่มีสี่แกนกลยุทธ์ที่ตั้งค่าได้ สำหรับจัดการข้อมูลข้ามรอบและ session มัน extend ToolUsingAgent ด้วยการ บีบอัด observation, การดึงข้อมูล memory, การค้นหา และการแตกย่อยงานที่ขับ เคลื่อนด้วยกลยุทธ์ นอกจากนี้ยังสืบทอด cross-session state persistence จาก รูปแบบของ OperativeAgent ด้วย

แกนกลยุทธ์:

แกน ค่าที่ใช้ได้ ค่าเริ่มต้น คำอธิบาย
memory_extraction causality_graph, scratchpad, structured_json, none causality_graph สิ่งที่พบจะถูกคงไว้ใน memory อย่างไร
observation_compression summarize, truncate, none summarize ผลลัพธ์ tool ถูกบีบอัดอย่างไรก่อนถูกเพิ่มเข้าบริบท
retrieval_strategy hybrid_with_self_eval, keyword, semantic, none hybrid_with_self_eval บริบทก่อนหน้าถูกเรียกคืนอย่างไรตอนเริ่มการรันแต่ละครั้ง
task_decomposition phased, monolithic, hierarchical phased งานที่ซับซ้อนถูกแตกย่อยอย่างไร

ทำงานอย่างไร:

  1. สร้าง system prompt พร้อมการตั้งค่ากลยุทธ์และคำอธิบาย tool
  2. เรียกคืน state ก่อนหน้าจาก memory backend
  3. โหลดประวัติ session จาก tick ก่อนหน้า
  4. รันลูป tool แบบ function-calling พร้อมใช้กลยุทธ์ที่ตั้งค่าไว้:
    • การบีบอัด Observation: ผลลัพธ์ tool ที่ยาวจะถูกสรุป (ผ่าน LLM) หรือตัดให้สั้นลง ก่อนถูกเพิ่มเข้า message context
    • การดึงข้อมูล Memory: หลังการเรียก tool แต่ละครั้ง สิ่งที่พบจะถูกดึงออกมาและเก็บไว้ตามกลยุทธ์ memory (ความสัมพันธ์เชิงเหตุผล, โน้ต scratchpad หรือ JSON แบบมีโครงสร้าง)
  5. บันทึก session และคงอยู่ของ state อัตโนมัติ

พารามิเตอร์ของ Constructor:

พารามิเตอร์ ชนิด ค่าเริ่มต้น คำอธิบาย
engine InferenceEngine -- Inference engine ที่จะใช้
model str -- Identifier ของโมเดล
tools list[BaseTool] [] Instance ของ tool ที่จะเปิดให้ใช้
bus EventBus None Event bus สำหรับ telemetry
max_turns int 25 จำนวนรอบการเรียกใช้ tool สูงสุด
temperature float 0.3 Temperature ของ sampling
max_tokens int 4096 จำนวน token สูงสุดที่จะสร้าง
system_prompt str None System prompt กำหนดเอง (override ค่าเริ่มต้น)
memory_extraction str "causality_graph" กลยุทธ์การดึงข้อมูล memory
observation_compression str "summarize" กลยุทธ์การบีบอัด observation
retrieval_strategy str "hybrid_with_self_eval" กลยุทธ์การดึงข้อมูล
task_decomposition str "phased" กลยุทธ์การแตกย่อยงาน
operator_id str None ID เฉพาะสำหรับ session และการคงอยู่ของ state
session_store Any None Session store backend สำหรับประวัติบทสนทนา
memory_backend Any None Memory backend สำหรับ state และการคงอยู่ของสิ่งที่พบ

เมื่อไหร่ควรใช้: สำหรับการประเมินผลแบบ long-horizon benchmark และงาน หลายขั้นตอนที่ซับซ้อน ซึ่งได้ประโยชน์จากกลยุทธ์ที่ตั้งค่าได้สำหรับการจัดการ memory, การบีบอัดบริบท และการแตกย่อยงาน มีประโยชน์เป็นพิเศษสำหรับ benchmark อย่าง GAIA, FRAMES และ LifelongAgent ที่การเลือกกลยุทธ์ส่งผลต่อประสิทธิภาพ

from openjarvis.agents.monitor_operative import MonitorOperativeAgent

agent = MonitorOperativeAgent(
    engine,
    model="qwen3:8b",
    tools=[...],
    operator_id="research-agent",
    memory_extraction="causality_graph",
    observation_compression="summarize",
    retrieval_strategy="hybrid_with_self_eval",
    task_decomposition="phased",
    session_store=session_store,
    memory_backend=memory_backend,
)
result = agent.run("Investigate the root cause of the production outage")
# Via CLI
jarvis ask --agent monitor_operative "Analyze the security audit findings"

SandboxedAgent

SandboxedAgent คือ wrapper แบบโปร่งใสที่รัน BaseAgent ใดก็ได้ ภายใน container ของ Docker (หรือ Podman) มันทำตามรูปแบบ wrapper เดียวกับ GuardrailsEngine -- configuration ของเอเจนต์ภายในถูก serialize และส่งไปยัง stdin ของ container แล้วอ่านผลลัพธ์กลับจาก stdout

ดูเอกสารอ้างอิง ContainerRunner ด้านล่างด้วย ซึ่ง จัดการวงจรชีวิตของ container

ทำงานอย่างไร:

  1. สร้าง JSON payload พร้อม prompt, ID เอเจนต์ที่ห่อไว้ และโมเดล
  2. เรียก ContainerRunner.run() ซึ่งเริ่ม container ด้วย --network none และ --rm เขียน payload ลง stdin และรอผลลัพธ์ JSON ทาง stdout
  3. Mount path ถูกตรวจสอบเทียบกับ allowlist ที่ตั้งค่าได้ ก่อนที่ container จะเริ่ม
  4. Parse ผลลัพธ์ที่คั่นด้วย sentinel และคืน AgentResult

พารามิเตอร์ของ Constructor:

พารามิเตอร์ ชนิด ค่าเริ่มต้น คำอธิบาย
agent BaseAgent -- เอเจนต์ที่ห่อไว้ ที่จะรันภายใน container
runner ContainerRunner -- Container runner จัดการวงจรชีวิตของ Docker
engine InferenceEngine None Override engine (ค่าเริ่มต้นคือของเอเจนต์ที่ห่อไว้)
model str "" Override model (ค่าเริ่มต้นคือของเอเจนต์ที่ห่อไว้)
workspace str "" Working directory ภายใน container
mounts list[str] [] Path ของ host ที่จะ bind-mount (อ่านอย่างเดียว)
secrets dict[str, str] {} แทรกเข้า payload (ไม่ใช่ environment variable)
bus EventBus None Event bus สำหรับ telemetry
from openjarvis.sandbox import ContainerRunner, SandboxedAgent
from openjarvis.agents.simple import SimpleAgent

runner = ContainerRunner(
    image="openjarvis-sandbox:latest",
    timeout=60,
    mount_allowlist_path="/etc/openjarvis/mount_allowlist.json",
)
inner = SimpleAgent(engine, model="qwen3:8b")
agent = SandboxedAgent(
    agent=inner,
    runner=runner,
    mounts=["/home/user/data"],
)
result = agent.run("Summarize the CSV files in /home/user/data")

ContainerRunner

ContainerRunner จัดการวงจรชีวิตของ container Docker (หรือ Podman) สำหรับ การรันแบบ sandbox SandboxedAgent ใช้มันโดยตรง แต่ใช้แบบ standalone ก็ได้ เช่นกัน

พารามิเตอร์ของ Constructor:

พารามิเตอร์ ชนิด ค่าเริ่มต้น คำอธิบาย
image str "openjarvis-sandbox:latest" Docker image ที่จะรัน
timeout int 300 เวลารัน container สูงสุดเป็นวินาที
mount_allowlist_path str "" Path ไปยังไฟล์ mount-allowlist แบบ JSON
max_concurrent int 5 จำนวน container พร้อมกันสูงสุด (ระดับข้อมูล)
runtime str "docker" Binary ของ container runtime (docker หรือ podman)

รูปแบบ Mount Allowlist:

mount_allowlist.json
{
  "roots": [
    {"path": "/home/user/projects", "read_only": false},
    {"path": "/data/shared", "read_only": true}
  ],
  "blocked_patterns": [".ssh", ".env", "*.pem", "*.key"]
}

หากไม่ได้ตั้งค่า mount_allowlist_path จะไม่มีการจำกัด root ใดๆ blocked_patterns มี .ssh, .env, *.pem, *.key, ไฟล์ credential และ ไดเรกทอรี config ของ cloud รวมอยู่เป็นค่าเริ่มต้นเสมอ

ต้องมี Docker

ContainerRunner จะ raise RuntimeError หากไม่พบ runtime ที่ตั้งค่าไว้ (docker หรือ podman) ใน PATH


การลงทะเบียนเอเจนต์

เอเจนต์ลงทะเบียนผ่าน decorator @AgentRegistry.register() ทำให้ค้นพบได้ ด้วยชื่อตอน runtime:

from openjarvis.core.registry import AgentRegistry

# Check if an agent is registered
AgentRegistry.contains("orchestrator")  # True

# Get the agent class
agent_cls = AgentRegistry.get("orchestrator")

# List all registered agent keys
AgentRegistry.keys()
# ["simple", "orchestrator", "native_react", "react", "native_openhands",
#  "rlm", "openhands", "claude_code", "operative", "monitor_operative"]

การผสานกับ Event Bus

เอเจนต์ทั้งหมดเผยแพร่ event บน EventBus เมื่อระบุ bus ไว้:

Event เมื่อไหร่
AGENT_TURN_START ตอนเริ่มการรัน (ผ่าน _emit_turn_start)
AGENT_TURN_END ตอนจบการรัน (ผ่าน _emit_turn_end)
TOOL_CALL_START ก่อนการรัน tool แต่ละครั้ง (subclass ของ ToolUsingAgent)
TOOL_CALL_END หลังการรัน tool แต่ละครั้ง (subclass ของ ToolUsingAgent)

Event ของ Inference

Event INFERENCE_START / INFERENCE_END ถูกเผยแพร่โดย wrapper InstrumentedEngine ไม่ใช่โดยเอเจนต์โดยตรง วิธีนี้ทำให้ telemetry เป็น opt-in และโปร่งใสต่อโค้ดของเอเจนต์

Event เหล่านี้ทำให้ระบบ telemetry และ trace บันทึกข้อมูลการโต้ตอบแบบละเอียด ได้โดยอัตโนมัติ


Managed Agent Streaming

Managed Agent API (/v1/managed-agents/{id}/messages) รองรับ การสตรีม token ของ LLM จริง ผ่าน SSE ส่งข้อความพร้อม stream: true เพื่อรับ token คำตอบของโมเดลขณะที่มันถูกสร้างขึ้น แทนที่จะรอคำตอบแบบเต็ม

ทำงานอย่างไร

Endpoint การสตรีมเรียก engine.stream_full() โดยตรง ซึ่ง yield object StreamChunk ที่มี content token, ชิ้นส่วนของ tool-call และเหตุผลการจบ วิธีนี้ให้การสตรีมแบบ token-by-token จริงจาก LLM -- ไม่ใช่การ replay คำแบบ ย้อนหลัง

สำหรับเอเจนต์แบบหลายรอบที่เรียกใช้ tool ลูปการสตรีมจะทำสิ่งเหล่านี้โดย อัตโนมัติ:

  1. Yield content token ให้ client ขณะที่มันมาถึง
  2. สะสมชิ้นส่วนของ tool-call (OpenAI ส่งสิ่งเหล่านี้แบบเพิ่มทีละส่วน)
  3. รัน tool เมื่อได้รับ finish_reason="tool_calls"
  4. ส่งผลลัพธ์ tool เป็น named SSE event (event: tool_result)
  5. ป้อนผลลัพธ์กลับให้ LLM สำหรับรอบถัดไป
  6. ทำซ้ำจนกว่าโมเดลจะสร้างคำตอบข้อความสุดท้าย หรือถึง max_turns

สตรีมข้อความ

curl -N -X POST http://localhost:8000/v1/managed-agents/{id}/messages \
  -H "Content-Type: application/json" \
  -d '{"content": "What is 2+2?", "stream": true}'

คำตอบทำตามรูปแบบ SSE ของ OpenAI:

  1. Content chunk -- data: {"choices": [{"delta": {"content": "token"}}]}
  2. Tool calls (ถ้าโมเดลขอใช้ tool) -- event: tool_calls\ndata: {"calls": [{"tool_name": "...", "arguments": "..."}]}
  3. Tool results -- event: tool_result\ndata: {"tool_name": "...", "output": "..."}
  4. Chunk สุดท้าย -- data: {"choices": [{"delta": {}, "finish_reason": "stop"}]}
  5. Sentinel จบ -- data: [DONE]

เมื่อ stream: false (ค่าเริ่มต้น) endpoint จะทำงานเหมือนเดิมทุกประการ -- ข้อความถูกใส่คิวไว้และเอเจนต์ต้องถูก trigger แยกต่างหากผ่าน /run

รายละเอียดพฤติกรรม

  • ข้อความของผู้ใช้จะถูกบันทึกลงฐานข้อมูลเสมอก่อนเริ่มสตรีม
  • หลังการสตรีมเสร็จสิ้น คำตอบที่รวบรวมได้ทั้งหมดจะถูกบันทึกเป็นข้อความ agent_to_user
  • ประวัติบทสนทนาจากข้อความก่อนหน้าจะถูกโหลดเป็นบริบท LLM โดยอัตโนมัติ
  • Method stream_full() ของ engine ถูกใช้สำหรับการสตรีม token จริง Engine ที่ไม่ได้ override มันจะ fallback ไปใช้ implementation เริ่มต้นที่ห่อ method stream() ธรรมดา
  • หาก engine ไม่พร้อมใช้งานบนเซิร์ฟเวอร์ จะคืน error 503
  • การรัน tool ระหว่างการสตรีมใช้ ToolRegistry เพื่อค้นหาและสร้าง instance ของ tool

ตัวอย่าง Python

import httpx

with httpx.stream(
    "POST",
    "http://localhost:8000/v1/managed-agents/{id}/messages",
    json={"content": "Summarize today's news", "stream": True},
) as response:
    for line in response.iter_lines():
        if line.startswith("data:") and "[DONE]" not in line:
            print(line[5:].strip())