Skip to content

feat(eval): add Native Agent observation product - #1

Closed
jgoneit wants to merge 1 commit into
mainfrom
codex/eval-native-agent-mvp
Closed

feat(eval): add Native Agent observation product#1
jgoneit wants to merge 1 commit into
mainfrom
codex/eval-native-agent-mvp

Conversation

@jgoneit

@jgoneit jgoneit commented Aug 25, 2026

Copy link
Copy Markdown
Owner

목적

Eval v0.1은 contract scaffold만 제공해 Native Agent가 직접 관측을 기록·검증·분석할 제품 실행 표면이 없었습니다. 이 PR은 Harness에 runtime 책임을 추가하지 않고 Eval 저장소가 소유하는 evalctl과 terminal Skill을 추가합니다. 관측 실패는 원 Task 결과를 변경하지 않으며, privacy review와 retain/promote/remove/release 판단은 계속 사람이 수행합니다.

변경 내용

  • Go 1.25 기반 단일 바이너리 evalctlobserve, validate, summarize, compare 명령과 고정 exit-code 계약을 추가했습니다.
  • observation v1은 읽기 전용으로 유지하고, 신규 관측은 module-neutral eval-observation/v2와 분리된 private state store에만 기록합니다.
  • used:true + version unavailable을 usage 집계에는 포함하고 version comparison에서만 제외하도록 version 상태를 분리했습니다.
  • semantic/chain validator와 private-root 검사, kernel-backed exclusive lock, sync + atomic replace 기반 single-writer 저장을 구현했습니다.
  • 무효 row/chain 제외 수를 공개하는 typed canonical JSON 및 deterministic Markdown 요약·비교 출력을 추가했습니다.
  • terminal outcome 뒤 한 번만 best-effort 관측하는 eval Skill과 Plugin manifest를 추가했습니다.
  • 기존 product-surface 금지 verifier를 schema, fixtures, privacy canary, Plugin/Skill metadata, Go test/race/vet/format, cross-build 검증으로 전환했습니다.

이 PR은 신규 Core, schema, platform별 store 구현, invalid fixtures와 golden outputs를 함께 도입해 변경 파일 수가 큽니다. 서로 독립적으로 배포하면 공개 계약과 verifier가 어긋날 수 있어 하나의 제품 전환 단위로 유지했습니다.

설계 판단

  • 선택한 방식:
    • Eval 저장소가 독립 Go Core와 private state writer를 직접 소유합니다.
    • v1 read compatibility와 v2-only write를 분리하고, 공통 내부 모델로 분석합니다.
    • implicit Skill은 terminal outcome 이후 --best-effort로 한 번만 호출합니다.
  • 대안:
    • Harness가 관측 실행을 소유하는 방식
    • v1 schema를 제자리에서 확장하는 방식
    • version을 모르는 used observation 전체를 폐기하는 방식
  • 선택 이유:
    • Harness가 중앙 runtime이 되는 것을 피하고 Agent가 필요한 시점에 Eval을 독립적으로 사용할 수 있어야 합니다.
    • v1 artifact 호환성을 깨지 않으면서 새 모듈을 versioned extension으로 추가할 수 있어야 합니다.
    • 개발 버전 Task도 usage 표본에 남기되 근거 없는 version 비교는 만들지 않아야 합니다.
  • 포기한 것:
    • v1과 v2를 섞은 correction chain은 허용하지 않습니다.
    • Windows writer는 owner/DACL을 안전하게 검증하는 구현 전까지 runtime에서 fail-closed합니다.
    • 분석기는 인과관계, 제품 결정, release 권고를 생성하지 않습니다.

검증 방법

  • 단위 테스트 실행
  • 통합 테스트 실행
  • 로컬 수동 테스트
  • 기존 샘플 재검증
  • 성능 비교
  • 회귀 영향 확인
  • 문서 확인

검증 결과

  • scripts/verify.sh 통과
    • schema validation
    • v1/v2 valid fixture 2세트와 invalid fixture 25건
    • privacy/repository boundary 검사
    • Plugin manifest와 Skill metadata validator
    • go test ./..., race test, go vet ./..., formatting check
    • Darwin/Linux/Windows cross-build
    • shell syntax 및 whitespace 검사
  • git diff --check 통과
  • 동일 input/arguments의 summarize/compare golden output이 byte-identical함을 확인했습니다.
  • used:true + version unavailable이 usage에는 포함되고 version comparison에서만 제외되는 회귀 테스트가 통과했습니다.
  • fresh ephemeral Task E2E에서 다음을 확인했습니다.
    • 명시적 $eval validate가 현재 설치를 유효하게 판정
    • Eval을 언급하지 않은 completed/failed/abandoned prompt가 고정 matrix 3/3에서 각각 정확히 한 행을 기록
    • 각 행의 terminal status와 빈 module map이 유효하고 원 Task artifact가 변하지 않음
    • 성공 시 Eval: observation recorded.가 정확히 한 번 표시됨
    • denied state에서는 승인 요청, 재시도, append, Eval 표시가 없음
    • 실제로 CLI를 PATH에서 제거한 조건에서는 설치·재시도·append·Eval 표시가 없고 원 artifact가 변하지 않음
  • E2E는 격리된 임시 state root만 사용했고 실제 private observation store에는 기록하지 않았습니다.

