Skip to content

feat(advisory): agy stdin 전달, 의회 병렬 실행, 세션 스코프 동의 - #177

Merged
ictechgy merged 6 commits into
mainfrom
feature/advisory-usability-hardening
Sep 1, 2026
Merged

feat(advisory): agy stdin 전달, 의회 병렬 실행, 세션 스코프 동의#177
ictechgy merged 6 commits into
mainfrom
feature/advisory-usability-hardening

Conversation

@ictechgy

@ictechgy ictechgy commented Sep 1, 2026

Copy link
Copy Markdown
Owner

원샷 wclass-advisory ask 표면의 사용성/노출 개선 4건. 코어 wclass 라우팅,
one-child 계약, 캠페인 기록·게이트·봉인 매니페스트는 바뀌지 않는다. 캠페인
dispatch 는 없었고 영속 기록도 쓰지 않았다.

변경

1. agy 태스크를 argv → stdin NDJSON (8292944)

agy 라우트는 --print 가 프롬프트를 인자로 요구한다는 이유로 태스크를 argv 로
넘겨 왔고, 그 노출을 문서화된 잔여로 안고 있었다. argv 에 실린 태스크는 같은
사용자의 모든 프로세스에서 ps 로 보인다.

agy 1.1.23 실측: --input-format stream-json 은 프롬프트를 stdin 의 NDJSON 한
줄에서 읽고, 같은 호출에 argv 프롬프트가 오면 CLI 가 스스로 거부한다
("a prompt given on the command line would be ignored"). --print 를 빼면 정상
동작한다. 즉 이 조합에는 태스크 자리가 존재하지 않는다 — 노출을 우리가 지우는
것이 아니라 CLI 가 닫는다.

_prepare_task_command 는 라우트가 NDJSON 입력을 선언했을 때만 stdin 을
감싼다. 실행 파일 이름이 아니라 선언된 형식으로, 토큰 위치로 판정한다. 출력
파싱은 이미 호환됐다(advice_text_extracted 가 마지막 NDJSON 줄부터 읽고,
_agy_usage{"event":"result","result":{...}} 봉투를 이미 처리).

2. ask --council 순차 → 동시 실행 (8ec0e63)

구성원은 서로의 출력을 보지 않는 독립 프로세스인데 순차로 돌았다. 대가는
벽시계가 합이 되는 것과, 전체 데드라인이 뒤쪽 구성원만 굶기는 것이었다.

동시에 띄우면 합이 최댓값이 되고 남은 예산을 모두가 같은 조건으로 나눈다.
상한은 제출 시점에 한 번만 정하고(스레드 안에서 재면 같은 입력이 실행마다 다른
결과를 낸다), 반환은 언제나 입력 순서다. 실패한 피어는 취소하지 않는다 — 이미
과금된 작업만 버리게 된다. 작업 트리 비교는 구성원 사이가 아니라 의회 전체의
앞뒤로 옮겼다.

advisory_parallel.run_parallel 은 재사용하지 않았다. 그쪽은 명령 단위라
grok 의 {{task_file}} 익명 파이프 전달을 실을 수 없다.

3. 세션 스코프 송신 동의 + 읽을 수 있는 미리보기 (c6cfb80)

호출마다 터미널 확인을 다시 받는 것이 사용을 막고 있었다. 저장소·시각 단위
grant 는 AGENTS.md 가 advisory 상태에 저장소 경로와 시각을 금지하므로
선택지가 아니다. 그래서 동의를 셸 세션의 수명에만 둔다:
WCLASS_ADVISORY_EGRESS=session. 디스크에 쓰지 않고, 셸이 닫히면 사라지며,
권한 크기는 --confirm-task-egress 와 정확히 같다.

값을 정확히 session 으로 요구한다. 이름 존재만 보면 0 을 넣어 끄려던
사용자가 오히려 켜게 된다.

영수증에 task_egress_confirmation_source(flag/session_environment/
terminal)를, 미리보기에 session_egress_grant_active 를 추가했다.

--human 미리보기는 json.dumps(indent=2) 를 그대로 찍어 이스케이프된 JSON
스키마 전문이 화면을 채웠다 — 읽으라고 만든 출력이 읽을 수 없었다. 이제 문장
요약을 찍고, 120자를 넘는 인자만 길이와 sha256 앞자리로 줄인다(정확한 바이트는
--json 에 그대로).

4. Skill 을 onboarding 18 로 (8bcaaa9)

