technical architecture plugins contextengine

ContextEngine 심층 분석: OpenClaw 2026.3.7은 컨텍스트 관리를 어떻게 플러그인으로 바꿨나

OpenClaws.io Team

OpenClaws.io Team

@openclaws

March 9, 2026

10 분 소요

ContextEngine 심층 분석: OpenClaw 2026.3.7은 컨텍스트 관리를 어떻게 플러그인으로 바꿨나

AI 에이전트가 쓸모 있는 답을 하느냐, 그럴듯한 헛소리를 자신 있게 하느냐. 그 차이를 가장 크게 가르는 건 컨텍스트 관리다. 대화 이력, 도구 출력, 외부 지식—이것들을 모델의 유한한 컨텍스트 윈도우에 어떻게 우겨넣느냐가 관건이다. OpenClaw 2026.3.7 전까지 이 로직은 코어에 하드코딩돼 있었다. 이제는 플러그인이다.

뭐가 문제였나

기존 OpenClaw는 슬라이딩 윈도우 압축을 썼다. 대화가 길어지면 오래된 메시지를 요약해서 새 메시지에 자리를 내준다. 돌아가긴 한다. 하지만 불만이 있었다:

  • 압축하면 정보가 날아간다. 코딩 에이전트가 50턴 전에 작성한 함수를 참조하고 싶다고? 요약에는 이미 없다.
  • 세션 끝나면 다 잊는다. 에이전트에 세션 간 기억이 없다.
  • 전략을 바꿀 수 없다. RAG 기반 조립, 대화 분기, 토큰 예산 커스터마이징—깔끔하게 끼워 넣을 방법이 없어서 hack밖에 답이 없었다.

커뮤니티는 여러 방법을 동원했다. 내부 모듈 monkey-patch, 코어 fork, 외부 오케스트레이션 레이어 구축. 다 임시방편이었다.

ContextEngine: 컨텍스트 관리를 교체 가능한 플러그인으로

2026.3.7은 깔끔하게 정리했다. 컨텍스트의 전체 라이프사이클을 인터페이스로 추출해서 플러그인 슬롯으로 열었다. 플러그인을 만들고 이 인터페이스를 구현하면, 에이전트의 컨텍스트 로직을 통째로 가져갈 수 있다.

어떻게 연결하나

플러그인 API를 통해 ContextEngine을 등록한다:

typescript
// In your plugin's bootstrap
export default function myContextPlugin(api: PluginAPI) {
  api.registerContextEngine('my-engine', (config) => {
    return new MyCustomContextEngine(config);
  });
}

설정에서 선택하면 끝:

yaml
plugins:
  slots:
    contextEngine: my-engine

아무것도 설정 안 하면? 그대로 쓰면 된다. OpenClaw가 LegacyContextEngine을 감싸서 슬라이딩 윈도우 동작을 그대로 유지한다. 기존 사용자에게 영향 제로.

7개의 훅—컨텍스트의 일생을 관리한다

ContextEngine 인터페이스는 7개의 훅을 제공한다. 엔진 기동부터 서브에이전트 종료까지, 모든 중요 지점에 개입할 수 있다:

1. `bootstrap()`

엔진 초기화 시 한 번 호출된다. 벡터 DB 연결, 그래프 구조 생성, 저장된 상태 로딩을 여기서 한다.

typescript
async bootstrap(): Promise<void> {
  this.vectorStore = await connectToVectorDB(this.config.dbUrl);
  this.sessionGraph = new DAG();
}

2. `ingest(message: Message)`

새 메시지가 올 때마다 발동한다—사용자 입력이든, 에이전트 응답이든, 도구 출력이든. 여기서 저장 방식과 인덱싱 방식을 결정한다.

typescript
async ingest(message: Message): Promise<void> {
  // Add to the DAG
  const node = this.sessionGraph.addNode(message);
  // Index for retrieval
  const embedding = await embed(message.content);
  await this.vectorStore.upsert(node.id, embedding, message);
}