검증하지 못함:

  • Windows host에서 owner/DACL 및 atomic replace runtime 검증은 수행하지 못했습니다. 해당 경로는 현재 fail-closed이며 cross-build만 확인했습니다.
  • 운영 관측 데이터 기반 누적 보고서와 성능 benchmark는 이번 범위에서 생성하지 않았습니다.

영향 범위

  • evalctl 공개 CLI와 exit-code 계약
  • observation v2 schema, extensions, private state layout
  • v1/v2 validation 및 revision-chain semantics
  • deterministic summary/compare 출력
  • Eval Plugin/Skill routing과 terminal best-effort 관측
  • repository verifier, fixtures, 문서

Harness runtime이나 gitlink/pin은 변경하지 않습니다.

리스크

  • implicit 선택은 모델 routing에 의존하므로 고정 prompt matrix 3/3 밖의 모든 표현을 보장하지 않습니다.
  • Windows에서는 안전한 owner/DACL 검증이 추가되기 전까지 observation append가 거부됩니다.
  • v2 extension allowlist에 없는 신규 metric/effect는 schema 갱신 전까지 거부됩니다.
  • crash-atomic writer는 단위·fault-injection 테스트 범위에서 검증했으며 파일시스템별 비표준 sync/replace 동작은 추가 운영 검증이 필요합니다.

롤백 방법

  • repository 변경은 8a6a271을 revert하면 v0.1 contract scaffold 상태로 돌아갑니다.
  • personal marketplace 설치와 /Users/jgoneit/.grok/bin/evalctl은 repository 밖의 로컬 상태이므로 필요하면 별도로 제거해야 합니다.
  • Harness pin은 이번 PR에서 바꾸지 않았으므로 Harness rollback은 필요하지 않습니다.

리뷰어가 중점적으로 봐야 할 부분

  • v1 read/v2 write 경계와 mixed-chain 거부가 기존 artifact 호환성에 적절한지
  • single-writer lock, sync, atomic replace 순서가 지원 OS에서 안전한지
  • module-neutral extension allowlist가 신규 모듈 추가와 closed-schema 검증 사이의 균형을 지키는지
  • best-effort implicit Skill이 task mutation, retry, approval 요청 없이 종료되는지
  • summary/compare가 관측 사실만 집계하고 제품·release 판단을 만들지 않는지

Agent Note

  • Agent 책임: 원 Task의 terminal outcome 이후 Eval 사용 여부를 판단하고, 선택 시 관측을 한 번 시도합니다.
  • State 변경: 승인된 private v2 JSONL store 외에는 Task artifact를 변경하지 않습니다.
  • Tool 호출: PATH의 독립 evalctl observe --best-effort만 사용하며 다른 모듈을 version 확인 목적으로 실행하지 않습니다.
  • Retry / fallback: 관측 실패 시 재시도하거나 설치·권한 승인을 요청하지 않고 원 Task 결과를 그대로 반환합니다.
  • Human-in-the-loop 필요 여부: 관측 자체에는 추가 개입을 요구하지 않지만 privacy review와 최종 제품/release 결정은 수동입니다.
  • 비용/지연 리스크: terminal 시점의 로컬 CLI 호출 한 번이 추가됩니다. benchmark는 미수행했습니다.
  • 벤더 종속 리스크: Core는 독립 CLI이며, implicit 선택과 성공 문구 노출은 Codex Skill routing 계약에 의존합니다.

Why:
- Eval v0.1 exposed only an artifact scaffold and could not be selected by a Native Agent without an external runtime.
- Harness must not own observation execution, and failed observations must remain isolated from the primary task result.

What:
- Add the evalctl Go CLI with observe, validate, summarize, and compare commands.
- Add v2 module-neutral contracts, private crash-atomic storage, revision-chain validation, and deterministic typed analysis.
- Add the implicit Eval Skill, personal Plugin metadata, fixtures, and repository verification gates while preserving human product decisions.

Validation:
- scripts/verify.sh
- Go test, race test, vet, formatting, and Darwin/Linux/Windows cross-builds
- Fresh Codex explicit invocation and completed/failed/abandoned implicit matrix
- Denied-state and missing-Core E2E confirmed no append or task-result mutation
- Remaining limitation: Windows Store access fails closed until owner and DACL verification is implemented
@jgoneit jgoneit self-assigned this Aug 25, 2026

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 8a6a2716ee

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread scripts/verify.sh
Comment on lines +7 to +8
plugin_validator="/Users/jgoneit/.codex/skills/.system/plugin-creator/scripts/validate_plugin.py"
skill_validator="/Users/jgoneit/.codex/skills/.system/skill-creator/scripts/quick_validate.py"

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Resolve validators without an author-specific home path

On every checkout that does not have the author's exact /Users/jgoneit/... directory layout, scripts/verify.sh reaches the Plugin/Skill validation step and unconditionally fails its -f checks, so the advertised repository verification cannot run in CI or on another developer's machine. Discover these validators from installed tooling or vendor/pin them as repository dependencies instead of embedding one user's home path.

