기능세션

승인 대기와 재개

권한 요청과 질문에 답하고, 세션을 pause했다가 resume한다

turn이 도구를 실행하기 전에 사람의 허락을 기다리거나 사람에게 질문하면 세션이 needs_input이 된다. 답하면 turn이 이어진다. 세션을 한동안 쉬게 하려면 pause하고, 나중에 resume한다. 시작에서 만든 세션(SDK는 sessionId, HTTP API는 SID)을 이어 쓴다. 답하려면 key에 sessions:approve가, pause·resume에는 sessions:control이 필요하다. 답하는 사람은 세션을 만든 사람, 에이전트 관리자, owner·admin뿐이다(권한). 아래 예제는 세션을 만든 key로 답한다. 이것은 개발용 서버의 운영자 key(scripts/local.sh key)에서만 된다. 웹 콘솔에서 발급한 개인·에이전트 key는 자기가 시작한 세션의 요청에 답하지 못하므로(403 self_approval) 다른 key로 만든 클라이언트로 답한다(승인용 key 따로 쓰기).

대기 중인 요청

import { waitForTurn } from "@werft-ai/sdk";

const sent = await werft.sessions.messages.send(sessionId, {
  message:
    'GATE-SPEC {"id":"deny","steps":[{"tool":"Bash","input":{"command":"rm -rf build","description":"clean build"}}],"final":"cleaned"}',
});
let pending = await werft.sessions.requests.list(sessionId);
while (pending.length === 0) {
  await new Promise((resolve) => setTimeout(resolve, 2000));
  pending = await werft.sessions.requests.list(sessionId);
}
const [request] = pending;
if (request.kind === "permission") console.log(request.tool, request.input);
Bash { command: 'rm -rf build', description: 'clean build' }

요청을 직접 읽지 않고 handler에 맡길 수도 있다. waitForTurn의 onRequest는 turn을 기다리는 동안 생긴 요청을, answerPending(werft, sessionId, handlers)는 지금 열린 요청을 오래된 것부터 한 번씩 handler에 넘긴다(질문에 답하기).

요청은 둘 중 하나다.

  • kind: "permission": 도구 실행 허락. tool과 input이 무엇을 실행할지 보여 준다.
  • kind: "question": 모델이 AskUserQuestion으로 묻는 질문. questions[]마다 question_id, 질문 문장 prompt, 고를 options[]가 있다.

요청은 30분 안에 답하지 않으면 거절로 끝난다. 그 뒤에 답하면 409 REQUEST_EXPIRED다.

엔진이 같은 종류의 호출을 되풀이해서 묻는 도구 권한은 허용 답에 remember: "session"을 실어 이 세션이 끝날 때까지 같은 규칙을 다시 묻지 않게 할 수 있다. 요청이 그런 규칙을 제안했을 때만 받아들이고, 거절 답이나 제안이 없는 요청에는 쓸 수 없다. 서버 허용 목록 밖의 도구, push 승인, 질문은 이 답으로 넓어지지 않는다. 허용 목록 검사는 이 답과 상관없이 모든 호출에서 그대로 적용된다. remember를 모르는 오래된 worker가 연 요청에는 제안이 붙지 않아 Slack 카드에도 이 세션 동안 허용 버튼이 보이지 않는다. 웹 콘솔에는 이 버튼이 없고, 지금은 Slack 카드와 이 API 필드로만 쓸 수 있다.

거절하기

const answered = await werft.sessions.requests.answer(sessionId, {
  request_id: request.request_id,
  kind: "permission",
  action: "deny",
  reason: "Do not delete build output.",
});
console.log(answered.receipt_status, answered.approval?.surface);
await waitForTurn(werft, sessionId, sent.turn_id);
accepted api

허락은 "action": "allow"다. reason은 거절에서만 쓰고 선택이다. 모델은 그 이유를 도구 결과로 받고 turn을 이어 간다. 응답의 approval에는 누가(answered_by), 어디서(surface: api·web·slack) 답했는지가 남는다.

질문에 답하기

const asked = await werft.sessions.messages.send(sessionId, {
  message:
    'GATE-SPEC {"id":"ask","steps":[{"tool":"AskUserQuestion","input":{"questions":[{"question":"Which environment?","header":"Environment","options":[{"label":"staging","description":"safe"},{"label":"production","description":"live"}],"multiSelect":false}]}}],"final":"selected"}',
});
const answeredTurn = await waitForTurn(werft, sessionId, asked.turn_id, {
  onRequest: {
    question(request) {
      const [question] = request.questions;
      console.log(question.prompt, question.options.map((o) => o.label));
      return {
        answers: [
          {
            question_id: question.question_id,
            selected_option_ids: [question.options[0].option_id],
          },
        ],
      };
    },
  },
  onAnswer: (outcome) => console.log(`answered: ${outcome.outcome}`),
});
console.log(`turn ${answeredTurn.turn_id}: ${answeredTurn.status}`);
Which environment? [ 'staging', 'production' ]
answered: accepted
turn 3: completed

고르는 대신 글로 답하려면 selected_option_ids: []와 free_text를 보낸다.

handler는 permission과 question 두 가지다. handler가 undefined를 돌려주거나 그 종류의 handler가 없으면 요청을 열어 두고 사람에게 맡긴다. onAnswer가 받는 outcome은 accepted(답이 접수됐다), left_open(handler가 답하지 않았다), too_late(이미 만료됐거나 다른 사람이 답했다. code가 REQUEST_EXPIRED나 REQUEST_STALE), conflict(같은 요청에 다른 답이 먼저 갔다) 중 하나다. 세션이 paused·stopped·recovery_required·closed가 되면 turn이 스스로 끝나지 않으므로 waitForTurn은 TurnBlockedError를 던진다.

pause와 resume

pause는 진행 중인 일을 마무리하고 checkpoint(작업 트리와 대화 기록)를 남긴 뒤 worker를 내린다. resume은 새 worker가 그 checkpoint에서 같은 작업 트리와 같은 대화를 복원한다. 둘 다 지금 revision을 expected_revision으로 보낸다. 그 사이 세션이 바뀌었으면 409 REVISION_CONFLICT다.

import { waitForReceipt } from "@werft-ai/sdk";

let session = await werft.sessions.get(sessionId);
const paused = await werft.sessions.pause(sessionId, {
  expected_revision: session.revision,
  reason: "lunch",
});
const receipt = await waitForReceipt(werft, paused.receipt_id);
console.log(receipt.operation, receipt.status);
session = await werft.sessions.get(sessionId);
console.log(session.admission_state, session.checkpoint_revision);
pause succeeded
paused 2

paused 동안 보낸 메시지는 409 SESSION_PAUSED다. 60초 안에 drain하지 못하면 attention이 PAUSE_BLOCKED가 된다. 열린 요청이 있으면 답하거나 interrupt한다.

const resumed = await werft.sessions.resume(sessionId, {
  expected_revision: session.revision,
});
await waitForReceipt(werft, resumed.receipt_id);
console.log((await werft.sessions.get(sessionId)).admission_state);
active

다음 단계

  • 닫기: 끝난 세션을 닫거나 멈춘다
  • receipt: 접수한 명령의 결과 확인

이 페이지