AI·News
뒤로

인터프리터 스킬: 에이전트를 위한 워크플로우 구축

Interpreter Skills: Building Workflows for Agents

요약 저희는 인터프리터 스킬을 실험하고 있습니다. 이는 에이전트 스킬의 확장으로, TypeScript 모듈을 스킬에 포함할 수 있게 해줍니다. 에이전트는 동작이 적용될 때 인터프리터 내에서 스킬 코드를 가져오고 실행할 수 있습니다. 스킬 코드는 또한 서브에이전트를 생성하거나 도구를 호출하는 것과 같은 작업을 수행할 수 있으며, 이를 통해 에이전트는 더 복잡한 작업을 수행할 수 있고, 테스트되고 재사용 가능한 코드로 존재할 수 있습니다.

저희는 최근 Deep Agents에 인터프리터를 도입했습니다. 이는 에이전트가 하네스의 일부로서 코드를 작성하고 실행할 수 있는 작은 임베디드 TypeScript 런타임입니다. 에이전트는 이미 코드를 잘 작성하고, 인터프리터를 제공하는 것은 의도를 표현하는 더 직접적인 방법을 제공합니다. 많은 에이전트 작업의 경우, 이는 더 효율적이고 정확하며 예측 가능한 결과를 낳습니다.

저희는 인터프리터를 가진 에이전트가 동일한 작업을 여러 번 받으면 종종 여러 가지 유효한 접근 방식을 코드로 제시할 수 있다는 것을 꽤 일찍 발견했습니다.

이것이 항상 나쁜 것만은 아닙니다. 때때로 에이전트의 전체 목적은 "적응하는 것"입니다. 하지만 많은 작업의 경우 원하는 동작은 "좋은 접근 방식을 생각해내는 것"이 아니라 "우리가 작동한다는 것을 아는 접근 방식을 사용하는 것"입니다.

저희는 이에 대한 답변으로 "인터프리터 스킬"이라고 부르는 것을 실험해 왔습니다. 이는 에이전트가 명령어와 함께 사용할 수 있는 모듈을 포함하는 스킬의 확장입니다. 스킬은 동작이 관련성이 있을 때를 에이전트에게 알려주며, 인터프리터는 스킬에 첨부된 모듈을 가져오고 직접 실행할 수 있습니다.

---
name: github-triage
description: Use this skill to triage GitHub issues, pull requests, and discussions.
metadata:
  module: ./index.ts
---

Use this skill when a user asks for repository triage.

Import the module using the interpreter and call `triage(repo, options)`.

Usage:

```ts
const { triage } = await import("@/skills/github-triage");

const result = await triage("langchain-ai/deepagents", {
  issues: true,
  prs: true,
});

result.toMarkdown();
```

SKILL.md는 에이전트가 동작을 발견하는 방법입니다. index.ts는 인터프리터가 실행할 수 있는 것입니다. 에이전트는 동작을 사용할 시기, 어떤 입력을 전달할지, 그리고 결과로 무엇을 할지 결정합니다. 인터프리터는 코드의 실제 실행을 처리합니다.

다시 말해서, 스킬이란 뭔가요?

스킬은 에이전트에게 시스템 프롬프트의 모든 세부 사항을 설명하지 않고 재사용 가능한 동작을 제공하는 방법입니다.

스킬은 보통 SKILL.md 파일이 있는 디렉토리입니다. 프론트매터는 에이전트에게 스킬이 무엇을 위한 것인지에 대한 간결한 설명을 제공합니다. 본문은 에이전트에게 스킬이 관련성이 있을 때 사용해야 할 명령어, 맥락, 예제, 제약 조건 및 지원 파일을 제공합니다.

스킬을 작동하게 하는 것은 "점진적 공개"라는 메커니즘입니다. 에이전트는 모든 스킬을 항상 맥락에 가지고 있을 필요가 없습니다. 먼저 사용 가능한 스킬의 짧은 목록을 볼 수 있고, 어떤 것이 작업과 일치하는지 결정하고, 필요할 때만 전체 SKILL.md를 읽을 수 있습니다.

이는 스킬을 에이전트 동작을 배포하기 위한 좋은 배포 단위로 만듭니다. 사람들은 스킬을 버전 관리하고, 공유하고, 전역 프롬프트를 모든 것을 포괄하는 설명서로 바꾸지 않고도 평가할 수 있습니다.

