콘텐츠로 이동

에이전트 엔드포인트

저장된 로컬 에이전트 프로필을 게시하면 설정한 모델과 도구를 OpenAI 호환 API 또는 A2A로 호출할 수 있습니다. 게시는 기본적으로 꺼져 있습니다.

프로필 게시

로컬 프로필 편집기에서 변경 사항을 저장한 뒤 공개 탭을 엽니다. 게시를 켜고 공개 슬러그를 지정한 뒤 적용합니다. 허용 도구, 승인 방식, 작업 폴더, 최대 반복 횟수, 실행 시간과 세션 보존 기간도 이 탭에서 설정합니다. 도구를 게시하려면 먼저 프로필에서 활성화해야 합니다. 선택한 도구를 더 이상 사용할 수 없다면 게시 목록에서 제거한 뒤 적용하세요. 원격 프로필을 로컬 에이전트로 다시 게시할 수는 없습니다.

API 백엔드 목록은 모델 API와 에이전트 API를 구분합니다. GO가 관리하는 aigo-agents 백엔드의 모델 이름은 agent:<슬러그>입니다. 이 백엔드를 직접 만들 필요는 없습니다. 게시를 켜면 해당 환경에서 내장 Management API가 시작되며, 라우터의 TCP 리스너도 필요합니다. 연결 패널에서 리스너와 해당 에이전트 모델의 실제 제공 여부를 확인한 뒤 주소를 복사하세요. 포트 설정을 저장했거나 Management API에 SSH 터널을 연결했다고 해서 라우터 포트에도 접근할 수 있는 것은 아닙니다. 데스크톱 앱의 연결 패널은 이 컴퓨터 기준의 주소를 보여 줍니다. 루프백 주소(127.0.0.1, [::1], localhost)에는 이 컴퓨터에서만 접근할 수 있다는 안내가 붙으며, 네트워크의 다른 클라이언트는 호스트 부분을 이 컴퓨터의 네트워크 주소로 바꿔 사용하세요.

연결과 승인

인터페이스 주소 승인 동작
Chat Completions 라우터 기본 URL의 /chat/completions 승인이 필요한 도구 거부
네이티브 Responses 라우터 기본 URL의 /responses 저장된 ask 모드에서 호출 앱에 승인 요청 가능
변환형 Responses 동일한 /responses 주소 텍스트 대화 변환, 승인이 필요한 도구 거부
A2A 0.3 Management API 카드의 url 저장된 ask 모드에서 호출 앱에 승인 요청 가능

연결 패널은 실행 중인 라우터에 실제 적용된 모드를 표시합니다. 네이티브 기능을 지원하더라도 관리 백엔드 설정까지 확인되어야 사용할 수 있습니다. 적용 중이거나 확인할 수 없는 상태를 네이티브 사용 가능 상태로 해석하지 마세요. 승인은 호출 앱에서 처리하며 데스크톱 승인 창을 띄우는 기능이 아닙니다. 저장된 모드가 deny이면 모든 인터페이스에서 승인 요청을 거부합니다.

라우터와 Management API의 클라이언트 키를 따로 사용하세요. 라우터 예제에는 추론 접근 키를 사용합니다. A2A에는 agent_invoke 권한이 필요하며 agent_read나 agent_write만으로 호출할 수는 없습니다. 일반 Management API 인증을 꺼도 A2A에는 인증된 사용자가 필요합니다. 라우터가 GO 내부에 연결할 때 쓰는 자격 증명은 클라이언트 키가 아닙니다.

