Codex CLI를 API처럼 연결해 AI PoC를 빠르게 만든 이유: Agent Runtime과 Feedback Latency

지난 글에서 나는 AI 도입의 가장 어려운 문제가 기술 자체보다 신뢰를 만드는 과정에 가까웠다는 이야기를 했다. 현업이 가진 암묵지를 듣고, AI가 만든 후보에 사람이 다시 맥락을 얹고, 그 결과를 빠르게 수정해 보여줄 수 있어야 저항이 질문으로 바뀌기 시작했다.

그 과정에서 한 문장이 계속 마음에 걸렸다.

“그 조건까지 넣으면 결과가 어떻게 달라지나요?”

그리고 우리가 흔히 하는 답이 있다.

“반영해서 다음 주 데모 때 보여드리겠습니다.”

예전에는 자연스러운 답이었다. 하지만 AX 프로젝트에서는 이 ‘다음 주’가 생각보다 큰 병목이 될 수 있다. 지금 나온 질문과 경험을 실제 동작으로 바꾸는 데 일주일이 걸리면 대화의 맥락도 함께 식어버리기 때문이다.

그래서 나는 어느 순간부터 PoC의 속도를 다른 방식으로 보기 시작했다.

코드를 얼마나 빨리 작성하는가가 아니라, 현업의 아이디어가 실제 실행 결과로 돌아오기까지 얼마나 걸리는가.

내가 줄이고 싶었던 것은 개발 시간 자체보다 Feedback Latency였다.

AI PoC가 느려지는 이유는 모델이 아니었다

AI PoC를 만든다고 하면 먼저 모델을 떠올리기 쉽다. 어떤 모델을 쓸지, Prompt를 어떻게 만들지, RAG를 붙일지, Agent로 갈지를 고민한다. 물론 모두 중요하다.

그런데 여러 PoC를 반복해서 만들다 보니 실제로 시간을 많이 잡아먹는 것은 모델 밖에 있는 경우가 더 많았다.

  • 누가 이 기능을 호출할 것인가
  • 인증은 어떻게 할 것인가
  • 입력을 어떤 형태로 받을 것인가
  • 실행 환경은 어디에 둘 것인가
  • 어떤 Context를 Agent에게 전달할 것인가
  • 어떤 Tool을 허용할 것인가
  • 결과를 어떤 Schema로 반환할 것인가
  • 실패하면 어디에서 다시 시작할 것인가
  • 같은 기능을 Web, n8n, Salesforce에서도 재사용할 수 있는가

첫 번째 PoC에서는 그냥 붙이면 된다. 두 번째도 그렇게 할 수 있다. 하지만 세 번째쯤부터는 느낌이 달라진다.

검증해야 할 비즈니스 아이디어보다 인증, 호출, 실행, 상태, 결과 형식을 다시 연결하는 시간이 더 아깝게 느껴진다.

반복적인 PoC 구축 과정과 Common Agent Runtime을 재사용하는 빠른 Feedback Loop 비교
매번 실행 주변부를 다시 구축하는 대신 Common Agent Runtime을 재사용하면 아이디어에서 동작하는 PoC까지의 Feedback Loop를 줄일 수 있다.

처음부터 거대한 Agent Platform을 만들 필요는 없다고 생각한다. 오히려 내가 잡은 기준은 단순했다.

두 번 이상 반복해서 만드는 부분이 보이면, 그때 공통 실행부로 올린다.

내 경우 그 시작점이 Coding Agent였다.

Codex CLI를 CLI가 아니라 Execution Engine으로 보기 시작했다

Codex CLI 같은 Coding Agent는 본래 터미널에서 사용하기 좋은 도구다. Repository를 읽고, 코드를 수정하고, 명령을 실행하고, 테스트 결과를 다시 확인하는 일에 강하다.

처음에는 나도 당연히 사람이 직접 실행하는 방식으로 사용했다. ChatGPT에서 아이디어를 정리하고, Codex에 작업을 맡기고, 결과를 서버나 Web App에서 확인한다. 이 방식만으로도 개발 속도는 꽤 빨라진다.

그런데 현업과 대화하면서 PoC를 계속 수정하려고 하니 다른 생각이 들었다.