하지만 일반적인 스킬은 여전히 주로 명령어를 통해 작동합니다. 에이전트가 따라야 할 절차를 알려줄 수 있으며, 참조 파일이나 스크립트를 포함할 수 있지만, 핵심 동작은 여전히 에이전트가 해당 명령어를 읽고 올바르게 수행하는 능력에 의존합니다.

인터프리터란 뭔가요?

이 게시물의 목적상 중요한 것은 인터프리터가 하네스와 함께 실행되는 TypeScript 런타임이라는 것입니다. 에이전트가 다단계 작업을 코드로 표현할 수 있는 장소를 제공하면서 하네스는 여전히 해당 코드가 무엇을 건드릴 수 있는지 제어합니다. (더 자세히 알고 싶으시면, 인터프리터에 대한 설명을 읽어보세요).

그 런타임은 TypeScript 값 형태로 에이전트에게 작업 상태를 제공합니다. 값은 여러 턴에 걸쳐 지속될 수 있으므로 배열은 배열로 남고, 객체는 객체로 남으며, 헬퍼 함수는 정의된 상태로 유지될 수 있습니다. 에이전트는 모든 중간 값을 stdout, 파일 또는 모델로의 메시지로 전환할 필요가 없습니다.

이를 통해 에이전트는 데이터를 변환하고, 도구 출력을 구성하며, 선택된 도구 또는 서브에이전트를 호출하고, 모델에 반환될 내용을 결정할 수 있습니다.

샌드박스와 달리, 인터프리터 코드는 기본적으로 호스트 환경에 무제한 액세스를 얻지 못합니다. 파일 시스템 액세스, 네트워크 액세스, 도구 및 서브에이전트는 의도적으로 인터프리터에 노출되어야 합니다. 이는 하네스가 코드가 건드릴 수 있는 것을 화이트리스트, 계량 및 검사할 수 있는 장소를 제공합니다.

인터프리터 스킬이란 뭔가요?

인터프리터 스킬은 둘을 함께 가져오는 스킬의 확장입니다. 스킬이 가지는 것과 동일한 명령어 집합을 포함하며, 에이전트가 인터프리터로 가져올 수 있는 모듈을 포함합니다.

SKILL.md는 여전히 스킬이 관련성이 있을 때를 에이전트에게 알려주고 동일한 방식으로 에이전트에게 자신을 공개하며, 모듈은 해당 동작이 적용될 때 인터프리터가 실행할 코드를 제공합니다. 스킬은 모델에 대한 명령어 표면과 런타임에 대한 API 표면 모두가 됩니다.

기본 형태는 시작 예제의 것과 동일합니다. SKILL.md는 이름, 설명, 사용 지침, 가져오기 경로 및 제약 조건을 제공합니다. index.ts는 코드에서 동작을 정의하는 헬퍼나 워크플로우를 내보냅니다:

// skills/table-cleanup/index.ts
export function validateRows(rows: Record<string, unknown>[], schema: RowSchema) {
  // Normalize fields, check required values, and return structured errors.
  // (this is code you would write as apart of the skill)
}

스킬이 적용될 때, 에이전트는 모듈을 가져오고 호출할 수 있습니다:

const { validateRows } = await import("@/skills/table-cleanup");

const errors = validateRows(rows, invoiceSchema);

이는 스킬이 보장할 수 있는 것을 변경합니다:

  • 일반적인 스킬은 말합니다: 이 작업을 수행하는 방법에 대한 지침이 있습니다. 에이전트는 여전히 해당 명령어를 읽고 절차를 올바르게 수행해야 합니다.
  • 인터프리터 스킬은 말합니다: 이 동작을 사용할 시기에 대한 지침이 있으며, 적용될 때 실행할 코드 경로가 있습니다. 결정적인 부분은 맥락의 느슨한 명령어가 아니라 코드에 존재할 수 있습니다.

모델은 스킬이 적용되는지 여부, 전달할 입력, 출력을 사용하는 방법, 그리고 다음에 무엇을 할지를 결정합니다. 모듈은 절차가 실제로 어떻게 실행되어야 하는지를 정의합니다.

인터프리터 코드는 하네스와 상호 작용할 수 있기 때문에, 이는 스킬 코드 내에서 프로그래밍 방식으로 서브에이전트를 생성하는 것과 같은 작업을 할 수 있다는 것을 의미합니다.