예전에는 업스트림에서 데이터가 오지 않는 동안 클라이언트가 네이티브 Responses 스트림을 닫아도 에이전트가 계속 실행됐습니다(라우터 이슈 #2003). 이 문제의 수정(라우터 PR #2004)은 GO에 번들된 continuum-router v1.32.0에 들어 있습니다. 관련된 두 문제는 아직 열려 있는데요, 라우터의 Chat Completions 중계는 업스트림이 조용한 동안 클라이언트 연결 해제를 놓칠 수 있고(라우터 이슈 #2008), 취소 요청이나 연결 해제, tasks/cancel로 게시 실행을 취소해도 실행만 멈출 뿐 진행 중인 모델 요청은 끝까지 생성되므로 한 번에 요청 하나만 처리하는 로컬 모델은 그동안 다른 요청을 받지 못합니다(#5461). 작업을 중단하려면 명시적인 취소 엔드포인트를 사용하세요. v1.29.0 같은 이전 라우터는 변환된 Responses만 제공하며, 실행 중인 라우터에 실제 적용된 모드는 연결 패널에서 확인할 수 있습니다.

셸 준비

예제에는 Bash, curl, jq, Python 3가 필요합니다. 키를 셸 기록에 직접 입력하지 말고 비공개로 불러오세요. 연결 패널의 값으로 아래 변수를 설정합니다. ROUTER_BASE는 /v1을 포함하고 MANAGEMENT_BASE는 Management API의 origin입니다. 로컬 기본 포트는 각각 39080과 8001이지만 실제 설정된 주소를 사용하세요. OUT에 저장하는 응답에는 대화 접근 권한을 가진 값이 포함되므로 파일을 공개하지 마세요.

승인 예제에는 ask로 저장된 별도 게시 프로필의 ASK_SLUG와, 승인 대상 허용 도구를 확실히 요청하는 ASK_INPUT이 필요합니다. 텍스트 전용 프로필에서는 승인 요청이 발생하지 않습니다. 아래 예제는 승인·거부·취소마다 새 요청을 만듭니다.

set -eu
umask 077
: "${ROUTER_BASE:?Set the copied OpenAI base URL, including /v1}"
: "${MANAGEMENT_BASE:?Set the Management API origin, without a trailing slash}"
: "${ROUTER_API_KEY:?Load a router access key}"
: "${MANAGEMENT_API_KEY:?Load a key with agent_invoke}"
: "${SLUG:?Set an enabled deny publication slug}"
MODEL="agent:$SLUG"
OUT=$(mktemp -d)
# For approval examples, also set ASK_SLUG and ASK_INPUT.
ASK_MODEL="agent:${ASK_SLUG:-}"

게시 모델 조회

curl --fail-with-body -sS "$ROUTER_BASE/models" \
  -H "Authorization: Bearer $ROUTER_API_KEY" > "$OUT/models.json"
jq -e --arg model "$MODEL" '.data[] | select(.id == $model)' "$OUT/models.json"

Chat 요청과 세션 이어가기

jq -n --arg model "$MODEL" '{model:$model,messages:[{role:"user",content:"Remember the word apricot."}]}' |
  curl --fail-with-body -sS "$ROUTER_BASE/chat/completions" -H "Authorization: Bearer $ROUTER_API_KEY" -H 'Content-Type: application/json' --data-binary @- > "$OUT/chat.json"
CHAT_SESSION=$(jq -er '.agent_session' "$OUT/chat.json")
jq -n --arg model "$MODEL" '{model:$model,messages:[{role:"user",content:"Which word did I ask you to remember?"}]}' |
  curl --fail-with-body -sS "$ROUTER_BASE/chat/completions" -H "Authorization: Bearer $ROUTER_API_KEY" -H "X-Agent-Session: $CHAT_SESSION" -H 'Content-Type: application/json' --data-binary @- > "$OUT/chat-next.json"
jq -e '.choices[0].message.content | type == "string"' "$OUT/chat-next.json"

Chat 출력 스트리밍

jq -n --arg model "$MODEL" '{model:$model,messages:[{role:"user",content:"Say hello."}],stream:true}' |
  curl --fail-with-body -sS -N "$ROUTER_BASE/chat/completions" -H "Authorization: Bearer $ROUTER_API_KEY" -H 'Content-Type: application/json' --data-binary @- > "$OUT/chat.sse"

일반 Chat 응답의 JSON 본문이나 스트리밍 SSE 청크에서 agent_session을 읽어 다음 요청의 X-Agent-Session 헤더로 보내세요. 프록시가 응답 헤더를 생략할 수 있으므로 헤더로 이 값을 받는다고 가정하지 마세요. 어느 위치에서 읽더라도 비밀 값으로 다루세요.

Responses 텍스트와 스트리밍

jq -n --arg model "$MODEL" '{model:$model,input:"Say hello."}' |
  curl --fail-with-body -sS "$ROUTER_BASE/responses" -H "Authorization: Bearer $ROUTER_API_KEY" -H 'Content-Type: application/json' --data-binary @- > "$OUT/response.json"
jq -e '.id and .status == "completed"' "$OUT/response.json"
jq -n --arg model "$MODEL" '{model:$model,input:"Say hello.",stream:true}' |
  curl --fail-with-body -sS -N "$ROUTER_BASE/responses" -H "Authorization: Bearer $ROUTER_API_KEY" -H 'Content-Type: application/json' --data-binary @- > "$OUT/response.sse"

이후 Responses 예제를 실행하려면 연결 패널에 관리 백엔드 설정 검증을 마친 네이티브 모드가 표시되어야 합니다. 변환 모드에서도 위 텍스트·스트리밍 호출은 사용할 수 있고 승인 필요 도구는 거부합니다. 라우터 v1.29.0의 변환 모드에서는 GET /v1/responses/{id}가 200, previous_response_id 이어가기가 200, 취소가 404를 반환했는데, 이 조회와 이어가기는 에이전트가 보존한 기록이 아니라 라우터 자체의 Responses 상태에서 나오므로 아래의 네이티브 기록·승인·수명 주기·store:false 동작이 적용되지 않습니다.

네이티브 응답 및 대화 기록

PREVIOUS=$(jq -er '.id' "$OUT/response.json")
jq -n --arg model "$MODEL" --arg previous "$PREVIOUS" '{model:$model,previous_response_id:$previous,input:"Explain your answer."}' |
  curl --fail-with-body -sS "$ROUTER_BASE/responses" -H "Authorization: Bearer $ROUTER_API_KEY" -H 'Content-Type: application/json' --data-binary @- > "$OUT/response-next.json"
CONVERSATION=$(jq -er '.conversation.id' "$OUT/response-next.json")
jq -n --arg model "$MODEL" --arg conversation "$CONVERSATION" '{model:$model,conversation:$conversation,input:"Summarize this conversation."}' |
  curl --fail-with-body -sS "$ROUTER_BASE/responses" -H "Authorization: Bearer $ROUTER_API_KEY" -H 'Content-Type: application/json' --data-binary @- > "$OUT/response-third.json"

네이티브 도구 요청 승인 또는 거부

for DECISION in true false; do
  jq -n --arg model "$ASK_MODEL" --arg input "$ASK_INPUT" '{model:$model,input:$input}' |
    curl --fail-with-body -sS "$ROUTER_BASE/responses" -H "Authorization: Bearer $ROUTER_API_KEY" -H 'Content-Type: application/json' --data-binary @- > "$OUT/pending-$DECISION.json"
  PENDING_ID=$(jq -er '.id' "$OUT/pending-$DECISION.json")
  APPROVAL_ID=$(jq -er '.output[] | select(.type == "mcp_approval_request") | .id' "$OUT/pending-$DECISION.json")
  jq -n --arg model "$ASK_MODEL" --arg previous "$PENDING_ID" --arg approval "$APPROVAL_ID" --argjson approve "$DECISION" '{model:$model,previous_response_id:$previous,input:[{type:"mcp_approval_response",approval_request_id:$approval,approve:$approve}]}' |
    curl --fail-with-body -sS "$ROUTER_BASE/responses" -H "Authorization: Bearer $ROUTER_API_KEY" -H 'Content-Type: application/json' --data-binary @- > "$OUT/resumed-$DECISION.json"
  jq -e --arg previous "$PENDING_ID" '.id != $previous' "$OUT/resumed-$DECISION.json"
done

조회, 승인 대기 취소, 삭제

jq -n --arg model "$ASK_MODEL" --arg input "$ASK_INPUT" '{model:$model,input:$input}' |
  curl --fail-with-body -sS "$ROUTER_BASE/responses" -H "Authorization: Bearer $ROUTER_API_KEY" -H 'Content-Type: application/json' --data-binary @- > "$OUT/cancel-pending.json"
RESPONSE_ID=$(jq -er '.id' "$OUT/cancel-pending.json")
jq -e '.output[] | select(.type == "mcp_approval_request")' "$OUT/cancel-pending.json" > /dev/null
curl --fail-with-body -sS "$ROUTER_BASE/responses/$RESPONSE_ID" -H "Authorization: Bearer $ROUTER_API_KEY" > "$OUT/retrieved.json"
curl --fail-with-body -sS -X POST "$ROUTER_BASE/responses/$RESPONSE_ID/cancel" -H "Authorization: Bearer $ROUTER_API_KEY" > "$OUT/cancelled-approval.json"
curl --fail-with-body -sS -X DELETE "$ROUTER_BASE/responses/$RESPONSE_ID" -H "Authorization: Bearer $ROUTER_API_KEY" > "$OUT/deleted.json"
jq -e '.deleted == true' "$OUT/deleted.json"

응답 기록을 보존하지 않고 실행

jq -n --arg model "$MODEL" '{model:$model,input:"Say hello.",store:false}' |
  curl --fail-with-body -sS "$ROUTER_BASE/responses" -H "Authorization: Bearer $ROUTER_API_KEY" -H 'Content-Type: application/json' --data-binary @- > "$OUT/ephemeral.json"
EPHEMERAL_ID=$(jq -er '.id' "$OUT/ephemeral.json")
HTTP_STATUS=$(curl -sS -o "$OUT/ephemeral-get.json" -w '%{http_code}' "$ROUTER_BASE/responses/$EPHEMERAL_ID" -H "Authorization: Bearer $ROUTER_API_KEY")
test "$HTTP_STATUS" = 404

인증된 A2A 카드 조회

curl --fail-with-body -sS "$MANAGEMENT_BASE/api/v1/a2a/$SLUG/.well-known/agent-card.json" -H "Authorization: Bearer $MANAGEMENT_API_KEY" > "$OUT/card.json"
A2A_URL=$(jq -er '.url' "$OUT/card.json")
jq -e '.protocolVersion == "0.3"' "$OUT/card.json"

A2A 작업 전송과 조회

MESSAGE_ID=$(python3 -c 'import uuid; print(uuid.uuid4())')
jq -n --arg message "$MESSAGE_ID" '{jsonrpc:"2.0",id:1,method:"message/send",params:{message:{kind:"message",messageId:$message,role:"user",parts:[{kind:"text",text:"Say hello."}]}}}' |
  curl --fail-with-body -sS "$A2A_URL" -H "Authorization: Bearer $MANAGEMENT_API_KEY" -H 'Content-Type: application/json' --data-binary @- > "$OUT/task.json"
TASK_ID=$(jq -er '.result.id' "$OUT/task.json")
for ATTEMPT in $(seq 1 120); do
  jq -n --arg id "$TASK_ID" '{jsonrpc:"2.0",id:2,method:"tasks/get",params:{id:$id}}' |
    curl --fail-with-body -sS "$A2A_URL" -H "Authorization: Bearer $MANAGEMENT_API_KEY" -H 'Content-Type: application/json' --data-binary @- > "$OUT/task-status.json"
  STATE=$(jq -er '.result.status.state' "$OUT/task-status.json")
  case "$STATE" in completed|failed|canceled|rejected|input-required) break;; esac
  sleep 1