“이 CLI를 내가 직접 실행하는 것이 아니라, 다른 시스템이 호출하게 만들면 어떨까?”

내가 원한 것은 완전 자율형 Agent가 아니었다. Repository를 읽고 실행할 수 있는 Coding Agent의 능력을 기존 개발환경 안에 넣어, 필요할 때 Application이 호출할 수 있는 실행 엔진으로 사용하는 것이었다.

Human
  ↓
Codex CLI

        ↓

Application
  ↓
API Boundary
  ↓
Agent Runtime
  ↓
Codex CLI

이렇게 보면 중요한 것은 Codex를 원격으로 실행하는 기술 자체가 아니다. Caller가 Agent 내부 구현을 몰라도 되는 안정적인 Boundary를 만드는 것이 더 중요하다.

CLI 앞에 얇은 API Boundary 하나를 두었다

내가 실제 PoC에서 사용한 구조도 복잡하지 않았다.

  1. Web, n8n, Salesforce 같은 Caller가 HTTP API를 호출한다.
  2. FastAPI가 인증과 입력 검증을 담당한다.
  3. Agent Runtime이 실행에 필요한 Context와 Parameter를 조립한다.
  4. Codex가 Reasoning과 작업 실행을 수행한다.
  5. 결과를 일정한 응답 계약으로 Caller에 반환한다.

여기서 FastAPI 자체가 핵심은 아니다. Node.js나 다른 Runtime이어도 된다. 내가 중요하게 본 것은 기술 스택보다 역할 경계였다.

Caller는 업무 접점을 책임진다

Web은 사용자 입력과 결과 표현을 담당한다. n8n은 Workflow orchestration과 여러 시스템 연결을 담당한다. Salesforce는 CRM Record와 실제 업무 사용자의 Context를 가지고 있다.

API Boundary는 계약을 책임진다

누가 호출할 수 있는지, 입력이 올바른지, 어떤 Context를 받아야 하는지, 결과를 어떤 형식으로 돌려줄지를 책임진다.

Agent Runtime은 실행을 책임진다

Model, Instruction, Tool profile, 실행 환경과 필요한 Context를 조립해 Agent를 실행한다.

이렇게 역할을 나누자 질문 자체가 바뀌었다.

“Salesforce가 기존 Agent Runtime에 어떤 Context를 전달해야 하지?”

“Salesforce에서 Codex를 어떻게 실행하지?”와 비슷해 보이지만 개발 관점에서는 꽤 큰 차이다.

Web, n8n, Salesforce가 공통 API Boundary와 Agent Runtime을 통해 Codex CLI 실행 환경으로 연결되는 구조
Caller를 API Boundary와 분리하면 Web, n8n, Salesforce가 같은 Agent Runtime을 재사용하면서 각 Use Case의 Context와 계약만 바꿀 수 있다.

Caller와 Runtime을 분리하자 PoC가 ‘개발’보다 ‘조립’에 가까워졌다

공통 실행부를 만들고 가장 크게 달라진 것은 새로운 Use Case의 시작점이었다.

예전에는 PoC가 하나 생길 때마다 인증부터 다시 생각하고, 모델 호출부를 만들고, Tool 연결을 붙이고, 결과 형식을 정했다.

공통 Runtime이 생긴 뒤에는 이미 존재하는 실행 기반 위에서 세 가지를 주로 정의하면 됐다.

  • 이번 Agent가 해야 할 역할은 무엇인가
  • Caller가 어떤 Context를 전달할 것인가
  • 결과를 어떤 Schema로 받아야 하는가

즉 실행 기반을 다시 만드는 일이 아니라 이번 Use Case의 차이만 정의하는 일에 가까워졌다.

이것은 완성된 플랫폼을 구축했다는 의미와는 다르다. 오히려 미완성인 PoC를 계속 바꾸기 쉬워졌다는 의미에 가깝다.

UI를 바꿔도 Runtime은 그대로다. n8n Workflow를 추가해도 Agent 실행 방식은 그대로다. Salesforce가 새로운 Caller가 되어도 Agent 내부를 다시 작성할 이유는 줄어든다.

다음 실험을 시작하는 비용을 낮추는 것. 내가 원했던 것도 바로 이런 상태였다.

Stable Runtime과 Variable Agent Definition을 분리했다

