핵심 개념
JamJet의 에이전트, 노드, 상태 및 내구성을 이해합니다.
핵심 개념
JamJet은 AI 에이전트를 위한 내구성 런타임입니다. 핵심 아이디어는 간단합니다. 워크플로는 일시적인 함수 호출이 아닌 복구 가능한 상태 전환으로 실행됩니다. 이것이 재생, 크래시 복구, 승인 일시 정지, 정확한 추적을 가능하게 만듭니다. 이 페이지는 핵심 프리미티브를 다룹니다.
작성 방식 선택하기
JamJet은 에이전트를 구축하는 세 가지 방법을 제공합니다. 세 가지 모두 동일한 IR로 컴파일되고 동일한 Rust 런타임에서 실행되며, 각각을 사용하는 시점에서 차이가 있습니다.
의사결정 플로우차트
에이전트가 추론 전략(react, plan-and-execute, critic)을 사용하여
LLM을 호출하나요?
│
├─ 예 → Agent + Workflow SDK 사용
│ (from jamjet import Agent, Workflow, tool)
│
└─ 아니오 → 플로우가 주로 도구 오케스트레이션, 라우팅 또는
코드 배포 없이 변경하고 싶은 설정인가요?
│
├─ 예 → YAML 워크플로우 사용
│
└─ 아니오 → Python 데코레이터 API 사용
(@workflow, @node)세 가지 방식
| 방식 | 적합한 경우 | 상태 | 예시 |
|---|---|---|---|
| Agent + Workflow SDK | 추론 기능이 있는 멀티 에이전트 시스템 | Pydantic BaseModel (타입 지정, 검증됨) | claims-processing, wealth-management |
| YAML 워크플로우 | 도구 파이프라인, 설정 기반 플로우 | YAML 스키마 (타입 지정됨) | rag-assistant |
| Python 데코레이터 API | 커스텀 로직, Python 라이브러리 접근 | Dict 기반 State | Python SDK docs |
Agent + Workflow SDK
에이전트가 사고해야 할 때 — 도구를 선택하고, 결과를 추론하고, 출력을 개선해야 할 때 사용하세요. 각 에이전트는 작업에 맞는 추론 전략을 가집니다.
from jamjet import Agent, Workflow, tool
from pydantic import BaseModel
@tool
def search_web(query: str) -> dict:
"""웹에서 정보를 검색합니다."""
...
researcher = Agent(
name="researcher",
model="claude-sonnet-4-6",
tools=[search_web],
instructions="주어진 주제에 대한 정확한 정보를 찾습니다.",
strategy="react", # 긴밀한 도구 사용 루프
max_iterations=5,
)
writer = Agent(
name="writer",
model="claude-sonnet-4-6",
instructions="연구 내용을 바탕으로 명확한 요약을 작성합니다.",
strategy="critic", # 초안 → 평가 → 개선
max_iterations=3,
)
workflow = Workflow("research-writer", version="0.1.0")
@workflow.state
class ResearchState(BaseModel):
topic: str
research: str | None = None
summary: str | None = None
@workflow.step
async def research(state: ResearchState) -> ResearchState:
result = await researcher.run(f"Research: {state.topic}")
return state.model_copy(update={"research": result.output})
@workflow.step
async def write(state: ResearchState) -> ResearchState:
result = await writer.run(f"Summarize:\n{state.research}")
return state.model_copy(update={"summary": result.output})다음의 경우 선택하세요: 에이전트에 전략(react, plan-and-execute, critic, consensus, debate)이 필요한 경우, 타입이 지정된 Pydantic 상태가 필요한 경우, 사람의 개입이 필요한 경우(human_approval=True), 또는 여러 전문 에이전트가 협업하는 경우.
YAML 워크플로우
플로우가 결정론적 오케스트레이션인 경우 사용하세요 — 이 도구를 호출하고, 그 다음 모델을 호출하고, 조건에 따라 라우팅합니다. 에이전트 추론이 필요하지 않습니다.
id: summarize-url
version: "0.1.0"
nodes:
fetch:
type: tool
server: web-fetcher
tool: fetch_page
arguments:
url: "{{ state.url }}"
output_key: page_content
next: summarize
summarize:
type: model
model: claude-haiku-4-5-20251001
prompt: "Summarize: {{ state.page_content }}"
output_key: summary다음과 같은 경우 선택: 플로우가 도구와 모델의 고정된 파이프라인이고, 개발자가 아닌 사람이 플로우를 편집해야 하거나, 코드를 재배포하지 않고 라우팅을 변경해야 할 때.
Python 데코레이터 API
노드 내부에서 커스텀 Python 로직이 필요한 경우 사용하세요 — 데이터 변환, 라이브러리 호출, YAML로는 너무 복잡한 조건부 로직.
from jamjet import workflow, node, State
@workflow(id="data-processor", version="0.1.0")
class DataProcessor:
@node(start=True)
async def process(self, state: State) -> State:
import pandas as pd
df = pd.read_csv(state["file_path"])
summary = df.describe().to_dict()
return {"analysis": summary}
@node
async def report(self, state: State) -> State:
response = await self.model(
model="claude-sonnet-4-6",
prompt=f"Write a report from this analysis:\n{state['analysis']}",
)
return {"report": response.text}다음과 같은 경우 선택: 노드 내부에서 Python 라이브러리가 필요하거나, 로직이 YAML로는 너무 복잡하지만 에이전트 추론은 필요하지 않거나, 클래스 기반 워크플로우를 선호할 때.
혼합해서 사용할 수 있나요?
네. 세 가지 모두 동일한 IR로 컴파일됩니다. YAML 워크플로우는 에이전트를 도구로 호출할 수 있습니다. Python 워크플로우 단계는 YAML로 정의된 서브 워크플로우를 실행할 수 있습니다. 런타임은 어떤 작성 방식으로 IR이 생성되었는지 신경 쓰지 않습니다.
워크플로우
워크플로우는 노드의 방향성 그래프입니다. 다음을 포함합니다:
- 고유한
id와version state_schema— 그래프를 통해 흐르는 데이터의 타입 구조- 실행이 시작되는
start노드 - 하나 이상의
end노드
workflow:
id: my-agent
version: 0.1.0
state_schema:
query: str
answer: str
confidence: float
start: think워크플로우는 실행 전에 IR(중간 표현) 그래프로 컴파일됩니다. IR은 Rust 스케줄러가 실제로 실행하는 것이며 — YAML과 Python은 단지 작성 인터페이스일 뿐입니다.
노드
노드는 워크플로우의 연산 단위입니다. 각 노드는 작동 방식을 결정하는 type을 가집니다:
| 노드 타입 | 기능 |
|---|---|
model | LLM 호출 (Claude, GPT-4, Gemini 등) |
tool | MCP를 통해 외부 도구 호출 |
http | HTTP 요청 실행 |
branch | 조건에 따라 실행 경로 분기 |
parallel | 여러 브랜치로 동시 실행 분산 |
wait | 외부 이벤트까지 실행 일시 중지 |
eval | 출력 품질 평가 (루브릭, 어설션, 지연시간) |
end | 워크플로우 종료 |
모든 노드는 state에서 읽고 쓰기를 수행합니다.
State
State는 워크플로우 실행의 공유 데이터 저장소입니다. 노드 간, 재시작 간에도 지속됩니다.
state_schema:
query: str # 사용자 입력
search_results: list[str] # 중간 데이터
answer: str # 최종 출력State는 타입이 지정됨 — 스키마는 컴파일 시점에 검증됩니다. 런타임에서 각 노드는 모든 state 키를 읽고 자신의 output_key에 쓸 수 있습니다.
tip: State는 메모리가 아닌 데이터베이스에 저장됩니다. 실행 중 런타임이 충돌하더라도 state는 완전히 복구되며, 마지막 체크포인트부터 실행이 재개됩니다.
실행
**실행(execution)**은 특정 입력으로 워크플로우를 한 번 실행하는 것입니다. 각 실행은 고유 ID를 받습니다 (예: exec_01JM4X8NKWP2).
실행의 특징:
- 내구성 — 데이터베이스에 저장되어 재시작에도 유지됨
- 관찰 가능 — 모든 state 전환이 이벤트로 기록됨
- 검사 가능 —
jamjet inspect로 전체 state, 이벤트 타임라인, 토큰 사용량 확인 가능
내구성
내구성은 JamJet의 핵심 보장 사항입니다: 런타임이 충돌하더라도 실행은 항상 완료됩니다.
이는 이벤트 소싱을 통해 작동합니다:
- 각 노드가 실행되기 전에
node_started이벤트가 데이터베이스에 기록됩니다 - 각 노드가 완료된 후 상태 패치와 함께
node_completed이벤트가 기록됩니다 - 재시작 시 스케줄러는 이벤트 로그를 재생하여 실행이 중단된 정확한 지점을 재구성합니다
- 실행은 완료되지 않은 첫 번째 노드부터 재개됩니다
작업은 손실되지 않습니다. 노드가 두 번 실행되지 않습니다.
참고: 이는 "최소 한 번" 전달 방식과 다릅니다. JamJet의 스케줄러는 분산 락을 사용하여 여러 워커 프로세스가 있어도 각 노드가 정확히 한 번 실행되도록 보장합니다.
에이전트
에이전트는 다음을 수행할 수 있는 워크플로입니다:
- 다른 에이전트가 발견하고 호출할 수 있음 (Agent Card를 통해)
- 다른 에이전트에게 작업 위임 (A2A 프로토콜을 통해)
- 여러 사용자 상호작용에 걸쳐 장기 실행 상태 유지
모든 에이전트는 Agent Card를 가지고 있습니다 — 기능, 엔드포인트, 입출력 스키마에 대한 기계 판독 가능한 설명입니다. 이는 A2A 프로토콜의 기반입니다.
스케줄러
JamJet 스케줄러는 Rust로 작성되었으며 jamjet dev(로컬) 또는 호스팅 런타임(프로덕션)의 일부로 실행됩니다.
작동 방식:
- 실행 큐에서 대기 중인 작업을 폴링합니다
- 중복 실행을 방지하기 위해 실행에 대한 락을 획득합니다
- 워커 스레드로 노드를 디스패치합니다
- 각 노드 이후 체크포인트를 기록합니다
스케줄러는 JamJet 워크플로가 기본적으로 내구성을 갖는 이유입니다 — 실행을 절대 잊지 않습니다.
로컬 vs. 프로덕션
| 기능 | jamjet dev (로컬) | 호스팅 / 자체 호스팅 |
|---|---|---|
| 스토리지 | SQLite | PostgreSQL |
| 워커 | 단일 프로세스 | 분산 |
| MCP 서버 | 로컬 stdio | 원격 SSE/HTTP |
| 인증 | 없음 | mTLS / API 키 |
프로그래밍 모델은 동일합니다 — 동일한 YAML 또는 Python 코드가 두 환경 모두에서 변경 없이 실행됩니다.
다음 단계
- 빠른 시작 — 첫 번째 내구성 에이전트 구축하기
- Python SDK — 데코레이터, 라우팅, 병렬 단계
- 충돌 복구 시도하기 — 내구성 실전 확인