HANDS-ON LAB

프로젝트 .cursor로 Cursor 에이전트 하네스 이해하기

하나의 투명한 실습 키트를 준비한 뒤, 고정 프롬프트와 로컬 결과 파일로 도구 호출·커밋 정책을 대조합니다. 전역 설정과 네트워크는 사용하지 않습니다.

총 시간약 70분
구성Prep + 시나리오 6개
대상Cursor IDE Agent · 프로젝트 .cursor
목차 열기 · 현재 Git

먼저 읽어보세요

완성될 구조를 먼저 확인합니다

아래가 전체 결과 트리입니다. README.md와 다른 프로젝트 파일은 학습자 소유라 준비기가 수정하지 않습니다. .cursor-harness-kit/은 내려받기와 수동 복사에서 바이트 단위로 같은 원본이고, 준비기는 그 원본 중 랩 소유 파일만 활성 위치에 설치합니다.

전체 결과 트리
PROJECT_ROOT/
├── README.md                                      ← 학습자 제공 · 변경하지 않음
├── (기존 프로젝트 파일)                           ← 모두 보존
├── .cursor-harness-kit/                           ← 검사 가능한 원본 키트
│   ├── install.py / prepare.py
│   └── files/{observe,deny_write,deny_commit,snapshot,compare,no-commit}
├── .cursor/
│   ├── hooks.json                                 ← 사용자 항목 보존 + 랩 항목 전환
│   ├── hooks/cursor-harness-lab-*.py              ← 랩 소유 3개
│   └── rules/
│       ├── (사용자 규칙)                          ← 보존
│       └── cursor-harness-lab-no-commit.mdc       ← S5에서만 활성
└── lab-results/
    ├── CURRENT / vcs.txt
    ├── snapshot.py / compare.py
    ├── commit-input.txt                           ← S3~S5 결정적 커밋 입력
    ├── s1/ … s5/                                  ← 현재 실행 결과
    ├── archive/sN-001/ …                          ← 재실행 전 같은 시나리오 결과
    └── compare-s1-s2.md / compare-s3-s4.md / compare-s5-s4.md
준비기의 경계

로컬 프로젝트 안에서만 작동하고 커밋, reset, checkout, clean, 네트워크 요청을 실행하지 않습니다. 기존 훅 JSON과 사용자 규칙을 보존하며, 랩 소유 경로에 다른 내용이 있거나 JSON을 읽을 수 없으면 변경 전에 충돌로 중단합니다.

대화 경계

각 S1~S5 준비가 끝나면 새 Agent 대화를 시작하세요. 모델의 이전 대화 기억을 변인에서 제외합니다. 고정 Agent 입력에는 터미널용 cd를 붙이지 않습니다.

시작 전

이미 열어 둔 루트를 고정합니다

2분
  • Cursor에서 실습할 기존 프로젝트 루트를 열고 Trust 했습니다.
  • README.md는 학습자가 준비했습니다. 키트가 생성하거나 내용을 강제하지 않습니다.
  • Git은 로컬 저장소만 사용합니다. SVN이라면 기존 working copy를 사용합니다.
  • python3가 동작합니다. 전역 Cursor 설정은 바꾸지 않습니다.

현재 터미널이 프로젝트 루트일 때 한 번 실행합니다. 새 터미널을 열면 셸 변수는 이어지지 않으므로 이 초기화를 다시 실행해야 합니다.

Terminal · 실행 위치: 이미 열린 프로젝트 루트 · 변경 파일: 없음
export PROJECT_ROOT="$PWD"
cd "${PROJECT_ROOT:?먼저 프로젝트 경로를 설정해 주세요}" || exit

Prep

파일을 받거나, 직접 만들어보세요

8분

아래 파일을 한 번에 내려받아도 되고, 내용을 확인하면서 직접 복사해 만들어도 됩니다. 두 방법으로 만들어지는 파일은 같아요. 먼저 각 파일이 하는 일을 살펴본 뒤 편한 방법 하나를 선택해 주세요.

1

VCS 하나 선택

선택값은 모든 준비 명령의 두 번째 인자로 들어가며 lab-results/vcs.txt에 유지됩니다.

버전 관리 선택
2

어떤 파일을 왜 만드는지 살펴보세요

각 파일이 언제 실행되고 무엇을 남기는지 정리했습니다. 아래에서 내용을 펼쳐 읽어볼 수 있어요.

키트 경로목적트리거효과생성 출력
3

방법 A · 준비된 파일 내려받기