번들이 "agy receives the task in argv" 를 가르치고 있었고 이제 거짓이다.
의회 설명도 순차를 전제했다. 둘 다 고치고 세션 동의를 추가했다.
RELEASING.md 대로 정확히 발행된 0.29.0 번들 해시를
RELEASE_0290_BUNDLE_FILE_SHA256 으로 원장에 넣었다(작업 트리와 v0.29.0
태그에서 각각 계산해 일치 확인).

검증

  • .weightclass/verify exit 0 — 1,593 tests, 35 skips
  • Ruff check/format 251 files, strict mypy 206 source files, compileall,
    git diff --check 모두 통과
  • verify_advisory_skill_ledger.py --previous-ref v0.29.0 통과
  • 실측: 실제 agy CLI 로 ask --vendor agy --workflow review --context task
    task_delivery: "stdin" 과 유효한 schema-1 결과를 반환. 실제
    --council agy,codex 가 13.9초에 끝나고 부분 결과를 보존한 뒤 exit 3.
    codex 의 ask_executor_failed 는 단독 실행에서도 재현되는 로컬 CLI 조건이며
    diff 에 codex 라인은 0줄이다.

수정한 기존 테스트 2건

  • tests/test_advisory_flexible_vendors.py: agy argv 단언 → stdin 계약.
    claude/codex 프로필 다이제스트와 grok {{task_file}} 전달은 불변.
  • tests/test_advisory_quick.py: test_council_total_deadline_prevents_later_ member_start 는 순차 실행이 만들던 뒤쪽 구성원 굶김 자체를 계약으로
    굳힌 테스트였다. 남는 계약(제출 전 예산이 0 이하면 아무도 시작하지 않음)으로
    재작성하고, 동시성을 배리어로 증명하는 테스트와 피어 수거 테스트를 추가.

재고정한 검증기 1건

.weightclass/verify-review 시드 두 개가 speculative_run.py 에 58줄이
추가되면서 밀렸다. 4347→4405, 4443→4501. 두 시드 모두 이전과 같은 심볼을 같은
±8줄 창
안에 두므로 검증기가 수용/거부하는 집합은 바뀌지 않는다.

이 PR 이 다루지 않는 것

  • 코어 wclass 의 네이티브 agy/grok 라우트는 여전히 argv 전달이며,
    AGENTS.md 의 예외 조항이 네이티브 라우팅에 대해 그대로 유효하다. 코어 agy 를
    stdin 으로 옮기면 모든 내장 agy 라우트 지문이 바뀌므로 별도 릴리스 처리가
    필요하다.
  • 봉인된 agy 캠페인 인구가 있다면 다음 dispatch 전에 문서화된
    migrate-routes --vendor agy 가 필요하다(schema-1 agy argv 변경).
  • 선행 문제: agy 자신의 --print-timeout 5분 기본값이 advisory 의 3,600초
    상한보다 먼저 자식을 끊는다. 이 배치 이전부터 있던 문제라 건드리지 않았다.

🤖 Generated with Claude Code

https://claude.ai/code/session_01P3dXGuZjrBWD669SgUuk65

ictechgy and others added 6 commits September 1, 2026 23:20
agy 라우트는 `--print` 가 프롬프트를 인자로 요구한다는 이유로 태스크를 argv
로 넘겨 왔고, 그 노출을 문서화된 잔여로 안고 있었다. argv 에 실린 태스크는
같은 사용자의 모든 프로세스에서 `ps` 로 보인다.

agy 1.1.23 에서 확인한 사실: `--input-format stream-json` 은 프롬프트를 stdin
의 NDJSON 한 줄에서 읽고, 같은 호출에 argv 프롬프트가 함께 오면 CLI 가 스스로
거부한다("a prompt given on the command line would be ignored"). `--print` 를
아예 빼면 정상 동작한다. 그래서 이 조합에는 태스크 자리가 존재하지 않는다 —
노출을 우리가 지우는 것이 아니라 CLI 가 닫는다.

- 두 agy 라우트 빌더에서 `--print {{task}}` 를 제거하고
  `--input-format stream-json --output-format stream-json` 을 쓴다.
- `_prepare_task_command` 는 슬롯 없는 라우트가 NDJSON 입력을 **선언**했을 때만
  stdin 페이로드를 봉투로 감싼다. 실행 파일 이름이 아니라 선언된 형식으로
  판정하므로 래퍼 이름이 달라도 봉투가 빠지지 않고, 토큰 위치로 찾으므로 다른
  플래그의 값 안에 있는 같은 글자로 켜지지 않는다.
