Workspace IndexAlgorithms › Day 99

Agent loop design — tool permissions, gates, and retries; data and model provenance TODO

Algorithms · Day 99 / 100 · G. AI Engineering (Day 97-100)

Concept

An agent loop is the control structure where a model calls tools, feeds the results back in as input, and repeats until it reaches its goal. Because this loop is inherently non-deterministic, safety has to come from system-level constraints wrapped around the loop, not from the model's own judgment. Concretely, that means granting least privilege per tool, putting an explicit gate — approval, dry run, or a limit — in front of any irreversible action, capping iteration count and cost, and having a retry policy that distinguishes failure types. Retries should only be applied automatically to tools that are guaranteed idempotent, or the same side effect ends up executing twice. Data and model provenance means keeping a traceable record of exactly which inputs and which model version produced a given decision — a prerequisite for debugging after the fact and for accountability.

You can't stop an agent from ever being wrong; what determines whether it's actually operable is whether a mistake can be undone and whether you can tell what caused it.

Code & Formula

# 에이전트 루프 설계 — 읽기 전용/쓰기 도구를 분리하고, 쓰기 도구에는 승인 게이트 +
# 멱등 키를 붙여 "재시도가 부작용을 중복 실행하지 않는지" 검증하는 최소 예시.

executed_writes = {}  # idempotency_key -> result (실제로 실행된 부작용의 기록)
call_log = []

def read_balance(account):  # 읽기 전용 도구: 언제 재시도해도 안전
    call_log.append(("read", account))
    return {"acct-1": 100}.get(account, 0)

def send_payment(account, amount, idempotency_key, approved):
    # 되돌릴 수 없는 행위 -> 승인 게이트 필수
    if not approved:
        raise PermissionError("승인 게이트 미통과: 결제 도구는 approved=True 필요")
    if idempotency_key in executed_writes:
        return executed_writes[idempotency_key]  # 재시도여도 재실행하지 않고 이전 결과 반환
    call_log.append(("write", account, amount))
    result = {"status": "sent", "account": account, "amount": amount}
    executed_writes[idempotency_key] = result
    return result

def agent_step_with_retry(tool_fn, *args, max_retries=3, **kwargs):
    for attempt in range(max_retries):
        try:
            return tool_fn(*args, **kwargs)
        except PermissionError:
            raise  # 권한 실패는 재시도로 해결되지 않음 -> 즉시 중단
        except Exception:
            if attempt == max_retries - 1:
                raise
    return None

balance = agent_step_with_retry(read_balance, "acct-1")
print("잔고 조회:", balance)

key = "payment-req-42"  # 이 요청 전체를 대표하는 멱등 키 (네트워크 재시도에도 동일)
r1 = agent_step_with_retry(send_payment, "acct-1", 10, key, True)
r2 = agent_step_with_retry(send_payment, "acct-1", 10, key, True)  # 네트워크 재시도 흉내
print("첫 결제 호출:", r1)
print("재시도 호출(동일 idempotency_key):", r2)
write_calls = [c for c in call_log if c[0] == "write"]
print("실제로 실행된 write 부작용 횟수:", len(write_calls), "(재시도에도 1회만 실행됨)")

try:
    send_payment("acct-1", 999, "payment-req-99", approved=False)
except PermissionError as e:
    print("승인 없는 결제 시도 차단:", e)

Exercise

Build a simple agent loop that separates tools into read-only and write, attach an approval gate, a call cap, and an idempotency key to the write tools, and verify that a retry doesn't cause duplicate execution.

Practical Connection

A tool that sends on-chain transactions is the textbook example of an irreversible action — without gates like pre-signing simulation, an amount cap, and nonce management, a single automatic retry becomes a duplicate transaction.

If you study this on a given day, add a note link and a ✅ to this line in the source curriculum (docs/knowledge/dev-100-curriculum.md) and this spot will lead straight to the note body. You can also write directly on this page — but regenerating overwrites it, so it's safer to keep anything you want to save as markdown under docs/algorithms/.


한국어

에이전트 루프 설계 TODO

Algorithms · Day 99 / 100 · G. AI 엔지니어링 (Day 97–100)

도구 권한·게이트·재시도, 데이터·모델 프로비넌스

개념

