이번 글의 목적
이번 단계에서는 처음으로 파일을 변경하는 tool을 붙입니다.
write/edit은 기본 비활성화
명시적으로 켰을 때만 사용
모든 변경은 기존 policy와 실행 계층을 통과
목표는 write와 edit을 기존 workspace safety, ToolPolicy, ToolExecutionService, ToolRegistry 위에 얹는 것입니다.
왜 write/edit은 늦게 붙일까
파일을 읽는 것과 파일을 바꾸는 것은 위험도가 다릅니다.
workspace safety
-> read-only tools
-> policy
-> Spring AI adapter
-> AgentRuntime
-> write/edit
이 순서 덕분에 write/edit도 기존 안전장치 위에서 동작할 수 있습니다.
M9에서 새로 중요한 것은 파일을 바꾸는 기능 자체보다, 파일을 바꿀 수 있는 조건을 좁게 유지하는 것입니다.
write tool
write는 새 파일을 만들거나 전체 파일을 덮어쓸 수 있습니다.
처음에는 덮어쓰기를 기본 차단하는 편이 안전합니다.
파일이 없으면 생성 허용
파일이 있으면 overwrite=false일 때 차단
overwrite=true일 때만 허용
입력은 단순합니다.
{
"path": "docs/example.md",
"content": "hello",
"overwrite": false
}
overwrite는 선택 값이고 기본값은 false입니다.
또 하나 중요한 제한이 있습니다.
parent directory가 없으면 실패합니다.
write가 자동으로 directory를 만들지 않습니다.
자동 directory 생성을 허용하면 작은 오타가 새 디렉터리와 파일 생성으로 이어질 수 있습니다.
M9에서는 그런 편의보다 명확한 실패를 택합니다.
반환 결과는 사람이 읽을 수 있는 짧은 메시지와 metadata로 남깁니다.
{
"path": "docs/example.md",
"created": true,
"overwritten": false,
"bytesWritten": 5
}
edit tool
edit은 더 조심해야 합니다.
처음에는 복잡한 patch engine을 만들지 않고, 단순한 search/replace 방식으로 시작합니다.
target file
old text
new text
예를 들어:
{
"path": "README.md",
"oldText": "Spring AI Agent",
"newText": "Spring AI Coding Agent"
}
핵심은 oldText가 정확히 한 번만 매칭될 때만 수정한다는 점입니다.
0번 발견되면 실패
2번 이상 발견되면 실패
정확히 1번 발견될 때만 수정
이 원칙은 매우 중요합니다.
애매한 수정은 하지 않는다.
oldText가 비어 있는 경우도 차단합니다.
빈 문자열은 모든 위치에 매칭될 수 있기 때문에 안전한 edit 조건으로 볼 수 없습니다.
edit은 UTF-8 text file만 대상으로 합니다.
binary file이거나 너무 큰 파일이면 실패합니다.
allowWrite로 기본 비활성화하기
write와 edit은 모두 ToolSafetyLevel.WRITE입니다.
따라서 기본 옵션에서는 활성화되지 않습니다.
allowWrite=false -> write/edit 비활성화
allowWrite=true -> write/edit 활성화
이 처리는 두 계층에서 확인됩니다.
먼저 ToolRegistry는 AgentRunOptions를 보고 model에 노출할 tool 목록을 고릅니다.
allowWrite=false이면 write, edit은 enabled tool 목록에 들어가지 않습니다.
그리고 ToolExecutionService는 실제 실행 직전에 DefaultToolPolicy를 통과시킵니다.
어떤 경로로든 WRITE tool이 실행 요청까지 들어오더라도, allowWrite=false이면 policy가 실행 전 거부합니다.
즉 M9의 write/edit은 단순히 UI나 prompt에서만 숨겨지는 것이 아닙니다.
registry와 execution policy 양쪽에서 닫혀 있습니다.
기존 안전장치를 재사용한다
M4, M5에서 만든 안전장치를 그대로 통과합니다.
SafePathResolver
SecretFileGuard
WorkspaceProperties
TextFileReader
ToolOutputLimiter
write는 workspace 밖 경로, path traversal, secret file 경로, 없는 parent directory를 차단합니다.
edit은 workspace 밖 경로, path traversal, secret file 경로를 차단하고, 대상 파일이 없거나 regular file이 아니면 실패합니다. 또 기존 text file 읽기 규칙을 따라 large/binary file을 거부합니다.
새 tool을 추가했지만, 안전 판단이 여기저기 흩어지지 않는 것이 중요합니다.
변경 결과 요약
write/edit 결과에는 변경 결과를 검토할 수 있는 정보를 남깁니다.
이번 단계에서는 별도의 diff engine을 만들지 않고, 짧은 결과 메시지와 metadata를 반환합니다.
write 결과 metadata는 다음 값을 담습니다.
{
"path": "docs/example.md",
"bytesWritten": 5,
"created": true,
"overwritten": false
}
edit 결과 metadata는 다음 값을 담습니다.
{
"path": "README.md",
"replacements": 1,
"bytesWritten": 1280
}
이 정도만 있어도 AgentRuntime의 tool event와 session history에서 어떤 파일이 변경되었는지 확인할 수 있습니다.
diff 반환은 이후 단계에서 붙일 수 있습니다.
M9에서는 기존 구조를 크게 흔들지 않고, 안전한 write/edit 수직 슬라이스를 완성하는 데 집중합니다.
실제 패키지 구조
M9에서 추가된 중심 클래스는 다음과 같습니다.
tool/builtin/
WriteTool.java
EditTool.java
둘 다 Spring @Component입니다.
그래서 별도 등록 코드 없이 기존 ToolRegistry 생성자에 자연스럽게 들어갑니다.
ToolRegistry는 등록된 tool의 ToolSafetyLevel과 AgentRunOptions를 보고 enabled tool 목록을 만듭니다.
READ_ONLY -> enabledByDefault
WRITE -> options.allowWrite()
BASH -> options.allowBash()
M9는 이 구조에 WRITE tool을 추가한 것입니다.
bash는 아직 만들지 않습니다.
테스트에서 확인한 것
M9 테스트는 tool 자체와 ToolExecutionService 경유 실행을 나눠 확인합니다.
WriteTool에서는 다음을 확인합니다.
- 새 파일 생성 성공
- 기존 파일 overwrite=false에서 실패
- 기존 파일 overwrite=true에서 성공
- workspace 밖 경로 거부
- secret file 경로 거부
- missing parent directory 실패
- ToolExecutionService 경유 시 allowWrite=false에서 실행 전 거부
- ToolExecutionService 경유 시 allowWrite=true에서 실행 성공
EditTool에서는 다음을 확인합니다.
- oldText 정확히 1회 매칭 시 수정 성공
- oldText 미매칭 실패
- oldText 다중 매칭 실패
- workspace 밖 경로 거부
- secret file 경로 거부
- binary/large file 실패
- ToolExecutionService 경유 시 allowWrite=false에서 실행 전 거부
- ToolExecutionService 경유 시 allowWrite=true에서 실행 성공
이 테스트들이 중요한 이유는 간단합니다.
write/edit은 정상 동작보다 실패 조건이 더 중요합니다.
좋은 write/edit tool은 바꿀 수 있을 때보다 바꾸면 안 될 때를 더 명확히 알아야 합니다.
마무리
M9는 Agent에게 처음으로 손을 주는 단계입니다.
이제 변경 조건이 명확할 때만 움직이고, 변경 결과를 사람이 검토할 수 있게 남길 수 있습니다.
[codex] Add write and edit tools by dd3ok · Pull Request #12 · dd3ok/pi-spring-ai
'개발 > AI' 카테고리의 다른 글
| Spring AI로 Pi 스타일 에이전트 하네스 만들기 (11) - README.md 로 흐름 정리 (0) | 2026.07.08 |
|---|---|
| Spring AI로 Pi 스타일 에이전트 하네스 만들기 (10) - 위험한 bash 다루기 (0) | 2026.07.07 |
| Spring AI로 Pi 스타일 에이전트 하네스 만들기 (8) - AgentRuntime 만들고 SSE로 스트리밍하기 (0) | 2026.07.03 |
| Spring AI로 Pi 스타일 에이전트 하네스 만들기 (7) - Tool Calling 감싸기 (1) | 2026.07.02 |
| Spring AI로 Pi 스타일 에이전트 하네스 만들기 (6) - Tool 실행 정책 (0) | 2026.07.01 |