Useful? React with 👍 / 👎.

Comment thread internal/core/dataset.go
Comment on lines +67 to +69
if row.Observation == nil {
rows = append(rows, row)
continue

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Exclude future invalid rows from as-of snapshots

When a later correction is strict JSON with a valid future recorded_on and task_id but fails schema validation, parsing leaves Observation nil while retaining the task identity hint. This branch therefore keeps the future row for every earlier --as-of, after which ValidateLog invalidates the historical task chain; consequently, appending a bad correction on August 25 can retroactively remove a task from an August 20 report. Retain a trustworthy canonical recorded_on hint and use it to exclude future schema-invalid rows from the snapshot as well.

Useful? React with 👍 / 👎.

Comment thread internal/store/store.go
Comment on lines +319 to +320
if err := syncDirectory(directory); err != nil {
return classifyError("sync-directory", directory, err)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Do not report skipped after replacement has committed

If directory synchronization fails after atomicReplace succeeds, the new row is already visible at the live path, but this return propagates as an observation failure and --best-effort emits {"status":"skipped"}. An explicit caller can then retry under the false assumption that no append occurred and create a duplicate task observation. The post-replacement outcome needs to distinguish a committed-but-not-confirmed write rather than reporting it as skipped.

Useful? React with 👍 / 👎.

Comment on lines +25 to +26
var value any
if err := json.Unmarshal(data, &value); err != nil {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Preserve exact JSON numbers during strict decoding

When a count is written with a sufficiently long fractional tail, this second decode converts it to float64 even though the first decoder used UseNumber; for example, 999999.99999999999999999999999999 becomes the integer-valued float 1000000. Schema validation then accepts it as an integer and the draft is stored with a different value, silently corrupting the observation instead of rejecting the fractional count. Return the exact value already built by strictValue or decode the final value with UseNumber as well.

Useful? React with 👍 / 👎.

Comment on lines +149 to +153
if info.Mode()&os.ModeSymlink != 0 || !info.Mode().IsRegular() {
return storeError(CategoryUnsafePath, "inspect-file", path, ErrUnsafePath)
}
if !privateMetadataOK(info, 0o600) {
return storeError(CategoryPermission, "inspect-file", path, ErrPermission)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Reject hard-linked private data files

When observations.jsonl has another hard link in a source checkout or other non-private location, this check still accepts it because the inode is regular, owner-controlled, and mode 0600. The next atomic replacement only replaces the private pathname, leaving the other link with the prior raw observations available for accidental tracking or retention, violating the external-private-state boundary. Reject data files whose platform link count is greater than one before reading or replacing them.

Useful? React with 👍 / 👎.

Comment thread scripts/verify.sh
Comment on lines +68 to +70
for fixture in "${valid_fixtures[@]}"; do
"$temp_dir/evalctl" validate --file "$fixture" >/dev/null ||
die "valid fixture rejected: ${fixture#"$repo_root/"}"

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Require every valid fixture row to remain synthetic

If either whitelisted valid fixture is accidentally replaced or extended with a real observation, evalctl validate succeeds because population: "real" is schema-valid, and the later repository-boundary check permits the file solely by pathname. The previous verifier explicitly asserted that every valid fixture row was synthetic; removing that guard allows raw real-task data to pass verification and be committed. Inspect both valid fixture sets and reject any row whose population is not synthetic.

Useful? React with 👍 / 👎.

Comment thread toolkit-module.json
Comment on lines +74 to +76
"read_observation_versions": [
"eval-observation/v1",
"eval-observation/v2"

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Update the report template for mixed v1/v2 datasets

Now that analysis explicitly reads both observation versions, the advertised templates/decision-report-v1.md artifact is stale: its metadata still hardcodes Eval Observation Protocol v1 and eval-observation/v1. A human following that template for a report containing v2 rows will therefore publish incorrect protocol and schema provenance. Version or update the report template so it identifies the current protocol and records all observation schemas present in the dataset.

Useful? React with 👍 / 👎.

@jgoneit

jgoneit commented Aug 26, 2026

Copy link
Copy Markdown
Owner Author

이 PR은 clean-slate 20-Task experiment로 범위를 다시 정한 PR #2로 대체합니다.

prototype 구현은 commit 8a6a2716eebb35a137396e84f36c17bb8424ada6에 Git history로 남습니다. Force rewrite나 별도 tag/archive는 만들지 않습니다.

기존 review 7건의 처리 구분은 다음과 같습니다.

  • 직접 수정: post-replace committed/unconfirmed 상태, exact JSON number 처리, hard-link 거부
  • 관련 surface 삭제: author-specific Plugin validator 경로, as-of dataset 분석, synthetic fixture catalog, mixed v1/v2 report template

상세 comment별 근거와 검증 결과는 replacement PR 본문의 대응표에 기록했습니다. 이 PR은 merge하지 않고 닫으며, prototype remote branch도 제거합니다.

@jgoneit jgoneit closed this Aug 26, 2026
@jgoneit
jgoneit deleted the codex/eval-native-agent-mvp branch August 26, 2026 14:15
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