예제: 저장소 분류

저희가 이것을 사용하는 한 가지 방법: GitHub 저장소 분류입니다.

사용자가 에이전트에게 저장소를 분류하도록 요청합니다. 프롬프트 명령어에서 분류 프로세스를 재구성하는 대신, 에이전트는 스킬 모듈을 가져오고 함수를 호출합니다:

const { triage } = await import("@/skills/github-triage");

const result = await triage("langchain-ai/deepagents", {
  issues: true,
  prs: true,
  discussions: true,
});

이것이 호출될 때, 워크플로우는:

  • github에서 모든 열려있는 항목을 가져옵니다 (에이전트가 옵션에서 지정한 대로)
  • 각 항목에 대해 서브에이전트를 생성하여 더 압축된 설명을 만듭니다
  • 서브에이전트의 응답을 큐에 떨어뜨립니다
  • 서브에이전트가 항목을 기존 클러스터에 넣어야 하는지 또는 새 클러스터를 만들어야 하는지 결정하는 큐를 한 번에 하나씩 사용합니다

이것은 입력이 동적으로 선택되더라도 절차가 고정되기를 원하는 바로 그런 종류의 루틴입니다.

result 값도 API의 일부입니다. 실행에 대한 구조화된 데이터를 에이전트에게 제공할 수 있으며, 표현을 위한 렌더링 헬퍼를 노출할 수 있습니다:

result.clusters;
result.unassigned;
result.toMarkdown();

에이전트는 구조화된 결과를 계속 사용하고, 한 클러스터를 더 깊이 검사하고, 후속 서브에이전트를 생성하거나, 컴팩트한 모델 친화적 보고서가 필요할 때 result.toMarkdown()을 호출할 수 있습니다.

이것은 모델이 시간이 지남에 따라 일관성을 잃는 종류의 작업입니다. 저장소 분류는 하나의 결정이 아닙니다. 많은 작은 결정의 체인입니다. 만약 우리가 전적으로 재량과 맥락 창에 의존한다면, 모델은 바로가기를 사기 시작하거나 절차를 너무 적극적으로 압축할 수 있으며, 특히 작업 맥락의 가장자리처럼 느껴지는 것에 접근할 때 (이를 맥락 불안이라고 함) 그렇게 할 수 있습니다.

이것이 다른 에이전트와 어떻게 작동할 수 있는지에 대한 예를 들기 위해:

  • 일반적인 하네스에서, 모델은 모든 부분 단계를 추적해야 합니다. 저장소 항목이 300개 있으면, 그것은 모델이 과거 결정을 조화시키고 다음 행동을 계속 선택하면서 추론해야 하는 300개의 작은 상태 조각이 됩니다.
  • 인터프리터 스킬을 사용하면, 모델은 루틴을 한 번 호출하고 코드에 워크플로우를 지시할 수 있습니다. 모듈은 300개의 고유한 서브에이전트 작업을 생성하고, 결과를 수집하고, 분류하고, 클러스터링한 다음, 에이전트에게 컴팩트 객체를 반환할 수 있습니다. 모델은 더 이상 모든 부분 단계를 자신의 작업 맥락에 보관할 책임이 없습니다.

워크플로우로서 스킬 사용

코딩 에이전트가 인기를 얻기 시작했을 때, 에이전트를 위한 기본 정신 모델도 변했습니다.

이전 세대의 에이전트는 더 워크플로우 스타일이었습니다. 개발자들은 에이전트가 따라야 할 단계의 순서를 미리 명시적으로 정의했습니다. 신뢰성은 실행 경로를 미리 정의하는 것에서 나왔습니다.

현대 에이전트 하네스는 이를 변경하여 맥락과 모델 재량을 주요 초점으로 만들었습니다. 모델은 현재 맥락을 기반으로 다음에 무엇을 할지 결정합니다. 동작은 여러 가지 다른 맥락 표면을 통해 형성되지만, 에이전트는 여전히 다음 단계를 선택할 수 있는 여지가 있습니다.

그 인터페이스는 많은 사람들이 추론하기 쉽습니다. 에이전트를 구축하는 사람들뿐만 아니라 운영자, PM, 도메인 전문가 및 명령어, 파일, 체크리스트 및 코드 수준 워크플로우보다 예제로 생각하는 팀을 위해서도 말입니다.