에이전트 루프는 모델이 도구를 호출하고 결과를 다시 입력으로 받아 목표에 도달할 때까지 반복하는 제어 구조이다. 이 루프는 본질적으로 비결정적이므로, 안전성은 모델의 판단이 아니라 루프를 감싸는 시스템 쪽 제약으로 확보해야 한다. 구체적으로는 도구별 최소 권한 부여, 되돌릴 수 없는 행위 앞의 명시적 게이트(승인·드라이런·한도), 반복 횟수와 비용 상한, 그리고 실패 유형을 구분한 재시도 정책이 필요하다. 재시도는 멱등성이 보장되는 도구에만 자동으로 적용해야 하며, 그렇지 않으면 같은 부작용이 중복 실행된다. 데이터·모델 프로비넌스는 어떤 입력과 어떤 모델 버전으로 그 결정이 나왔는지 추적 가능하게 남기는 것으로, 사후 디버깅과 책임 규명의 전제 조건이다.

에이전트가 틀리는 것 자체는 막을 수 없고, 틀렸을 때 되돌릴 수 있는지와 무엇 때문에 틀렸는지 알 수 있는지가 실제 운영 가능 여부를 가른다.

코드 · 수식

# 에이전트 루프 설계 — 읽기 전용/쓰기 도구를 분리하고, 쓰기 도구에는 승인 게이트 +
# 멱등 키를 붙여 "재시도가 부작용을 중복 실행하지 않는지" 검증하는 최소 예시.

executed_writes = {}  # idempotency_key -> result (실제로 실행된 부작용의 기록)
call_log = []

def read_balance(account):  # 읽기 전용 도구: 언제 재시도해도 안전
    call_log.append(("read", account))
    return {"acct-1": 100}.get(account, 0)

def send_payment(account, amount, idempotency_key, approved):
    # 되돌릴 수 없는 행위 -> 승인 게이트 필수
    if not approved:
        raise PermissionError("승인 게이트 미통과: 결제 도구는 approved=True 필요")
    if idempotency_key in executed_writes:
        return executed_writes[idempotency_key]  # 재시도여도 재실행하지 않고 이전 결과 반환
    call_log.append(("write", account, amount))
    result = {"status": "sent", "account": account, "amount": amount}
    executed_writes[idempotency_key] = result
    return result

def agent_step_with_retry(tool_fn, *args, max_retries=3, **kwargs):
    for attempt in range(max_retries):
        try:
            return tool_fn(*args, **kwargs)
        except PermissionError:
            raise  # 권한 실패는 재시도로 해결되지 않음 -> 즉시 중단
        except Exception:
            if attempt == max_retries - 1:
                raise
    return None

balance = agent_step_with_retry(read_balance, "acct-1")
print("잔고 조회:", balance)

key = "payment-req-42"  # 이 요청 전체를 대표하는 멱등 키 (네트워크 재시도에도 동일)
r1 = agent_step_with_retry(send_payment, "acct-1", 10, key, True)
r2 = agent_step_with_retry(send_payment, "acct-1", 10, key, True)  # 네트워크 재시도 흉내
print("첫 결제 호출:", r1)
print("재시도 호출(동일 idempotency_key):", r2)
write_calls = [c for c in call_log if c[0] == "write"]
print("실제로 실행된 write 부작용 횟수:", len(write_calls), "(재시도에도 1회만 실행됨)")

try:
    send_payment("acct-1", 999, "payment-req-99", approved=False)
except PermissionError as e:
    print("승인 없는 결제 시도 차단:", e)

연습

간단한 에이전트 루프를 만들되 도구를 읽기 전용과 쓰기로 나누고, 쓰기 도구에는 승인 게이트·호출 상한·멱등 키를 붙여 재시도가 중복 실행을 만들지 않는지 검증해 보기.

실무 · Verex 연결

온체인 트랜잭션을 보내는 도구는 되돌릴 수 없는 행위의 전형이라, 서명 전 시뮬레이션·금액 상한·논스 관리 같은 게이트가 없으면 자동 재시도 한 번이 곧 중복 전송이 된다.

공부한 날 원본 커리큘럼(docs/knowledge/dev-100-curriculum.md)의 이 줄에 노트 링크와 ✅ 를 붙이면, 이 자리는 노트 본문으로 바로 이어집니다. 노트 없이 이 페이지에 바로 적어도 됩니다 — 다만 다시 생성하면 덮어쓰이므로, 남길 글은 docs/algorithms/ 의 마크다운으로 쓰는 편이 안전합니다.

← 98. RAG 설계·리트리버 품질 지표와 평가 하네스(골든·프로퍼티·회귀)100. [Final] 내 스택의 ADR + 위협 모델 한 편 쓰기 →