- 출력 파싱은 그대로 둔다. `advice_text_extracted` 는 이미 마지막 줄부터 JSON
  으로 읽고, `_agy_usage` 는 이미 `{"event":"result","result":{...}}` 봉투를
  처리한다.

`build_default_evidence_route("agy", ...)` 의 task_delivery 가 argv 에서 stdin
으로 바뀌므로, 기존 flexible-vendor 수용 테스트의 agy 단언을 새 계약으로
갱신했다. claude/codex 프로필 다이제스트와 grok 의 `{{task_file}}` 전달은
바뀌지 않는다.

`.weightclass/verify-review` 의 시드 두 개는 `speculative_run.py` 에 58줄이
추가되면서 위치가 밀렸다. 4347→4405, 4443→4501 로 재고정했고, 두 시드 모두
이전과 같은 심볼을 같은 ±8줄 창 안에 두므로 검증 의미는 바뀌지 않는다.

실측: 실제 agy CLI 로 `ask --vendor agy --workflow review --context task` 가
`task_delivery: "stdin"` 과 유효한 schema-1 결과를 반환하는 것을 확인했다.

주의: 코어 `wclass` 의 네이티브 agy/grok 라우트는 이 커밋에서 바뀌지 않는다.
그쪽은 여전히 argv 전달이며 AGENTS.md 의 예외 조항이 그대로 적용된다.
봉인된 agy 캠페인 인구가 있다면 새 dispatch 전에 문서화된
`migrate-routes --vendor agy` 를 먼저 수행해야 한다.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01P3dXGuZjrBWD669SgUuk65
의회 구성원은 서로의 출력을 보지 않고 같은 태스크·컨텍스트만 받는 독립
프로세스다. 그런데 순차로 돌려 왔고, 그 대가는 두 가지였다.

- 네 구성원이면 벽시계가 네 호출의 **합** 이 된다.
- 전체 데드라인(`--total-timeout-seconds`)이 뒤쪽 구성원만 굶겼다. 앞선
  구성원이 시간을 다 쓰면 남은 구성원은 시작도 못 하고
  `ask_council_deadline` 이 됐다.

동시에 띄우면 둘 다 사라진다. 합이 최댓값이 되고, 남은 예산을 모두가 같은
조건으로 나눠 갖는다.

- `_run_members_concurrently` 가 구성원 수만큼의 스레드 풀에서 각자
  `run_child` 를 부른다. 자식은 이미 자기 프로세스 그룹에서 돌고 자기 상한으로
  닫히며, 상태 수거는 `os.waitpid(pid)` 로 지목한 자식만 기다리므로 구성원끼리
  서로의 종료 상태를 가져가지 않는다.
- 상한은 제출 시점에 한 번만 정한다. 스레드 안에서 다시 재면 스케줄링 지연이
  구성원마다 다른 상한이 되어 같은 입력이 실행마다 다른 결과를 낸다.
- 반환은 언제나 입력 순서다. 완료 순서로 돌려주면 같은 의회를 두 번 돌렸을 때
  영수증의 구성원 순서가 달라진다.
- 실패한 피어는 취소하지 않는다. 이미 시작된 호출은 과금이 끝났고 중간에
  끊으면 그 비용만 버린다. `advisory_parallel` 이 캠페인에서 내린 결정과 같다.
- 구성원 하나가 CLI 실패가 아닌 오류를 내도 나머지를 끝까지 거둔 뒤 입력
  순서로 가장 앞선 오류를 낸다. 먼저 빠져나가면 자식이 부모 없이 남는다.
- 작업 트리 비교를 구성원 사이가 아니라 의회 전체의 앞뒤로 옮겼다.

기존 `test_council_total_deadline_prevents_later_member_start` 는 순차 실행이
만들던 **뒤쪽 구성원 굶김** 자체를 계약으로 굳혀 놓은 테스트였다. 그 동작이
이 커밋에서 사라지므로, 남는 계약(제출 전에 이미 예산이 0 이하면 아무도
시작하지 않는다)을 그대로 재작성하고, 동시성 자체를 배리어로 증명하는 테스트와
피어 수거 테스트를 추가했다.

실측: 실제 agy+codex 2인 의회가 13.9초에 끝났고, codex 는 단독 실행에서도 같은
`ask_executor_failed` 를 내는 로컬 CLI 문제였다. 의회는 설계대로 부분 결과를
보존하고 exit 3 으로 닫았다.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01P3dXGuZjrBWD669SgUuk65
두 가지가 `ask` 를 일상적으로 쓰기 어렵게 만들고 있었다.