done
jq -e '.result.status.state == "completed"' "$OUT/task-status.json"

A2A 작업 스트리밍

MESSAGE_ID=$(python3 -c 'import uuid; print(uuid.uuid4())')
jq -n --arg message "$MESSAGE_ID" '{jsonrpc:"2.0",id:3,method:"message/stream",params:{message:{kind:"message",messageId:$message,role:"user",parts:[{kind:"text",text:"Say hello."}]}}}' |
  curl --fail-with-body -sS -N "$A2A_URL" -H "Authorization: Bearer $MANAGEMENT_API_KEY" -H 'Content-Type: application/json' --data-binary @- > "$OUT/task.sse"

승인 대기 A2A 작업 승인·거부·취소

curl --fail-with-body -sS "$MANAGEMENT_BASE/api/v1/a2a/$ASK_SLUG/.well-known/agent-card.json" -H "Authorization: Bearer $MANAGEMENT_API_KEY" > "$OUT/ask-card.json"
ASK_A2A_URL=$(jq -er '.url' "$OUT/ask-card.json")
for DECISION in approve deny cancel; do
  MESSAGE_ID=$(python3 -c 'import uuid; print(uuid.uuid4())')
  jq -n --arg message "$MESSAGE_ID" --arg input "$ASK_INPUT" '{jsonrpc:"2.0",id:4,method:"message/send",params:{message:{kind:"message",messageId:$message,role:"user",parts:[{kind:"text",text:$input}]}}}' |
    curl --fail-with-body -sS "$ASK_A2A_URL" -H "Authorization: Bearer $MANAGEMENT_API_KEY" -H 'Content-Type: application/json' --data-binary @- > "$OUT/ask-task.json"
  TASK_ID=$(jq -er '.result.id' "$OUT/ask-task.json")
  for ATTEMPT in $(seq 1 120); do
    jq -n --arg id "$TASK_ID" '{jsonrpc:"2.0",id:5,method:"tasks/get",params:{id:$id}}' |
      curl --fail-with-body -sS "$ASK_A2A_URL" -H "Authorization: Bearer $MANAGEMENT_API_KEY" -H 'Content-Type: application/json' --data-binary @- > "$OUT/ask-status.json"
    STATE=$(jq -er '.result.status.state' "$OUT/ask-status.json")
    case "$STATE" in input-required|completed|failed|canceled|rejected) break;; esac
    sleep 1
  done
  jq -e '.result.status.state == "input-required"' "$OUT/ask-status.json" > /dev/null
  CONTEXT_ID=$(jq -er '.result.contextId' "$OUT/ask-status.json")
  CALL_ID=$(jq -er '.result.status.message.metadata.aigoApproval.callId' "$OUT/ask-status.json")
  if [ "$DECISION" = cancel ]; then
    jq -n --arg id "$TASK_ID" '{jsonrpc:"2.0",id:6,method:"tasks/cancel",params:{id:$id}}' > "$OUT/decision.json"
  else
    MESSAGE_ID=$(python3 -c 'import uuid; print(uuid.uuid4())')
    jq -n --arg task "$TASK_ID" --arg context "$CONTEXT_ID" --arg call "$CALL_ID" --arg decision "$DECISION" --arg message "$MESSAGE_ID" '{jsonrpc:"2.0",id:6,method:"message/send",params:{message:{kind:"message",messageId:$message,role:"user",taskId:$task,contextId:$context,parts:[{kind:"text",text:$decision}],metadata:{aigoApproval:{callId:$call,response:$decision}}}}}' > "$OUT/decision.json"
  fi
  curl --fail-with-body -sS "$ASK_A2A_URL" -H "Authorization: Bearer $MANAGEMENT_API_KEY" -H 'Content-Type: application/json' --data-binary @"$OUT/decision.json" > "$OUT/a2a-$DECISION.json"
  jq -e '.result and (has("error") | not)' "$OUT/a2a-$DECISION.json"
  for ATTEMPT in $(seq 1 120); do
    jq -n --arg id "$TASK_ID" '{jsonrpc:"2.0",id:7,method:"tasks/get",params:{id:$id}}' |
      curl --fail-with-body -sS "$ASK_A2A_URL" -H "Authorization: Bearer $MANAGEMENT_API_KEY" -H 'Content-Type: application/json' --data-binary @- > "$OUT/final-$DECISION.json"
    STATE=$(jq -er '.result.status.state' "$OUT/final-$DECISION.json")
    case "$STATE" in completed|failed|canceled|rejected|input-required) break;; esac
    sleep 1
  done
  if [ "$DECISION" = cancel ]; then
    jq -e '.result.status.state == "canceled"' "$OUT/final-$DECISION.json"
  else
    jq -e '.result.status.state | . == "completed" or . == "failed" or . == "input-required"' "$OUT/final-$DECISION.json"
  fi