실행부를 공통화하면 다음 문제가 생긴다. Use Case마다 필요한 Agent의 성격이 다르다.

어떤 작업은 빠른 모델이면 충분하고, 어떤 작업은 더 강한 Reasoning이 필요하다. 어떤 작업은 자유로운 텍스트가 필요하지만, 다른 작업은 반드시 JSON Schema를 지켜야 한다. Repository를 수정하는 Agent와 문서를 분석하는 Agent가 같은 Instruction을 사용할 이유도 없다.

이 차이를 전부 코드 분기로 넣기 시작하면 공통 Runtime이 다시 Use Case 코드로 오염된다. 그래서 나는 실행 구성을 세 층으로 나눠 생각하는 편이 더 낫다고 봤다.

Stable Runtime

  • authentication boundary
  • execution boundary
  • 공통 context assembly
  • 상태와 결과 계약
  • logging / audit에 필요한 실행 정보

Variable Agent Definition

  • model
  • reasoning level
  • instructions
  • output schema
  • tool profile

Runtime Context

  • user
  • current record
  • allowed scope
  • business context
  • 이번 요청의 input

개념적인 호출 계약은 다음처럼 볼 수 있다.

{
  "input": "Analyze the current opportunity and propose next actions.",
  "runtime_context": {
    "user_id": "005...",
    "record_id": "006...",
    "scope": ["opportunity", "account"]
  },
  "agent": {
    "model": "...",
    "reasoning": "...",
    "instructions": "...",
    "tool_profile": "sales-assistant"
  }
}

이 JSON은 특정 제품의 표준 Schema가 아니라 내가 실행 계약을 설계할 때 사용하는 개념적인 구분이다.

KEY TAKEAWAY. Application이 이미 알고 있는 정보와 Agent가 추론해야 하는 정보를 한 덩어리로 만들지 않는다.

모든 것을 Parameter로 만들 필요는 없었다

재사용성을 생각하기 시작하면 쉽게 빠지는 함정이 하나 있다. 모든 것을 설정 가능하게 만들고 싶어진다.

Model도 Parameter, Tool도 Parameter, State도 Parameter, Retry도 Parameter, Permission도 Parameter로 빼다 보면 어느 순간 PoC를 빨리 만들기 위한 기반이 아니라 또 하나의 Platform 제품을 개발하고 있게 된다.

나는 여기에서도 같은 기준을 적용하는 편이 낫다고 봤다.

반복해서 바뀌는 값만 밖으로 꺼낸다. 아직 한 번밖에 필요하지 않은 것은 그냥 둔다.

Model, Reasoning, Instruction, Output Schema처럼 여러 실험에서 반복적으로 바뀌는 것은 외부화할 가치가 크다. 반면 아직 존재하지 않는 미래의 Use Case까지 예측해 Runtime을 추상화하는 것은 오히려 Feedback Latency를 다시 늘릴 수 있다.

빠른 PoC를 위한 공통 기반은 가능한 많은 기능을 갖춘 기반이 아니라, 다음 실험에 필요한 변경 면적을 줄이는 기반이어야 한다.

마침 Codex의 Harness도 API로 제공되기 시작했다

이 글을 정리하던 2026년 9월 10일, OpenAI는 Agents API를 public beta로 공개했다.

OpenAI의 설명에 따르면 Agents API는 Codex를 구동하는 harness와 infrastructure를 API로 제공하며, 장기 실행 세션, context management, tool use, subagent orchestration 같은 실행 계층을 관리한다. OpenAI 공식 소개

내가 기존 환경에서 Codex CLI 앞에 직접 API Boundary와 Runtime을 만들기 시작했던 것과 제품 구현이 동일하다는 뜻은 아니다.

현재 흐름에서 중요한 점. Agent를 사용하는 UI와 Agent를 실제로 실행하는 Harness/Runtime은 분리해서 생각할 수 있다.

CLI, IDE, Web UI가 Agent 그 자체일 필요는 없다. Agent의 실행 계층을 API로 다룰 수 있다면 Caller는 업무에 필요한 Context와 결과 계약에 더 집중할 수 있다.

하지만 Enterprise 환경에서는 여기서 문제가 끝나지 않는다. 오히려 실행이 쉬워질수록 더 중요한 질문이 생긴다.