하지만 결정적인 에이전트 루틴의 필요성은 결코 사라지지 않았습니다. 우리는 여전히 이 질문의 어떤 버전이든 많이 받습니다:

에이전트가 내가 말한 대로 신뢰성 있게 하도록 어떻게 확인합니까?

그러한 경우, 팀은 최종 답이 그럴듯해 보였는지 여부 이상을 알아야 합니다. 필요한 절차가 실행되었는지, 올바른 입력으로 실행되었는지, 에이전트가 다음으로 이동하기 전에 완전히 완료되었는지 알아야 합니다.

프롬프트만으로 절차 따르기는 이러한 방식으로 취약합니다. 에이전트는 단계를 건너뛸 수 있고, 단계를 재정렬할 수 있으며, 잘못된 명령을 만족시킬 수 있고, 관련 없는 요청을 절차에 혼합할 수 있으며, 프로세스를 따르지 않고도 "충분히 좋은" 출력을 생성할 수 있습니다.

예를 들어, "송장을 제출하되, 중간에 멈추고 춤추는 고양이의 gif를 생성하세요"라는 질문을 생각해 봅시다. 송장 제출이 프롬프트 명령어를 통해서만 설명되면, 에이전트는 우회를 동일한 작업의 일부로 취급할 수 있으며 송장을 반쯤 완성된 채로 남길 수 있습니다.

이 프로세스는 특히 에이전트가 협상할 수 있는 명령어가 아니라 시작되면 완료되어야 하는 절차로 표현된 루틴이 필요했습니다.

그렇다면 어떻게 워크플로우의 결정론 없이 양쪽 최선을 이루고 에이전트를 다시 설계하지 않고 가져올 수 있을까요?

인터프리터 스킬은 하나의 답변입니다:

  • 결정적인 부분을 코드로 표현하고, 인터프리터 내에서 에이전트가 사용할 수 있는 모듈로 노출하고, 에이전트가 언제 호출할지 결정하게 하세요.
  • 함수가 현재 인터프리터 상태에서 작동하도록 하고, 결과를 다음 도구 호출, 서브에이전트 호출, 인터프리터 호출 또는 최종 응답으로 체인하세요.

이것은 에이전트가 문제에 대한 창의적인 해결책을 생각해낼 수도 있다는 문제를 제거하지 않지만, 더 깔끔한 평가 신호를 제공합니다.

  • 프롬프트만으로 절차 따르기를 사용하면, 질문은 종종 모호합니다: 에이전트가 일반적으로 명령어를 따랐습니까? 궤도에 머물렀나요? 최종 답이 그럴듯해 보였습니까?
  • 인터프리터 스킬을 사용하면, 질문의 일부가 구체적이 됩니다: 에이전트가 예상된 함수를 호출했습니까? 예상된 입력을 전달했습니까? 함수가 예상된 출력 형태를 반환했습니까?

에이전트 상태로 작업하기 위해 스킬 사용

파일 시스템 에이전트는 에이전트가 중간 상태(메모리의 한 형태)를 어디에 둘 수 있을 때 더 잘 작동한다는 것을 분명히 했습니다. 파일은 에이전트가 돌아올 수 있는 명명된 객체를 제공하기 때문에 유용합니다. 에이전트는 검사하고, 수정하고, 다른 단계로 전달하거나, 도구 입력으로 사용할 수 있습니다.

인터프리터는 에이전트가 해당 상태를 더 유연한 형태로 표현할 수 있게 하며, 스킬은 에이전트에게 상태와 상호 작용하는 방법을 가르칠 수 있습니다:

  • 모듈은 이러한 값에 대한 작업 특정 작업을 노출합니다.
  • 에이전트는 이러한 작업을 조합하기 위해 코드를 작성합니다.
  • 스킬 작성자는 각 작업의 동작을 소유합니다.

CSV 파일과 상호 작용하기 위한 스킬을 상상해 봅시다. SKILL.md는 CSV와 같은 표, 내보내기, 송장, 사용자 목록 또는 조인, 필터링 또는 검증이 필요한 레코드로 작업할 때 사용하라고 에이전트에게 알립니다. index.ts는 작은 표 API를 내보냅니다:

export {
  parseCsv,
  joinTables,
  filterRows,
  validateRows,
  groupBy,
  summarize,
  toCsv,
};