아래 버튼을 누르면 이 페이지에 공개된 파일들이 하나의 tar 압축 파일로 저장됩니다. 내려받는 것만으로 실행되지는 않아요. 이어지는 명령은 임시 폴더에 압축을 풀고, 기존 파일과 충돌하지 않는지 확인한 뒤 프로젝트에 복사합니다.

Terminal · 실행 위치: PROJECT_ROOT · 변경 파일: .cursor-harness-kit/**
cd "${PROJECT_ROOT:?먼저 프로젝트 경로를 설정해 주세요}" || exit
export LAB_KIT_UNPACK="${TMPDIR:-/tmp}/cursor-harness-kit-$$"
mkdir "$LAB_KIT_UNPACK" || exit
tar -xf "${HOME}/Downloads/cursor-harness-kit.tar" -C "$LAB_KIT_UNPACK" || exit
python3 "$LAB_KIT_UNPACK/cursor-harness-kit/install.py"

브라우저의 저장 위치를 바꿨다면 tar 경로만 실제 저장 위치로 바꿉니다. 추출 결과의 모든 파일은 바로 아래 방법 B와 동일합니다.

4

방법 B · 내용을 확인하고 직접 만들기

다운로드가 꺼려진다면 이 방법을 사용하세요. 아래 명령에 만들 파일의 이름과 내용이 모두 들어 있습니다. 전체 보기로 확인한 뒤 프로젝트 터미널에 붙여넣으면 됩니다. 같은 경로에 다른 내용의 파일이 있으면 덮어쓰지 않고 멈춥니다.

Terminal · 실행 위치: PROJECT_ROOT · 변경 파일: .cursor-harness-kit/**
5

파일을 하나씩 만들고 싶다면

위 구조대로 .cursor-harness-kit 폴더와 그 안의 files 폴더를 만들어 주세요. 각 제목에 적힌 경로로 파일을 만든 뒤 내용을 복사해 저장하면 됩니다. 아래 복사 버튼은 터미널 명령이 아니라 해당 파일의 내용만 복사합니다.

CHECKPOINT

  • .cursor-harness-kit/prepare.pyfiles/ 6개가 있습니다.
  • 다운로드와 직접 만들기 중 어느 방법을 골라도 파일 내용은 같습니다.
  • 아직 .cursor/hooks.json은 준비기가 건드리지 않았고 홈 전역 설정도 수정하지 않았습니다.

시나리오 1

정책 없이 tool 관찰

10분

기준선을 만들기 위해 계측 훅만 활성화합니다. 준비가 필요한 이유는 이전 랩 정책을 제거하되 사용자 훅은 남겨 변인을 고정하기 위해서입니다.

hook lifecyclepre/before = attempt, post/after = allow, failure = fail
고정값프롬프트 A · README는 학습자 제공

● 새 파일: CURRENT·활성 스크립트↻ 교체: 랩 소유 hook 항목▶ 활성: observe만

S1 준비 후 파일 구조
프로젝트/
├── README.md                         ← 기존 파일 유지
├── .cursor/
│   ├── hooks.json                    ← 이번 설정으로 변경 (사용자 훅 유지)
│   ├── hooks/
│   │   ├── cursor-harness-lab-observe.py     ← 기록 켜짐
│   │   ├── cursor-harness-lab-deny-write.py  ← 사용하지 않음
│   │   └── cursor-harness-lab-deny-commit.py ← 사용하지 않음
│   └── rules/                        ← 사용자 규칙 유지
└── lab-results/
    ├── CURRENT                       ← s1
    ├── vcs.txt                       ← 선택한 Git 또는 SVN
    ├── snapshot.py / compare.py      ← 결과 정리 도구
    ├── s1/
    │   ├── events.jsonl              ← 실행 기록
    │   └── summary.md                ← 실행 후 요약 명령으로 생성
    └── archive/                      ← 재실행 전 기록 보관
Terminal · PROJECT_ROOT · 변경: 위 S1 트리 · 커밋 없음
cd "${PROJECT_ROOT:?먼저 프로젝트 경로를 설정해 주세요}" || exit
python3 .cursor-harness-kit/prepare.py s1 git

새 Agent 대화를 열고 다음 입력을 한 글자도 바꾸지 않습니다.

Cursor Agent 입력 · 고정 프롬프트 A
README.md를 읽은 다음, 파일 끝에 아래 한 줄을 추가해 주세요. 커밋은 하지 마세요.

<!-- lab-edit -->
Terminal · PROJECT_ROOT · 변경: lab-results/s1/summary.md
cd "${PROJECT_ROOT:?먼저 프로젝트 경로를 설정해 주세요}" || exit
python3 lab-results/snapshot.py
실제 결과 파일기대 비교
s1/events.jsonl, s1/summary.mdWrite 또는 StrReplace allow, 정책 deny 없음

CHECKPOINT

  • lab-results/s1/summary.md에서 수정 도구 allow를 확인했습니다.
  • 채팅 답변이 아닌 실제 결과 파일로 판정했습니다.

시나리오 2

tool 제한 후 같은 프롬프트

12분

준비기는 S1의 랩 항목을 제거하고 observe 다음에 deny-write를 연결합니다. 사용자 훅·규칙과 S1 로그는 그대로입니다.

hook lifecyclepreToolUse: observe attempt → deny-write decision
고정값프롬프트 A
S2 준비 후 파일 구조
프로젝트/
├── README.md                         ← 기존 파일 유지
├── .cursor/
│   ├── hooks.json                    ← 이번 설정으로 변경 (사용자 훅 유지)
│   ├── hooks/
│   │   ├── cursor-harness-lab-observe.py     ← 기록 켜짐
│   │   ├── cursor-harness-lab-deny-write.py  ← 수정 차단 켜짐
│   │   └── cursor-harness-lab-deny-commit.py ← 사용하지 않음
│   └── rules/                        ← 사용자 규칙 유지
└── lab-results/
    ├── CURRENT                       ← s2
    ├── vcs.txt                       ← 선택한 Git 또는 SVN
    ├── snapshot.py / compare.py      ← 결과 정리 도구
    ├── s2/
    │   ├── events.jsonl              ← 실행 기록
    │   └── summary.md                ← 실행 후 요약 명령으로 생성
    └── archive/                      ← 재실행 전 기록 보관
Terminal · PROJECT_ROOT · 변경: 위 S2 트리 · 커밋 없음
cd "${PROJECT_ROOT:?먼저 프로젝트 경로를 설정해 주세요}" || exit
python3 .cursor-harness-kit/prepare.py s2 git

새 Agent 대화에서 같은 입력을 보냅니다.

Cursor Agent 입력 · 고정 프롬프트 A
README.md를 읽은 다음, 파일 끝에 아래 한 줄을 추가해 주세요. 커밋은 하지 마세요.

<!-- lab-edit -->
Terminal · PROJECT_ROOT · 변경: summary와 compare-s1-s2.md
cd "${PROJECT_ROOT:?먼저 프로젝트 경로를 설정해 주세요}" || exit
python3 lab-results/snapshot.py
python3 lab-results/compare.py s1 s2
도구s1s2비교
Write/StrReplaceallowdeny제한됨

CHECKPOINT

  • s2/events.jsonl, s2/summary.md, compare-s1-s2.md가 실제 결과 체크리스트입니다.
  • 수정 도구가 s1 allow에서 s2 deny로 바뀌었습니다.
커스텀 — 고정 실험을 통과한 뒤에만

별도 결과 id에서 Shell도 제한하는 가설을 시험하세요. 고정 프롬프트 A는 재사용하지 않고 직접 입력을 작성합니다.

시나리오 3

설정 없이 VCS 커밋 관찰

8분

먼저 차단 없이 커밋을 요청해 볼게요. 준비 명령이 commit-input.txt에 이번 실험용 변경을 만들어주므로 따로 파일을 고칠 필요가 없습니다. 실제 커밋은 뒤에서 에이전트에게 요청합니다.

hook lifecyclebeforeShellExecution attempt → afterShellExecution allow
활성 정책observe만 · 선택한 VCS 1개
S3 준비 후 파일 구조
프로젝트/
├── README.md                         ← 기존 파일 유지
├── .cursor/
│   ├── hooks.json                    ← 이번 설정으로 변경 (사용자 훅 유지)
│   ├── hooks/
│   │   ├── cursor-harness-lab-observe.py     ← 기록 켜짐
│   │   ├── cursor-harness-lab-deny-write.py  ← 사용하지 않음
│   │   └── cursor-harness-lab-deny-commit.py ← 사용하지 않음
│   └── rules/                        ← 사용자 규칙 유지
└── lab-results/
    ├── CURRENT                       ← s3
    ├── vcs.txt                       ← 선택한 Git 또는 SVN
    ├── snapshot.py / compare.py      ← 결과 정리 도구
    ├── commit-input.txt              ← 이번 커밋 실험용 변경
    ├── s3/
    │   ├── events.jsonl              ← 실행 기록
    │   └── summary.md                ← 실행 후 요약 명령으로 생성
    └── archive/                      ← 재실행 전 기록 보관
Terminal · PROJECT_ROOT · 변경: 위 S3 트리 · 로컬 Git만
cd "${PROJECT_ROOT:?먼저 프로젝트 경로를 설정해 주세요}" || exit
python3 .cursor-harness-kit/prepare.py s3 git

새 Agent 대화에서 선택한 고정 프롬프트 B를 보냅니다.

Cursor Agent 입력 · Git
현재 워크스페이스의 변경사항을 로컬 git으로만 커밋하세요. 원격 저장소는 사용하지 마세요. push 하지 마세요. 커밋 메시지는 정확히 다음을 사용하세요: lab: scenario commit
Terminal · PROJECT_ROOT · 변경: s3/summary.md
cd "${PROJECT_ROOT:?먼저 프로젝트 경로를 설정해 주세요}" || exit
python3 lab-results/snapshot.py
실제 결과 파일기대 동작S4와 비교할 기준선
s3/events.jsonl, s3/summary.md선택한 VCS commit의 attempt와 allow정책 deny가 없음

CHECKPOINT

  • s3/events.jsonls3/summary.md에서 commit attempt/allow를 확인했습니다.
  • commit-input.txt 외의 학습자 파일은 준비가 바꾸지 않았습니다.

시나리오 4

커밋 강제 거부

12분

이번에는 커밋을 요청해도 실행되지 않도록 막아볼게요. 준비 명령이 커밋할 변경을 만들고 차단 훅을 연결합니다. 변경 내용을 보는 명령은 허용되고 커밋만 거부되는지 확인해 주세요.

hook lifecyclebeforeShellExecution: observe attempt → deny-commit decision
고정값선택한 프롬프트 B
S4 준비 후 파일 구조
프로젝트/
├── README.md                         ← 기존 파일 유지
├── .cursor/
│   ├── hooks.json                    ← 이번 설정으로 변경 (사용자 훅 유지)
│   ├── hooks/
│   │   ├── cursor-harness-lab-observe.py     ← 기록 켜짐
│   │   ├── cursor-harness-lab-deny-write.py  ← 사용하지 않음
│   │   └── cursor-harness-lab-deny-commit.py ← 커밋 차단 켜짐
│   └── rules/                        ← 사용자 규칙 유지
└── lab-results/
    ├── CURRENT                       ← s4
    ├── vcs.txt                       ← 선택한 Git 또는 SVN
    ├── snapshot.py / compare.py      ← 결과 정리 도구
    ├── commit-input.txt              ← 이번 커밋 실험용 변경
    ├── s4/
    │   ├── events.jsonl              ← 실행 기록
    │   └── summary.md                ← 실행 후 요약 명령으로 생성
    └── archive/                      ← 재실행 전 기록 보관
Terminal · PROJECT_ROOT · 변경: 위 S4 트리 · 커밋 없음
cd "${PROJECT_ROOT:?먼저 프로젝트 경로를 설정해 주세요}" || exit
python3 .cursor-harness-kit/prepare.py s4 git

새 Agent 대화에서 S3과 같은 VCS별 입력을 보냅니다.

Cursor Agent 입력 · Git
현재 워크스페이스의 변경사항을 로컬 git으로만 커밋하세요. 원격 저장소는 사용하지 마세요. push 하지 마세요. 커밋 메시지는 정확히 다음을 사용하세요: lab: scenario commit
Terminal · PROJECT_ROOT · 변경: summary와 compare-s3-s4.md
cd "${PROJECT_ROOT:?먼저 프로젝트 경로를 설정해 주세요}" || exit
python3 lab-results/snapshot.py
python3 lab-results/compare.py s3 s4
명령s3s4비교
commitallowdeny제한됨
status/diffallow/없음allow/없음조회 허용

CHECKPOINT

  • s4/events.jsonl, s4/summary.md, compare-s3-s4.md에서 commit deny와 reason=deny-commit을 확인했습니다.
커스텀 — 고정 실험을 통과한 뒤에만

add 또는 메시지 조건을 직접 설계하고 별도 결과 id를 쓰세요. 본편 프롬프트 B는 사용하지 않습니다.

시나리오 5

Rules vs Hooks

10분

이번에는 커밋 차단 훅 대신 “커밋하지 마세요”라는 글로 된 규칙만 넣어볼게요. 준비 명령이 훅을 바꾸고 커밋할 변경도 만들어줍니다. 기존에 사용하던 규칙은 그대로 남습니다.

hook lifecycleobserve만 · deny decision 없음
Rule지시이며 강제가 아님
S5 준비 후 파일 구조
프로젝트/
├── README.md                         ← 기존 파일 유지
├── .cursor/
│   ├── hooks.json                    ← 이번 설정으로 변경 (사용자 훅 유지)
│   ├── hooks/
│   │   ├── cursor-harness-lab-observe.py     ← 기록 켜짐
│   │   ├── cursor-harness-lab-deny-write.py  ← 사용하지 않음
│   │   └── cursor-harness-lab-deny-commit.py ← 사용하지 않음
│   └── rules/                        ← 사용자 규칙 유지 + 실습 커밋 금지 규칙 추가
└── lab-results/
    ├── CURRENT                       ← s5
    ├── vcs.txt                       ← 선택한 Git 또는 SVN
    ├── snapshot.py / compare.py      ← 결과 정리 도구
    ├── commit-input.txt              ← 이번 커밋 실험용 변경
    ├── s5/
    │   ├── events.jsonl              ← 실행 기록
    │   └── summary.md                ← 실행 후 요약 명령으로 생성
    └── archive/                      ← 재실행 전 기록 보관
Terminal · PROJECT_ROOT · 변경: 위 S5 트리 · 커밋 없음
cd "${PROJECT_ROOT:?먼저 프로젝트 경로를 설정해 주세요}" || exit
python3 .cursor-harness-kit/prepare.py s5 git

새 Agent 대화에서 같은 VCS별 프롬프트 B를 보냅니다.

Cursor Agent 입력 · Git
현재 워크스페이스의 변경사항을 로컬 git으로만 커밋하세요. 원격 저장소는 사용하지 마세요. push 하지 마세요. 커밋 메시지는 정확히 다음을 사용하세요: lab: scenario commit
Terminal · PROJECT_ROOT · 변경: summary와 compare-s5-s4.md
cd "${PROJECT_ROOT:?먼저 프로젝트 경로를 설정해 주세요}" || exit
python3 lab-results/snapshot.py
python3 lab-results/compare.py s5 s4
명령s5 (Rules만)s4 (Hooks)비교
commitallow 또는 시도 없음denyS4만 강제 거부
S5의 통과 기준은 ‘커밋이 반드시 실패하는 것’이 아닙니다. compare-s5-s4.md에서 s4만 deny가 보장되는지를 봅니다. s5가 allow이면 Rules만으로는 부족하다는 뜻입니다.

CHECKPOINT

  • s5/events.jsonl, s5/summary.md, compare-s5-s4.md가 실제 결과입니다.
  • S5에는 deny-commit reason이 없고 S4에는 있습니다.
커스텀 — 고정 실험을 통과한 뒤에만

Rule 문구와 직접 만든 입력을 바꿔 별도 결과로 비교하세요.

시나리오 6

기존 계측 결과만 읽기

8분

S6 준비 명령은 없습니다. CURRENT, 훅, Rule, 결과 폴더를 재설정하거나 새 로그를 만들지 않고 S1~S5 결과만 읽습니다.

S6에서 읽기만 하는 트리
lab-results/s2/events.jsonl              = 읽기 전용
lab-results/s4/events.jsonl              = 읽기 전용
lab-results/compare-*.md                 = 읽기 전용
CURRENT / .cursor/**                     = 변경 없음
Terminal · PROJECT_ROOT · 변경 파일: 없음
cd "${PROJECT_ROOT:?먼저 프로젝트 경로를 설정해 주세요}" || exit
python3 -c 'import json; p="lab-results/s2/events.jsonl"; [print(line, end="") for line in open(p, encoding="utf-8") if json.loads(line).get("outcome") == "deny"]'
필드비교에서 뜻
phase / outcomeattempt 또는 allow·deny·fail 결정
tool / command도구와 셸 명령
reasondeny-write 또는 deny-commit

CHECKPOINT

  • S2 또는 S4 기존 JSONL에서 deny와 reason을 찾았습니다.
  • S6용 디렉터리나 로그가 생기지 않았습니다.

Custom

체크포인트 뒤에만 확장합니다

S2·S4·S5의 실제 결과 체크포인트를 통과한 뒤 가설, 랩 소유 정책 변경, 직접 쓴 Agent 입력, 별도 snapshot 순서로 확장합니다.

한 장 요약

지시 · 계측 · 정책을 분리합니다

파일막는가시나리오
계측cursor-harness-lab-observe.py아니오S1~S5
tool 정책deny-write.pyS2
commit 정책deny-commit.pyS4
지시cursor-harness-lab-no-commit.mdc아니오S5
전역~/.cursor사용하지 않음

이번 실습의 하드 차단은 Hooks만 담당합니다.

IDE .cursor/permissions.json과 CLI .cursor/cli.json은 다루지 않습니다.