Agentic하게 만들수록 결정론적인 영역이 더 중요해졌다

Agent가 Tool을 선택하고 여러 단계를 스스로 계획할 수 있다면 분명 편리하다. 하지만 Application이 이미 알고 있는 사실까지 Agent가 다시 추론하게 만들 필요가 있을까?

예를 들어 Salesforce에서 사용자가 현재 Opportunity 화면을 보고 있다면 Application은 이미 여러 사실을 알고 있다.

  • 누가 호출했는가
  • 어떤 Record를 보고 있는가
  • 어떤 조직과 권한 범위에 속해 있는가
  • 어떤 데이터 범위까지 접근할 수 있는가
  • 어떤 실행이 허용되는가

이 정보는 Agent가 자연어를 해석해 추측해야 할 대상이 아니다.

확정 가능한 Context는 Application이 제공하고, 불확실한 판단만 Agent에게 맡기는 편이 더 단순하고 안전하다.

이 지점부터 내 관심은 “Agent에게 Tool을 얼마나 많이 붙일까?”에서 조금 달라졌다. Agent에게 자유도를 줄수록 그 바깥에는 오히려 더 명확한 통제면이 필요했다.

Agent
- intent interpretation
- reasoning
- planning
- candidate action selection

Application / Runtime
- identity
- current record
- scope
- permission
- execution guard
- state
- audit
Agentic Reasoning과 결정론적인 Application Control의 책임 경계를 비교한 구조도
Intent·Reasoning·Planning은 Agent가 담당하고, Identity·Scope·Permission·State 같은 확정 가능한 통제는 Application이 소유한다.

이 원칙은 단순히 보안을 강화하기 위한 장치만은 아니다.

Agent가 매번 사용자의 identity와 현재 record, 실행 범위를 추론하지 않아도 되기 때문에 Reasoning 자체도 더 가벼워진다. Tool 호출에 필요한 인자를 확정 가능한 값으로 먼저 제공할 수 있고, 불필요한 탐색과 반복 호출도 줄일 수 있다.

즉 Deterministic Control은 Agentic함을 제한하는 반대말이 아니라, Agent가 정말 판단해야 하는 문제에 집중하게 만드는 기반이 될 수 있다.

내가 만든 것은 Codex 원격 실행 API가 아니라 Feedback Loop였다

처음 질문은 단순했다. “Codex CLI를 다른 시스템에서 호출할 수 있을까?”

하지만 실제로 만들어보니 내가 필요했던 것은 CLI를 원격으로 실행하는 기능 자체가 아니었다. 현업에서 나온 질문을 실제 동작으로 바꾸고, 다시 보여주고, 다시 수정하는 시간을 줄이는 Feedback Loop였다.

Coding Agent는 사람이 직접 사용하는 도구에만 머물 필요가 없다. Caller와 Agent Runtime을 분리하면 같은 실행 기반을 Web, n8n, Salesforce 같은 서로 다른 업무 접점에서 재사용할 수 있다.

Stable Runtime과 Variable Agent Definition을 분리하면 새로운 PoC에서 바꿔야 할 범위를 줄일 수 있다. 그리고 Agent가 더 많은 일을 하게 할수록, Application이 확정적으로 알고 있는 Context와 통제 책임은 더 명시적으로 분리해야 한다.

현업의 질문이 실제 동작으로 돌아오는 시간이었다.

이전 글에서 말했던 것처럼 PoC는 기술 가능성을 증명하는 데서 끝나지 않는다. 현업과 시스템이 서로를 이해하기 위한 대화 도구가 될 수 있다.

그렇다면 좋은 실행 기반의 역할도 분명해진다. 그 대화가 끊어지지 않게 하는 것.

그리고 여기에서 다음 질문이 남는다.

Agent에게 Tool 실행을 어디까지 맡기고, 어디부터 Application이 결정론적으로 통제해야 할까?

다음 글에서는 이 경계를 더 깊게 다뤄보려고 한다. Tool을 지나치게 원자화하지 않으면서도 Agent 내부에 불필요한 Agentic 판단을 중첩하지 않는 방법, 그리고 user·record·scope처럼 실행 전에 확정할 수 있는 Runtime Context를 Caller가 먼저 전달하는 구조를 정리해볼 생각이다.