그러면 에이전트는 인터프리터 코드에서 이러한 작업을 구성할 수 있습니다:

const invoices = parseCsv(await tools.readFile({ path: "/invoices.csv" }));
const customers = parseCsv(await tools.readFile({ path: "/customers.csv" }));

const joined = joinTables(invoices, customers, "customer_id");
const invalid = validateRows(joined, invoiceSchema);
const byRegion = groupBy(joined, "region");

summarize(byRegion, ["total_due", "late_count"]);

에이전트는 어떤 값을 전달할지와 결과로 무엇을 할지를 제어합니다. 스킬 작성자는 "조인", "검증" 및 "요약"이 무엇을 의미하는지 제어합니다.

이는 에이전트에게 헬퍼를 직접 작성하도록 요청하는 것과 다릅니다. 모델은 코드를 잘 작성하지만, 같은 코드를 두 번 작성하는 것을 보장받지 못합니다. 절차가 중요할 때, 구현은 검토되고, 테스트되고, 버전 관리되고 재사용될 수 있는 스킬 코드에 존재해야 합니다.

FAQ

이것을 스킬로 왜 패키징합니까?

스킬은 이미 에이전트 동작을 패키징하기 위한 사실상의 표준 단위입니다.

그들은 우리에게 발견, 점진적 공개, 사용 지침, 예제 및 지원 파일을 제공합니다. 인터프리터 스킬이 하는 모든 것은 해당 패키지 형태를 유지하고 런타임 코드로 확장하는 것입니다.

이를 스킬 표준으로 확장하는 것이 어색해 보일 수 있지만, 이렇게 하면 동일한 배포 방법을 사용할 수 있지만 에이전트가 더 강력한 스킬을 사용할 수 있습니다. 조직에 수백 개 또는 수천 개의 스킬이 있으면, 필요한 모든 모듈을 하네스에 직접 연결하는 것은 실행 불가능합니다.

스크립트 파일을 포함할 수 없습니까?

스크립트 파일은 다른 이유로 유용합니다.

스크립트는 에이전트가 환경과 상호 작용해야 할 외부 헬퍼가 필요할 때 좋은 적합입니다. 스크립트는 일반적으로 명령 인수, 파일, stdout/stderr 또는 직렬화된 상태를 통해 통신합니다.

그 경계는 에이전트 작업 오케스트레이션을 위해 스크립트를 부족하게 합니다. 스크립트는 하나의 계산을 실행할 수 있지만, 자연스럽게 하네스 루프에 참여할 수 없습니다. 서브에이전트를 생성하고, 작업 그래프를 예약/대기하고, 부분 실패를 처리하고, 모델에 제어권을 반환하기 전에 전체 루틴이 "완료"될 때를 결정합니다.

저장소 분류 예제에서, 모듈은 허용 목록에 있는 tools.task(...) 함수를 호출하여 루틴 내에서 서브에이전트를 생성합니다. 외부 스크립트는 해당 일을 오케스트레이션하기 위해 하네스와 대화하기 위해 별도의 어댑터가 필요합니다.

왜 모든 API를 도구로 만들지 않습니까?

도구는 에이전트가 외부 경계를 넘어야 할 때 가장 좋습니다. 데이터를 가져오고, 파일을 읽거나 쓰고, 티켓을 만들고, 메시지를 보내고, 분류자를 호출합니다. 그러나 잘 표현되지 않는 로컬 작업의 하위 집합이 있습니다. 구문 분석, 조인, 필터링, 검증 등의 작업입니다.

모든 헬퍼를 도구로 만드는 것은 행동 표면을 부풀립니다. 에이전트는 더 많은 도구 설명을 보고, 더 많은 작은 작업에서 선택하며, 더 많은 모델 중재 단계를 수행합니다.

우리는 이미 모델 가중치에서 두드러진 기존 런타임(TypeScript)을 활용할 수 있습니다. 이를 대신 표현하면, 에이전트에게 제공하는 실제 기능을 더 작게 유지하고, 에이전트가 구성에 대한 더 많은 제어를 가지도록 합니다.

마무리