done

기록, 수명 주기와 제한

X-Agent-Session이 없는 요청은 매번 새 대화를 시작합니다. 서버에 보존된 해당 대화 기록을 이어가려면 반환된 agent_session을 X-Agent-Session으로 보내세요. 클라이언트가 이전 사용자·어시스턴트 메시지를 새 요청에 다시 보내는 방식도 사용할 수 있습니다. 네이티브 Responses는 최신 previous_response_id 또는 반환된 conversation.id 중 하나만 받습니다. 저장된 기록을 이어갈 때는 새 사용자 입력만 보내세요. 이 값은 대화에 접근할 권한을 부여합니다. 메모리에 보존하는 값이며 영속적인 OpenAI Conversations API 객체가 아닙니다. 게시 설정 변경·해제, 재시작, 만료 시 세션 접근이 취소됩니다.

만료되었거나 접근이 취소된 값을 보내면 아래와 같이 응답합니다. GO는 Chat 세션을 찾지 못하면 HTTP 404와 error.code: agent_session_not_found를 반환하지만, 라우터는 429를 제외한 백엔드의 모든 4xx를 HTTP 400으로 바꾸고 본문의 error.code는 그대로 전달하므로 Chat 오류는 상태 코드가 아니라 코드 값으로 구분하세요.