3. `assemble(budget: TokenBudget): AssembledContext`

가장 중요한 훅. 모델 호출 직전에 OpenClaw가 토큰 예산을 들고 온다. "컨텍스트 조립해줘." 여기서 반환하는 게 모델이 보는 전부다.

엔진마다 완전히 다른 전략을 쓸 수 있다:

typescript
async assemble(budget: TokenBudget): AssembledContext {
  const recentMessages = this.sessionGraph.getRecent(budget.soft * 0.6);
  const relevantHistory = await this.vectorStore.query(
    this.currentQuery,
    budget.soft * 0.3
  );
  const systemContext = this.buildSystemPrompt(budget.soft * 0.1);

  return {
    system: systemContext,
    messages: [...relevantHistory, ...recentMessages],
    tokenEstimate: this.estimateTokens([systemContext, ...relevantHistory, ...recentMessages]),
  };
}

4. `compact()`

컨텍스트가 하드 리밋을 넘겼다. 다이어트 시간. 기본 엔진은 오래된 메시지를 요약한다. 플러그인이라면 그래프에서 관련도 낮은 노드를 잘라내거나, 벡터 스토어로 보내거나, assemble()에서 이미 예산 내에 들어왔다면 아무것도 안 할 수도 있다.

5. `afterTurn(turn: Turn)`

한 턴이 끝났다—사용자가 말하고, 에이전트가 답했다. 상태 저장, 인덱스 갱신, 백그라운드 작업 트리거에 적합하다.

6. `prepareSubagentSpawn(parentContext: Context): SubagentContext`

에이전트가 서브에이전트를 생성하려 한다. 컨텍스트를 얼마나 줄 것인가? 다 주면 토큰 예산이 폭발하고, 안 주면 아무것도 모른다. 이 훅으로 서브에이전트가 가져갈 정보를 정밀하게 제어할 수 있다.

typescript
prepareSubagentSpawn(parentContext: Context): SubagentContext {
  // Give the subagent a focused slice of context
  const relevantNodes = this.sessionGraph.getSubtree(parentContext.taskId);
  return {
    messages: relevantNodes.map(n => n.message),
    metadata: { parentSessionId: this.sessionId },
  };
}

7. `onSubagentEnded(result: SubagentResult)`

서브에이전트가 일을 끝내고 돌아왔다. 결과를 부모 컨텍스트에 어떻게 반영할 것인가? 전부 머지, 요약, 선별—당신이 결정한다.

아키텍처 디테일: 슬롯은 훅이 아니다

ContextEngine은 플러그인 레지스트리에서 슬롯으로 구현돼 있다. 훅이 아니다. 훅은 가산적이다—10개의 플러그인이 onMessage를 동시에 리슨할 수 있다. 슬롯은 배타적이다. 동시에 하나의 ContextEngine만 작동한다.

┌─────────────────────────────────────┐
│           Plugin Registry           │
│                                     │
│  Hooks (가산적):                     │
│    onMessage  → [plugin1, plugin2]  │
│    onTool     → [plugin3]           │
│                                     │
│  Slots (배타적):                     │
│    contextEngine → my-engine        │
│    (기본값: LegacyContextEngine)     │
└─────────────────────────────────────┘

기동 시 OpenClaw가 plugins.slots.contextEngine 설정값을 읽고, 등록된 팩토리를 찾아서 인스턴스화한다. 못 찾으면? 기동 실패, 에러 메시지 출력. 조용히 기본값으로 돌아가지 않는다—의도적인 설계다. 어떤 컨텍스트 엔진이 돌아가고 있는지 항상 알고 있어야 한다.

서브에이전트 간 격리는 AsyncLocalStorage로 구현. 각 서브에이전트가 독립된 런타임 스코프를 가지므로 플러그인 상태가 다른 에이전트로 새지 않는다.

이미 쓰이고 있다

Lossless-Claw