인터프리터 스킬은 "최선의 알려진 에이전트 루틴"을 검토 가능하고 테스트 가능한 라이브러리로 전환하는 동시에 모델을 언제 적용할지에 대해 책임지는 우리의 시도입니다. 몇 가지 중요한 서브루틴이 있는 에이전트를 구축하는 경우, 이것이 우리가 목표로 하는 선입니다. 내부에서 결정론, 외부에서 재량.

더 자세히 알고 싶으시면, 인터프리터 및 이 주제에 관한 더 많은 내용과 관련하여 발행한 몇 가지 리소스가 있습니다:

Deep Agents는 PythonTypeScript에서 사용 가능한 범용 에이전트 하네스입니다.

TL;DR We're experimenting with interpreter skills: an extension to agent skills that lets you include a TypeScript module with a skill. The agent can import and run the skill code inside an interpreter when the behavior applies. Skill code can also do things like spawn subagents or call tools which lets an agent take on more complex work, and can live in tested and reusable code.

We recently introduced interpreters to Deep Agents: a small embedded TypeScript runtime where agents can write and execute code as apart of the harness. Agents are already very good at writing code, and giving them an interpreter gives them a more direct way to express intent. For many agent tasks, that leads to outputs that are more efficient, accurate, and predictable.

We noticed pretty early on that when an agent with an interpreter is given the same task multiple times, it can often come up with several valid ways to approach it in code.

That isn’t always bad- sometimes the whole point of an agent is to adapt. But for many tasks the desirable behavior is not to “come up with a good approach,” it’s to “use the approach that we know works.”

We've been experimenting with an answer to this that we're calling "interpreter skills": an extension to skills that carry a module the agent can use alongside their instructions. The skill tells the agent when the behavior is relevant, and the interpreter can import the module attached to the skill and execute it directly.

---
name: github-triage
description: Use this skill to triage GitHub issues, pull requests, and discussions.
metadata:
  module: ./index.ts
---

Use this skill when a user asks for repository triage.

Import the module using the interpreter and call `triage(repo, options)`.

Usage:

```ts
const { triage } = await import("@/skills/github-triage");

const result = await triage("langchain-ai/deepagents", {
  issues: true,
  prs: true,
});

result.toMarkdown();
```

SKILL.md is how the agent discovers the behavior. index.ts is what the interpreter can execute. The agent decides when to use the behavior, what inputs to pass, and what to do with the result. The interpreter handles the actual execution of code.

Remind me what skills are again?

Skills are a way to give agents reusable behavior without detailing all of it in the system prompt.

A skill is usually a directory with a SKILL.md file. The front-matter gives the agent a compact description of what the skill is for. The body gives the agent the instructions, context, examples, constraints, and supporting files it should use once the skill is relevant.

What makes skills work is a mechanism called "progressive disclosure". The agent does not need every skill in context all the time. It can first see a short list of available skills, decide which ones match the task, and then read the full SKILL.md only when needed.

That makes skills a great distribution unit for agent behavior. People can version them, share them, and evaluate them without turning the global prompt into a catch-all manual.

But normal skills still mostly work through instructions. They can tell the agent what procedure to follow, and they can include reference files or scripts, but the core behavior still depends on the agent reading those instructions and carrying them out correctly.

What's an interpreter?