인터페이스 만료되었거나 취소된 값 게시 비활성화
Chat Completions, X-Agent-Session HTTP 400, error.code: agent_session_not_found 라우터 카탈로그에서 모델이 빠진 뒤에는 라우터의 HTTP 404, error.code: model_not_found, 그 전 몇 초 동안은 HTTP 400, error.code: agent_error
네이티브 Responses, GET /responses/{id}, previous_response_id, conversation HTTP 404 HTTP 404
contextId를 보낸 A2A message/send HTTP 200, JSON-RPC 오류 -32001, Context not found or expired REST 오류 본문의 HTTP 404
A2A tasks/get HTTP 200, JSON-RPC 오류 -32001, Task not found REST 오류 본문의 HTTP 404

네이티브 응답에 승인 요청이 있으면 해당 응답 단계는 완료되지만 실행은 승인을 기다립니다. 승인하거나 거부하면 새 응답 ID를 만들고 같은 실행을 재개합니다. 최신 출력에 추가 승인 요청이 있는지 확인하세요. 승인 ID는 한 번만 사용할 수 있습니다. 가장 최근의 완료된 승인 응답을 취소하면 대기 중인 실행은 멈추지만 완료된 응답 내용은 유지됩니다. 이전 단계의 ID로 이후 실행을 취소할 수는 없습니다. 만료되거나 접근이 취소된 응답은 404를 반환합니다.