GitHub · Martian Engineering

첫 번째 본격 ContextEngine 플러그인. 슬라이딩 윈도우 압축을 DAG 기반 요약 시스템으로 교체해서, 토큰 리밋 안에서 모든 원본 메시지를 보존한다.

  • 메시지 하나가 방향성 비순환 그래프의 노드 하나가 된다
  • 토픽 관련성에 따라 노드가 "에피소드"로 묶인다
  • 예산 초과 시, 오래된 에피소드가 요약되지만 원본 메시지는 그래프에 남는다
  • 이후 턴에서 과거 내용을 언급하면, 요약이 아닌 원본을 꺼낸다

개발자라면 체감할 것이다. 100턴 전에 정의한 함수가 갑자기 필요해진다. 슬라이딩 윈도우는 진작에 밀어냈지만, Lossless-Claw는 꺼내준다.

MemOS Cloud Plugin

GitHub · MemTensor

하는 일은 딱 하나. 에이전트에 세션을 넘는 기억을 심어준다.

  • 기동 시 MemOS Cloud에서 현재 주제와 관련된 기억을 불러온다
  • 매 턴 종료 후 새로운 내용을 저장한다
  • 컨텍스트 조립 시 기억을 주입한다

지난주에 한 대화, 당신의 선호, 진행 중인 프로젝트—에이전트가 다 기억한다. 매번 다시 설명할 필요 없다.

다음에 뭐가 나올까

GitHub 이슈와 Discord를 보면, 이런 것들이 움직이고 있다:

  • RAG 네이티브 조립: 메시지 이력 대신 검색된 문서 청크로 컨텍스트를 구성. OpenClaw가 대화형 검색 엔진이 된다.
  • 멀티 에이전트 공유 메모리: 여러 에이전트가 지식 그래프를 공유. 협업 워크플로에서 정보 동기화 중복이 사라진다.
  • 토큰 예산 자동 최적화: 사용 중인 모델의 가격과 성능에 따라 컨텍스트 배분을 동적으로 조정.
  • 대화 분기: 트리 구조 컨텍스트. 다른 방향으로 갈라져 탐색하다가 언제든 돌아온다. 이력은 보존된다.

왜 이게 전환점인가

OpenClaw는 채널도, 모델도, 도구도 추가할 수 있었다. 하지만 컨텍스트—에이전트의 "머리가 어떻게 돌아가는지"—는 코어 안의 블랙박스였다. fork하지 않으면 손댈 수 없었다.

ContextEngine이 그 상자를 열었다. 여기서부터는 연쇄한다:

  1. 1.플러그인 개발자가 에이전트 UX에서 가장 어렵고 가장 가치 있는 영역—컨텍스트 품질—에서 승부할 수 있게 됐다.
  2. 2.기업 사용자는 컴플라이언스 제어가 가능해진다. 모델에 보내기 전에 마스킹, 보존 정책 강제 적용, 프롬프트 입력 전수 감사.
  3. 3.연구자는 새로운 컨텍스트 전략을 테스트하는 데 프로젝트 전체를 fork할 필요가 없다.
  4. 4.모델 프로바이더는 자사 아키텍처에 최적화된 플러그인을 출시할 수 있다. 롱 컨텍스트 모델과 숏 컨텍스트 모델은 전략이 달라야 한다.

프레임워크에서 플랫폼으로의 전환이란 이런 것이다. 리브랜딩이 아니다. "비전"을 선언하는 블로그 포스트도 아니다. 생태계가 자기 강화 루프를 만들어내는 구체적인 아키텍처 변경이다. 플러그인이 늘면 → 사용자가 늘고 → 개발자가 늘고 → 플러그인이 더 늘어난다. 이 루프가 돌기 시작하면, 멈추기 어렵다.

랍스터가 새 집게를 하나 더 얻었다.

소식 받기

새 기능과 연동 소식을 알려드려요. 스팸 없음, 언제든 구독 취소 가능.