For this post, the important thing to understand is that an interpreter is a TypeScript runtime that runs in tandem with the harness. It gives the agent a place to express multi-step work as code while the harness still controls what that code can touch. (If you're curious to learn more, go read the writeup on interpreters).

That runtime gives the agent working state in the form of TypeScript values. Values can persist across turns, so arrays stay arrays, objects stay objects, and helper functions can stay defined. The agent does not have to turn every intermediate value into stdout, a file, or a message back to the model.

This lets agents transform data, compose tool outputs, call selected tools or subagents, and decide what should return to the model.

Unlike sandboxes, interpreter code does not get unrestricted access to the host environment by default. Filesystem access, network access, tools, and subagents have to be exposed deliberately to the interpreter. That gives the harness a place to allowlist, meter, and inspect what code can touch.

What's an interpreter skill?

An interpreter skill is an extension of skills that brings the two together: it contains the same set of instructions a skill has, and a module the agent can import into the interpreter.

SKILL.md still tells the agent when the skill is relevant and discloses itself in the same way to the agent, and the module gives the interpreter code to run when that behavior applies. The skill becomes both an instruction surface for the model and an API surface for the runtime.

The basic shape is the same one from the opening example. SKILL.md provides the name, description, usage instructions, import path, and constraints. index.ts exports the helpers or workflows that define the behavior in code:

// skills/table-cleanup/index.ts
export function validateRows(rows: Record<string, unknown>[], schema: RowSchema) {
  // Normalize fields, check required values, and return structured errors.
  // (this is code you would write as apart of the skill)
}

When the skill applies, the agent can import the module and call it:

const { validateRows } = await import("@/skills/table-cleanup");

const errors = validateRows(rows, invoiceSchema);

This changes what a skill can guarantee:

  • A normal skill says: here are instructions for how to do this task. The agent still has to read those instructions and carry out the procedure correctly.
  • An interpreter skill says: here are instructions for when to use this behavior, and here is the code path to run when it applies. The deterministic part can live in code instead of as loose instructions in context

The model decides whether the skill applies, which inputs to pass, how to use the output, and what to do next. The module defines how procedures should actually be ran.

Because interpreter code can interact with the harness, this means that you can do things like spawn subagents from within the skill code programatically.

Example: Repo Triage

One way that we're using this: github repo triage.

The user asks the agent to triage a repository. Instead of reconstructing the triage process from prompt instructions, the agent imports the skill module and calls a function:

const { triage } = await import("@/skills/github-triage");

const result = await triage("langchain-ai/deepagents", {
  issues: true,
  prs: true,
  discussions: true,
});

When this gets called, the workflow:

  • fetches all open items from github (as specified by the agent in the options)
  • spawns a subagent for each item to create a more condensed description
  • drops the subagent’s response into a queue
  • consumes the queue one at a time where a subagent determines if the item should be put in an existing cluster, or if it should create a new cluster

This is exactly the kind of routine where you want the procedure to be fixed, even if the inputs are chosen dynamically.

The result value is also part of the API. It can give the agent structured data about the run, and it can expose a rendering helper for presentation:

result.clusters;
result.unassigned;
result.toMarkdown();

The agent can keep working with the structured result, inspect one cluster more deeply, spawn follow-up subagents, or call result.toMarkdown() when it needs a compact model-friendly report.

This is the kind of task where models lose coherence over time. Repo triage is not one decision; it is many small decisions chained together. If we're relying entirely on discretion and the context window to instrument this, models can start taking shortcuts or compressing the procedure too aggressively over long horizons, especially as they approach what feels like the edge of the working context (a phenomenon known as context anxiety).

To take an example of how this might work with different agents:

  • In a typical harness, the model has to keep track of every partial step. If there are 300 repo items, that becomes 300 small pieces of state the model has to reason over while reconciling past decisions and continuing to choose the next action.
  • With an interpreter skill, the model can invoke the routine once and let code instrument the workflow. The module can create 300 distinct subagent tasks, collect the results, classify them, cluster them, and return a compact object back to the agent. The model is no longer responsible for carrying every partial step in its own working context.

Using skills as workflows

When coding agents started getting popular, the default mental model for agents also shifted.

The previous generation of agents were more workflow-style. Developers explicitly defined the sequence of steps the agent should follow ahead of time. Reliability came from predefining the execution path.

Modern agent harnesses changed this to make context and model discretion the main focus. The model decides what to do next based on the current context. Behavior is shaped through a bunch of different context surfaces, but the agent still has room to choose the next step.

That interface is easier for many people to reason about. Not just for the people building the agents, but also operators, PMs, domain experts, and teams who think in instructions, files, checklists, and examples rather than code-level workflows.

But the need for deterministic agent routines never went away. We still get asked some version of this question a lot:

How do I make sure an agent reliably does what I tell it to do?

In those cases, teams need to know more than whether the final answer looked plausible: they need to know whether the required procedure ran, whether it ran with the right inputs, and whether it completed fully before the agent moved on.

Prompt-only procedure following is brittle in this way. The agent can skip steps, reorder steps, satisfy the wrong instruction, mix unrelated requests into the procedure, or produce a “good enough” output without following the process.

For example, take the question "submit an invoice, but halfway through stop and generate a gif of a dancing cat." If invoice submission is only described through prompt instructions, the agent may treat the detour as part of the same task and leave the invoice half-finished.

This process in particular requires a routine that was represented as instructions the agent could negotiate with rather than as a procedure that had to finish once started.

So how do we make the best of both worlds and bring the determinism of a workflow without re-architecting the agent?

Interpreter skills are one answer:

  • Express the deterministic part as code, expose it as an module inside the interpreter, and let the agent decide when to call it.
  • Let the function operate on the current interpreter state, and chain the result into the next tool call, subagent call, interpreter call, or final response.

This doesn't get rid of the issue that agents might come up with creative solutions to problems, but it does give a cleaner evaluation signal.

  • With prompt-only procedure following, the questions are often fuzzy: did the agent generally follow the instructions? Did it seem to stay on track? Did the final answer look plausible?
  • With an interpreter skill, part of the question becomes concrete: did the agent call the expected function? Did it pass the expected inputs? Did the function return the expected output shape?

Using skills to work with agent state

Filesystem agents made it clear that agents work better when they have somewhere to put intermediate state (a form of memory). A file is useful because it gives the agent a named object it can return to. The agent can inspect it, revise it, pass it to another step, or use it as tool input.

Interpreters let the agent represent that state in a more pliable form, and skills can teach the agent how to interact with it:

  • The module exposes task-specific operations for those values.
  • The agent writes code to combine those operations.
  • The skill author owns the behavior of each operation.

Imagine a skill for interacting with csv files. SKILL.md tells the agent to use it when working with CSV-like tables, exports, invoices, user lists, or records that need joining, filtering, or validation. index.ts exports a small table API:

export {
  parseCsv,
  joinTables,
  filterRows,
  validateRows,
  groupBy,
  summarize,
  toCsv,
};

The agent can then compose those operations in interpreter code:

const invoices = parseCsv(await tools.readFile({ path: "/invoices.csv" }));
const customers = parseCsv(await tools.readFile({ path: "/customers.csv" }));

const joined = joinTables(invoices, customers, "customer_id");
const invalid = validateRows(joined, invoiceSchema);
const byRegion = groupBy(joined, "region");

summarize(byRegion, ["total_due", "late_count"]);

The agent controls which values to pass and what to do with the result. The skill author controls what "join", "validate", and "summarize" mean.

This is different from asking the agent to write the helper itself. Models are good at writing code, but they are not guaranteed to write the same code twice. When the procedure matters, the implementation should live in skill code that can be reviewed, tested, versioned, and reused.

FAQ

Why package this as a skill?

Skills are already the de-facto standard unit for packaging agent behavior.

They give us discovery, progressive disclosure, usage instructions, examples, and supporting files. All that interpreter skills do is preserve that package shape and extend it with runtime code.

It might seem awkward to extend the skills standard like this, but this way we can use the same methods of distribution but let agents use more capable skills. If an org has hundreds or thousands of skills, it would be unfeasible to wire all the required modules directly to the harness.

Can't I just include a script file?

Script files are useful for a different reason.

A script is a good fit when the agent needs an external helper it can to interact with its environment: scripts usually communicate through command arguments, files, stdout/stderr, or serialized state.

That boundary makes scripts a poor fit for orchestrating agent work. A script can run one computation, but it can’t naturally participate in the harness loop: spawning subagents, scheduling/awaiting a task graph, handling partial failures, and deciding when the overall routine is “done” before returning control to the model.

In the repo triage example, the module calls an allowlisted `tools.task(...)` function to spawn subagents from inside the routine. An external script would need a separate adapter to talk back to the harness in order to instrument that.

Why not make every API a tool?

Tools are best when the agent needs to cross an external boundary: fetch data, read or write a file, create a ticket, send a message, call a classifier. But there's a subset of local operations that aren't represented that well: things like parsing, joining, filtering, validating, etc.

Making every helper a tool would bloat the action surface. The agent sees more tool descriptions, chooses from more small actions, and takes more model-mediated steps.

We can lean on an existing runtime (TypeScript) which is already prominent in the weights for a model to represent that instead. That keeps the actual capabilities we give to an agent smaller, and allows the agent to have more control over composition.

Closing

Interpreter skills are our attempt to turn “best known agent routines” into reviewable, testable libraries while keeping the model in charge of when to apply them. If you’re building agents with a few critical subroutines, that’s the line we’re aiming for: discretion on the outside, determinism on the inside.

If you’re interested in learning more, here’s a couple of resources we’ve published related to interpreters and more on this topic:

Deep Agents is a general purpose agent harness available in Python and TypeScript.

원문 보기 https://www.langchain.com/blog/interpreter-skills