**동의 마찰.** 태스크를 내보낼 때마다 터미널 확인을 다시 받는다. 탈출구는
`--confirm-task-egress` 하나뿐이고 그건 호출 단위다. 저장소·벤더 단위 grant 를
디스크에 남기는 방법은 이 프로젝트에서는 선택지가 아니다 — AGENTS.md 가
advisory 상태에 저장소 경로와 시각을 금지한다.

그래서 동의를 셸 세션의 수명에만 둔다. `WCLASS_ADVISORY_EGRESS=session` 을
export 하면 그 셸 동안 확인을 건너뛴다. 아무것도 쓰지 않고, 셸이 닫히면
사라진다. 권한의 크기는 `--confirm-task-egress` 와 정확히 같다 — 넓히는 것이
아니라 같은 승인을 한 번만 말하게 하는 것이다.

값을 정확히 `session` 으로 요구한다. 이름의 존재만 보면, 비어 있는 값이나 `0`
을 넣어 **끄려던** 사용자가 오히려 켜게 된다.

영수증에 `task_egress_confirmation_source` 를 추가했다(`flag`,
`session_environment`, `terminal`). 출처를 남기지 않으면 세션 승인으로 지나간
호출과 사람이 y 를 친 호출이 같은 기록으로 남는다. 둘은 같은 권한이지만 같은
사건이 아니다. `--preview` 는 `session_egress_grant_active` 로 이 세션에 이미
승인이 있는지 밝힌다 — 밝히지 않으면 미리보기가 실제 실행과 다른 그림을 준다.

**`--human` 이 사람용이 아니었다.** 미리보기의 사람용 출력은
`json.dumps(indent=2)` 를 그대로 찍었고, 그 안에 이스케이프된 JSON 스키마
전문이 들어 있어 나머지 플래그가 화면 밖으로 밀렸다. 읽으라고 만든 출력이
읽을 수 없었다.

이제 워크플로/스테이지, 컨텍스트, 자식 수와 상한, 저장소 요청과 격리 여부,
동의 상태, 그리고 라우트별 전달 방식과 프로세스 노출을 문장으로 찍는다. argv
는 그대로 두되 120자를 넘는 인자만 길이와 sha256 앞자리로 줄인다 — 값을
지우는 것이 아니므로 두 실행의 스키마가 같은지 비교할 수 있고, 정확한 바이트는
`--json` 에 그대로 있다.

`--vendor agy --preview --human` 이 이제 `stdin delivery, no argv exposure` 를
한 줄로 보여 준다. 앞 커밋의 전달 방식 변경이 검토 화면에서 확인된다.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01P3dXGuZjrBWD669SgUuk65
이번 브랜치의 세 변경(agy stdin 전달, 의회 병렬 실행, 세션 스코프 동의와
읽을 수 있는 미리보기)과 그 증거를 HANDOFF 에 남긴다.

다루지 않은 것도 함께 적었다: 코어 `wclass` 의 네이티브 agy/grok 라우트는
여전히 argv 전달이라 AGENTS.md 의 예외가 그대로 서 있고, 봉인된 agy 캠페인
인구는 다음 dispatch 전에 `migrate-routes --vendor agy` 가 필요하며, agy 자신의
`--print-timeout` 5분 기본값이 advisory 의 3,600초 상한보다 먼저 자식을 끊는
선행 문제가 남아 있다.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01P3dXGuZjrBWD669SgUuk65
Skill 번들이 에이전트에게 사실이 아닌 것을 가르치고 있었다.

- "For agy, weightclass itself performs the documented argv delivery" 와
  "agy receives the task in argv and therefore has documented local
  process-inspection exposure" 는 앞선 커밋 이후 거짓이다. agy 는
  `--input-format stream-json` 아래 stdin 의 NDJSON 한 줄로 태스크를 받고,
  CLI 자신이 argv 프롬프트를 거부한다. 이제 내장 라우트 중 태스크를 argv 에
  싣는 것은 없다.
- 의회 설명이 여전히 순차 실행을 전제했다. 구성원이 함께 시작하고, 전체
  데드라인을 나눠 가지며, 결과는 요청한 벤더 순서로 보고된다는 사실을 넣었다.
- 터미널 사용자가 `WCLASS_ADVISORY_EGRESS=session` 으로 셸 한 번만 승인할 수
  있다는 것과, 영수증이 승인 출처를 남긴다는 것을 추가했다.