실행 중인 네이티브 요청을 취소하면 code: response_cancelled인 표준 error 이벤트를 보내고 GET은 cancelled 상태를 반환합니다. response.cancelled 이벤트는 보내지 않습니다. 실행 실패는 response.failed로 전달합니다. 공식 SDK의 get_final_response()는 response.completed를 기대하므로 실패·취소는 원시 이벤트에서 확인하세요. 버퍼링된 Chat Completions 실행이 시작된 뒤 실패하면 choices 없이 error.code: agent_error를 반환하는데, GO는 라우터가 다시 시도하거나 폴백하지 않는 HTTP 422로 보내므로 실행이 반복되지 않고 클라이언트는 HTTP 400을 받습니다. 스트리밍 Chat 요청은 이미 HTTP 200을 보낸 상태라서 실패를 error 이벤트와 그 뒤의 [DONE]으로 알립니다. 실행 중인 Chat 요청의 연결이 끊기면 라우터가 연결 해제를 전달하는 경우 실행을 취소합니다(위의 라우터 이슈 #2008 참고). 번들된 라우터에서는 네이티브 스트림을 닫아도 실행이 취소되며, 이전 라우터에서는 명시적인 취소 요청을 사용하세요. 승인 응답 단계가 완료된 뒤에는 연결을 닫아도 정해진 시간 동안 승인을 기다립니다. A2A 작업은 스트림 연결이 끊겨도 유지되므로 tasks/cancel로 취소해야 합니다.

A2A의 message/send는 작업 완료 전에 반환합니다. tasks/get으로 조회하고 HTTP 200이어도 JSON-RPC 오류를 확인하세요. 새 대화 턴에는 contextId를 사용하며 taskId는 대기 중인 승인이나 작업 조회·취소에 사용합니다. 종료된 작업을 다시 시작할 수는 없습니다. 텍스트 부분만 받으며 블로킹 요청, 푸시 알림, 작업 기록 조회는 지원하지 않습니다. 스트림 이벤트가 누락되면 부분 출력을 성공으로 간주하지 말고 작업을 조회하세요. 승인 거부는 도구 오류로 에이전트에 전달됩니다. 승인하거나 거부한 뒤 모델은 작업을 완료하거나 실패할 수 있으며 추가 승인을 요청할 수도 있습니다. 상태가 input-required이면 새 승인 요청을 확인하고 현재 callId, 작업 ID와 컨텍스트 ID로 응답하세요. 이미 사용한 승인 ID를 다시 보내지 마세요. 다른 GO 인스턴스에 등록할 때는 인증된 카드 URL을 사용합니다. 로컬 프로필 게시와 원격 프로필 등록은 별도 작업입니다.

Chat과 두 Responses 모드의 실행 상한은 510초이며 저장된 실행·세션 제한이 더 짧으면 그 제한을 따릅니다. 네이티브 승인 대기와 재개는 원래 실행의 마감 시간을 공유합니다. A2A는 저장된 제한을 사용합니다. 서비스는 최대 1,024개 세션과 세션당 128개 턴을 허용합니다. 네이티브 보존 한도는 응답 4,096개, 대화 1,024개, 대화당 응답 단계 128개, 응답당 인코딩된 출력 1MiB, 전체 응답 64MiB입니다. A2A는 최대 2,048개 작업과 작업당 1MiB 출력을 보존합니다. 용량을 초과하면 오류를 반환합니다.

store:false는 deny 프로필의 새 네이티브 실행에 사용합니다. 응답·대화와 비공개 실행 기록을 보존하지 않으며 승인 대기, 이전 ID, 백그라운드 실행과 함께 사용할 수 없습니다. 도구가 쓴 파일과 감사 기록은 남습니다. 지시문과 도구는 저장된 프로필이 정하므로 클라이언트의 시스템·도구 재정의, null이 아닌 instructions, 사용자 지정 도구 목록, 멀티모달 입력은 거부합니다.

문제 해결

macOS 클라이언트에서 No route to host가 발생하는 경우

macOS에서 클라이언트 프로세스가 LAN의 다른 호스트에 있는 라우터나 Management API에 연결하지 못하고 No route to host를 반환하는데, 같은 Mac의 /usr/bin/curl로는 같은 주소에 연결되는 경우가 있습니다. macOS는 LAN 주소로 연결할 때 로컬 네트워크 권한을 요구하고 그 권한을 프로세스를 시작한 앱 단위로 판단하는데, SSH 로그인이나 백그라운드 서비스에서 시작한 Python SDK 스크립트 같은 클라이언트는 권한을 물어볼 앱이 없어 연결이 거부됩니다. 시스템 설정 > 개인정보 보호 및 보안 > 로컬 네트워크에서 클라이언트를 실행할 터미널이나 앱의 접근을 허용한 뒤, 그 앱에서 클라이언트를 시작하세요.

에이전트는 목록에 있지만 모델이 실행되지 않은 경우

headless aigo-server를 포함해 GO를 재시작하면 게시 설정이 복원되어 agent:<슬러그>가 다시 /v1/models에 나타나지만, 에이전트 프로필이 사용하는 로컬 모델은 다시 실행하지 않습니다. 재시작할 때마다 에이전트를 호출하기 전에 해당 모델을 실행하세요.

다시 게시한 에이전트가 /v1/models에 없는 경우

게시하거나 다시 게시한 뒤 agent:<슬러그>가 라우터의 /v1/models에 다시 나타나기까지 몇 초가 걸릴 수 있습니다(테스트에서는 3~8초). GO는 1초 주기로 라우터 카탈로그를 갱신하고 라우터가 그 변경을 적용하기 때문이죠. 첫 호출 전에 모델이 목록에 나타날 때까지 /v1/models를 반복해서 확인하세요.

접근과 부수 효과

LAN으로 연결하려면 라우터와 Management API 리스너를 각각 노출해야 합니다. 서버에 도달할 수 있는 주소와 각 리스너에 맞는 키를 사용하세요. 호출할 기기에서 연결을 확인하세요. 호출자에게 필요한 도구와 작업 폴더만 허용하세요. 로컬 프로필에 위임이나 스킬 기능이 있다고 해서 게시 API에서도 사용할 수 있는 것은 아닙니다.

재시도 보호는 네이티브 Responses 지원 여부와 별개입니다. GO가 실제 적용된 보호 설정을 확인하면 라우터의 에이전트 요청 자동 재실행, trace 기반 요청 병합, 응답 캐시를 끕니다. 보호 미지원 경고가 있으면 연결 실패 시 도구 작업이 반복될 수 있으므로 반복해도 안전한 도구를 사용하고 서로 다른 요청에는 다른 trace ID를 쓰세요. 라우터 보호가 있어도 클라이언트가 직접 다시 시도하면 부수 효과가 반복될 수 있습니다. 게시 에이전트 프로토콜은 트랜잭션 롤백이나 애플리케이션 수준의 정확히 한 번 실행을 보장하지 않습니다.