번들이 바뀌므로 `manifest.json` 의 managed_onboarding 을 17 에서 18 로 올리고,
RELEASING.md 가 요구하는 대로 **정확히 발행된 0.29.0 번들** 의 네 파일 해시를
`RELEASE_0290_BUNDLE_FILE_SHA256` 으로 원장에 추가했다. 그 해시는 작업 트리와
`v0.29.0` 태그에서 각각 계산해 동일함을 확인했으므로, 설치된 0.29.0 번들은
덮어쓰기 충돌 없이 안전하게 업그레이드된다.

`tests/verify_advisory_skill_ledger.py --previous-ref v0.29.0` 이 통과한다.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01P3dXGuZjrBWD669SgUuk65
외부 리뷰(GLM)와 자체 검토에서 나온 지적을 반영한다.

**동의 공개가 사라지던 문제(HIGH).** 세션 승인이 있으면 터미널을 아예 열지
않았다. 그런데 그 화면이 이 도구에 존재하는 **유일한** 매회 공개다 — 벤더
목록, 전달 방식, `--context diff|files` 의 "may contain secrets" 경고. 승인한
맥락과 다른 벤더·다른 컨텍스트로 나가는 호출을 사용자가 볼 방법이 사라졌다.
이제 승인 여부와 무관하게 같은 공개를 찍고, 응답만 읽지 않는다. 터미널이 없으면
공개는 못 하되 승인은 유효하다 — 자동화가 이 승인의 용도다.

**"권한 크기가 같다"는 서술을 고쳤다.** 행위는 같지만 범위와 수명은 같지 않다.
플래그는 한 호출을, 이 변수는 셸의 모든 호출과 **모든 자손 프로세스** 를
승인한다. rc 파일에 export 하면 확인은 영구히 도달 불가능해진다. 행위별 게이트가
설정 상태로 바뀌는 계약 변경임을 docstring 과 README 에 명시했다.
검증한 것: 이 변수는 `default_child_env` 허용 목록에 없어 벤더 자식에게 가지
않는다. 회귀 테스트로 고정했다.

**오해를 부르는 죽은 자리표시자 제거(HIGH, latent).** 구성원 예외 경로가
실패 자리에 `ask_cli_unavailable` 을 채웠다. 이 경로는 반드시 예외로 끝나므로
그 값이 영수증에 닿지는 않지만, "자식이 뜨지 않았다" 는 뜻이라 나중에 이 목록을
쓰게 되는 순간 egress 집계가 틀린다. 구성 자체가 틀렸으므로 없앴다.

**전달 모순을 닫았다(MEDIUM).** 태스크 자리와 NDJSON stdin 선언이 함께 있는
라우트는 태스크를 두 곳으로 보내겠다는 뜻이다. 한쪽을 골라 주면 검토한 사람이
본 것과 다른 전달이 조용히 일어나므로, 고르지 말고 `RunFailure` 로 닫는다.

**검토 화면이 봉투를 숨기던 문제(MEDIUM).** stdin 이라는 말만으로는 검토가
끝나지 않는다. 라우트가 NDJSON 입력을 선언하면 weightclass 가 태스크를 봉투로
감싸므로, 검토한 argv 와 자식이 실제로 받는 바이트가 다르다. 미리보기에
`task_stdin_encoding` 을 넣고 사람용 출력에 한 줄로 찍는다.

**agy 버전 하한이 조용했다(LOW→MEDIUM).** 라우트가 `--input-format` 에
의존하는데 능력 검사가 그 플래그를 요구하지 않아, 구버전 agy 는 태스크를 보낸
뒤 실행 중 실패로만 드러났다. 검사에 추가해 태스크 입력 **전에** 닫는다.
agy 1.1.23 에서 통과를 확인했다.

확인했으나 문제가 아니었던 지적:
- `MAX_COMMAND_TOKEN_BYTES` 는 4,096 이다. 리뷰가 본 `"[REDACTED]"` 는
  packet-ask 스크러버의 오탐이었다.
- `bounded_capture` 는 0 이하만 거부하고 작은 양수는 정상 타임아웃으로 처리한다.
  `ValueError` 로 상태가 오분류되지 않는다.
- 동시성 분석은 리뷰와 결론이 같다. pid 지정 `waitpid`, 자식별 세션,
  `close_fds` 기본값이 구성원 간 간섭을 막는다.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01P3dXGuZjrBWD669SgUuk65
@ictechgy
ictechgy merged commit 7fbad0a into main Sep 1, 2026
8 checks passed
@ictechgy
ictechgy deleted the feature/advisory-usability-hardening branch September 1, 2026 15:04
@ictechgy ictechgy mentioned this pull request Sep 1, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant