From 83a48b08a688423869ef67fb6103a3903c9cdc2b Mon Sep 17 00:00:00 2001 From: puppe1990 Date: Tue, 19 May 2026 19:57:14 -0300 Subject: [PATCH 1/2] externalize course sources from builder repo --- .gitignore | 4 + README.md | 95 +-- WHITELABEL.md | 94 ++- .../agent-ops-bootcamp/course.manifest.mjs | 183 ----- courses/agent-ops-bootcamp/docs/en/index.md | 1 - .../debug-long-running-workflows/index.md | 14 - .../operate-agents-with-guardrails/index.md | 13 - .../ship-with-runtime-feedback/index.md | 14 - .../docs/en/projects/incident-drill/index.md | 7 - .../docs/en/projects/index.md | 3 - .../en/projects/release-checklist/index.md | 8 - .../docs/en/resources/index.md | 3 - .../en/resources/openai-advanced/index.md | 3 - .../docs/en/resources/reference/index.md | 3 - .../docs/en/resources/templates/index.md | 9 - .../docs/en/skills/index.md | 7 - courses/agent-ops-bootcamp/docs/index.md | 14 - .../course.manifest.mjs | 674 ------------------ .../docs/ar/index.md | 73 -- .../code/failure-pattern-demo.ts | 170 ----- .../code/failure-signals-checklist.md | 9 - .../code/index.md | 7 - .../code/underspecified-task.md | 18 - .../index.md | 117 --- .../code/harness-components.md | 21 - .../code/harness-vs-no-harness.ts | 180 ----- .../code/index.md | 8 - .../code/minimal-harness-loop.ts | 36 - .../index.md | 109 --- .../code/index.md | 7 - .../code/repo-knowledge-layout.txt | 13 - .../code/repo-reader.ts | 189 ----- .../code/system-of-record-checklist.md | 11 - .../index.md | 137 ---- .../code/AGENTS-short.md | 15 - .../code/anti-patterns.md | 7 - .../code/index.md | 7 - .../code/split-vs-monolithic.ts | 209 ------ .../index.md | 132 ---- .../code/continuity-checklist.md | 6 - .../code/index.md | 7 - .../code/session-handoff.md | 17 - .../code/session-simulator.ts | 162 ----- .../index.md | 180 ----- .../code/index.md | 8 - .../code/init-check.ts | 296 -------- .../code/init.sh | 10 - .../code/initializer-output-checklist.md | 7 - .../index.md | 157 ---- .../code/index.md | 8 - .../code/next-task-template.md | 6 - .../code/scope-surface-example.md | 17 - .../code/scope-tracker.ts | 151 ---- .../index.md | 134 ---- .../code/feature-list-validator.ts | 218 ------ .../code/feature_list.json | 15 - .../code/index.md | 8 - .../code/pass-gate-policy.md | 8 - .../index.md | 137 ---- .../code/clean-state-checklist.md | 6 - .../code/index.md | 8 - .../code/victory-detector.ts | 241 ------- .../index.md | 145 ---- .../code/architecture-rules.md | 6 - .../code/e2e-runner.ts | 253 ------- .../code/index.md | 8 - .../code/review-feedback-to-rule.md | 10 - .../index.md | 158 ---- .../code/evaluator-rubric.md | 8 - .../code/index.md | 8 - .../code/runtime-logger.ts | 260 ------- .../code/sprint-contract.md | 12 - .../index.md | 154 ---- .../code/benchmark-comparison-template.md | 18 - .../code/benchmark-runner.ts | 216 ------ .../code/cleanup-loop.md | 9 - .../code/cleanup-scanner.ts | 336 --------- .../code/index.md | 8 - .../index.md | 186 ----- .../docs/ar/projects/index.md | 23 - .../index.md | 25 - .../index.md | 22 - .../index.md | 22 - .../project-04-incremental-indexing/index.md | 22 - .../index.md | 22 - .../index.md | 25 - .../docs/ar/resources/index.md | 50 -- .../ar/resources/openai-advanced/index.md | 35 - .../openai-advanced/repo-template/AGENTS.md | 57 -- .../repo-template/ARCHITECTURE.md | 57 -- .../repo-template/docs/DESIGN.md | 27 - .../repo-template/docs/FRONTEND.md | 24 - .../repo-template/docs/PLANS.md | 34 - .../repo-template/docs/PRODUCT_SENSE.md | 26 - .../repo-template/docs/QUALITY_SCORE.md | 39 - .../repo-template/docs/RELIABILITY.md | 32 - .../repo-template/docs/SECURITY.md | 27 - .../docs/design-docs/core-beliefs.md | 9 - .../repo-template/docs/design-docs/index.md | 21 - .../docs/exec-plans/active/index.md | 10 - .../docs/exec-plans/completed/index.md | 5 - .../docs/exec-plans/tech-debt-tracker.md | 7 - .../repo-template/docs/generated/db-schema.md | 14 - .../repo-template/docs/product-specs/index.md | 13 - .../docs/product-specs/new-user-onboarding.md | 26 - .../design-system-reference-llms.txt | 9 - .../docs/references/nixpacks-llms.txt | 8 - .../repo-template/docs/references/uv-llms.txt | 8 - .../openai-advanced/repo-template/index.md | 24 - .../sops/chrome-devtools-validation-loop.md | 51 -- .../sops/encode-knowledge-into-repo.md | 45 -- .../resources/openai-advanced/sops/index.md | 25 - .../sops/layered-domain-architecture.md | 50 -- .../sops/observability-feedback-loop.md | 44 -- .../reference/coding-agent-startup-flow.md | 33 - .../docs/ar/resources/reference/index.md | 29 - .../reference/initializer-agent-playbook.md | 36 - .../docs/ar/resources/reference/method-map.md | 18 - .../resources/reference/prompt-calibration.md | 24 - .../docs/ar/resources/templates/AGENTS.md | 53 -- .../docs/ar/resources/templates/CLAUDE.md | 46 -- .../ar/resources/templates/claude-progress.md | 35 - .../templates/clean-state-checklist.md | 8 - .../resources/templates/evaluator-rubric.md | 24 - .../ar/resources/templates/feature_list.json | 64 -- .../docs/ar/resources/templates/index.md | 41 -- .../docs/ar/resources/templates/init.sh | 29 - .../resources/templates/quality-document.md | 43 -- .../ar/resources/templates/session-handoff.md | 30 - .../docs/ar/skills/index.md | 49 -- .../docs/de/index.md | 73 -- .../code/failure-pattern-demo.ts | 170 ----- .../code/failure-signals-checklist.md | 9 - .../code/index.md | 7 - .../code/underspecified-task.md | 18 - .../index.md | 117 --- .../code/harness-components.md | 21 - .../code/harness-vs-no-harness.ts | 180 ----- .../code/index.md | 8 - .../code/minimal-harness-loop.ts | 36 - .../index.md | 109 --- .../code/index.md | 7 - .../code/repo-knowledge-layout.txt | 13 - .../code/repo-reader.ts | 189 ----- .../code/system-of-record-checklist.md | 11 - .../index.md | 137 ---- .../code/AGENTS-short.md | 15 - .../code/anti-patterns.md | 7 - .../code/index.md | 7 - .../code/split-vs-monolithic.ts | 209 ------ .../index.md | 132 ---- .../code/continuity-checklist.md | 6 - .../code/index.md | 7 - .../code/session-handoff.md | 17 - .../code/session-simulator.ts | 162 ----- .../index.md | 180 ----- .../code/index.md | 8 - .../code/init-check.ts | 296 -------- .../code/init.sh | 10 - .../code/initializer-output-checklist.md | 7 - .../index.md | 157 ---- .../code/index.md | 8 - .../code/next-task-template.md | 6 - .../code/scope-surface-example.md | 17 - .../code/scope-tracker.ts | 151 ---- .../index.md | 134 ---- .../code/feature-list-validator.ts | 218 ------ .../code/feature_list.json | 15 - .../code/index.md | 8 - .../code/pass-gate-policy.md | 8 - .../index.md | 138 ---- .../code/clean-state-checklist.md | 6 - .../code/index.md | 8 - .../code/victory-detector.ts | 241 ------- .../index.md | 145 ---- .../code/architecture-rules.md | 6 - .../code/e2e-runner.ts | 253 ------- .../code/index.md | 8 - .../code/review-feedback-to-rule.md | 10 - .../index.md | 158 ---- .../code/evaluator-rubric.md | 8 - .../code/index.md | 8 - .../code/runtime-logger.ts | 260 ------- .../code/sprint-contract.md | 12 - .../index.md | 154 ---- .../code/benchmark-comparison-template.md | 18 - .../code/benchmark-runner.ts | 216 ------ .../code/cleanup-loop.md | 9 - .../code/cleanup-scanner.ts | 336 --------- .../code/index.md | 8 - .../index.md | 184 ----- .../docs/de/projects/index.md | 23 - .../index.md | 25 - .../index.md | 22 - .../index.md | 22 - .../project-04-incremental-indexing/index.md | 22 - .../index.md | 22 - .../index.md | 25 - .../docs/de/resources/index.md | 50 -- .../de/resources/openai-advanced/index.md | 83 --- .../openai-advanced/repo-template/AGENTS.md | 57 -- .../repo-template/ARCHITECTURE.md | 57 -- .../repo-template/docs/DESIGN.md | 28 - .../repo-template/docs/FRONTEND.md | 24 - .../repo-template/docs/PLANS.md | 37 - .../repo-template/docs/PRODUCT_SENSE.md | 26 - .../repo-template/docs/QUALITY_SCORE.md | 39 - .../repo-template/docs/RELIABILITY.md | 33 - .../repo-template/docs/SECURITY.md | 28 - .../docs/design-docs/core-beliefs.md | 10 - .../repo-template/docs/design-docs/index.md | 21 - .../docs/exec-plans/active/index.md | 10 - .../docs/exec-plans/completed/index.md | 5 - .../docs/exec-plans/tech-debt-tracker.md | 8 - .../repo-template/docs/generated/db-schema.md | 14 - .../repo-template/docs/product-specs/index.md | 15 - .../docs/product-specs/new-user-onboarding.md | 26 - .../design-system-reference-llms.txt | 9 - .../docs/references/nixpacks-llms.txt | 8 - .../repo-template/docs/references/uv-llms.txt | 8 - .../openai-advanced/repo-template/index.md | 24 - .../sops/chrome-devtools-validation-loop.md | 51 -- .../sops/encode-knowledge-into-repo.md | 45 -- .../resources/openai-advanced/sops/index.md | 26 - .../sops/layered-domain-architecture.md | 51 -- .../sops/observability-feedback-loop.md | 46 -- .../reference/coding-agent-startup-flow.md | 33 - .../docs/de/resources/reference/index.md | 54 -- .../reference/initializer-agent-playbook.md | 38 - .../docs/de/resources/reference/method-map.md | 19 - .../resources/reference/prompt-calibration.md | 25 - .../docs/de/resources/templates/AGENTS.md | 55 -- .../docs/de/resources/templates/CLAUDE.md | 47 -- .../de/resources/templates/claude-progress.md | 35 - .../templates/clean-state-checklist.md | 8 - .../resources/templates/evaluator-rubric.md | 24 - .../de/resources/templates/feature_list.json | 64 -- .../docs/de/resources/templates/index.md | 255 ------- .../docs/de/resources/templates/init.sh | 29 - .../resources/templates/quality-document.md | 43 -- .../de/resources/templates/session-handoff.md | 30 - .../docs/de/skills/index.md | 49 -- .../docs/en/index.md | 1 - .../code/failure-pattern-demo.ts | 170 ----- .../code/failure-signals-checklist.md | 10 - .../code/index.md | 7 - .../code/underspecified-task.md | 18 - .../index.md | 117 --- .../code/harness-components.md | 21 - .../code/harness-vs-no-harness.ts | 180 ----- .../code/index.md | 8 - .../code/minimal-harness-loop.ts | 36 - .../index.md | 109 --- .../code/index.md | 7 - .../code/repo-knowledge-layout.txt | 13 - .../code/repo-reader.ts | 189 ----- .../code/system-of-record-checklist.md | 11 - .../index.md | 137 ---- .../code/AGENTS-short.md | 15 - .../code/anti-patterns.md | 7 - .../code/index.md | 7 - .../code/split-vs-monolithic.ts | 209 ------ .../index.md | 132 ---- .../code/continuity-checklist.md | 6 - .../code/index.md | 7 - .../code/session-handoff.md | 17 - .../code/session-simulator.ts | 162 ----- .../index.md | 180 ----- .../code/index.md | 8 - .../code/init-check.ts | 296 -------- .../code/init.sh | 10 - .../code/initializer-output-checklist.md | 7 - .../index.md | 157 ---- .../code/index.md | 8 - .../code/next-task-template.md | 6 - .../code/scope-surface-example.md | 17 - .../code/scope-tracker.ts | 151 ---- .../index.md | 134 ---- .../code/feature-list-validator.ts | 218 ------ .../code/feature_list.json | 15 - .../code/index.md | 8 - .../code/pass-gate-policy.md | 8 - .../index.md | 137 ---- .../code/clean-state-checklist.md | 6 - .../code/index.md | 8 - .../code/victory-detector.ts | 241 ------- .../index.md | 145 ---- .../code/architecture-rules.md | 6 - .../code/e2e-runner.ts | 253 ------- .../code/index.md | 8 - .../code/review-feedback-to-rule.md | 10 - .../index.md | 158 ---- .../code/evaluator-rubric.md | 8 - .../code/index.md | 8 - .../code/runtime-logger.ts | 260 ------- .../code/sprint-contract.md | 12 - .../index.md | 154 ---- .../code/benchmark-comparison-template.md | 18 - .../code/benchmark-runner.ts | 216 ------ .../code/cleanup-loop.md | 9 - .../code/cleanup-scanner.ts | 336 --------- .../code/index.md | 8 - .../index.md | 186 ----- .../docs/en/projects/index.md | 23 - .../index.md | 25 - .../index.md | 22 - .../index.md | 22 - .../project-04-incremental-indexing/index.md | 22 - .../index.md | 22 - .../index.md | 25 - .../docs/en/resources/index.md | 58 -- .../en/resources/openai-advanced/index.md | 83 --- .../openai-advanced/repo-template/AGENTS.md | 61 -- .../repo-template/ARCHITECTURE.md | 57 -- .../repo-template/docs/DESIGN.md | 28 - .../repo-template/docs/FRONTEND.md | 24 - .../repo-template/docs/PLANS.md | 35 - .../repo-template/docs/PRODUCT_SENSE.md | 26 - .../repo-template/docs/QUALITY_SCORE.md | 40 -- .../repo-template/docs/RELIABILITY.md | 33 - .../repo-template/docs/SECURITY.md | 27 - .../docs/design-docs/core-beliefs.md | 9 - .../repo-template/docs/design-docs/index.md | 21 - .../docs/exec-plans/active/index.md | 10 - .../docs/exec-plans/completed/index.md | 5 - .../docs/exec-plans/tech-debt-tracker.md | 7 - .../repo-template/docs/generated/db-schema.md | 14 - .../repo-template/docs/product-specs/index.md | 14 - .../docs/product-specs/new-user-onboarding.md | 26 - .../design-system-reference-llms.txt | 9 - .../docs/references/nixpacks-llms.txt | 8 - .../repo-template/docs/references/uv-llms.txt | 8 - .../openai-advanced/repo-template/index.md | 24 - .../sops/chrome-devtools-validation-loop.md | 51 -- .../sops/encode-knowledge-into-repo.md | 45 -- .../resources/openai-advanced/sops/index.md | 25 - .../sops/layered-domain-architecture.md | 50 -- .../sops/observability-feedback-loop.md | 44 -- .../reference/coding-agent-startup-flow.md | 34 - .../docs/en/resources/reference/index.md | 86 --- .../reference/initializer-agent-playbook.md | 37 - .../docs/en/resources/reference/method-map.md | 19 - .../resources/reference/prompt-calibration.md | 25 - .../docs/en/resources/templates/AGENTS.md | 55 -- .../docs/en/resources/templates/CLAUDE.md | 47 -- .../en/resources/templates/claude-progress.md | 35 - .../templates/clean-state-checklist.md | 8 - .../resources/templates/evaluator-rubric.md | 24 - .../en/resources/templates/feature_list.json | 64 -- .../docs/en/resources/templates/index.md | 226 ------ .../docs/en/resources/templates/init.sh | 29 - .../resources/templates/quality-document.md | 43 -- .../en/resources/templates/session-handoff.md | 30 - .../docs/en/skills/index.md | 49 -- .../docs/es/index.md | 73 -- .../code/failure-pattern-demo.ts | 170 ----- .../code/failure-signals-checklist.md | 9 - .../code/index.md | 7 - .../code/underspecified-task.md | 18 - .../index.md | 117 --- .../code/harness-components.md | 21 - .../code/harness-vs-no-harness.ts | 180 ----- .../code/index.md | 8 - .../code/minimal-harness-loop.ts | 36 - .../index.md | 109 --- .../code/index.md | 7 - .../code/repo-knowledge-layout.txt | 13 - .../code/repo-reader.ts | 189 ----- .../code/system-of-record-checklist.md | 11 - .../index.md | 137 ---- .../code/AGENTS-short.md | 15 - .../code/anti-patterns.md | 7 - .../code/index.md | 7 - .../code/split-vs-monolithic.ts | 209 ------ .../index.md | 132 ---- .../code/continuity-checklist.md | 6 - .../code/index.md | 7 - .../code/session-handoff.md | 17 - .../code/session-simulator.ts | 162 ----- .../index.md | 180 ----- .../code/index.md | 8 - .../code/init-check.ts | 296 -------- .../code/init.sh | 10 - .../code/initializer-output-checklist.md | 7 - .../index.md | 157 ---- .../code/index.md | 8 - .../code/next-task-template.md | 6 - .../code/scope-surface-example.md | 17 - .../code/scope-tracker.ts | 151 ---- .../index.md | 134 ---- .../code/feature-list-validator.ts | 218 ------ .../code/feature_list.json | 15 - .../code/index.md | 8 - .../code/pass-gate-policy.md | 8 - .../index.md | 138 ---- .../code/clean-state-checklist.md | 6 - .../code/index.md | 8 - .../code/victory-detector.ts | 241 ------- .../index.md | 145 ---- .../code/architecture-rules.md | 6 - .../code/e2e-runner.ts | 253 ------- .../code/index.md | 8 - .../code/review-feedback-to-rule.md | 10 - .../index.md | 158 ---- .../code/evaluator-rubric.md | 8 - .../code/index.md | 8 - .../code/runtime-logger.ts | 260 ------- .../code/sprint-contract.md | 12 - .../index.md | 154 ---- .../code/benchmark-comparison-template.md | 18 - .../code/benchmark-runner.ts | 216 ------ .../code/cleanup-loop.md | 9 - .../code/cleanup-scanner.ts | 336 --------- .../code/index.md | 8 - .../index.md | 184 ----- .../docs/es/projects/index.md | 23 - .../index.md | 25 - .../index.md | 22 - .../index.md | 22 - .../project-04-incremental-indexing/index.md | 22 - .../index.md | 22 - .../index.md | 25 - .../docs/es/resources/index.md | 50 -- .../es/resources/openai-advanced/index.md | 79 -- .../openai-advanced/repo-template/AGENTS.md | 61 -- .../repo-template/ARCHITECTURE.md | 57 -- .../repo-template/docs/DESIGN.md | 28 - .../repo-template/docs/FRONTEND.md | 24 - .../repo-template/docs/PLANS.md | 34 - .../repo-template/docs/PRODUCT_SENSE.md | 25 - .../repo-template/docs/QUALITY_SCORE.md | 39 - .../repo-template/docs/RELIABILITY.md | 32 - .../repo-template/docs/SECURITY.md | 27 - .../docs/design-docs/core-beliefs.md | 9 - .../repo-template/docs/design-docs/index.md | 21 - .../docs/exec-plans/active/index.md | 10 - .../docs/exec-plans/completed/index.md | 5 - .../docs/exec-plans/tech-debt-tracker.md | 7 - .../repo-template/docs/generated/db-schema.md | 14 - .../repo-template/docs/product-specs/index.md | 13 - .../docs/product-specs/new-user-onboarding.md | 26 - .../design-system-reference-llms.txt | 9 - .../docs/references/nixpacks-llms.txt | 8 - .../repo-template/docs/references/uv-llms.txt | 8 - .../openai-advanced/repo-template/index.md | 24 - .../sops/chrome-devtools-validation-loop.md | 51 -- .../sops/encode-knowledge-into-repo.md | 45 -- .../resources/openai-advanced/sops/index.md | 25 - .../sops/layered-domain-architecture.md | 50 -- .../sops/observability-feedback-loop.md | 44 -- .../reference/coding-agent-startup-flow.md | 33 - .../docs/es/resources/reference/index.md | 85 --- .../reference/initializer-agent-playbook.md | 37 - .../docs/es/resources/reference/method-map.md | 19 - .../resources/reference/prompt-calibration.md | 25 - .../docs/es/resources/templates/AGENTS.md | 55 -- .../docs/es/resources/templates/CLAUDE.md | 47 -- .../es/resources/templates/claude-progress.md | 35 - .../templates/clean-state-checklist.md | 8 - .../resources/templates/evaluator-rubric.md | 24 - .../es/resources/templates/feature_list.json | 64 -- .../docs/es/resources/templates/index.md | 226 ------ .../docs/es/resources/templates/init.sh | 29 - .../resources/templates/quality-document.md | 43 -- .../es/resources/templates/session-handoff.md | 30 - .../docs/es/skills/index.md | 49 -- .../docs/fr/index.md | 73 -- .../code/failure-pattern-demo.ts | 170 ----- .../code/failure-signals-checklist.md | 9 - .../code/index.md | 7 - .../code/underspecified-task.md | 18 - .../index.md | 113 --- .../code/harness-components.md | 21 - .../code/harness-vs-no-harness.ts | 180 ----- .../code/index.md | 8 - .../code/minimal-harness-loop.ts | 36 - .../index.md | 109 --- .../code/index.md | 7 - .../code/repo-knowledge-layout.txt | 13 - .../code/repo-reader.ts | 189 ----- .../code/system-of-record-checklist.md | 11 - .../index.md | 137 ---- .../code/AGENTS-short.md | 15 - .../code/anti-patterns.md | 7 - .../code/index.md | 7 - .../code/split-vs-monolithic.ts | 209 ------ .../index.md | 132 ---- .../code/continuity-checklist.md | 6 - .../code/index.md | 7 - .../code/session-handoff.md | 17 - .../code/session-simulator.ts | 162 ----- .../index.md | 180 ----- .../code/index.md | 8 - .../code/init-check.ts | 296 -------- .../code/init.sh | 10 - .../code/initializer-output-checklist.md | 7 - .../index.md | 157 ---- .../code/index.md | 8 - .../code/next-task-template.md | 6 - .../code/scope-surface-example.md | 17 - .../code/scope-tracker.ts | 151 ---- .../index.md | 134 ---- .../code/feature-list-validator.ts | 218 ------ .../code/feature_list.json | 15 - .../code/index.md | 8 - .../code/pass-gate-policy.md | 8 - .../index.md | 138 ---- .../code/clean-state-checklist.md | 6 - .../code/index.md | 8 - .../code/victory-detector.ts | 241 ------- .../index.md | 145 ---- .../code/architecture-rules.md | 6 - .../code/e2e-runner.ts | 253 ------- .../code/index.md | 8 - .../code/review-feedback-to-rule.md | 10 - .../index.md | 158 ---- .../code/evaluator-rubric.md | 8 - .../code/index.md | 8 - .../code/runtime-logger.ts | 260 ------- .../code/sprint-contract.md | 12 - .../index.md | 154 ---- .../code/benchmark-comparison-template.md | 18 - .../code/benchmark-runner.ts | 216 ------ .../code/cleanup-loop.md | 9 - .../code/cleanup-scanner.ts | 336 --------- .../code/index.md | 8 - .../index.md | 186 ----- .../docs/fr/projects/index.md | 23 - .../index.md | 25 - .../index.md | 22 - .../index.md | 22 - .../project-04-incremental-indexing/index.md | 22 - .../index.md | 22 - .../index.md | 25 - .../docs/fr/resources/index.md | 50 -- .../fr/resources/openai-advanced/index.md | 79 -- .../openai-advanced/repo-template/AGENTS.md | 61 -- .../repo-template/ARCHITECTURE.md | 63 -- .../repo-template/docs/DESIGN.md | 28 - .../repo-template/docs/FRONTEND.md | 28 - .../repo-template/docs/PLANS.md | 35 - .../repo-template/docs/PRODUCT_SENSE.md | 25 - .../repo-template/docs/QUALITY_SCORE.md | 37 - .../repo-template/docs/RELIABILITY.md | 36 - .../repo-template/docs/SECURITY.md | 25 - .../docs/design-docs/core-beliefs.md | 9 - .../repo-template/docs/design-docs/index.md | 21 - .../docs/exec-plans/active/index.md | 10 - .../docs/exec-plans/completed/index.md | 5 - .../docs/exec-plans/tech-debt-tracker.md | 7 - .../repo-template/docs/generated/db-schema.md | 14 - .../repo-template/docs/product-specs/index.md | 14 - .../docs/product-specs/new-user-onboarding.md | 26 - .../design-system-reference-llms.txt | 9 - .../docs/references/nixpacks-llms.txt | 8 - .../repo-template/docs/references/uv-llms.txt | 8 - .../openai-advanced/repo-template/index.md | 25 - .../sops/chrome-devtools-validation-loop.md | 52 -- .../sops/encode-knowledge-into-repo.md | 48 -- .../resources/openai-advanced/sops/index.md | 27 - .../sops/layered-domain-architecture.md | 55 -- .../sops/observability-feedback-loop.md | 47 -- .../reference/coding-agent-startup-flow.md | 36 - .../docs/fr/resources/reference/index.md | 89 --- .../reference/initializer-agent-playbook.md | 38 - .../docs/fr/resources/reference/method-map.md | 20 - .../resources/reference/prompt-calibration.md | 26 - .../docs/fr/resources/templates/AGENTS.md | 57 -- .../docs/fr/resources/templates/CLAUDE.md | 48 -- .../fr/resources/templates/claude-progress.md | 35 - .../templates/clean-state-checklist.md | 8 - .../resources/templates/evaluator-rubric.md | 24 - .../fr/resources/templates/feature_list.json | 64 -- .../docs/fr/resources/templates/index.md | 255 ------- .../docs/fr/resources/templates/init.sh | 29 - .../resources/templates/quality-document.md | 46 -- .../fr/resources/templates/session-handoff.md | 30 - .../docs/fr/skills/index.md | 49 -- .../learn-harness-engineering/docs/index.md | 58 -- .../docs/ja/index.md | 73 -- .../code/failure-pattern-demo.ts | 170 ----- .../code/failure-signals-checklist.md | 9 - .../code/index.md | 7 - .../code/underspecified-task.md | 18 - .../index.md | 117 --- .../code/harness-components.md | 21 - .../code/harness-vs-no-harness.ts | 180 ----- .../code/index.md | 8 - .../code/minimal-harness-loop.ts | 36 - .../index.md | 109 --- .../code/index.md | 7 - .../code/repo-knowledge-layout.txt | 13 - .../code/repo-reader.ts | 189 ----- .../code/system-of-record-checklist.md | 11 - .../index.md | 137 ---- .../code/AGENTS-short.md | 15 - .../code/anti-patterns.md | 7 - .../code/index.md | 7 - .../code/split-vs-monolithic.ts | 209 ------ .../index.md | 132 ---- .../code/continuity-checklist.md | 6 - .../code/index.md | 7 - .../code/session-handoff.md | 17 - .../code/session-simulator.ts | 162 ----- .../index.md | 180 ----- .../code/index.md | 8 - .../code/init-check.ts | 296 -------- .../code/init.sh | 10 - .../code/initializer-output-checklist.md | 7 - .../index.md | 157 ---- .../code/index.md | 8 - .../code/next-task-template.md | 6 - .../code/scope-surface-example.md | 17 - .../code/scope-tracker.ts | 151 ---- .../index.md | 134 ---- .../code/feature-list-validator.ts | 218 ------ .../code/feature_list.json | 15 - .../code/index.md | 8 - .../code/pass-gate-policy.md | 8 - .../index.md | 138 ---- .../code/clean-state-checklist.md | 6 - .../code/index.md | 8 - .../code/victory-detector.ts | 241 ------- .../index.md | 145 ---- .../code/architecture-rules.md | 6 - .../code/e2e-runner.ts | 253 ------- .../code/index.md | 8 - .../code/review-feedback-to-rule.md | 10 - .../index.md | 158 ---- .../code/evaluator-rubric.md | 8 - .../code/index.md | 8 - .../code/runtime-logger.ts | 260 ------- .../code/sprint-contract.md | 12 - .../index.md | 154 ---- .../code/benchmark-comparison-template.md | 18 - .../code/benchmark-runner.ts | 216 ------ .../code/cleanup-loop.md | 9 - .../code/cleanup-scanner.ts | 336 --------- .../code/index.md | 8 - .../index.md | 186 ----- .../docs/ja/projects/index.md | 23 - .../index.md | 25 - .../index.md | 22 - .../index.md | 22 - .../project-04-incremental-indexing/index.md | 22 - .../index.md | 22 - .../index.md | 25 - .../docs/ja/resources/index.md | 50 -- .../ja/resources/openai-advanced/index.md | 83 --- .../openai-advanced/repo-template/AGENTS.md | 55 -- .../repo-template/ARCHITECTURE.md | 55 -- .../repo-template/docs/DESIGN.md | 25 - .../repo-template/docs/FRONTEND.md | 23 - .../repo-template/docs/PLANS.md | 34 - .../repo-template/docs/PRODUCT_SENSE.md | 24 - .../repo-template/docs/QUALITY_SCORE.md | 39 - .../repo-template/docs/RELIABILITY.md | 32 - .../repo-template/docs/SECURITY.md | 27 - .../docs/design-docs/core-beliefs.md | 9 - .../repo-template/docs/design-docs/index.md | 21 - .../docs/exec-plans/active/index.md | 11 - .../docs/exec-plans/completed/index.md | 5 - .../docs/exec-plans/tech-debt-tracker.md | 8 - .../repo-template/docs/generated/db-schema.md | 14 - .../repo-template/docs/product-specs/index.md | 13 - .../docs/product-specs/new-user-onboarding.md | 26 - .../design-system-reference-llms.txt | 9 - .../docs/references/nixpacks-llms.txt | 8 - .../repo-template/docs/references/uv-llms.txt | 8 - .../openai-advanced/repo-template/index.md | 21 - .../sops/chrome-devtools-validation-loop.md | 52 -- .../sops/encode-knowledge-into-repo.md | 45 -- .../resources/openai-advanced/sops/index.md | 25 - .../sops/layered-domain-architecture.md | 51 -- .../sops/observability-feedback-loop.md | 45 -- .../reference/coding-agent-startup-flow.md | 33 - .../docs/ja/resources/reference/index.md | 31 - .../reference/initializer-agent-playbook.md | 37 - .../docs/ja/resources/reference/method-map.md | 19 - .../resources/reference/prompt-calibration.md | 25 - .../docs/ja/resources/templates/AGENTS.md | 53 -- .../docs/ja/resources/templates/CLAUDE.md | 44 -- .../ja/resources/templates/claude-progress.md | 35 - .../templates/clean-state-checklist.md | 8 - .../resources/templates/evaluator-rubric.md | 24 - .../ja/resources/templates/feature_list.json | 64 -- .../docs/ja/resources/templates/index.md | 41 -- .../docs/ja/resources/templates/init.sh | 29 - .../resources/templates/quality-document.md | 43 -- .../ja/resources/templates/session-handoff.md | 30 - .../docs/ja/skills/index.md | 49 -- .../docs/ko/index.md | 1 - .../code/failure-pattern-demo.ts | 170 ----- .../code/failure-signals-checklist.md | 9 - .../code/index.md | 7 - .../code/underspecified-task.md | 18 - .../index.md | 121 ---- .../code/harness-components.md | 21 - .../code/harness-vs-no-harness.ts | 180 ----- .../code/index.md | 8 - .../code/minimal-harness-loop.ts | 36 - .../index.md | 109 --- .../code/index.md | 7 - .../code/repo-knowledge-layout.txt | 13 - .../code/repo-reader.ts | 189 ----- .../code/system-of-record-checklist.md | 11 - .../index.md | 137 ---- .../code/AGENTS-short.md | 14 - .../code/anti-patterns.md | 7 - .../code/index.md | 7 - .../code/split-vs-monolithic.ts | 209 ------ .../index.md | 132 ---- .../code/continuity-checklist.md | 6 - .../code/index.md | 7 - .../code/session-handoff.md | 17 - .../code/session-simulator.ts | 162 ----- .../index.md | 180 ----- .../code/index.md | 8 - .../code/init-check.ts | 296 -------- .../code/init.sh | 10 - .../code/initializer-output-checklist.md | 7 - .../index.md | 157 ---- .../code/index.md | 8 - .../code/next-task-template.md | 6 - .../code/scope-surface-example.md | 17 - .../code/scope-tracker.ts | 151 ---- .../index.md | 134 ---- .../code/feature-list-validator.ts | 218 ------ .../code/feature_list.json | 15 - .../code/index.md | 8 - .../code/pass-gate-policy.md | 8 - .../index.md | 137 ---- .../code/clean-state-checklist.md | 6 - .../code/index.md | 8 - .../code/victory-detector.ts | 241 ------- .../index.md | 145 ---- .../code/architecture-rules.md | 6 - .../code/e2e-runner.ts | 253 ------- .../code/index.md | 8 - .../code/review-feedback-to-rule.md | 10 - .../index.md | 158 ---- .../code/evaluator-rubric.md | 8 - .../code/index.md | 8 - .../code/runtime-logger.ts | 260 ------- .../code/sprint-contract.md | 12 - .../index.md | 154 ---- .../code/benchmark-comparison-template.md | 18 - .../code/benchmark-runner.ts | 216 ------ .../code/cleanup-loop.md | 9 - .../code/cleanup-scanner.ts | 336 --------- .../code/index.md | 8 - .../index.md | 186 ----- .../docs/ko/projects/index.md | 24 - .../index.md | 27 - .../index.md | 24 - .../index.md | 24 - .../project-04-incremental-indexing/index.md | 24 - .../index.md | 24 - .../index.md | 27 - .../docs/ko/resources/index.md | 50 -- .../ko/resources/openai-advanced/index.md | 73 -- .../openai-advanced/repo-template/AGENTS.md | 55 -- .../repo-template/ARCHITECTURE.md | 57 -- .../repo-template/docs/DESIGN.md | 27 - .../repo-template/docs/FRONTEND.md | 25 - .../repo-template/docs/PLANS.md | 36 - .../repo-template/docs/PRODUCT_SENSE.md | 26 - .../repo-template/docs/QUALITY_SCORE.md | 41 -- .../repo-template/docs/RELIABILITY.md | 34 - .../repo-template/docs/SECURITY.md | 29 - .../docs/design-docs/core-beliefs.md | 11 - .../repo-template/docs/design-docs/index.md | 23 - .../docs/exec-plans/active/index.md | 11 - .../docs/exec-plans/completed/index.md | 5 - .../docs/exec-plans/tech-debt-tracker.md | 9 - .../repo-template/docs/generated/db-schema.md | 15 - .../repo-template/docs/product-specs/index.md | 15 - .../docs/product-specs/new-user-onboarding.md | 28 - .../design-system-reference-llms.txt | 9 - .../docs/references/nixpacks-llms.txt | 8 - .../repo-template/docs/references/uv-llms.txt | 8 - .../openai-advanced/repo-template/index.md | 23 - .../sops/chrome-devtools-validation-loop.md | 50 -- .../sops/encode-knowledge-into-repo.md | 44 -- .../resources/openai-advanced/sops/index.md | 25 - .../sops/layered-domain-architecture.md | 49 -- .../sops/observability-feedback-loop.md | 43 -- .../reference/coding-agent-startup-flow.md | 33 - .../docs/ko/resources/reference/glossary.md | 120 ---- .../docs/ko/resources/reference/index.md | 17 - .../reference/initializer-agent-playbook.md | 35 - .../docs/ko/resources/reference/method-map.md | 16 - .../resources/reference/prompt-calibration.md | 23 - .../docs/ko/resources/templates/AGENTS.md | 52 -- .../docs/ko/resources/templates/CLAUDE.md | 45 -- .../ko/resources/templates/claude-progress.md | 37 - .../templates/clean-state-checklist.md | 10 - .../resources/templates/evaluator-rubric.md | 24 - .../ko/resources/templates/feature_list.json | 64 -- .../docs/ko/resources/templates/index.md | 226 ------ .../docs/ko/resources/templates/init.sh | 29 - .../resources/templates/quality-document.md | 45 -- .../ko/resources/templates/session-handoff.md | 32 - .../docs/ko/skills/index.md | 49 -- .../public/screenshots/readme/en-home.png | Bin 163315 -> 0 bytes .../screenshots/readme/en-lecture-01.png | Bin 155119 -> 0 bytes .../screenshots/readme/en-resources.png | Bin 102372 -> 0 bytes .../public/screenshots/readme/zh-home.png | Bin 180700 -> 0 bytes .../screenshots/readme/zh-lecture-01.png | Bin 199030 -> 0 bytes .../screenshots/readme/zh-resources.png | Bin 119075 -> 0 bytes .../docs/ru/index.md | 73 -- .../code/failure-pattern-demo.ts | 170 ----- .../code/failure-signals-checklist.md | 10 - .../code/index.md | 7 - .../code/underspecified-task.md | 18 - .../index.md | 117 --- .../code/harness-components.md | 21 - .../code/harness-vs-no-harness.ts | 180 ----- .../code/index.md | 8 - .../code/minimal-harness-loop.ts | 36 - .../index.md | 109 --- .../code/index.md | 7 - .../code/repo-knowledge-layout.txt | 13 - .../code/repo-reader.ts | 189 ----- .../code/system-of-record-checklist.md | 11 - .../index.md | 137 ---- .../code/AGENTS-short.md | 15 - .../code/anti-patterns.md | 7 - .../code/index.md | 7 - .../code/split-vs-monolithic.ts | 209 ------ .../index.md | 132 ---- .../code/continuity-checklist.md | 6 - .../code/index.md | 7 - .../code/session-handoff.md | 17 - .../code/session-simulator.ts | 162 ----- .../index.md | 180 ----- .../code/index.md | 8 - .../code/init-check.ts | 296 -------- .../code/init.sh | 10 - .../code/initializer-output-checklist.md | 7 - .../index.md | 157 ---- .../code/index.md | 8 - .../code/next-task-template.md | 6 - .../code/scope-surface-example.md | 17 - .../code/scope-tracker.ts | 151 ---- .../index.md | 134 ---- .../code/feature-list-validator.ts | 218 ------ .../code/feature_list.json | 15 - .../code/index.md | 8 - .../code/pass-gate-policy.md | 8 - .../index.md | 137 ---- .../code/clean-state-checklist.md | 6 - .../code/index.md | 8 - .../code/victory-detector.ts | 241 ------- .../index.md | 145 ---- .../code/architecture-rules.md | 6 - .../code/e2e-runner.ts | 253 ------- .../code/index.md | 8 - .../code/review-feedback-to-rule.md | 10 - .../index.md | 158 ---- .../code/evaluator-rubric.md | 8 - .../code/index.md | 8 - .../code/runtime-logger.ts | 260 ------- .../code/sprint-contract.md | 12 - .../index.md | 154 ---- .../code/benchmark-comparison-template.md | 18 - .../code/benchmark-runner.ts | 216 ------ .../code/cleanup-loop.md | 9 - .../code/cleanup-scanner.ts | 336 --------- .../code/index.md | 8 - .../index.md | 186 ----- .../docs/ru/projects/index.md | 23 - .../index.md | 25 - .../index.md | 22 - .../index.md | 22 - .../project-04-incremental-indexing/index.md | 22 - .../index.md | 22 - .../index.md | 25 - .../docs/ru/resources/index.md | 50 -- .../ru/resources/openai-advanced/index.md | 83 --- .../openai-advanced/repo-template/AGENTS.md | 61 -- .../repo-template/ARCHITECTURE.md | 57 -- .../repo-template/docs/DESIGN.md | 28 - .../repo-template/docs/FRONTEND.md | 24 - .../repo-template/docs/PLANS.md | 35 - .../repo-template/docs/PRODUCT_SENSE.md | 26 - .../repo-template/docs/QUALITY_SCORE.md | 39 - .../repo-template/docs/RELIABILITY.md | 32 - .../repo-template/docs/SECURITY.md | 27 - .../docs/design-docs/core-beliefs.md | 9 - .../repo-template/docs/design-docs/index.md | 21 - .../docs/exec-plans/active/index.md | 10 - .../docs/exec-plans/completed/index.md | 5 - .../docs/exec-plans/tech-debt-tracker.md | 7 - .../repo-template/docs/generated/db-schema.md | 14 - .../repo-template/docs/product-specs/index.md | 14 - .../docs/product-specs/new-user-onboarding.md | 26 - .../design-system-reference-llms.txt | 9 - .../docs/references/nixpacks-llms.txt | 8 - .../repo-template/docs/references/uv-llms.txt | 8 - .../openai-advanced/repo-template/index.md | 26 - .../sops/chrome-devtools-validation-loop.md | 51 -- .../sops/encode-knowledge-into-repo.md | 45 -- .../resources/openai-advanced/sops/index.md | 25 - .../sops/layered-domain-architecture.md | 50 -- .../sops/observability-feedback-loop.md | 44 -- .../reference/coding-agent-startup-flow.md | 33 - .../docs/ru/resources/reference/index.md | 17 - .../reference/initializer-agent-playbook.md | 35 - .../docs/ru/resources/reference/method-map.md | 16 - .../resources/reference/prompt-calibration.md | 23 - .../docs/ru/resources/templates/AGENTS.md | 50 -- .../docs/ru/resources/templates/CLAUDE.md | 43 -- .../ru/resources/templates/claude-progress.md | 35 - .../templates/clean-state-checklist.md | 8 - .../resources/templates/evaluator-rubric.md | 24 - .../ru/resources/templates/feature_list.json | 64 -- .../docs/ru/resources/templates/index.md | 226 ------ .../docs/ru/resources/templates/init.sh | 29 - .../resources/templates/quality-document.md | 43 -- .../ru/resources/templates/session-handoff.md | 30 - .../docs/ru/skills/index.md | 49 -- .../docs/uz/GLOSSARY.md | 377 ---------- .../docs/uz/STYLE-GUIDE.md | 127 ---- .../docs/uz/index.md | 74 -- .../code/failure-pattern-demo.ts | 170 ----- .../code/failure-signals-checklist.md | 9 - .../code/index.md | 7 - .../code/underspecified-task.md | 18 - .../index.md | 118 --- .../code/harness-components.md | 21 - .../code/harness-vs-no-harness.ts | 180 ----- .../code/index.md | 8 - .../code/minimal-harness-loop.ts | 36 - .../index.md | 109 --- .../code/index.md | 7 - .../code/repo-knowledge-layout.txt | 13 - .../code/repo-reader.ts | 189 ----- .../code/system-of-record-checklist.md | 11 - .../index.md | 137 ---- .../code/AGENTS-short.md | 14 - .../code/anti-patterns.md | 7 - .../code/index.md | 7 - .../code/split-vs-monolithic.ts | 209 ------ .../index.md | 132 ---- .../code/continuity-checklist.md | 6 - .../code/index.md | 7 - .../code/session-handoff.md | 17 - .../code/session-simulator.ts | 162 ----- .../index.md | 180 ----- .../code/index.md | 8 - .../code/init-check.ts | 296 -------- .../code/init.sh | 10 - .../code/initializer-output-checklist.md | 7 - .../index.md | 157 ---- .../code/index.md | 8 - .../code/next-task-template.md | 6 - .../code/scope-surface-example.md | 17 - .../code/scope-tracker.ts | 151 ---- .../index.md | 134 ---- .../code/feature-list-validator.ts | 218 ------ .../code/feature_list.json | 15 - .../code/index.md | 8 - .../code/pass-gate-policy.md | 8 - .../index.md | 137 ---- .../code/clean-state-checklist.md | 6 - .../code/index.md | 8 - .../code/victory-detector.ts | 241 ------- .../index.md | 145 ---- .../code/architecture-rules.md | 6 - .../code/e2e-runner.ts | 253 ------- .../code/index.md | 8 - .../code/review-feedback-to-rule.md | 10 - .../index.md | 158 ---- .../code/evaluator-rubric.md | 8 - .../code/index.md | 8 - .../code/runtime-logger.ts | 260 ------- .../code/sprint-contract.md | 12 - .../index.md | 154 ---- .../code/benchmark-comparison-template.md | 18 - .../code/benchmark-runner.ts | 216 ------ .../code/cleanup-loop.md | 9 - .../code/cleanup-scanner.ts | 336 --------- .../code/index.md | 8 - .../index.md | 186 ----- .../docs/uz/projects/index.md | 23 - .../index.md | 25 - .../index.md | 22 - .../index.md | 22 - .../project-04-incremental-indexing/index.md | 22 - .../index.md | 22 - .../index.md | 25 - .../docs/uz/resources/index.md | 50 -- .../uz/resources/openai-advanced/index.md | 73 -- .../openai-advanced/repo-template/AGENTS.md | 55 -- .../repo-template/ARCHITECTURE.md | 55 -- .../repo-template/docs/DESIGN.md | 25 - .../repo-template/docs/FRONTEND.md | 23 - .../repo-template/docs/PLANS.md | 34 - .../repo-template/docs/PRODUCT_SENSE.md | 24 - .../repo-template/docs/QUALITY_SCORE.md | 39 - .../repo-template/docs/RELIABILITY.md | 32 - .../repo-template/docs/SECURITY.md | 27 - .../docs/design-docs/core-beliefs.md | 9 - .../repo-template/docs/design-docs/index.md | 21 - .../docs/exec-plans/active/index.md | 9 - .../docs/exec-plans/completed/index.md | 3 - .../docs/exec-plans/tech-debt-tracker.md | 7 - .../repo-template/docs/generated/db-schema.md | 13 - .../repo-template/docs/product-specs/index.md | 13 - .../docs/product-specs/new-user-onboarding.md | 26 - .../design-system-reference-llms.txt | 9 - .../docs/references/nixpacks-llms.txt | 8 - .../repo-template/docs/references/uv-llms.txt | 8 - .../openai-advanced/repo-template/index.md | 21 - .../sops/chrome-devtools-validation-loop.md | 48 -- .../sops/encode-knowledge-into-repo.md | 42 -- .../resources/openai-advanced/sops/index.md | 19 - .../sops/layered-domain-architecture.md | 48 -- .../sops/observability-feedback-loop.md | 41 -- .../reference/coding-agent-startup-flow.md | 33 - .../docs/uz/resources/reference/index.md | 71 -- .../reference/initializer-agent-playbook.md | 35 - .../docs/uz/resources/reference/method-map.md | 16 - .../resources/reference/prompt-calibration.md | 23 - .../docs/uz/resources/templates/AGENTS.md | 50 -- .../docs/uz/resources/templates/CLAUDE.md | 43 -- .../uz/resources/templates/claude-progress.md | 35 - .../templates/clean-state-checklist.md | 8 - .../resources/templates/evaluator-rubric.md | 24 - .../uz/resources/templates/feature_list.json | 64 -- .../docs/uz/resources/templates/index.md | 226 ------ .../docs/uz/resources/templates/init.sh | 29 - .../resources/templates/quality-document.md | 43 -- .../uz/resources/templates/session-handoff.md | 30 - .../docs/uz/skills/index.md | 49 -- .../docs/vi/index.md | 73 -- .../code/failure-pattern-demo.ts | 170 ----- .../code/failure-signals-checklist.md | 9 - .../code/index.md | 7 - .../code/underspecified-task.md | 18 - .../index.md | 117 --- .../code/harness-components.md | 21 - .../code/harness-vs-no-harness.ts | 180 ----- .../code/index.md | 8 - .../code/minimal-harness-loop.ts | 36 - .../index.md | 109 --- .../code/index.md | 7 - .../code/repo-knowledge-layout.txt | 13 - .../code/repo-reader.ts | 189 ----- .../code/system-of-record-checklist.md | 11 - .../index.md | 137 ---- .../code/AGENTS-short.md | 15 - .../code/anti-patterns.md | 7 - .../code/index.md | 7 - .../code/split-vs-monolithic.ts | 209 ------ .../index.md | 132 ---- .../code/continuity-checklist.md | 6 - .../code/index.md | 7 - .../code/session-handoff.md | 17 - .../code/session-simulator.ts | 162 ----- .../index.md | 180 ----- .../code/index.md | 8 - .../code/init-check.ts | 296 -------- .../code/init.sh | 10 - .../code/initializer-output-checklist.md | 7 - .../index.md | 157 ---- .../code/index.md | 8 - .../code/next-task-template.md | 6 - .../code/scope-surface-example.md | 17 - .../code/scope-tracker.ts | 151 ---- .../index.md | 134 ---- .../code/feature-list-validator.ts | 218 ------ .../code/feature_list.json | 15 - .../code/index.md | 8 - .../code/pass-gate-policy.md | 8 - .../index.md | 137 ---- .../code/clean-state-checklist.md | 6 - .../code/index.md | 8 - .../code/victory-detector.ts | 241 ------- .../index.md | 145 ---- .../code/architecture-rules.md | 6 - .../code/e2e-runner.ts | 253 ------- .../code/index.md | 8 - .../code/review-feedback-to-rule.md | 10 - .../index.md | 158 ---- .../code/evaluator-rubric.md | 8 - .../code/index.md | 8 - .../code/runtime-logger.ts | 260 ------- .../code/sprint-contract.md | 12 - .../index.md | 154 ---- .../code/benchmark-comparison-template.md | 18 - .../code/benchmark-runner.ts | 216 ------ .../code/cleanup-loop.md | 9 - .../code/cleanup-scanner.ts | 336 --------- .../code/index.md | 8 - .../index.md | 186 ----- .../docs/vi/projects/index.md | 23 - .../index.md | 25 - .../index.md | 22 - .../index.md | 22 - .../project-04-incremental-indexing/index.md | 22 - .../index.md | 22 - .../index.md | 25 - .../docs/vi/resources/index.md | 52 -- .../vi/resources/openai-advanced/index.md | 74 -- .../openai-advanced/repo-template/AGENTS.md | 55 -- .../repo-template/ARCHITECTURE.md | 55 -- .../repo-template/docs/DESIGN.md | 25 - .../repo-template/docs/FRONTEND.md | 23 - .../repo-template/docs/PLANS.md | 34 - .../repo-template/docs/PRODUCT_SENSE.md | 24 - .../repo-template/docs/QUALITY_SCORE.md | 39 - .../repo-template/docs/RELIABILITY.md | 32 - .../repo-template/docs/SECURITY.md | 27 - .../docs/design-docs/core-beliefs.md | 9 - .../repo-template/docs/design-docs/index.md | 21 - .../docs/exec-plans/active/index.md | 9 - .../docs/exec-plans/completed/index.md | 3 - .../docs/exec-plans/tech-debt-tracker.md | 7 - .../repo-template/docs/generated/db-schema.md | 13 - .../repo-template/docs/product-specs/index.md | 13 - .../docs/product-specs/new-user-onboarding.md | 26 - .../design-system-reference-llms.txt | 9 - .../docs/references/nixpacks-llms.txt | 8 - .../repo-template/docs/references/uv-llms.txt | 8 - .../openai-advanced/repo-template/index.md | 21 - .../sops/chrome-devtools-validation-loop.md | 48 -- .../sops/encode-knowledge-into-repo.md | 42 -- .../resources/openai-advanced/sops/index.md | 25 - .../sops/layered-domain-architecture.md | 47 -- .../sops/observability-feedback-loop.md | 41 -- .../reference/coding-agent-startup-flow.md | 33 - .../docs/vi/resources/reference/index.md | 19 - .../reference/initializer-agent-playbook.md | 35 - .../docs/vi/resources/reference/method-map.md | 16 - .../resources/reference/prompt-calibration.md | 23 - .../docs/vi/resources/templates/AGENTS.md | 50 -- .../docs/vi/resources/templates/CLAUDE.md | 43 -- .../vi/resources/templates/claude-progress.md | 35 - .../templates/clean-state-checklist.md | 8 - .../resources/templates/evaluator-rubric.md | 24 - .../vi/resources/templates/feature_list.json | 64 -- .../docs/vi/resources/templates/index.md | 15 - .../docs/vi/resources/templates/init.sh | 29 - .../resources/templates/quality-document.md | 43 -- .../vi/resources/templates/session-handoff.md | 30 - .../docs/vi/skills/index.md | 49 -- .../docs/zh-TW/index.md | 73 -- .../code/failure-pattern-demo.ts | 170 ----- .../code/failure-signals-checklist.md | 9 - .../code/index.md | 7 - .../code/underspecified-task.md | 18 - .../index.md | 117 --- .../code/harness-components.md | 21 - .../code/harness-vs-no-harness.ts | 180 ----- .../code/index.md | 8 - .../code/minimal-harness-loop.ts | 36 - .../index.md | 109 --- .../code/index.md | 7 - .../code/repo-knowledge-layout.txt | 13 - .../code/repo-reader.ts | 189 ----- .../code/system-of-record-checklist.md | 11 - .../index.md | 137 ---- .../code/AGENTS-short.md | 14 - .../code/anti-patterns.md | 7 - .../code/index.md | 7 - .../code/split-vs-monolithic.ts | 209 ------ .../index.md | 132 ---- .../code/continuity-checklist.md | 6 - .../code/index.md | 7 - .../code/session-handoff.md | 17 - .../code/session-simulator.ts | 162 ----- .../index.md | 180 ----- .../code/index.md | 8 - .../code/init-check.ts | 296 -------- .../code/init.sh | 10 - .../code/initializer-output-checklist.md | 7 - .../index.md | 157 ---- .../code/index.md | 8 - .../code/next-task-template.md | 6 - .../code/scope-surface-example.md | 17 - .../code/scope-tracker.ts | 151 ---- .../index.md | 134 ---- .../code/feature-list-validator.ts | 218 ------ .../code/feature_list.json | 15 - .../code/index.md | 8 - .../code/pass-gate-policy.md | 8 - .../index.md | 142 ---- .../code/clean-state-checklist.md | 6 - .../code/index.md | 8 - .../code/victory-detector.ts | 241 ------- .../index.md | 145 ---- .../code/architecture-rules.md | 6 - .../code/e2e-runner.ts | 253 ------- .../code/index.md | 8 - .../code/review-feedback-to-rule.md | 10 - .../index.md | 158 ---- .../code/evaluator-rubric.md | 8 - .../code/index.md | 8 - .../code/runtime-logger.ts | 260 ------- .../code/sprint-contract.md | 12 - .../index.md | 173 ----- .../code/benchmark-comparison-template.md | 18 - .../code/benchmark-runner.ts | 216 ------ .../code/cleanup-loop.md | 9 - .../code/cleanup-scanner.ts | 336 --------- .../code/index.md | 8 - .../index.md | 198 ----- .../docs/zh-TW/projects/index.md | 23 - .../index.md | 70 -- .../index.md | 88 --- .../index.md | 77 -- .../project-04-incremental-indexing/index.md | 76 -- .../index.md | 93 --- .../index.md | 101 --- .../docs/zh-TW/resources/index.md | 52 -- .../zh-TW/resources/openai-advanced/index.md | 76 -- .../openai-advanced/repo-template/AGENTS.md | 55 -- .../repo-template/ARCHITECTURE.md | 55 -- .../repo-template/docs/DESIGN.md | 26 - .../repo-template/docs/FRONTEND.md | 23 - .../repo-template/docs/PLANS.md | 34 - .../repo-template/docs/PRODUCT_SENSE.md | 24 - .../repo-template/docs/QUALITY_SCORE.md | 39 - .../repo-template/docs/RELIABILITY.md | 32 - .../repo-template/docs/SECURITY.md | 27 - .../docs/design-docs/core-beliefs.md | 9 - .../repo-template/docs/design-docs/index.md | 21 - .../docs/exec-plans/active/index.md | 9 - .../docs/exec-plans/completed/index.md | 3 - .../docs/exec-plans/tech-debt-tracker.md | 7 - .../repo-template/docs/generated/db-schema.md | 13 - .../repo-template/docs/product-specs/index.md | 13 - .../docs/product-specs/new-user-onboarding.md | 26 - .../design-system-reference-llms.txt | 9 - .../docs/references/nixpacks-llms.txt | 7 - .../repo-template/docs/references/uv-llms.txt | 7 - .../openai-advanced/repo-template/index.md | 22 - .../sops/chrome-devtools-validation-loop.md | 48 -- .../sops/encode-knowledge-into-repo.md | 42 -- .../resources/openai-advanced/sops/index.md | 23 - .../sops/layered-domain-architecture.md | 47 -- .../sops/observability-feedback-loop.md | 42 -- .../reference/coding-agent-startup-flow.md | 33 - .../docs/zh-TW/resources/reference/index.md | 71 -- .../reference/initializer-agent-playbook.md | 35 - .../zh-TW/resources/reference/method-map.md | 16 - .../resources/reference/prompt-calibration.md | 23 - .../docs/zh-TW/resources/templates/AGENTS.md | 50 -- .../docs/zh-TW/resources/templates/CLAUDE.md | 43 -- .../resources/templates/claude-progress.md | 35 - .../templates/clean-state-checklist.md | 8 - .../resources/templates/evaluator-rubric.md | 24 - .../resources/templates/feature_list.json | 64 -- .../docs/zh-TW/resources/templates/index.md | 226 ------ .../docs/zh-TW/resources/templates/init.sh | 29 - .../resources/templates/quality-document.md | 43 -- .../resources/templates/session-handoff.md | 30 - .../docs/zh-TW/skills/index.md | 49 -- .../docs/zh/index.md | 73 -- .../code/failure-pattern-demo.ts | 170 ----- .../code/failure-signals-checklist.md | 9 - .../code/index.md | 7 - .../code/underspecified-task.md | 18 - .../index.md | 117 --- .../code/harness-components.md | 21 - .../code/harness-vs-no-harness.ts | 180 ----- .../code/index.md | 8 - .../code/minimal-harness-loop.ts | 36 - .../index.md | 109 --- .../code/index.md | 7 - .../code/repo-knowledge-layout.txt | 13 - .../code/repo-reader.ts | 189 ----- .../code/system-of-record-checklist.md | 11 - .../index.md | 137 ---- .../code/AGENTS-short.md | 14 - .../code/anti-patterns.md | 7 - .../code/index.md | 7 - .../code/split-vs-monolithic.ts | 209 ------ .../index.md | 132 ---- .../code/continuity-checklist.md | 6 - .../code/index.md | 7 - .../code/session-handoff.md | 17 - .../code/session-simulator.ts | 162 ----- .../index.md | 180 ----- .../code/index.md | 8 - .../code/init-check.ts | 296 -------- .../code/init.sh | 10 - .../code/initializer-output-checklist.md | 7 - .../index.md | 157 ---- .../code/index.md | 8 - .../code/next-task-template.md | 6 - .../code/scope-surface-example.md | 17 - .../code/scope-tracker.ts | 151 ---- .../index.md | 134 ---- .../code/feature-list-validator.ts | 218 ------ .../code/feature_list.json | 15 - .../code/index.md | 8 - .../code/pass-gate-policy.md | 8 - .../index.md | 142 ---- .../code/clean-state-checklist.md | 6 - .../code/index.md | 8 - .../code/victory-detector.ts | 241 ------- .../index.md | 145 ---- .../code/architecture-rules.md | 6 - .../code/e2e-runner.ts | 253 ------- .../code/index.md | 8 - .../code/review-feedback-to-rule.md | 10 - .../index.md | 158 ---- .../code/evaluator-rubric.md | 8 - .../code/index.md | 8 - .../code/runtime-logger.ts | 260 ------- .../code/sprint-contract.md | 12 - .../index.md | 173 ----- .../code/benchmark-comparison-template.md | 18 - .../code/benchmark-runner.ts | 216 ------ .../code/cleanup-loop.md | 9 - .../code/cleanup-scanner.ts | 336 --------- .../code/index.md | 8 - .../index.md | 198 ----- .../docs/zh/projects/index.md | 23 - .../index.md | 70 -- .../index.md | 88 --- .../index.md | 77 -- .../project-04-incremental-indexing/index.md | 76 -- .../index.md | 93 --- .../index.md | 101 --- .../docs/zh/resources/index.md | 52 -- .../zh/resources/openai-advanced/index.md | 76 -- .../openai-advanced/repo-template/AGENTS.md | 55 -- .../repo-template/ARCHITECTURE.md | 55 -- .../repo-template/docs/DESIGN.md | 26 - .../repo-template/docs/FRONTEND.md | 23 - .../repo-template/docs/PLANS.md | 34 - .../repo-template/docs/PRODUCT_SENSE.md | 24 - .../repo-template/docs/QUALITY_SCORE.md | 39 - .../repo-template/docs/RELIABILITY.md | 32 - .../repo-template/docs/SECURITY.md | 27 - .../docs/design-docs/core-beliefs.md | 9 - .../repo-template/docs/design-docs/index.md | 21 - .../docs/exec-plans/active/index.md | 9 - .../docs/exec-plans/completed/index.md | 3 - .../docs/exec-plans/tech-debt-tracker.md | 7 - .../repo-template/docs/generated/db-schema.md | 13 - .../repo-template/docs/product-specs/index.md | 13 - .../docs/product-specs/new-user-onboarding.md | 26 - .../design-system-reference-llms.txt | 9 - .../docs/references/nixpacks-llms.txt | 7 - .../repo-template/docs/references/uv-llms.txt | 7 - .../openai-advanced/repo-template/index.md | 22 - .../sops/chrome-devtools-validation-loop.md | 48 -- .../sops/encode-knowledge-into-repo.md | 42 -- .../resources/openai-advanced/sops/index.md | 23 - .../sops/layered-domain-architecture.md | 47 -- .../sops/observability-feedback-loop.md | 42 -- .../reference/coding-agent-startup-flow.md | 33 - .../docs/zh/resources/reference/index.md | 71 -- .../reference/initializer-agent-playbook.md | 35 - .../docs/zh/resources/reference/method-map.md | 16 - .../resources/reference/prompt-calibration.md | 23 - .../docs/zh/resources/templates/AGENTS.md | 50 -- .../docs/zh/resources/templates/CLAUDE.md | 43 -- .../zh/resources/templates/claude-progress.md | 35 - .../templates/clean-state-checklist.md | 8 - .../resources/templates/evaluator-rubric.md | 24 - .../zh/resources/templates/feature_list.json | 64 -- .../docs/zh/resources/templates/index.md | 226 ------ .../docs/zh/resources/templates/init.sh | 29 - .../resources/templates/quality-document.md | 43 -- .../zh/resources/templates/session-handoff.md | 30 - .../docs/zh/skills/index.md | 49 -- docs/.vitepress/active-course.mjs | 15 +- docs/.vitepress/config.mts | 63 +- docs/.vitepress/course-catalog.mjs | 59 ++ docs/.vitepress/course-content.mjs | 24 +- docs/.vitepress/course-curriculum.mjs | 15 +- docs/.vitepress/course-locales.mjs | 17 +- docs/.vitepress/course-navigation.mjs | 111 +-- docs/.vitepress/course-registry.mjs | 59 ++ docs/.vitepress/course-routes.mjs | 67 ++ docs/.vitepress/course.config.mjs | 10 +- .../theme/components/CourseCatalogHome.vue | 104 +++ .../theme/components/CourseHome.vue | 67 +- docs/.vitepress/theme/index.js | 28 +- docs/.vitepress/theme/style.css | 217 ++++++ docs/index.md | 1 + eslint.config.mjs | 3 +- package.json | 15 +- scripts/course-source-config.mjs | 25 + scripts/new-course.mjs | 13 +- scripts/set-active-course.mjs | 50 +- scripts/sync-course-docs.mjs | 134 ++++ tests/course-branding.test.mjs | 232 ++++-- 1394 files changed, 1186 insertions(+), 83999 deletions(-) delete mode 100644 courses/agent-ops-bootcamp/course.manifest.mjs delete mode 100644 courses/agent-ops-bootcamp/docs/en/index.md delete mode 100644 courses/agent-ops-bootcamp/docs/en/lectures/debug-long-running-workflows/index.md delete mode 100644 courses/agent-ops-bootcamp/docs/en/lectures/operate-agents-with-guardrails/index.md delete mode 100644 courses/agent-ops-bootcamp/docs/en/lectures/ship-with-runtime-feedback/index.md delete mode 100644 courses/agent-ops-bootcamp/docs/en/projects/incident-drill/index.md delete mode 100644 courses/agent-ops-bootcamp/docs/en/projects/index.md delete mode 100644 courses/agent-ops-bootcamp/docs/en/projects/release-checklist/index.md delete mode 100644 courses/agent-ops-bootcamp/docs/en/resources/index.md delete mode 100644 courses/agent-ops-bootcamp/docs/en/resources/openai-advanced/index.md delete mode 100644 courses/agent-ops-bootcamp/docs/en/resources/reference/index.md delete mode 100644 courses/agent-ops-bootcamp/docs/en/resources/templates/index.md delete mode 100644 courses/agent-ops-bootcamp/docs/en/skills/index.md delete mode 100644 courses/agent-ops-bootcamp/docs/index.md delete mode 100644 courses/learn-harness-engineering/course.manifest.mjs delete mode 100644 courses/learn-harness-engineering/docs/ar/index.md delete mode 100644 courses/learn-harness-engineering/docs/ar/lectures/lecture-01-why-capable-agents-still-fail/code/failure-pattern-demo.ts delete mode 100644 courses/learn-harness-engineering/docs/ar/lectures/lecture-01-why-capable-agents-still-fail/code/failure-signals-checklist.md delete mode 100644 courses/learn-harness-engineering/docs/ar/lectures/lecture-01-why-capable-agents-still-fail/code/index.md delete mode 100644 courses/learn-harness-engineering/docs/ar/lectures/lecture-01-why-capable-agents-still-fail/code/underspecified-task.md delete mode 100644 courses/learn-harness-engineering/docs/ar/lectures/lecture-01-why-capable-agents-still-fail/index.md delete mode 100644 courses/learn-harness-engineering/docs/ar/lectures/lecture-02-what-a-harness-actually-is/code/harness-components.md delete mode 100644 courses/learn-harness-engineering/docs/ar/lectures/lecture-02-what-a-harness-actually-is/code/harness-vs-no-harness.ts delete mode 100644 courses/learn-harness-engineering/docs/ar/lectures/lecture-02-what-a-harness-actually-is/code/index.md delete mode 100644 courses/learn-harness-engineering/docs/ar/lectures/lecture-02-what-a-harness-actually-is/code/minimal-harness-loop.ts delete mode 100644 courses/learn-harness-engineering/docs/ar/lectures/lecture-02-what-a-harness-actually-is/index.md delete mode 100644 courses/learn-harness-engineering/docs/ar/lectures/lecture-03-why-the-repository-must-become-the-system-of-record/code/index.md delete mode 100644 courses/learn-harness-engineering/docs/ar/lectures/lecture-03-why-the-repository-must-become-the-system-of-record/code/repo-knowledge-layout.txt delete mode 100644 courses/learn-harness-engineering/docs/ar/lectures/lecture-03-why-the-repository-must-become-the-system-of-record/code/repo-reader.ts delete mode 100644 courses/learn-harness-engineering/docs/ar/lectures/lecture-03-why-the-repository-must-become-the-system-of-record/code/system-of-record-checklist.md delete mode 100644 courses/learn-harness-engineering/docs/ar/lectures/lecture-03-why-the-repository-must-become-the-system-of-record/index.md delete mode 100644 courses/learn-harness-engineering/docs/ar/lectures/lecture-04-why-one-giant-instruction-file-fails/code/AGENTS-short.md delete mode 100644 courses/learn-harness-engineering/docs/ar/lectures/lecture-04-why-one-giant-instruction-file-fails/code/anti-patterns.md delete mode 100644 courses/learn-harness-engineering/docs/ar/lectures/lecture-04-why-one-giant-instruction-file-fails/code/index.md delete mode 100644 courses/learn-harness-engineering/docs/ar/lectures/lecture-04-why-one-giant-instruction-file-fails/code/split-vs-monolithic.ts delete mode 100644 courses/learn-harness-engineering/docs/ar/lectures/lecture-04-why-one-giant-instruction-file-fails/index.md delete mode 100644 courses/learn-harness-engineering/docs/ar/lectures/lecture-05-why-long-running-tasks-lose-continuity/code/continuity-checklist.md delete mode 100644 courses/learn-harness-engineering/docs/ar/lectures/lecture-05-why-long-running-tasks-lose-continuity/code/index.md delete mode 100644 courses/learn-harness-engineering/docs/ar/lectures/lecture-05-why-long-running-tasks-lose-continuity/code/session-handoff.md delete mode 100644 courses/learn-harness-engineering/docs/ar/lectures/lecture-05-why-long-running-tasks-lose-continuity/code/session-simulator.ts delete mode 100644 courses/learn-harness-engineering/docs/ar/lectures/lecture-05-why-long-running-tasks-lose-continuity/index.md delete mode 100644 courses/learn-harness-engineering/docs/ar/lectures/lecture-06-why-initialization-needs-its-own-phase/code/index.md delete mode 100644 courses/learn-harness-engineering/docs/ar/lectures/lecture-06-why-initialization-needs-its-own-phase/code/init-check.ts delete mode 100644 courses/learn-harness-engineering/docs/ar/lectures/lecture-06-why-initialization-needs-its-own-phase/code/init.sh delete mode 100644 courses/learn-harness-engineering/docs/ar/lectures/lecture-06-why-initialization-needs-its-own-phase/code/initializer-output-checklist.md delete mode 100644 courses/learn-harness-engineering/docs/ar/lectures/lecture-06-why-initialization-needs-its-own-phase/index.md delete mode 100644 courses/learn-harness-engineering/docs/ar/lectures/lecture-07-why-agents-overreach-and-under-finish/code/index.md delete mode 100644 courses/learn-harness-engineering/docs/ar/lectures/lecture-07-why-agents-overreach-and-under-finish/code/next-task-template.md delete mode 100644 courses/learn-harness-engineering/docs/ar/lectures/lecture-07-why-agents-overreach-and-under-finish/code/scope-surface-example.md delete mode 100644 courses/learn-harness-engineering/docs/ar/lectures/lecture-07-why-agents-overreach-and-under-finish/code/scope-tracker.ts delete mode 100644 courses/learn-harness-engineering/docs/ar/lectures/lecture-07-why-agents-overreach-and-under-finish/index.md delete mode 100644 courses/learn-harness-engineering/docs/ar/lectures/lecture-08-why-feature-lists-are-harness-primitives/code/feature-list-validator.ts delete mode 100644 courses/learn-harness-engineering/docs/ar/lectures/lecture-08-why-feature-lists-are-harness-primitives/code/feature_list.json delete mode 100644 courses/learn-harness-engineering/docs/ar/lectures/lecture-08-why-feature-lists-are-harness-primitives/code/index.md delete mode 100644 courses/learn-harness-engineering/docs/ar/lectures/lecture-08-why-feature-lists-are-harness-primitives/code/pass-gate-policy.md delete mode 100644 courses/learn-harness-engineering/docs/ar/lectures/lecture-08-why-feature-lists-are-harness-primitives/index.md delete mode 100644 courses/learn-harness-engineering/docs/ar/lectures/lecture-09-why-agents-declare-victory-too-early/code/clean-state-checklist.md delete mode 100644 courses/learn-harness-engineering/docs/ar/lectures/lecture-09-why-agents-declare-victory-too-early/code/index.md delete mode 100644 courses/learn-harness-engineering/docs/ar/lectures/lecture-09-why-agents-declare-victory-too-early/code/victory-detector.ts delete mode 100644 courses/learn-harness-engineering/docs/ar/lectures/lecture-09-why-agents-declare-victory-too-early/index.md delete mode 100644 courses/learn-harness-engineering/docs/ar/lectures/lecture-10-why-end-to-end-testing-changes-results/code/architecture-rules.md delete mode 100644 courses/learn-harness-engineering/docs/ar/lectures/lecture-10-why-end-to-end-testing-changes-results/code/e2e-runner.ts delete mode 100644 courses/learn-harness-engineering/docs/ar/lectures/lecture-10-why-end-to-end-testing-changes-results/code/index.md delete mode 100644 courses/learn-harness-engineering/docs/ar/lectures/lecture-10-why-end-to-end-testing-changes-results/code/review-feedback-to-rule.md delete mode 100644 courses/learn-harness-engineering/docs/ar/lectures/lecture-10-why-end-to-end-testing-changes-results/index.md delete mode 100644 courses/learn-harness-engineering/docs/ar/lectures/lecture-11-why-observability-belongs-inside-the-harness/code/evaluator-rubric.md delete mode 100644 courses/learn-harness-engineering/docs/ar/lectures/lecture-11-why-observability-belongs-inside-the-harness/code/index.md delete mode 100644 courses/learn-harness-engineering/docs/ar/lectures/lecture-11-why-observability-belongs-inside-the-harness/code/runtime-logger.ts delete mode 100644 courses/learn-harness-engineering/docs/ar/lectures/lecture-11-why-observability-belongs-inside-the-harness/code/sprint-contract.md delete mode 100644 courses/learn-harness-engineering/docs/ar/lectures/lecture-11-why-observability-belongs-inside-the-harness/index.md delete mode 100644 courses/learn-harness-engineering/docs/ar/lectures/lecture-12-why-every-session-must-leave-a-clean-state/code/benchmark-comparison-template.md delete mode 100644 courses/learn-harness-engineering/docs/ar/lectures/lecture-12-why-every-session-must-leave-a-clean-state/code/benchmark-runner.ts delete mode 100644 courses/learn-harness-engineering/docs/ar/lectures/lecture-12-why-every-session-must-leave-a-clean-state/code/cleanup-loop.md delete mode 100644 courses/learn-harness-engineering/docs/ar/lectures/lecture-12-why-every-session-must-leave-a-clean-state/code/cleanup-scanner.ts delete mode 100644 courses/learn-harness-engineering/docs/ar/lectures/lecture-12-why-every-session-must-leave-a-clean-state/code/index.md delete mode 100644 courses/learn-harness-engineering/docs/ar/lectures/lecture-12-why-every-session-must-leave-a-clean-state/index.md delete mode 100644 courses/learn-harness-engineering/docs/ar/projects/index.md delete mode 100644 courses/learn-harness-engineering/docs/ar/projects/project-01-baseline-vs-minimal-harness/index.md delete mode 100644 courses/learn-harness-engineering/docs/ar/projects/project-02-agent-readable-workspace/index.md delete mode 100644 courses/learn-harness-engineering/docs/ar/projects/project-03-multi-session-continuity/index.md delete mode 100644 courses/learn-harness-engineering/docs/ar/projects/project-04-incremental-indexing/index.md delete mode 100644 courses/learn-harness-engineering/docs/ar/projects/project-05-grounded-qa-verification/index.md delete mode 100644 courses/learn-harness-engineering/docs/ar/projects/project-06-runtime-observability-and-debugging/index.md delete mode 100644 courses/learn-harness-engineering/docs/ar/resources/index.md delete mode 100644 courses/learn-harness-engineering/docs/ar/resources/openai-advanced/index.md delete mode 100644 courses/learn-harness-engineering/docs/ar/resources/openai-advanced/repo-template/AGENTS.md delete mode 100644 courses/learn-harness-engineering/docs/ar/resources/openai-advanced/repo-template/ARCHITECTURE.md delete mode 100644 courses/learn-harness-engineering/docs/ar/resources/openai-advanced/repo-template/docs/DESIGN.md delete mode 100644 courses/learn-harness-engineering/docs/ar/resources/openai-advanced/repo-template/docs/FRONTEND.md delete mode 100644 courses/learn-harness-engineering/docs/ar/resources/openai-advanced/repo-template/docs/PLANS.md delete mode 100644 courses/learn-harness-engineering/docs/ar/resources/openai-advanced/repo-template/docs/PRODUCT_SENSE.md delete mode 100644 courses/learn-harness-engineering/docs/ar/resources/openai-advanced/repo-template/docs/QUALITY_SCORE.md delete mode 100644 courses/learn-harness-engineering/docs/ar/resources/openai-advanced/repo-template/docs/RELIABILITY.md delete mode 100644 courses/learn-harness-engineering/docs/ar/resources/openai-advanced/repo-template/docs/SECURITY.md delete mode 100644 courses/learn-harness-engineering/docs/ar/resources/openai-advanced/repo-template/docs/design-docs/core-beliefs.md delete mode 100644 courses/learn-harness-engineering/docs/ar/resources/openai-advanced/repo-template/docs/design-docs/index.md delete mode 100644 courses/learn-harness-engineering/docs/ar/resources/openai-advanced/repo-template/docs/exec-plans/active/index.md delete mode 100644 courses/learn-harness-engineering/docs/ar/resources/openai-advanced/repo-template/docs/exec-plans/completed/index.md delete mode 100644 courses/learn-harness-engineering/docs/ar/resources/openai-advanced/repo-template/docs/exec-plans/tech-debt-tracker.md delete mode 100644 courses/learn-harness-engineering/docs/ar/resources/openai-advanced/repo-template/docs/generated/db-schema.md delete mode 100644 courses/learn-harness-engineering/docs/ar/resources/openai-advanced/repo-template/docs/product-specs/index.md delete mode 100644 courses/learn-harness-engineering/docs/ar/resources/openai-advanced/repo-template/docs/product-specs/new-user-onboarding.md delete mode 100644 courses/learn-harness-engineering/docs/ar/resources/openai-advanced/repo-template/docs/references/design-system-reference-llms.txt delete mode 100644 courses/learn-harness-engineering/docs/ar/resources/openai-advanced/repo-template/docs/references/nixpacks-llms.txt delete mode 100644 courses/learn-harness-engineering/docs/ar/resources/openai-advanced/repo-template/docs/references/uv-llms.txt delete mode 100644 courses/learn-harness-engineering/docs/ar/resources/openai-advanced/repo-template/index.md delete mode 100644 courses/learn-harness-engineering/docs/ar/resources/openai-advanced/sops/chrome-devtools-validation-loop.md delete mode 100644 courses/learn-harness-engineering/docs/ar/resources/openai-advanced/sops/encode-knowledge-into-repo.md delete mode 100644 courses/learn-harness-engineering/docs/ar/resources/openai-advanced/sops/index.md delete mode 100644 courses/learn-harness-engineering/docs/ar/resources/openai-advanced/sops/layered-domain-architecture.md delete mode 100644 courses/learn-harness-engineering/docs/ar/resources/openai-advanced/sops/observability-feedback-loop.md delete mode 100644 courses/learn-harness-engineering/docs/ar/resources/reference/coding-agent-startup-flow.md delete mode 100644 courses/learn-harness-engineering/docs/ar/resources/reference/index.md delete mode 100644 courses/learn-harness-engineering/docs/ar/resources/reference/initializer-agent-playbook.md delete mode 100644 courses/learn-harness-engineering/docs/ar/resources/reference/method-map.md delete mode 100644 courses/learn-harness-engineering/docs/ar/resources/reference/prompt-calibration.md delete mode 100644 courses/learn-harness-engineering/docs/ar/resources/templates/AGENTS.md delete mode 100644 courses/learn-harness-engineering/docs/ar/resources/templates/CLAUDE.md delete mode 100644 courses/learn-harness-engineering/docs/ar/resources/templates/claude-progress.md delete mode 100644 courses/learn-harness-engineering/docs/ar/resources/templates/clean-state-checklist.md delete mode 100644 courses/learn-harness-engineering/docs/ar/resources/templates/evaluator-rubric.md delete mode 100644 courses/learn-harness-engineering/docs/ar/resources/templates/feature_list.json delete mode 100644 courses/learn-harness-engineering/docs/ar/resources/templates/index.md delete mode 100755 courses/learn-harness-engineering/docs/ar/resources/templates/init.sh delete mode 100644 courses/learn-harness-engineering/docs/ar/resources/templates/quality-document.md delete mode 100644 courses/learn-harness-engineering/docs/ar/resources/templates/session-handoff.md delete mode 100644 courses/learn-harness-engineering/docs/ar/skills/index.md delete mode 100644 courses/learn-harness-engineering/docs/de/index.md delete mode 100644 courses/learn-harness-engineering/docs/de/lectures/lecture-01-why-capable-agents-still-fail/code/failure-pattern-demo.ts delete mode 100644 courses/learn-harness-engineering/docs/de/lectures/lecture-01-why-capable-agents-still-fail/code/failure-signals-checklist.md delete mode 100644 courses/learn-harness-engineering/docs/de/lectures/lecture-01-why-capable-agents-still-fail/code/index.md delete mode 100644 courses/learn-harness-engineering/docs/de/lectures/lecture-01-why-capable-agents-still-fail/code/underspecified-task.md delete mode 100644 courses/learn-harness-engineering/docs/de/lectures/lecture-01-why-capable-agents-still-fail/index.md delete mode 100644 courses/learn-harness-engineering/docs/de/lectures/lecture-02-what-a-harness-actually-is/code/harness-components.md delete mode 100644 courses/learn-harness-engineering/docs/de/lectures/lecture-02-what-a-harness-actually-is/code/harness-vs-no-harness.ts delete mode 100644 courses/learn-harness-engineering/docs/de/lectures/lecture-02-what-a-harness-actually-is/code/index.md delete mode 100644 courses/learn-harness-engineering/docs/de/lectures/lecture-02-what-a-harness-actually-is/code/minimal-harness-loop.ts delete mode 100644 courses/learn-harness-engineering/docs/de/lectures/lecture-02-what-a-harness-actually-is/index.md delete mode 100644 courses/learn-harness-engineering/docs/de/lectures/lecture-03-why-the-repository-must-become-the-system-of-record/code/index.md delete mode 100644 courses/learn-harness-engineering/docs/de/lectures/lecture-03-why-the-repository-must-become-the-system-of-record/code/repo-knowledge-layout.txt delete mode 100644 courses/learn-harness-engineering/docs/de/lectures/lecture-03-why-the-repository-must-become-the-system-of-record/code/repo-reader.ts delete mode 100644 courses/learn-harness-engineering/docs/de/lectures/lecture-03-why-the-repository-must-become-the-system-of-record/code/system-of-record-checklist.md delete mode 100644 courses/learn-harness-engineering/docs/de/lectures/lecture-03-why-the-repository-must-become-the-system-of-record/index.md delete mode 100644 courses/learn-harness-engineering/docs/de/lectures/lecture-04-why-one-giant-instruction-file-fails/code/AGENTS-short.md delete mode 100644 courses/learn-harness-engineering/docs/de/lectures/lecture-04-why-one-giant-instruction-file-fails/code/anti-patterns.md delete mode 100644 courses/learn-harness-engineering/docs/de/lectures/lecture-04-why-one-giant-instruction-file-fails/code/index.md delete mode 100644 courses/learn-harness-engineering/docs/de/lectures/lecture-04-why-one-giant-instruction-file-fails/code/split-vs-monolithic.ts delete mode 100644 courses/learn-harness-engineering/docs/de/lectures/lecture-04-why-one-giant-instruction-file-fails/index.md delete mode 100644 courses/learn-harness-engineering/docs/de/lectures/lecture-05-why-long-running-tasks-lose-continuity/code/continuity-checklist.md delete mode 100644 courses/learn-harness-engineering/docs/de/lectures/lecture-05-why-long-running-tasks-lose-continuity/code/index.md delete mode 100644 courses/learn-harness-engineering/docs/de/lectures/lecture-05-why-long-running-tasks-lose-continuity/code/session-handoff.md delete mode 100644 courses/learn-harness-engineering/docs/de/lectures/lecture-05-why-long-running-tasks-lose-continuity/code/session-simulator.ts delete mode 100644 courses/learn-harness-engineering/docs/de/lectures/lecture-05-why-long-running-tasks-lose-continuity/index.md delete mode 100644 courses/learn-harness-engineering/docs/de/lectures/lecture-06-why-initialization-needs-its-own-phase/code/index.md delete mode 100644 courses/learn-harness-engineering/docs/de/lectures/lecture-06-why-initialization-needs-its-own-phase/code/init-check.ts delete mode 100644 courses/learn-harness-engineering/docs/de/lectures/lecture-06-why-initialization-needs-its-own-phase/code/init.sh delete mode 100644 courses/learn-harness-engineering/docs/de/lectures/lecture-06-why-initialization-needs-its-own-phase/code/initializer-output-checklist.md delete mode 100644 courses/learn-harness-engineering/docs/de/lectures/lecture-06-why-initialization-needs-its-own-phase/index.md delete mode 100644 courses/learn-harness-engineering/docs/de/lectures/lecture-07-why-agents-overreach-and-under-finish/code/index.md delete mode 100644 courses/learn-harness-engineering/docs/de/lectures/lecture-07-why-agents-overreach-and-under-finish/code/next-task-template.md delete mode 100644 courses/learn-harness-engineering/docs/de/lectures/lecture-07-why-agents-overreach-and-under-finish/code/scope-surface-example.md delete mode 100644 courses/learn-harness-engineering/docs/de/lectures/lecture-07-why-agents-overreach-and-under-finish/code/scope-tracker.ts delete mode 100644 courses/learn-harness-engineering/docs/de/lectures/lecture-07-why-agents-overreach-and-under-finish/index.md delete mode 100644 courses/learn-harness-engineering/docs/de/lectures/lecture-08-why-feature-lists-are-harness-primitives/code/feature-list-validator.ts delete mode 100644 courses/learn-harness-engineering/docs/de/lectures/lecture-08-why-feature-lists-are-harness-primitives/code/feature_list.json delete mode 100644 courses/learn-harness-engineering/docs/de/lectures/lecture-08-why-feature-lists-are-harness-primitives/code/index.md delete mode 100644 courses/learn-harness-engineering/docs/de/lectures/lecture-08-why-feature-lists-are-harness-primitives/code/pass-gate-policy.md delete mode 100644 courses/learn-harness-engineering/docs/de/lectures/lecture-08-why-feature-lists-are-harness-primitives/index.md delete mode 100644 courses/learn-harness-engineering/docs/de/lectures/lecture-09-why-agents-declare-victory-too-early/code/clean-state-checklist.md delete mode 100644 courses/learn-harness-engineering/docs/de/lectures/lecture-09-why-agents-declare-victory-too-early/code/index.md delete mode 100644 courses/learn-harness-engineering/docs/de/lectures/lecture-09-why-agents-declare-victory-too-early/code/victory-detector.ts delete mode 100644 courses/learn-harness-engineering/docs/de/lectures/lecture-09-why-agents-declare-victory-too-early/index.md delete mode 100644 courses/learn-harness-engineering/docs/de/lectures/lecture-10-why-end-to-end-testing-changes-results/code/architecture-rules.md delete mode 100644 courses/learn-harness-engineering/docs/de/lectures/lecture-10-why-end-to-end-testing-changes-results/code/e2e-runner.ts delete mode 100644 courses/learn-harness-engineering/docs/de/lectures/lecture-10-why-end-to-end-testing-changes-results/code/index.md delete mode 100644 courses/learn-harness-engineering/docs/de/lectures/lecture-10-why-end-to-end-testing-changes-results/code/review-feedback-to-rule.md delete mode 100644 courses/learn-harness-engineering/docs/de/lectures/lecture-10-why-end-to-end-testing-changes-results/index.md delete mode 100644 courses/learn-harness-engineering/docs/de/lectures/lecture-11-why-observability-belongs-inside-the-harness/code/evaluator-rubric.md delete mode 100644 courses/learn-harness-engineering/docs/de/lectures/lecture-11-why-observability-belongs-inside-the-harness/code/index.md delete mode 100644 courses/learn-harness-engineering/docs/de/lectures/lecture-11-why-observability-belongs-inside-the-harness/code/runtime-logger.ts delete mode 100644 courses/learn-harness-engineering/docs/de/lectures/lecture-11-why-observability-belongs-inside-the-harness/code/sprint-contract.md delete mode 100644 courses/learn-harness-engineering/docs/de/lectures/lecture-11-why-observability-belongs-inside-the-harness/index.md delete mode 100644 courses/learn-harness-engineering/docs/de/lectures/lecture-12-why-every-session-must-leave-a-clean-state/code/benchmark-comparison-template.md delete mode 100644 courses/learn-harness-engineering/docs/de/lectures/lecture-12-why-every-session-must-leave-a-clean-state/code/benchmark-runner.ts delete mode 100644 courses/learn-harness-engineering/docs/de/lectures/lecture-12-why-every-session-must-leave-a-clean-state/code/cleanup-loop.md delete mode 100644 courses/learn-harness-engineering/docs/de/lectures/lecture-12-why-every-session-must-leave-a-clean-state/code/cleanup-scanner.ts delete mode 100644 courses/learn-harness-engineering/docs/de/lectures/lecture-12-why-every-session-must-leave-a-clean-state/code/index.md delete mode 100644 courses/learn-harness-engineering/docs/de/lectures/lecture-12-why-every-session-must-leave-a-clean-state/index.md delete mode 100644 courses/learn-harness-engineering/docs/de/projects/index.md delete mode 100644 courses/learn-harness-engineering/docs/de/projects/project-01-baseline-vs-minimal-harness/index.md delete mode 100644 courses/learn-harness-engineering/docs/de/projects/project-02-agent-readable-workspace/index.md delete mode 100644 courses/learn-harness-engineering/docs/de/projects/project-03-multi-session-continuity/index.md delete mode 100644 courses/learn-harness-engineering/docs/de/projects/project-04-incremental-indexing/index.md delete mode 100644 courses/learn-harness-engineering/docs/de/projects/project-05-grounded-qa-verification/index.md delete mode 100644 courses/learn-harness-engineering/docs/de/projects/project-06-runtime-observability-and-debugging/index.md delete mode 100644 courses/learn-harness-engineering/docs/de/resources/index.md delete mode 100644 courses/learn-harness-engineering/docs/de/resources/openai-advanced/index.md delete mode 100644 courses/learn-harness-engineering/docs/de/resources/openai-advanced/repo-template/AGENTS.md delete mode 100644 courses/learn-harness-engineering/docs/de/resources/openai-advanced/repo-template/ARCHITECTURE.md delete mode 100644 courses/learn-harness-engineering/docs/de/resources/openai-advanced/repo-template/docs/DESIGN.md delete mode 100644 courses/learn-harness-engineering/docs/de/resources/openai-advanced/repo-template/docs/FRONTEND.md delete mode 100644 courses/learn-harness-engineering/docs/de/resources/openai-advanced/repo-template/docs/PLANS.md delete mode 100644 courses/learn-harness-engineering/docs/de/resources/openai-advanced/repo-template/docs/PRODUCT_SENSE.md delete mode 100644 courses/learn-harness-engineering/docs/de/resources/openai-advanced/repo-template/docs/QUALITY_SCORE.md delete mode 100644 courses/learn-harness-engineering/docs/de/resources/openai-advanced/repo-template/docs/RELIABILITY.md delete mode 100644 courses/learn-harness-engineering/docs/de/resources/openai-advanced/repo-template/docs/SECURITY.md delete mode 100644 courses/learn-harness-engineering/docs/de/resources/openai-advanced/repo-template/docs/design-docs/core-beliefs.md delete mode 100644 courses/learn-harness-engineering/docs/de/resources/openai-advanced/repo-template/docs/design-docs/index.md delete mode 100644 courses/learn-harness-engineering/docs/de/resources/openai-advanced/repo-template/docs/exec-plans/active/index.md delete mode 100644 courses/learn-harness-engineering/docs/de/resources/openai-advanced/repo-template/docs/exec-plans/completed/index.md delete mode 100644 courses/learn-harness-engineering/docs/de/resources/openai-advanced/repo-template/docs/exec-plans/tech-debt-tracker.md delete mode 100644 courses/learn-harness-engineering/docs/de/resources/openai-advanced/repo-template/docs/generated/db-schema.md delete mode 100644 courses/learn-harness-engineering/docs/de/resources/openai-advanced/repo-template/docs/product-specs/index.md delete mode 100644 courses/learn-harness-engineering/docs/de/resources/openai-advanced/repo-template/docs/product-specs/new-user-onboarding.md delete mode 100644 courses/learn-harness-engineering/docs/de/resources/openai-advanced/repo-template/docs/references/design-system-reference-llms.txt delete mode 100644 courses/learn-harness-engineering/docs/de/resources/openai-advanced/repo-template/docs/references/nixpacks-llms.txt delete mode 100644 courses/learn-harness-engineering/docs/de/resources/openai-advanced/repo-template/docs/references/uv-llms.txt delete mode 100644 courses/learn-harness-engineering/docs/de/resources/openai-advanced/repo-template/index.md delete mode 100644 courses/learn-harness-engineering/docs/de/resources/openai-advanced/sops/chrome-devtools-validation-loop.md delete mode 100644 courses/learn-harness-engineering/docs/de/resources/openai-advanced/sops/encode-knowledge-into-repo.md delete mode 100644 courses/learn-harness-engineering/docs/de/resources/openai-advanced/sops/index.md delete mode 100644 courses/learn-harness-engineering/docs/de/resources/openai-advanced/sops/layered-domain-architecture.md delete mode 100644 courses/learn-harness-engineering/docs/de/resources/openai-advanced/sops/observability-feedback-loop.md delete mode 100644 courses/learn-harness-engineering/docs/de/resources/reference/coding-agent-startup-flow.md delete mode 100644 courses/learn-harness-engineering/docs/de/resources/reference/index.md delete mode 100644 courses/learn-harness-engineering/docs/de/resources/reference/initializer-agent-playbook.md delete mode 100644 courses/learn-harness-engineering/docs/de/resources/reference/method-map.md delete mode 100644 courses/learn-harness-engineering/docs/de/resources/reference/prompt-calibration.md delete mode 100644 courses/learn-harness-engineering/docs/de/resources/templates/AGENTS.md delete mode 100644 courses/learn-harness-engineering/docs/de/resources/templates/CLAUDE.md delete mode 100644 courses/learn-harness-engineering/docs/de/resources/templates/claude-progress.md delete mode 100644 courses/learn-harness-engineering/docs/de/resources/templates/clean-state-checklist.md delete mode 100644 courses/learn-harness-engineering/docs/de/resources/templates/evaluator-rubric.md delete mode 100644 courses/learn-harness-engineering/docs/de/resources/templates/feature_list.json delete mode 100644 courses/learn-harness-engineering/docs/de/resources/templates/index.md delete mode 100755 courses/learn-harness-engineering/docs/de/resources/templates/init.sh delete mode 100644 courses/learn-harness-engineering/docs/de/resources/templates/quality-document.md delete mode 100644 courses/learn-harness-engineering/docs/de/resources/templates/session-handoff.md delete mode 100644 courses/learn-harness-engineering/docs/de/skills/index.md delete mode 100644 courses/learn-harness-engineering/docs/en/index.md delete mode 100644 courses/learn-harness-engineering/docs/en/lectures/lecture-01-why-capable-agents-still-fail/code/failure-pattern-demo.ts delete mode 100644 courses/learn-harness-engineering/docs/en/lectures/lecture-01-why-capable-agents-still-fail/code/failure-signals-checklist.md delete mode 100644 courses/learn-harness-engineering/docs/en/lectures/lecture-01-why-capable-agents-still-fail/code/index.md delete mode 100644 courses/learn-harness-engineering/docs/en/lectures/lecture-01-why-capable-agents-still-fail/code/underspecified-task.md delete mode 100644 courses/learn-harness-engineering/docs/en/lectures/lecture-01-why-capable-agents-still-fail/index.md delete mode 100644 courses/learn-harness-engineering/docs/en/lectures/lecture-02-what-a-harness-actually-is/code/harness-components.md delete mode 100644 courses/learn-harness-engineering/docs/en/lectures/lecture-02-what-a-harness-actually-is/code/harness-vs-no-harness.ts delete mode 100644 courses/learn-harness-engineering/docs/en/lectures/lecture-02-what-a-harness-actually-is/code/index.md delete mode 100644 courses/learn-harness-engineering/docs/en/lectures/lecture-02-what-a-harness-actually-is/code/minimal-harness-loop.ts delete mode 100644 courses/learn-harness-engineering/docs/en/lectures/lecture-02-what-a-harness-actually-is/index.md delete mode 100644 courses/learn-harness-engineering/docs/en/lectures/lecture-03-why-the-repository-must-become-the-system-of-record/code/index.md delete mode 100644 courses/learn-harness-engineering/docs/en/lectures/lecture-03-why-the-repository-must-become-the-system-of-record/code/repo-knowledge-layout.txt delete mode 100644 courses/learn-harness-engineering/docs/en/lectures/lecture-03-why-the-repository-must-become-the-system-of-record/code/repo-reader.ts delete mode 100644 courses/learn-harness-engineering/docs/en/lectures/lecture-03-why-the-repository-must-become-the-system-of-record/code/system-of-record-checklist.md delete mode 100644 courses/learn-harness-engineering/docs/en/lectures/lecture-03-why-the-repository-must-become-the-system-of-record/index.md delete mode 100644 courses/learn-harness-engineering/docs/en/lectures/lecture-04-why-one-giant-instruction-file-fails/code/AGENTS-short.md delete mode 100644 courses/learn-harness-engineering/docs/en/lectures/lecture-04-why-one-giant-instruction-file-fails/code/anti-patterns.md delete mode 100644 courses/learn-harness-engineering/docs/en/lectures/lecture-04-why-one-giant-instruction-file-fails/code/index.md delete mode 100644 courses/learn-harness-engineering/docs/en/lectures/lecture-04-why-one-giant-instruction-file-fails/code/split-vs-monolithic.ts delete mode 100644 courses/learn-harness-engineering/docs/en/lectures/lecture-04-why-one-giant-instruction-file-fails/index.md delete mode 100644 courses/learn-harness-engineering/docs/en/lectures/lecture-05-why-long-running-tasks-lose-continuity/code/continuity-checklist.md delete mode 100644 courses/learn-harness-engineering/docs/en/lectures/lecture-05-why-long-running-tasks-lose-continuity/code/index.md delete mode 100644 courses/learn-harness-engineering/docs/en/lectures/lecture-05-why-long-running-tasks-lose-continuity/code/session-handoff.md delete mode 100644 courses/learn-harness-engineering/docs/en/lectures/lecture-05-why-long-running-tasks-lose-continuity/code/session-simulator.ts delete mode 100644 courses/learn-harness-engineering/docs/en/lectures/lecture-05-why-long-running-tasks-lose-continuity/index.md delete mode 100644 courses/learn-harness-engineering/docs/en/lectures/lecture-06-why-initialization-needs-its-own-phase/code/index.md delete mode 100644 courses/learn-harness-engineering/docs/en/lectures/lecture-06-why-initialization-needs-its-own-phase/code/init-check.ts delete mode 100644 courses/learn-harness-engineering/docs/en/lectures/lecture-06-why-initialization-needs-its-own-phase/code/init.sh delete mode 100644 courses/learn-harness-engineering/docs/en/lectures/lecture-06-why-initialization-needs-its-own-phase/code/initializer-output-checklist.md delete mode 100644 courses/learn-harness-engineering/docs/en/lectures/lecture-06-why-initialization-needs-its-own-phase/index.md delete mode 100644 courses/learn-harness-engineering/docs/en/lectures/lecture-07-why-agents-overreach-and-under-finish/code/index.md delete mode 100644 courses/learn-harness-engineering/docs/en/lectures/lecture-07-why-agents-overreach-and-under-finish/code/next-task-template.md delete mode 100644 courses/learn-harness-engineering/docs/en/lectures/lecture-07-why-agents-overreach-and-under-finish/code/scope-surface-example.md delete mode 100644 courses/learn-harness-engineering/docs/en/lectures/lecture-07-why-agents-overreach-and-under-finish/code/scope-tracker.ts delete mode 100644 courses/learn-harness-engineering/docs/en/lectures/lecture-07-why-agents-overreach-and-under-finish/index.md delete mode 100644 courses/learn-harness-engineering/docs/en/lectures/lecture-08-why-feature-lists-are-harness-primitives/code/feature-list-validator.ts delete mode 100644 courses/learn-harness-engineering/docs/en/lectures/lecture-08-why-feature-lists-are-harness-primitives/code/feature_list.json delete mode 100644 courses/learn-harness-engineering/docs/en/lectures/lecture-08-why-feature-lists-are-harness-primitives/code/index.md delete mode 100644 courses/learn-harness-engineering/docs/en/lectures/lecture-08-why-feature-lists-are-harness-primitives/code/pass-gate-policy.md delete mode 100644 courses/learn-harness-engineering/docs/en/lectures/lecture-08-why-feature-lists-are-harness-primitives/index.md delete mode 100644 courses/learn-harness-engineering/docs/en/lectures/lecture-09-why-agents-declare-victory-too-early/code/clean-state-checklist.md delete mode 100644 courses/learn-harness-engineering/docs/en/lectures/lecture-09-why-agents-declare-victory-too-early/code/index.md delete mode 100644 courses/learn-harness-engineering/docs/en/lectures/lecture-09-why-agents-declare-victory-too-early/code/victory-detector.ts delete mode 100644 courses/learn-harness-engineering/docs/en/lectures/lecture-09-why-agents-declare-victory-too-early/index.md delete mode 100644 courses/learn-harness-engineering/docs/en/lectures/lecture-10-why-end-to-end-testing-changes-results/code/architecture-rules.md delete mode 100644 courses/learn-harness-engineering/docs/en/lectures/lecture-10-why-end-to-end-testing-changes-results/code/e2e-runner.ts delete mode 100644 courses/learn-harness-engineering/docs/en/lectures/lecture-10-why-end-to-end-testing-changes-results/code/index.md delete mode 100644 courses/learn-harness-engineering/docs/en/lectures/lecture-10-why-end-to-end-testing-changes-results/code/review-feedback-to-rule.md delete mode 100644 courses/learn-harness-engineering/docs/en/lectures/lecture-10-why-end-to-end-testing-changes-results/index.md delete mode 100644 courses/learn-harness-engineering/docs/en/lectures/lecture-11-why-observability-belongs-inside-the-harness/code/evaluator-rubric.md delete mode 100644 courses/learn-harness-engineering/docs/en/lectures/lecture-11-why-observability-belongs-inside-the-harness/code/index.md delete mode 100644 courses/learn-harness-engineering/docs/en/lectures/lecture-11-why-observability-belongs-inside-the-harness/code/runtime-logger.ts delete mode 100644 courses/learn-harness-engineering/docs/en/lectures/lecture-11-why-observability-belongs-inside-the-harness/code/sprint-contract.md delete mode 100644 courses/learn-harness-engineering/docs/en/lectures/lecture-11-why-observability-belongs-inside-the-harness/index.md delete mode 100644 courses/learn-harness-engineering/docs/en/lectures/lecture-12-why-every-session-must-leave-a-clean-state/code/benchmark-comparison-template.md delete mode 100644 courses/learn-harness-engineering/docs/en/lectures/lecture-12-why-every-session-must-leave-a-clean-state/code/benchmark-runner.ts delete mode 100644 courses/learn-harness-engineering/docs/en/lectures/lecture-12-why-every-session-must-leave-a-clean-state/code/cleanup-loop.md delete mode 100644 courses/learn-harness-engineering/docs/en/lectures/lecture-12-why-every-session-must-leave-a-clean-state/code/cleanup-scanner.ts delete mode 100644 courses/learn-harness-engineering/docs/en/lectures/lecture-12-why-every-session-must-leave-a-clean-state/code/index.md delete mode 100644 courses/learn-harness-engineering/docs/en/lectures/lecture-12-why-every-session-must-leave-a-clean-state/index.md delete mode 100644 courses/learn-harness-engineering/docs/en/projects/index.md delete mode 100644 courses/learn-harness-engineering/docs/en/projects/project-01-baseline-vs-minimal-harness/index.md delete mode 100644 courses/learn-harness-engineering/docs/en/projects/project-02-agent-readable-workspace/index.md delete mode 100644 courses/learn-harness-engineering/docs/en/projects/project-03-multi-session-continuity/index.md delete mode 100644 courses/learn-harness-engineering/docs/en/projects/project-04-incremental-indexing/index.md delete mode 100644 courses/learn-harness-engineering/docs/en/projects/project-05-grounded-qa-verification/index.md delete mode 100644 courses/learn-harness-engineering/docs/en/projects/project-06-runtime-observability-and-debugging/index.md delete mode 100644 courses/learn-harness-engineering/docs/en/resources/index.md delete mode 100644 courses/learn-harness-engineering/docs/en/resources/openai-advanced/index.md delete mode 100644 courses/learn-harness-engineering/docs/en/resources/openai-advanced/repo-template/AGENTS.md delete mode 100644 courses/learn-harness-engineering/docs/en/resources/openai-advanced/repo-template/ARCHITECTURE.md delete mode 100644 courses/learn-harness-engineering/docs/en/resources/openai-advanced/repo-template/docs/DESIGN.md delete mode 100644 courses/learn-harness-engineering/docs/en/resources/openai-advanced/repo-template/docs/FRONTEND.md delete mode 100644 courses/learn-harness-engineering/docs/en/resources/openai-advanced/repo-template/docs/PLANS.md delete mode 100644 courses/learn-harness-engineering/docs/en/resources/openai-advanced/repo-template/docs/PRODUCT_SENSE.md delete mode 100644 courses/learn-harness-engineering/docs/en/resources/openai-advanced/repo-template/docs/QUALITY_SCORE.md delete mode 100644 courses/learn-harness-engineering/docs/en/resources/openai-advanced/repo-template/docs/RELIABILITY.md delete mode 100644 courses/learn-harness-engineering/docs/en/resources/openai-advanced/repo-template/docs/SECURITY.md delete mode 100644 courses/learn-harness-engineering/docs/en/resources/openai-advanced/repo-template/docs/design-docs/core-beliefs.md delete mode 100644 courses/learn-harness-engineering/docs/en/resources/openai-advanced/repo-template/docs/design-docs/index.md delete mode 100644 courses/learn-harness-engineering/docs/en/resources/openai-advanced/repo-template/docs/exec-plans/active/index.md delete mode 100644 courses/learn-harness-engineering/docs/en/resources/openai-advanced/repo-template/docs/exec-plans/completed/index.md delete mode 100644 courses/learn-harness-engineering/docs/en/resources/openai-advanced/repo-template/docs/exec-plans/tech-debt-tracker.md delete mode 100644 courses/learn-harness-engineering/docs/en/resources/openai-advanced/repo-template/docs/generated/db-schema.md delete mode 100644 courses/learn-harness-engineering/docs/en/resources/openai-advanced/repo-template/docs/product-specs/index.md delete mode 100644 courses/learn-harness-engineering/docs/en/resources/openai-advanced/repo-template/docs/product-specs/new-user-onboarding.md delete mode 100644 courses/learn-harness-engineering/docs/en/resources/openai-advanced/repo-template/docs/references/design-system-reference-llms.txt delete mode 100644 courses/learn-harness-engineering/docs/en/resources/openai-advanced/repo-template/docs/references/nixpacks-llms.txt delete mode 100644 courses/learn-harness-engineering/docs/en/resources/openai-advanced/repo-template/docs/references/uv-llms.txt delete mode 100644 courses/learn-harness-engineering/docs/en/resources/openai-advanced/repo-template/index.md delete mode 100644 courses/learn-harness-engineering/docs/en/resources/openai-advanced/sops/chrome-devtools-validation-loop.md delete mode 100644 courses/learn-harness-engineering/docs/en/resources/openai-advanced/sops/encode-knowledge-into-repo.md delete mode 100644 courses/learn-harness-engineering/docs/en/resources/openai-advanced/sops/index.md delete mode 100644 courses/learn-harness-engineering/docs/en/resources/openai-advanced/sops/layered-domain-architecture.md delete mode 100644 courses/learn-harness-engineering/docs/en/resources/openai-advanced/sops/observability-feedback-loop.md delete mode 100644 courses/learn-harness-engineering/docs/en/resources/reference/coding-agent-startup-flow.md delete mode 100644 courses/learn-harness-engineering/docs/en/resources/reference/index.md delete mode 100644 courses/learn-harness-engineering/docs/en/resources/reference/initializer-agent-playbook.md delete mode 100644 courses/learn-harness-engineering/docs/en/resources/reference/method-map.md delete mode 100644 courses/learn-harness-engineering/docs/en/resources/reference/prompt-calibration.md delete mode 100644 courses/learn-harness-engineering/docs/en/resources/templates/AGENTS.md delete mode 100644 courses/learn-harness-engineering/docs/en/resources/templates/CLAUDE.md delete mode 100644 courses/learn-harness-engineering/docs/en/resources/templates/claude-progress.md delete mode 100644 courses/learn-harness-engineering/docs/en/resources/templates/clean-state-checklist.md delete mode 100644 courses/learn-harness-engineering/docs/en/resources/templates/evaluator-rubric.md delete mode 100644 courses/learn-harness-engineering/docs/en/resources/templates/feature_list.json delete mode 100644 courses/learn-harness-engineering/docs/en/resources/templates/index.md delete mode 100755 courses/learn-harness-engineering/docs/en/resources/templates/init.sh delete mode 100644 courses/learn-harness-engineering/docs/en/resources/templates/quality-document.md delete mode 100644 courses/learn-harness-engineering/docs/en/resources/templates/session-handoff.md delete mode 100644 courses/learn-harness-engineering/docs/en/skills/index.md delete mode 100644 courses/learn-harness-engineering/docs/es/index.md delete mode 100644 courses/learn-harness-engineering/docs/es/lectures/lecture-01-why-capable-agents-still-fail/code/failure-pattern-demo.ts delete mode 100644 courses/learn-harness-engineering/docs/es/lectures/lecture-01-why-capable-agents-still-fail/code/failure-signals-checklist.md delete mode 100644 courses/learn-harness-engineering/docs/es/lectures/lecture-01-why-capable-agents-still-fail/code/index.md delete mode 100644 courses/learn-harness-engineering/docs/es/lectures/lecture-01-why-capable-agents-still-fail/code/underspecified-task.md delete mode 100644 courses/learn-harness-engineering/docs/es/lectures/lecture-01-why-capable-agents-still-fail/index.md delete mode 100644 courses/learn-harness-engineering/docs/es/lectures/lecture-02-what-a-harness-actually-is/code/harness-components.md delete mode 100644 courses/learn-harness-engineering/docs/es/lectures/lecture-02-what-a-harness-actually-is/code/harness-vs-no-harness.ts delete mode 100644 courses/learn-harness-engineering/docs/es/lectures/lecture-02-what-a-harness-actually-is/code/index.md delete mode 100644 courses/learn-harness-engineering/docs/es/lectures/lecture-02-what-a-harness-actually-is/code/minimal-harness-loop.ts delete mode 100644 courses/learn-harness-engineering/docs/es/lectures/lecture-02-what-a-harness-actually-is/index.md delete mode 100644 courses/learn-harness-engineering/docs/es/lectures/lecture-03-why-the-repository-must-become-the-system-of-record/code/index.md delete mode 100644 courses/learn-harness-engineering/docs/es/lectures/lecture-03-why-the-repository-must-become-the-system-of-record/code/repo-knowledge-layout.txt delete mode 100644 courses/learn-harness-engineering/docs/es/lectures/lecture-03-why-the-repository-must-become-the-system-of-record/code/repo-reader.ts delete mode 100644 courses/learn-harness-engineering/docs/es/lectures/lecture-03-why-the-repository-must-become-the-system-of-record/code/system-of-record-checklist.md delete mode 100644 courses/learn-harness-engineering/docs/es/lectures/lecture-03-why-the-repository-must-become-the-system-of-record/index.md delete mode 100644 courses/learn-harness-engineering/docs/es/lectures/lecture-04-why-one-giant-instruction-file-fails/code/AGENTS-short.md delete mode 100644 courses/learn-harness-engineering/docs/es/lectures/lecture-04-why-one-giant-instruction-file-fails/code/anti-patterns.md delete mode 100644 courses/learn-harness-engineering/docs/es/lectures/lecture-04-why-one-giant-instruction-file-fails/code/index.md delete mode 100644 courses/learn-harness-engineering/docs/es/lectures/lecture-04-why-one-giant-instruction-file-fails/code/split-vs-monolithic.ts delete mode 100644 courses/learn-harness-engineering/docs/es/lectures/lecture-04-why-one-giant-instruction-file-fails/index.md delete mode 100644 courses/learn-harness-engineering/docs/es/lectures/lecture-05-why-long-running-tasks-lose-continuity/code/continuity-checklist.md delete mode 100644 courses/learn-harness-engineering/docs/es/lectures/lecture-05-why-long-running-tasks-lose-continuity/code/index.md delete mode 100644 courses/learn-harness-engineering/docs/es/lectures/lecture-05-why-long-running-tasks-lose-continuity/code/session-handoff.md delete mode 100644 courses/learn-harness-engineering/docs/es/lectures/lecture-05-why-long-running-tasks-lose-continuity/code/session-simulator.ts delete mode 100644 courses/learn-harness-engineering/docs/es/lectures/lecture-05-why-long-running-tasks-lose-continuity/index.md delete mode 100644 courses/learn-harness-engineering/docs/es/lectures/lecture-06-why-initialization-needs-its-own-phase/code/index.md delete mode 100644 courses/learn-harness-engineering/docs/es/lectures/lecture-06-why-initialization-needs-its-own-phase/code/init-check.ts delete mode 100644 courses/learn-harness-engineering/docs/es/lectures/lecture-06-why-initialization-needs-its-own-phase/code/init.sh delete mode 100644 courses/learn-harness-engineering/docs/es/lectures/lecture-06-why-initialization-needs-its-own-phase/code/initializer-output-checklist.md delete mode 100644 courses/learn-harness-engineering/docs/es/lectures/lecture-06-why-initialization-needs-its-own-phase/index.md delete mode 100644 courses/learn-harness-engineering/docs/es/lectures/lecture-07-why-agents-overreach-and-under-finish/code/index.md delete mode 100644 courses/learn-harness-engineering/docs/es/lectures/lecture-07-why-agents-overreach-and-under-finish/code/next-task-template.md delete mode 100644 courses/learn-harness-engineering/docs/es/lectures/lecture-07-why-agents-overreach-and-under-finish/code/scope-surface-example.md delete mode 100644 courses/learn-harness-engineering/docs/es/lectures/lecture-07-why-agents-overreach-and-under-finish/code/scope-tracker.ts delete mode 100644 courses/learn-harness-engineering/docs/es/lectures/lecture-07-why-agents-overreach-and-under-finish/index.md delete mode 100644 courses/learn-harness-engineering/docs/es/lectures/lecture-08-why-feature-lists-are-harness-primitives/code/feature-list-validator.ts delete mode 100644 courses/learn-harness-engineering/docs/es/lectures/lecture-08-why-feature-lists-are-harness-primitives/code/feature_list.json delete mode 100644 courses/learn-harness-engineering/docs/es/lectures/lecture-08-why-feature-lists-are-harness-primitives/code/index.md delete mode 100644 courses/learn-harness-engineering/docs/es/lectures/lecture-08-why-feature-lists-are-harness-primitives/code/pass-gate-policy.md delete mode 100644 courses/learn-harness-engineering/docs/es/lectures/lecture-08-why-feature-lists-are-harness-primitives/index.md delete mode 100644 courses/learn-harness-engineering/docs/es/lectures/lecture-09-why-agents-declare-victory-too-early/code/clean-state-checklist.md delete mode 100644 courses/learn-harness-engineering/docs/es/lectures/lecture-09-why-agents-declare-victory-too-early/code/index.md delete mode 100644 courses/learn-harness-engineering/docs/es/lectures/lecture-09-why-agents-declare-victory-too-early/code/victory-detector.ts delete mode 100644 courses/learn-harness-engineering/docs/es/lectures/lecture-09-why-agents-declare-victory-too-early/index.md delete mode 100644 courses/learn-harness-engineering/docs/es/lectures/lecture-10-why-end-to-end-testing-changes-results/code/architecture-rules.md delete mode 100644 courses/learn-harness-engineering/docs/es/lectures/lecture-10-why-end-to-end-testing-changes-results/code/e2e-runner.ts delete mode 100644 courses/learn-harness-engineering/docs/es/lectures/lecture-10-why-end-to-end-testing-changes-results/code/index.md delete mode 100644 courses/learn-harness-engineering/docs/es/lectures/lecture-10-why-end-to-end-testing-changes-results/code/review-feedback-to-rule.md delete mode 100644 courses/learn-harness-engineering/docs/es/lectures/lecture-10-why-end-to-end-testing-changes-results/index.md delete mode 100644 courses/learn-harness-engineering/docs/es/lectures/lecture-11-why-observability-belongs-inside-the-harness/code/evaluator-rubric.md delete mode 100644 courses/learn-harness-engineering/docs/es/lectures/lecture-11-why-observability-belongs-inside-the-harness/code/index.md delete mode 100644 courses/learn-harness-engineering/docs/es/lectures/lecture-11-why-observability-belongs-inside-the-harness/code/runtime-logger.ts delete mode 100644 courses/learn-harness-engineering/docs/es/lectures/lecture-11-why-observability-belongs-inside-the-harness/code/sprint-contract.md delete mode 100644 courses/learn-harness-engineering/docs/es/lectures/lecture-11-why-observability-belongs-inside-the-harness/index.md delete mode 100644 courses/learn-harness-engineering/docs/es/lectures/lecture-12-why-every-session-must-leave-a-clean-state/code/benchmark-comparison-template.md delete mode 100644 courses/learn-harness-engineering/docs/es/lectures/lecture-12-why-every-session-must-leave-a-clean-state/code/benchmark-runner.ts delete mode 100644 courses/learn-harness-engineering/docs/es/lectures/lecture-12-why-every-session-must-leave-a-clean-state/code/cleanup-loop.md delete mode 100644 courses/learn-harness-engineering/docs/es/lectures/lecture-12-why-every-session-must-leave-a-clean-state/code/cleanup-scanner.ts delete mode 100644 courses/learn-harness-engineering/docs/es/lectures/lecture-12-why-every-session-must-leave-a-clean-state/code/index.md delete mode 100644 courses/learn-harness-engineering/docs/es/lectures/lecture-12-why-every-session-must-leave-a-clean-state/index.md delete mode 100644 courses/learn-harness-engineering/docs/es/projects/index.md delete mode 100644 courses/learn-harness-engineering/docs/es/projects/project-01-baseline-vs-minimal-harness/index.md delete mode 100644 courses/learn-harness-engineering/docs/es/projects/project-02-agent-readable-workspace/index.md delete mode 100644 courses/learn-harness-engineering/docs/es/projects/project-03-multi-session-continuity/index.md delete mode 100644 courses/learn-harness-engineering/docs/es/projects/project-04-incremental-indexing/index.md delete mode 100644 courses/learn-harness-engineering/docs/es/projects/project-05-grounded-qa-verification/index.md delete mode 100644 courses/learn-harness-engineering/docs/es/projects/project-06-runtime-observability-and-debugging/index.md delete mode 100644 courses/learn-harness-engineering/docs/es/resources/index.md delete mode 100644 courses/learn-harness-engineering/docs/es/resources/openai-advanced/index.md delete mode 100644 courses/learn-harness-engineering/docs/es/resources/openai-advanced/repo-template/AGENTS.md delete mode 100644 courses/learn-harness-engineering/docs/es/resources/openai-advanced/repo-template/ARCHITECTURE.md delete mode 100644 courses/learn-harness-engineering/docs/es/resources/openai-advanced/repo-template/docs/DESIGN.md delete mode 100644 courses/learn-harness-engineering/docs/es/resources/openai-advanced/repo-template/docs/FRONTEND.md delete mode 100644 courses/learn-harness-engineering/docs/es/resources/openai-advanced/repo-template/docs/PLANS.md delete mode 100644 courses/learn-harness-engineering/docs/es/resources/openai-advanced/repo-template/docs/PRODUCT_SENSE.md delete mode 100644 courses/learn-harness-engineering/docs/es/resources/openai-advanced/repo-template/docs/QUALITY_SCORE.md delete mode 100644 courses/learn-harness-engineering/docs/es/resources/openai-advanced/repo-template/docs/RELIABILITY.md delete mode 100644 courses/learn-harness-engineering/docs/es/resources/openai-advanced/repo-template/docs/SECURITY.md delete mode 100644 courses/learn-harness-engineering/docs/es/resources/openai-advanced/repo-template/docs/design-docs/core-beliefs.md delete mode 100644 courses/learn-harness-engineering/docs/es/resources/openai-advanced/repo-template/docs/design-docs/index.md delete mode 100644 courses/learn-harness-engineering/docs/es/resources/openai-advanced/repo-template/docs/exec-plans/active/index.md delete mode 100644 courses/learn-harness-engineering/docs/es/resources/openai-advanced/repo-template/docs/exec-plans/completed/index.md delete mode 100644 courses/learn-harness-engineering/docs/es/resources/openai-advanced/repo-template/docs/exec-plans/tech-debt-tracker.md delete mode 100644 courses/learn-harness-engineering/docs/es/resources/openai-advanced/repo-template/docs/generated/db-schema.md delete mode 100644 courses/learn-harness-engineering/docs/es/resources/openai-advanced/repo-template/docs/product-specs/index.md delete mode 100644 courses/learn-harness-engineering/docs/es/resources/openai-advanced/repo-template/docs/product-specs/new-user-onboarding.md delete mode 100644 courses/learn-harness-engineering/docs/es/resources/openai-advanced/repo-template/docs/references/design-system-reference-llms.txt delete mode 100644 courses/learn-harness-engineering/docs/es/resources/openai-advanced/repo-template/docs/references/nixpacks-llms.txt delete mode 100644 courses/learn-harness-engineering/docs/es/resources/openai-advanced/repo-template/docs/references/uv-llms.txt delete mode 100644 courses/learn-harness-engineering/docs/es/resources/openai-advanced/repo-template/index.md delete mode 100644 courses/learn-harness-engineering/docs/es/resources/openai-advanced/sops/chrome-devtools-validation-loop.md delete mode 100644 courses/learn-harness-engineering/docs/es/resources/openai-advanced/sops/encode-knowledge-into-repo.md delete mode 100644 courses/learn-harness-engineering/docs/es/resources/openai-advanced/sops/index.md delete mode 100644 courses/learn-harness-engineering/docs/es/resources/openai-advanced/sops/layered-domain-architecture.md delete mode 100644 courses/learn-harness-engineering/docs/es/resources/openai-advanced/sops/observability-feedback-loop.md delete mode 100644 courses/learn-harness-engineering/docs/es/resources/reference/coding-agent-startup-flow.md delete mode 100644 courses/learn-harness-engineering/docs/es/resources/reference/index.md delete mode 100644 courses/learn-harness-engineering/docs/es/resources/reference/initializer-agent-playbook.md delete mode 100644 courses/learn-harness-engineering/docs/es/resources/reference/method-map.md delete mode 100644 courses/learn-harness-engineering/docs/es/resources/reference/prompt-calibration.md delete mode 100644 courses/learn-harness-engineering/docs/es/resources/templates/AGENTS.md delete mode 100644 courses/learn-harness-engineering/docs/es/resources/templates/CLAUDE.md delete mode 100644 courses/learn-harness-engineering/docs/es/resources/templates/claude-progress.md delete mode 100644 courses/learn-harness-engineering/docs/es/resources/templates/clean-state-checklist.md delete mode 100644 courses/learn-harness-engineering/docs/es/resources/templates/evaluator-rubric.md delete mode 100644 courses/learn-harness-engineering/docs/es/resources/templates/feature_list.json delete mode 100644 courses/learn-harness-engineering/docs/es/resources/templates/index.md delete mode 100755 courses/learn-harness-engineering/docs/es/resources/templates/init.sh delete mode 100644 courses/learn-harness-engineering/docs/es/resources/templates/quality-document.md delete mode 100644 courses/learn-harness-engineering/docs/es/resources/templates/session-handoff.md delete mode 100644 courses/learn-harness-engineering/docs/es/skills/index.md delete mode 100644 courses/learn-harness-engineering/docs/fr/index.md delete mode 100644 courses/learn-harness-engineering/docs/fr/lectures/lecture-01-why-capable-agents-still-fail/code/failure-pattern-demo.ts delete mode 100644 courses/learn-harness-engineering/docs/fr/lectures/lecture-01-why-capable-agents-still-fail/code/failure-signals-checklist.md delete mode 100644 courses/learn-harness-engineering/docs/fr/lectures/lecture-01-why-capable-agents-still-fail/code/index.md delete mode 100644 courses/learn-harness-engineering/docs/fr/lectures/lecture-01-why-capable-agents-still-fail/code/underspecified-task.md delete mode 100644 courses/learn-harness-engineering/docs/fr/lectures/lecture-01-why-capable-agents-still-fail/index.md delete mode 100644 courses/learn-harness-engineering/docs/fr/lectures/lecture-02-what-a-harness-actually-is/code/harness-components.md delete mode 100644 courses/learn-harness-engineering/docs/fr/lectures/lecture-02-what-a-harness-actually-is/code/harness-vs-no-harness.ts delete mode 100644 courses/learn-harness-engineering/docs/fr/lectures/lecture-02-what-a-harness-actually-is/code/index.md delete mode 100644 courses/learn-harness-engineering/docs/fr/lectures/lecture-02-what-a-harness-actually-is/code/minimal-harness-loop.ts delete mode 100644 courses/learn-harness-engineering/docs/fr/lectures/lecture-02-what-a-harness-actually-is/index.md delete mode 100644 courses/learn-harness-engineering/docs/fr/lectures/lecture-03-why-the-repository-must-become-the-system-of-record/code/index.md delete mode 100644 courses/learn-harness-engineering/docs/fr/lectures/lecture-03-why-the-repository-must-become-the-system-of-record/code/repo-knowledge-layout.txt delete mode 100644 courses/learn-harness-engineering/docs/fr/lectures/lecture-03-why-the-repository-must-become-the-system-of-record/code/repo-reader.ts delete mode 100644 courses/learn-harness-engineering/docs/fr/lectures/lecture-03-why-the-repository-must-become-the-system-of-record/code/system-of-record-checklist.md delete mode 100644 courses/learn-harness-engineering/docs/fr/lectures/lecture-03-why-the-repository-must-become-the-system-of-record/index.md delete mode 100644 courses/learn-harness-engineering/docs/fr/lectures/lecture-04-why-one-giant-instruction-file-fails/code/AGENTS-short.md delete mode 100644 courses/learn-harness-engineering/docs/fr/lectures/lecture-04-why-one-giant-instruction-file-fails/code/anti-patterns.md delete mode 100644 courses/learn-harness-engineering/docs/fr/lectures/lecture-04-why-one-giant-instruction-file-fails/code/index.md delete mode 100644 courses/learn-harness-engineering/docs/fr/lectures/lecture-04-why-one-giant-instruction-file-fails/code/split-vs-monolithic.ts delete mode 100644 courses/learn-harness-engineering/docs/fr/lectures/lecture-04-why-one-giant-instruction-file-fails/index.md delete mode 100644 courses/learn-harness-engineering/docs/fr/lectures/lecture-05-why-long-running-tasks-lose-continuity/code/continuity-checklist.md delete mode 100644 courses/learn-harness-engineering/docs/fr/lectures/lecture-05-why-long-running-tasks-lose-continuity/code/index.md delete mode 100644 courses/learn-harness-engineering/docs/fr/lectures/lecture-05-why-long-running-tasks-lose-continuity/code/session-handoff.md delete mode 100644 courses/learn-harness-engineering/docs/fr/lectures/lecture-05-why-long-running-tasks-lose-continuity/code/session-simulator.ts delete mode 100644 courses/learn-harness-engineering/docs/fr/lectures/lecture-05-why-long-running-tasks-lose-continuity/index.md delete mode 100644 courses/learn-harness-engineering/docs/fr/lectures/lecture-06-why-initialization-needs-its-own-phase/code/index.md delete mode 100644 courses/learn-harness-engineering/docs/fr/lectures/lecture-06-why-initialization-needs-its-own-phase/code/init-check.ts delete mode 100644 courses/learn-harness-engineering/docs/fr/lectures/lecture-06-why-initialization-needs-its-own-phase/code/init.sh delete mode 100644 courses/learn-harness-engineering/docs/fr/lectures/lecture-06-why-initialization-needs-its-own-phase/code/initializer-output-checklist.md delete mode 100644 courses/learn-harness-engineering/docs/fr/lectures/lecture-06-why-initialization-needs-its-own-phase/index.md delete mode 100644 courses/learn-harness-engineering/docs/fr/lectures/lecture-07-why-agents-overreach-and-under-finish/code/index.md delete mode 100644 courses/learn-harness-engineering/docs/fr/lectures/lecture-07-why-agents-overreach-and-under-finish/code/next-task-template.md delete mode 100644 courses/learn-harness-engineering/docs/fr/lectures/lecture-07-why-agents-overreach-and-under-finish/code/scope-surface-example.md delete mode 100644 courses/learn-harness-engineering/docs/fr/lectures/lecture-07-why-agents-overreach-and-under-finish/code/scope-tracker.ts delete mode 100644 courses/learn-harness-engineering/docs/fr/lectures/lecture-07-why-agents-overreach-and-under-finish/index.md delete mode 100644 courses/learn-harness-engineering/docs/fr/lectures/lecture-08-why-feature-lists-are-harness-primitives/code/feature-list-validator.ts delete mode 100644 courses/learn-harness-engineering/docs/fr/lectures/lecture-08-why-feature-lists-are-harness-primitives/code/feature_list.json delete mode 100644 courses/learn-harness-engineering/docs/fr/lectures/lecture-08-why-feature-lists-are-harness-primitives/code/index.md delete mode 100644 courses/learn-harness-engineering/docs/fr/lectures/lecture-08-why-feature-lists-are-harness-primitives/code/pass-gate-policy.md delete mode 100644 courses/learn-harness-engineering/docs/fr/lectures/lecture-08-why-feature-lists-are-harness-primitives/index.md delete mode 100644 courses/learn-harness-engineering/docs/fr/lectures/lecture-09-why-agents-declare-victory-too-early/code/clean-state-checklist.md delete mode 100644 courses/learn-harness-engineering/docs/fr/lectures/lecture-09-why-agents-declare-victory-too-early/code/index.md delete mode 100644 courses/learn-harness-engineering/docs/fr/lectures/lecture-09-why-agents-declare-victory-too-early/code/victory-detector.ts delete mode 100644 courses/learn-harness-engineering/docs/fr/lectures/lecture-09-why-agents-declare-victory-too-early/index.md delete mode 100644 courses/learn-harness-engineering/docs/fr/lectures/lecture-10-why-end-to-end-testing-changes-results/code/architecture-rules.md delete mode 100644 courses/learn-harness-engineering/docs/fr/lectures/lecture-10-why-end-to-end-testing-changes-results/code/e2e-runner.ts delete mode 100644 courses/learn-harness-engineering/docs/fr/lectures/lecture-10-why-end-to-end-testing-changes-results/code/index.md delete mode 100644 courses/learn-harness-engineering/docs/fr/lectures/lecture-10-why-end-to-end-testing-changes-results/code/review-feedback-to-rule.md delete mode 100644 courses/learn-harness-engineering/docs/fr/lectures/lecture-10-why-end-to-end-testing-changes-results/index.md delete mode 100644 courses/learn-harness-engineering/docs/fr/lectures/lecture-11-why-observability-belongs-inside-the-harness/code/evaluator-rubric.md delete mode 100644 courses/learn-harness-engineering/docs/fr/lectures/lecture-11-why-observability-belongs-inside-the-harness/code/index.md delete mode 100644 courses/learn-harness-engineering/docs/fr/lectures/lecture-11-why-observability-belongs-inside-the-harness/code/runtime-logger.ts delete mode 100644 courses/learn-harness-engineering/docs/fr/lectures/lecture-11-why-observability-belongs-inside-the-harness/code/sprint-contract.md delete mode 100644 courses/learn-harness-engineering/docs/fr/lectures/lecture-11-why-observability-belongs-inside-the-harness/index.md delete mode 100644 courses/learn-harness-engineering/docs/fr/lectures/lecture-12-why-every-session-must-leave-a-clean-state/code/benchmark-comparison-template.md delete mode 100644 courses/learn-harness-engineering/docs/fr/lectures/lecture-12-why-every-session-must-leave-a-clean-state/code/benchmark-runner.ts delete mode 100644 courses/learn-harness-engineering/docs/fr/lectures/lecture-12-why-every-session-must-leave-a-clean-state/code/cleanup-loop.md delete mode 100644 courses/learn-harness-engineering/docs/fr/lectures/lecture-12-why-every-session-must-leave-a-clean-state/code/cleanup-scanner.ts delete mode 100644 courses/learn-harness-engineering/docs/fr/lectures/lecture-12-why-every-session-must-leave-a-clean-state/code/index.md delete mode 100644 courses/learn-harness-engineering/docs/fr/lectures/lecture-12-why-every-session-must-leave-a-clean-state/index.md delete mode 100644 courses/learn-harness-engineering/docs/fr/projects/index.md delete mode 100644 courses/learn-harness-engineering/docs/fr/projects/project-01-baseline-vs-minimal-harness/index.md delete mode 100644 courses/learn-harness-engineering/docs/fr/projects/project-02-agent-readable-workspace/index.md delete mode 100644 courses/learn-harness-engineering/docs/fr/projects/project-03-multi-session-continuity/index.md delete mode 100644 courses/learn-harness-engineering/docs/fr/projects/project-04-incremental-indexing/index.md delete mode 100644 courses/learn-harness-engineering/docs/fr/projects/project-05-grounded-qa-verification/index.md delete mode 100644 courses/learn-harness-engineering/docs/fr/projects/project-06-runtime-observability-and-debugging/index.md delete mode 100644 courses/learn-harness-engineering/docs/fr/resources/index.md delete mode 100644 courses/learn-harness-engineering/docs/fr/resources/openai-advanced/index.md delete mode 100644 courses/learn-harness-engineering/docs/fr/resources/openai-advanced/repo-template/AGENTS.md delete mode 100644 courses/learn-harness-engineering/docs/fr/resources/openai-advanced/repo-template/ARCHITECTURE.md delete mode 100644 courses/learn-harness-engineering/docs/fr/resources/openai-advanced/repo-template/docs/DESIGN.md delete mode 100644 courses/learn-harness-engineering/docs/fr/resources/openai-advanced/repo-template/docs/FRONTEND.md delete mode 100644 courses/learn-harness-engineering/docs/fr/resources/openai-advanced/repo-template/docs/PLANS.md delete mode 100644 courses/learn-harness-engineering/docs/fr/resources/openai-advanced/repo-template/docs/PRODUCT_SENSE.md delete mode 100644 courses/learn-harness-engineering/docs/fr/resources/openai-advanced/repo-template/docs/QUALITY_SCORE.md delete mode 100644 courses/learn-harness-engineering/docs/fr/resources/openai-advanced/repo-template/docs/RELIABILITY.md delete mode 100644 courses/learn-harness-engineering/docs/fr/resources/openai-advanced/repo-template/docs/SECURITY.md delete mode 100644 courses/learn-harness-engineering/docs/fr/resources/openai-advanced/repo-template/docs/design-docs/core-beliefs.md delete mode 100644 courses/learn-harness-engineering/docs/fr/resources/openai-advanced/repo-template/docs/design-docs/index.md delete mode 100644 courses/learn-harness-engineering/docs/fr/resources/openai-advanced/repo-template/docs/exec-plans/active/index.md delete mode 100644 courses/learn-harness-engineering/docs/fr/resources/openai-advanced/repo-template/docs/exec-plans/completed/index.md delete mode 100644 courses/learn-harness-engineering/docs/fr/resources/openai-advanced/repo-template/docs/exec-plans/tech-debt-tracker.md delete mode 100644 courses/learn-harness-engineering/docs/fr/resources/openai-advanced/repo-template/docs/generated/db-schema.md delete mode 100644 courses/learn-harness-engineering/docs/fr/resources/openai-advanced/repo-template/docs/product-specs/index.md delete mode 100644 courses/learn-harness-engineering/docs/fr/resources/openai-advanced/repo-template/docs/product-specs/new-user-onboarding.md delete mode 100644 courses/learn-harness-engineering/docs/fr/resources/openai-advanced/repo-template/docs/references/design-system-reference-llms.txt delete mode 100644 courses/learn-harness-engineering/docs/fr/resources/openai-advanced/repo-template/docs/references/nixpacks-llms.txt delete mode 100644 courses/learn-harness-engineering/docs/fr/resources/openai-advanced/repo-template/docs/references/uv-llms.txt delete mode 100644 courses/learn-harness-engineering/docs/fr/resources/openai-advanced/repo-template/index.md delete mode 100644 courses/learn-harness-engineering/docs/fr/resources/openai-advanced/sops/chrome-devtools-validation-loop.md delete mode 100644 courses/learn-harness-engineering/docs/fr/resources/openai-advanced/sops/encode-knowledge-into-repo.md delete mode 100644 courses/learn-harness-engineering/docs/fr/resources/openai-advanced/sops/index.md delete mode 100644 courses/learn-harness-engineering/docs/fr/resources/openai-advanced/sops/layered-domain-architecture.md delete mode 100644 courses/learn-harness-engineering/docs/fr/resources/openai-advanced/sops/observability-feedback-loop.md delete mode 100644 courses/learn-harness-engineering/docs/fr/resources/reference/coding-agent-startup-flow.md delete mode 100644 courses/learn-harness-engineering/docs/fr/resources/reference/index.md delete mode 100644 courses/learn-harness-engineering/docs/fr/resources/reference/initializer-agent-playbook.md delete mode 100644 courses/learn-harness-engineering/docs/fr/resources/reference/method-map.md delete mode 100644 courses/learn-harness-engineering/docs/fr/resources/reference/prompt-calibration.md delete mode 100644 courses/learn-harness-engineering/docs/fr/resources/templates/AGENTS.md delete mode 100644 courses/learn-harness-engineering/docs/fr/resources/templates/CLAUDE.md delete mode 100644 courses/learn-harness-engineering/docs/fr/resources/templates/claude-progress.md delete mode 100644 courses/learn-harness-engineering/docs/fr/resources/templates/clean-state-checklist.md delete mode 100644 courses/learn-harness-engineering/docs/fr/resources/templates/evaluator-rubric.md delete mode 100644 courses/learn-harness-engineering/docs/fr/resources/templates/feature_list.json delete mode 100644 courses/learn-harness-engineering/docs/fr/resources/templates/index.md delete mode 100755 courses/learn-harness-engineering/docs/fr/resources/templates/init.sh delete mode 100644 courses/learn-harness-engineering/docs/fr/resources/templates/quality-document.md delete mode 100644 courses/learn-harness-engineering/docs/fr/resources/templates/session-handoff.md delete mode 100644 courses/learn-harness-engineering/docs/fr/skills/index.md delete mode 100644 courses/learn-harness-engineering/docs/index.md delete mode 100644 courses/learn-harness-engineering/docs/ja/index.md delete mode 100644 courses/learn-harness-engineering/docs/ja/lectures/lecture-01-why-capable-agents-still-fail/code/failure-pattern-demo.ts delete mode 100644 courses/learn-harness-engineering/docs/ja/lectures/lecture-01-why-capable-agents-still-fail/code/failure-signals-checklist.md delete mode 100644 courses/learn-harness-engineering/docs/ja/lectures/lecture-01-why-capable-agents-still-fail/code/index.md delete mode 100644 courses/learn-harness-engineering/docs/ja/lectures/lecture-01-why-capable-agents-still-fail/code/underspecified-task.md delete mode 100644 courses/learn-harness-engineering/docs/ja/lectures/lecture-01-why-capable-agents-still-fail/index.md delete mode 100644 courses/learn-harness-engineering/docs/ja/lectures/lecture-02-what-a-harness-actually-is/code/harness-components.md delete mode 100644 courses/learn-harness-engineering/docs/ja/lectures/lecture-02-what-a-harness-actually-is/code/harness-vs-no-harness.ts delete mode 100644 courses/learn-harness-engineering/docs/ja/lectures/lecture-02-what-a-harness-actually-is/code/index.md delete mode 100644 courses/learn-harness-engineering/docs/ja/lectures/lecture-02-what-a-harness-actually-is/code/minimal-harness-loop.ts delete mode 100644 courses/learn-harness-engineering/docs/ja/lectures/lecture-02-what-a-harness-actually-is/index.md delete mode 100644 courses/learn-harness-engineering/docs/ja/lectures/lecture-03-why-the-repository-must-become-the-system-of-record/code/index.md delete mode 100644 courses/learn-harness-engineering/docs/ja/lectures/lecture-03-why-the-repository-must-become-the-system-of-record/code/repo-knowledge-layout.txt delete mode 100644 courses/learn-harness-engineering/docs/ja/lectures/lecture-03-why-the-repository-must-become-the-system-of-record/code/repo-reader.ts delete mode 100644 courses/learn-harness-engineering/docs/ja/lectures/lecture-03-why-the-repository-must-become-the-system-of-record/code/system-of-record-checklist.md delete mode 100644 courses/learn-harness-engineering/docs/ja/lectures/lecture-03-why-the-repository-must-become-the-system-of-record/index.md delete mode 100644 courses/learn-harness-engineering/docs/ja/lectures/lecture-04-why-one-giant-instruction-file-fails/code/AGENTS-short.md delete mode 100644 courses/learn-harness-engineering/docs/ja/lectures/lecture-04-why-one-giant-instruction-file-fails/code/anti-patterns.md delete mode 100644 courses/learn-harness-engineering/docs/ja/lectures/lecture-04-why-one-giant-instruction-file-fails/code/index.md delete mode 100644 courses/learn-harness-engineering/docs/ja/lectures/lecture-04-why-one-giant-instruction-file-fails/code/split-vs-monolithic.ts delete mode 100644 courses/learn-harness-engineering/docs/ja/lectures/lecture-04-why-one-giant-instruction-file-fails/index.md delete mode 100644 courses/learn-harness-engineering/docs/ja/lectures/lecture-05-why-long-running-tasks-lose-continuity/code/continuity-checklist.md delete mode 100644 courses/learn-harness-engineering/docs/ja/lectures/lecture-05-why-long-running-tasks-lose-continuity/code/index.md delete mode 100644 courses/learn-harness-engineering/docs/ja/lectures/lecture-05-why-long-running-tasks-lose-continuity/code/session-handoff.md delete mode 100644 courses/learn-harness-engineering/docs/ja/lectures/lecture-05-why-long-running-tasks-lose-continuity/code/session-simulator.ts delete mode 100644 courses/learn-harness-engineering/docs/ja/lectures/lecture-05-why-long-running-tasks-lose-continuity/index.md delete mode 100644 courses/learn-harness-engineering/docs/ja/lectures/lecture-06-why-initialization-needs-its-own-phase/code/index.md delete mode 100644 courses/learn-harness-engineering/docs/ja/lectures/lecture-06-why-initialization-needs-its-own-phase/code/init-check.ts delete mode 100644 courses/learn-harness-engineering/docs/ja/lectures/lecture-06-why-initialization-needs-its-own-phase/code/init.sh delete mode 100644 courses/learn-harness-engineering/docs/ja/lectures/lecture-06-why-initialization-needs-its-own-phase/code/initializer-output-checklist.md delete mode 100644 courses/learn-harness-engineering/docs/ja/lectures/lecture-06-why-initialization-needs-its-own-phase/index.md delete mode 100644 courses/learn-harness-engineering/docs/ja/lectures/lecture-07-why-agents-overreach-and-under-finish/code/index.md delete mode 100644 courses/learn-harness-engineering/docs/ja/lectures/lecture-07-why-agents-overreach-and-under-finish/code/next-task-template.md delete mode 100644 courses/learn-harness-engineering/docs/ja/lectures/lecture-07-why-agents-overreach-and-under-finish/code/scope-surface-example.md delete mode 100644 courses/learn-harness-engineering/docs/ja/lectures/lecture-07-why-agents-overreach-and-under-finish/code/scope-tracker.ts delete mode 100644 courses/learn-harness-engineering/docs/ja/lectures/lecture-07-why-agents-overreach-and-under-finish/index.md delete mode 100644 courses/learn-harness-engineering/docs/ja/lectures/lecture-08-why-feature-lists-are-harness-primitives/code/feature-list-validator.ts delete mode 100644 courses/learn-harness-engineering/docs/ja/lectures/lecture-08-why-feature-lists-are-harness-primitives/code/feature_list.json delete mode 100644 courses/learn-harness-engineering/docs/ja/lectures/lecture-08-why-feature-lists-are-harness-primitives/code/index.md delete mode 100644 courses/learn-harness-engineering/docs/ja/lectures/lecture-08-why-feature-lists-are-harness-primitives/code/pass-gate-policy.md delete mode 100644 courses/learn-harness-engineering/docs/ja/lectures/lecture-08-why-feature-lists-are-harness-primitives/index.md delete mode 100644 courses/learn-harness-engineering/docs/ja/lectures/lecture-09-why-agents-declare-victory-too-early/code/clean-state-checklist.md delete mode 100644 courses/learn-harness-engineering/docs/ja/lectures/lecture-09-why-agents-declare-victory-too-early/code/index.md delete mode 100644 courses/learn-harness-engineering/docs/ja/lectures/lecture-09-why-agents-declare-victory-too-early/code/victory-detector.ts delete mode 100644 courses/learn-harness-engineering/docs/ja/lectures/lecture-09-why-agents-declare-victory-too-early/index.md delete mode 100644 courses/learn-harness-engineering/docs/ja/lectures/lecture-10-why-end-to-end-testing-changes-results/code/architecture-rules.md delete mode 100644 courses/learn-harness-engineering/docs/ja/lectures/lecture-10-why-end-to-end-testing-changes-results/code/e2e-runner.ts delete mode 100644 courses/learn-harness-engineering/docs/ja/lectures/lecture-10-why-end-to-end-testing-changes-results/code/index.md delete mode 100644 courses/learn-harness-engineering/docs/ja/lectures/lecture-10-why-end-to-end-testing-changes-results/code/review-feedback-to-rule.md delete mode 100644 courses/learn-harness-engineering/docs/ja/lectures/lecture-10-why-end-to-end-testing-changes-results/index.md delete mode 100644 courses/learn-harness-engineering/docs/ja/lectures/lecture-11-why-observability-belongs-inside-the-harness/code/evaluator-rubric.md delete mode 100644 courses/learn-harness-engineering/docs/ja/lectures/lecture-11-why-observability-belongs-inside-the-harness/code/index.md delete mode 100644 courses/learn-harness-engineering/docs/ja/lectures/lecture-11-why-observability-belongs-inside-the-harness/code/runtime-logger.ts delete mode 100644 courses/learn-harness-engineering/docs/ja/lectures/lecture-11-why-observability-belongs-inside-the-harness/code/sprint-contract.md delete mode 100644 courses/learn-harness-engineering/docs/ja/lectures/lecture-11-why-observability-belongs-inside-the-harness/index.md delete mode 100644 courses/learn-harness-engineering/docs/ja/lectures/lecture-12-why-every-session-must-leave-a-clean-state/code/benchmark-comparison-template.md delete mode 100644 courses/learn-harness-engineering/docs/ja/lectures/lecture-12-why-every-session-must-leave-a-clean-state/code/benchmark-runner.ts delete mode 100644 courses/learn-harness-engineering/docs/ja/lectures/lecture-12-why-every-session-must-leave-a-clean-state/code/cleanup-loop.md delete mode 100644 courses/learn-harness-engineering/docs/ja/lectures/lecture-12-why-every-session-must-leave-a-clean-state/code/cleanup-scanner.ts delete mode 100644 courses/learn-harness-engineering/docs/ja/lectures/lecture-12-why-every-session-must-leave-a-clean-state/code/index.md delete mode 100644 courses/learn-harness-engineering/docs/ja/lectures/lecture-12-why-every-session-must-leave-a-clean-state/index.md delete mode 100644 courses/learn-harness-engineering/docs/ja/projects/index.md delete mode 100644 courses/learn-harness-engineering/docs/ja/projects/project-01-baseline-vs-minimal-harness/index.md delete mode 100644 courses/learn-harness-engineering/docs/ja/projects/project-02-agent-readable-workspace/index.md delete mode 100644 courses/learn-harness-engineering/docs/ja/projects/project-03-multi-session-continuity/index.md delete mode 100644 courses/learn-harness-engineering/docs/ja/projects/project-04-incremental-indexing/index.md delete mode 100644 courses/learn-harness-engineering/docs/ja/projects/project-05-grounded-qa-verification/index.md delete mode 100644 courses/learn-harness-engineering/docs/ja/projects/project-06-runtime-observability-and-debugging/index.md delete mode 100644 courses/learn-harness-engineering/docs/ja/resources/index.md delete mode 100644 courses/learn-harness-engineering/docs/ja/resources/openai-advanced/index.md delete mode 100644 courses/learn-harness-engineering/docs/ja/resources/openai-advanced/repo-template/AGENTS.md delete mode 100644 courses/learn-harness-engineering/docs/ja/resources/openai-advanced/repo-template/ARCHITECTURE.md delete mode 100644 courses/learn-harness-engineering/docs/ja/resources/openai-advanced/repo-template/docs/DESIGN.md delete mode 100644 courses/learn-harness-engineering/docs/ja/resources/openai-advanced/repo-template/docs/FRONTEND.md delete mode 100644 courses/learn-harness-engineering/docs/ja/resources/openai-advanced/repo-template/docs/PLANS.md delete mode 100644 courses/learn-harness-engineering/docs/ja/resources/openai-advanced/repo-template/docs/PRODUCT_SENSE.md delete mode 100644 courses/learn-harness-engineering/docs/ja/resources/openai-advanced/repo-template/docs/QUALITY_SCORE.md delete mode 100644 courses/learn-harness-engineering/docs/ja/resources/openai-advanced/repo-template/docs/RELIABILITY.md delete mode 100644 courses/learn-harness-engineering/docs/ja/resources/openai-advanced/repo-template/docs/SECURITY.md delete mode 100644 courses/learn-harness-engineering/docs/ja/resources/openai-advanced/repo-template/docs/design-docs/core-beliefs.md delete mode 100644 courses/learn-harness-engineering/docs/ja/resources/openai-advanced/repo-template/docs/design-docs/index.md delete mode 100644 courses/learn-harness-engineering/docs/ja/resources/openai-advanced/repo-template/docs/exec-plans/active/index.md delete mode 100644 courses/learn-harness-engineering/docs/ja/resources/openai-advanced/repo-template/docs/exec-plans/completed/index.md delete mode 100644 courses/learn-harness-engineering/docs/ja/resources/openai-advanced/repo-template/docs/exec-plans/tech-debt-tracker.md delete mode 100644 courses/learn-harness-engineering/docs/ja/resources/openai-advanced/repo-template/docs/generated/db-schema.md delete mode 100644 courses/learn-harness-engineering/docs/ja/resources/openai-advanced/repo-template/docs/product-specs/index.md delete mode 100644 courses/learn-harness-engineering/docs/ja/resources/openai-advanced/repo-template/docs/product-specs/new-user-onboarding.md delete mode 100644 courses/learn-harness-engineering/docs/ja/resources/openai-advanced/repo-template/docs/references/design-system-reference-llms.txt delete mode 100644 courses/learn-harness-engineering/docs/ja/resources/openai-advanced/repo-template/docs/references/nixpacks-llms.txt delete mode 100644 courses/learn-harness-engineering/docs/ja/resources/openai-advanced/repo-template/docs/references/uv-llms.txt delete mode 100644 courses/learn-harness-engineering/docs/ja/resources/openai-advanced/repo-template/index.md delete mode 100644 courses/learn-harness-engineering/docs/ja/resources/openai-advanced/sops/chrome-devtools-validation-loop.md delete mode 100644 courses/learn-harness-engineering/docs/ja/resources/openai-advanced/sops/encode-knowledge-into-repo.md delete mode 100644 courses/learn-harness-engineering/docs/ja/resources/openai-advanced/sops/index.md delete mode 100644 courses/learn-harness-engineering/docs/ja/resources/openai-advanced/sops/layered-domain-architecture.md delete mode 100644 courses/learn-harness-engineering/docs/ja/resources/openai-advanced/sops/observability-feedback-loop.md delete mode 100644 courses/learn-harness-engineering/docs/ja/resources/reference/coding-agent-startup-flow.md delete mode 100644 courses/learn-harness-engineering/docs/ja/resources/reference/index.md delete mode 100644 courses/learn-harness-engineering/docs/ja/resources/reference/initializer-agent-playbook.md delete mode 100644 courses/learn-harness-engineering/docs/ja/resources/reference/method-map.md delete mode 100644 courses/learn-harness-engineering/docs/ja/resources/reference/prompt-calibration.md delete mode 100644 courses/learn-harness-engineering/docs/ja/resources/templates/AGENTS.md delete mode 100644 courses/learn-harness-engineering/docs/ja/resources/templates/CLAUDE.md delete mode 100644 courses/learn-harness-engineering/docs/ja/resources/templates/claude-progress.md delete mode 100644 courses/learn-harness-engineering/docs/ja/resources/templates/clean-state-checklist.md delete mode 100644 courses/learn-harness-engineering/docs/ja/resources/templates/evaluator-rubric.md delete mode 100644 courses/learn-harness-engineering/docs/ja/resources/templates/feature_list.json delete mode 100644 courses/learn-harness-engineering/docs/ja/resources/templates/index.md delete mode 100755 courses/learn-harness-engineering/docs/ja/resources/templates/init.sh delete mode 100644 courses/learn-harness-engineering/docs/ja/resources/templates/quality-document.md delete mode 100644 courses/learn-harness-engineering/docs/ja/resources/templates/session-handoff.md delete mode 100644 courses/learn-harness-engineering/docs/ja/skills/index.md delete mode 100644 courses/learn-harness-engineering/docs/ko/index.md delete mode 100644 courses/learn-harness-engineering/docs/ko/lectures/lecture-01-why-capable-agents-still-fail/code/failure-pattern-demo.ts delete mode 100644 courses/learn-harness-engineering/docs/ko/lectures/lecture-01-why-capable-agents-still-fail/code/failure-signals-checklist.md delete mode 100644 courses/learn-harness-engineering/docs/ko/lectures/lecture-01-why-capable-agents-still-fail/code/index.md delete mode 100644 courses/learn-harness-engineering/docs/ko/lectures/lecture-01-why-capable-agents-still-fail/code/underspecified-task.md delete mode 100644 courses/learn-harness-engineering/docs/ko/lectures/lecture-01-why-capable-agents-still-fail/index.md delete mode 100644 courses/learn-harness-engineering/docs/ko/lectures/lecture-02-what-a-harness-actually-is/code/harness-components.md delete mode 100644 courses/learn-harness-engineering/docs/ko/lectures/lecture-02-what-a-harness-actually-is/code/harness-vs-no-harness.ts delete mode 100644 courses/learn-harness-engineering/docs/ko/lectures/lecture-02-what-a-harness-actually-is/code/index.md delete mode 100644 courses/learn-harness-engineering/docs/ko/lectures/lecture-02-what-a-harness-actually-is/code/minimal-harness-loop.ts delete mode 100644 courses/learn-harness-engineering/docs/ko/lectures/lecture-02-what-a-harness-actually-is/index.md delete mode 100644 courses/learn-harness-engineering/docs/ko/lectures/lecture-03-why-the-repository-must-become-the-system-of-record/code/index.md delete mode 100644 courses/learn-harness-engineering/docs/ko/lectures/lecture-03-why-the-repository-must-become-the-system-of-record/code/repo-knowledge-layout.txt delete mode 100644 courses/learn-harness-engineering/docs/ko/lectures/lecture-03-why-the-repository-must-become-the-system-of-record/code/repo-reader.ts delete mode 100644 courses/learn-harness-engineering/docs/ko/lectures/lecture-03-why-the-repository-must-become-the-system-of-record/code/system-of-record-checklist.md delete mode 100644 courses/learn-harness-engineering/docs/ko/lectures/lecture-03-why-the-repository-must-become-the-system-of-record/index.md delete mode 100644 courses/learn-harness-engineering/docs/ko/lectures/lecture-04-why-one-giant-instruction-file-fails/code/AGENTS-short.md delete mode 100644 courses/learn-harness-engineering/docs/ko/lectures/lecture-04-why-one-giant-instruction-file-fails/code/anti-patterns.md delete mode 100644 courses/learn-harness-engineering/docs/ko/lectures/lecture-04-why-one-giant-instruction-file-fails/code/index.md delete mode 100644 courses/learn-harness-engineering/docs/ko/lectures/lecture-04-why-one-giant-instruction-file-fails/code/split-vs-monolithic.ts delete mode 100644 courses/learn-harness-engineering/docs/ko/lectures/lecture-04-why-one-giant-instruction-file-fails/index.md delete mode 100644 courses/learn-harness-engineering/docs/ko/lectures/lecture-05-why-long-running-tasks-lose-continuity/code/continuity-checklist.md delete mode 100644 courses/learn-harness-engineering/docs/ko/lectures/lecture-05-why-long-running-tasks-lose-continuity/code/index.md delete mode 100644 courses/learn-harness-engineering/docs/ko/lectures/lecture-05-why-long-running-tasks-lose-continuity/code/session-handoff.md delete mode 100644 courses/learn-harness-engineering/docs/ko/lectures/lecture-05-why-long-running-tasks-lose-continuity/code/session-simulator.ts delete mode 100644 courses/learn-harness-engineering/docs/ko/lectures/lecture-05-why-long-running-tasks-lose-continuity/index.md delete mode 100644 courses/learn-harness-engineering/docs/ko/lectures/lecture-06-why-initialization-needs-its-own-phase/code/index.md delete mode 100644 courses/learn-harness-engineering/docs/ko/lectures/lecture-06-why-initialization-needs-its-own-phase/code/init-check.ts delete mode 100644 courses/learn-harness-engineering/docs/ko/lectures/lecture-06-why-initialization-needs-its-own-phase/code/init.sh delete mode 100644 courses/learn-harness-engineering/docs/ko/lectures/lecture-06-why-initialization-needs-its-own-phase/code/initializer-output-checklist.md delete mode 100644 courses/learn-harness-engineering/docs/ko/lectures/lecture-06-why-initialization-needs-its-own-phase/index.md delete mode 100644 courses/learn-harness-engineering/docs/ko/lectures/lecture-07-why-agents-overreach-and-under-finish/code/index.md delete mode 100644 courses/learn-harness-engineering/docs/ko/lectures/lecture-07-why-agents-overreach-and-under-finish/code/next-task-template.md delete mode 100644 courses/learn-harness-engineering/docs/ko/lectures/lecture-07-why-agents-overreach-and-under-finish/code/scope-surface-example.md delete mode 100644 courses/learn-harness-engineering/docs/ko/lectures/lecture-07-why-agents-overreach-and-under-finish/code/scope-tracker.ts delete mode 100644 courses/learn-harness-engineering/docs/ko/lectures/lecture-07-why-agents-overreach-and-under-finish/index.md delete mode 100644 courses/learn-harness-engineering/docs/ko/lectures/lecture-08-why-feature-lists-are-harness-primitives/code/feature-list-validator.ts delete mode 100644 courses/learn-harness-engineering/docs/ko/lectures/lecture-08-why-feature-lists-are-harness-primitives/code/feature_list.json delete mode 100644 courses/learn-harness-engineering/docs/ko/lectures/lecture-08-why-feature-lists-are-harness-primitives/code/index.md delete mode 100644 courses/learn-harness-engineering/docs/ko/lectures/lecture-08-why-feature-lists-are-harness-primitives/code/pass-gate-policy.md delete mode 100644 courses/learn-harness-engineering/docs/ko/lectures/lecture-08-why-feature-lists-are-harness-primitives/index.md delete mode 100644 courses/learn-harness-engineering/docs/ko/lectures/lecture-09-why-agents-declare-victory-too-early/code/clean-state-checklist.md delete mode 100644 courses/learn-harness-engineering/docs/ko/lectures/lecture-09-why-agents-declare-victory-too-early/code/index.md delete mode 100644 courses/learn-harness-engineering/docs/ko/lectures/lecture-09-why-agents-declare-victory-too-early/code/victory-detector.ts delete mode 100644 courses/learn-harness-engineering/docs/ko/lectures/lecture-09-why-agents-declare-victory-too-early/index.md delete mode 100644 courses/learn-harness-engineering/docs/ko/lectures/lecture-10-why-end-to-end-testing-changes-results/code/architecture-rules.md delete mode 100644 courses/learn-harness-engineering/docs/ko/lectures/lecture-10-why-end-to-end-testing-changes-results/code/e2e-runner.ts delete mode 100644 courses/learn-harness-engineering/docs/ko/lectures/lecture-10-why-end-to-end-testing-changes-results/code/index.md delete mode 100644 courses/learn-harness-engineering/docs/ko/lectures/lecture-10-why-end-to-end-testing-changes-results/code/review-feedback-to-rule.md delete mode 100644 courses/learn-harness-engineering/docs/ko/lectures/lecture-10-why-end-to-end-testing-changes-results/index.md delete mode 100644 courses/learn-harness-engineering/docs/ko/lectures/lecture-11-why-observability-belongs-inside-the-harness/code/evaluator-rubric.md delete mode 100644 courses/learn-harness-engineering/docs/ko/lectures/lecture-11-why-observability-belongs-inside-the-harness/code/index.md delete mode 100644 courses/learn-harness-engineering/docs/ko/lectures/lecture-11-why-observability-belongs-inside-the-harness/code/runtime-logger.ts delete mode 100644 courses/learn-harness-engineering/docs/ko/lectures/lecture-11-why-observability-belongs-inside-the-harness/code/sprint-contract.md delete mode 100644 courses/learn-harness-engineering/docs/ko/lectures/lecture-11-why-observability-belongs-inside-the-harness/index.md delete mode 100644 courses/learn-harness-engineering/docs/ko/lectures/lecture-12-why-every-session-must-leave-a-clean-state/code/benchmark-comparison-template.md delete mode 100644 courses/learn-harness-engineering/docs/ko/lectures/lecture-12-why-every-session-must-leave-a-clean-state/code/benchmark-runner.ts delete mode 100644 courses/learn-harness-engineering/docs/ko/lectures/lecture-12-why-every-session-must-leave-a-clean-state/code/cleanup-loop.md delete mode 100644 courses/learn-harness-engineering/docs/ko/lectures/lecture-12-why-every-session-must-leave-a-clean-state/code/cleanup-scanner.ts delete mode 100644 courses/learn-harness-engineering/docs/ko/lectures/lecture-12-why-every-session-must-leave-a-clean-state/code/index.md delete mode 100644 courses/learn-harness-engineering/docs/ko/lectures/lecture-12-why-every-session-must-leave-a-clean-state/index.md delete mode 100644 courses/learn-harness-engineering/docs/ko/projects/index.md delete mode 100644 courses/learn-harness-engineering/docs/ko/projects/project-01-baseline-vs-minimal-harness/index.md delete mode 100644 courses/learn-harness-engineering/docs/ko/projects/project-02-agent-readable-workspace/index.md delete mode 100644 courses/learn-harness-engineering/docs/ko/projects/project-03-multi-session-continuity/index.md delete mode 100644 courses/learn-harness-engineering/docs/ko/projects/project-04-incremental-indexing/index.md delete mode 100644 courses/learn-harness-engineering/docs/ko/projects/project-05-grounded-qa-verification/index.md delete mode 100644 courses/learn-harness-engineering/docs/ko/projects/project-06-runtime-observability-and-debugging/index.md delete mode 100644 courses/learn-harness-engineering/docs/ko/resources/index.md delete mode 100644 courses/learn-harness-engineering/docs/ko/resources/openai-advanced/index.md delete mode 100644 courses/learn-harness-engineering/docs/ko/resources/openai-advanced/repo-template/AGENTS.md delete mode 100644 courses/learn-harness-engineering/docs/ko/resources/openai-advanced/repo-template/ARCHITECTURE.md delete mode 100644 courses/learn-harness-engineering/docs/ko/resources/openai-advanced/repo-template/docs/DESIGN.md delete mode 100644 courses/learn-harness-engineering/docs/ko/resources/openai-advanced/repo-template/docs/FRONTEND.md delete mode 100644 courses/learn-harness-engineering/docs/ko/resources/openai-advanced/repo-template/docs/PLANS.md delete mode 100644 courses/learn-harness-engineering/docs/ko/resources/openai-advanced/repo-template/docs/PRODUCT_SENSE.md delete mode 100644 courses/learn-harness-engineering/docs/ko/resources/openai-advanced/repo-template/docs/QUALITY_SCORE.md delete mode 100644 courses/learn-harness-engineering/docs/ko/resources/openai-advanced/repo-template/docs/RELIABILITY.md delete mode 100644 courses/learn-harness-engineering/docs/ko/resources/openai-advanced/repo-template/docs/SECURITY.md delete mode 100644 courses/learn-harness-engineering/docs/ko/resources/openai-advanced/repo-template/docs/design-docs/core-beliefs.md delete mode 100644 courses/learn-harness-engineering/docs/ko/resources/openai-advanced/repo-template/docs/design-docs/index.md delete mode 100644 courses/learn-harness-engineering/docs/ko/resources/openai-advanced/repo-template/docs/exec-plans/active/index.md delete mode 100644 courses/learn-harness-engineering/docs/ko/resources/openai-advanced/repo-template/docs/exec-plans/completed/index.md delete mode 100644 courses/learn-harness-engineering/docs/ko/resources/openai-advanced/repo-template/docs/exec-plans/tech-debt-tracker.md delete mode 100644 courses/learn-harness-engineering/docs/ko/resources/openai-advanced/repo-template/docs/generated/db-schema.md delete mode 100644 courses/learn-harness-engineering/docs/ko/resources/openai-advanced/repo-template/docs/product-specs/index.md delete mode 100644 courses/learn-harness-engineering/docs/ko/resources/openai-advanced/repo-template/docs/product-specs/new-user-onboarding.md delete mode 100644 courses/learn-harness-engineering/docs/ko/resources/openai-advanced/repo-template/docs/references/design-system-reference-llms.txt delete mode 100644 courses/learn-harness-engineering/docs/ko/resources/openai-advanced/repo-template/docs/references/nixpacks-llms.txt delete mode 100644 courses/learn-harness-engineering/docs/ko/resources/openai-advanced/repo-template/docs/references/uv-llms.txt delete mode 100644 courses/learn-harness-engineering/docs/ko/resources/openai-advanced/repo-template/index.md delete mode 100644 courses/learn-harness-engineering/docs/ko/resources/openai-advanced/sops/chrome-devtools-validation-loop.md delete mode 100644 courses/learn-harness-engineering/docs/ko/resources/openai-advanced/sops/encode-knowledge-into-repo.md delete mode 100644 courses/learn-harness-engineering/docs/ko/resources/openai-advanced/sops/index.md delete mode 100644 courses/learn-harness-engineering/docs/ko/resources/openai-advanced/sops/layered-domain-architecture.md delete mode 100644 courses/learn-harness-engineering/docs/ko/resources/openai-advanced/sops/observability-feedback-loop.md delete mode 100644 courses/learn-harness-engineering/docs/ko/resources/reference/coding-agent-startup-flow.md delete mode 100644 courses/learn-harness-engineering/docs/ko/resources/reference/glossary.md delete mode 100644 courses/learn-harness-engineering/docs/ko/resources/reference/index.md delete mode 100644 courses/learn-harness-engineering/docs/ko/resources/reference/initializer-agent-playbook.md delete mode 100644 courses/learn-harness-engineering/docs/ko/resources/reference/method-map.md delete mode 100644 courses/learn-harness-engineering/docs/ko/resources/reference/prompt-calibration.md delete mode 100644 courses/learn-harness-engineering/docs/ko/resources/templates/AGENTS.md delete mode 100644 courses/learn-harness-engineering/docs/ko/resources/templates/CLAUDE.md delete mode 100644 courses/learn-harness-engineering/docs/ko/resources/templates/claude-progress.md delete mode 100644 courses/learn-harness-engineering/docs/ko/resources/templates/clean-state-checklist.md delete mode 100644 courses/learn-harness-engineering/docs/ko/resources/templates/evaluator-rubric.md delete mode 100644 courses/learn-harness-engineering/docs/ko/resources/templates/feature_list.json delete mode 100644 courses/learn-harness-engineering/docs/ko/resources/templates/index.md delete mode 100755 courses/learn-harness-engineering/docs/ko/resources/templates/init.sh delete mode 100644 courses/learn-harness-engineering/docs/ko/resources/templates/quality-document.md delete mode 100644 courses/learn-harness-engineering/docs/ko/resources/templates/session-handoff.md delete mode 100644 courses/learn-harness-engineering/docs/ko/skills/index.md delete mode 100644 courses/learn-harness-engineering/docs/public/screenshots/readme/en-home.png delete mode 100644 courses/learn-harness-engineering/docs/public/screenshots/readme/en-lecture-01.png delete mode 100644 courses/learn-harness-engineering/docs/public/screenshots/readme/en-resources.png delete mode 100644 courses/learn-harness-engineering/docs/public/screenshots/readme/zh-home.png delete mode 100644 courses/learn-harness-engineering/docs/public/screenshots/readme/zh-lecture-01.png delete mode 100644 courses/learn-harness-engineering/docs/public/screenshots/readme/zh-resources.png delete mode 100644 courses/learn-harness-engineering/docs/ru/index.md delete mode 100644 courses/learn-harness-engineering/docs/ru/lectures/lecture-01-why-capable-agents-still-fail/code/failure-pattern-demo.ts delete mode 100644 courses/learn-harness-engineering/docs/ru/lectures/lecture-01-why-capable-agents-still-fail/code/failure-signals-checklist.md delete mode 100644 courses/learn-harness-engineering/docs/ru/lectures/lecture-01-why-capable-agents-still-fail/code/index.md delete mode 100644 courses/learn-harness-engineering/docs/ru/lectures/lecture-01-why-capable-agents-still-fail/code/underspecified-task.md delete mode 100644 courses/learn-harness-engineering/docs/ru/lectures/lecture-01-why-capable-agents-still-fail/index.md delete mode 100644 courses/learn-harness-engineering/docs/ru/lectures/lecture-02-what-a-harness-actually-is/code/harness-components.md delete mode 100644 courses/learn-harness-engineering/docs/ru/lectures/lecture-02-what-a-harness-actually-is/code/harness-vs-no-harness.ts delete mode 100644 courses/learn-harness-engineering/docs/ru/lectures/lecture-02-what-a-harness-actually-is/code/index.md delete mode 100644 courses/learn-harness-engineering/docs/ru/lectures/lecture-02-what-a-harness-actually-is/code/minimal-harness-loop.ts delete mode 100644 courses/learn-harness-engineering/docs/ru/lectures/lecture-02-what-a-harness-actually-is/index.md delete mode 100644 courses/learn-harness-engineering/docs/ru/lectures/lecture-03-why-the-repository-must-become-the-system-of-record/code/index.md delete mode 100644 courses/learn-harness-engineering/docs/ru/lectures/lecture-03-why-the-repository-must-become-the-system-of-record/code/repo-knowledge-layout.txt delete mode 100644 courses/learn-harness-engineering/docs/ru/lectures/lecture-03-why-the-repository-must-become-the-system-of-record/code/repo-reader.ts delete mode 100644 courses/learn-harness-engineering/docs/ru/lectures/lecture-03-why-the-repository-must-become-the-system-of-record/code/system-of-record-checklist.md delete mode 100644 courses/learn-harness-engineering/docs/ru/lectures/lecture-03-why-the-repository-must-become-the-system-of-record/index.md delete mode 100644 courses/learn-harness-engineering/docs/ru/lectures/lecture-04-why-one-giant-instruction-file-fails/code/AGENTS-short.md delete mode 100644 courses/learn-harness-engineering/docs/ru/lectures/lecture-04-why-one-giant-instruction-file-fails/code/anti-patterns.md delete mode 100644 courses/learn-harness-engineering/docs/ru/lectures/lecture-04-why-one-giant-instruction-file-fails/code/index.md delete mode 100644 courses/learn-harness-engineering/docs/ru/lectures/lecture-04-why-one-giant-instruction-file-fails/code/split-vs-monolithic.ts delete mode 100644 courses/learn-harness-engineering/docs/ru/lectures/lecture-04-why-one-giant-instruction-file-fails/index.md delete mode 100644 courses/learn-harness-engineering/docs/ru/lectures/lecture-05-why-long-running-tasks-lose-continuity/code/continuity-checklist.md delete mode 100644 courses/learn-harness-engineering/docs/ru/lectures/lecture-05-why-long-running-tasks-lose-continuity/code/index.md delete mode 100644 courses/learn-harness-engineering/docs/ru/lectures/lecture-05-why-long-running-tasks-lose-continuity/code/session-handoff.md delete mode 100644 courses/learn-harness-engineering/docs/ru/lectures/lecture-05-why-long-running-tasks-lose-continuity/code/session-simulator.ts delete mode 100644 courses/learn-harness-engineering/docs/ru/lectures/lecture-05-why-long-running-tasks-lose-continuity/index.md delete mode 100644 courses/learn-harness-engineering/docs/ru/lectures/lecture-06-why-initialization-needs-its-own-phase/code/index.md delete mode 100644 courses/learn-harness-engineering/docs/ru/lectures/lecture-06-why-initialization-needs-its-own-phase/code/init-check.ts delete mode 100644 courses/learn-harness-engineering/docs/ru/lectures/lecture-06-why-initialization-needs-its-own-phase/code/init.sh delete mode 100644 courses/learn-harness-engineering/docs/ru/lectures/lecture-06-why-initialization-needs-its-own-phase/code/initializer-output-checklist.md delete mode 100644 courses/learn-harness-engineering/docs/ru/lectures/lecture-06-why-initialization-needs-its-own-phase/index.md delete mode 100644 courses/learn-harness-engineering/docs/ru/lectures/lecture-07-why-agents-overreach-and-under-finish/code/index.md delete mode 100644 courses/learn-harness-engineering/docs/ru/lectures/lecture-07-why-agents-overreach-and-under-finish/code/next-task-template.md delete mode 100644 courses/learn-harness-engineering/docs/ru/lectures/lecture-07-why-agents-overreach-and-under-finish/code/scope-surface-example.md delete mode 100644 courses/learn-harness-engineering/docs/ru/lectures/lecture-07-why-agents-overreach-and-under-finish/code/scope-tracker.ts delete mode 100644 courses/learn-harness-engineering/docs/ru/lectures/lecture-07-why-agents-overreach-and-under-finish/index.md delete mode 100644 courses/learn-harness-engineering/docs/ru/lectures/lecture-08-why-feature-lists-are-harness-primitives/code/feature-list-validator.ts delete mode 100644 courses/learn-harness-engineering/docs/ru/lectures/lecture-08-why-feature-lists-are-harness-primitives/code/feature_list.json delete mode 100644 courses/learn-harness-engineering/docs/ru/lectures/lecture-08-why-feature-lists-are-harness-primitives/code/index.md delete mode 100644 courses/learn-harness-engineering/docs/ru/lectures/lecture-08-why-feature-lists-are-harness-primitives/code/pass-gate-policy.md delete mode 100644 courses/learn-harness-engineering/docs/ru/lectures/lecture-08-why-feature-lists-are-harness-primitives/index.md delete mode 100644 courses/learn-harness-engineering/docs/ru/lectures/lecture-09-why-agents-declare-victory-too-early/code/clean-state-checklist.md delete mode 100644 courses/learn-harness-engineering/docs/ru/lectures/lecture-09-why-agents-declare-victory-too-early/code/index.md delete mode 100644 courses/learn-harness-engineering/docs/ru/lectures/lecture-09-why-agents-declare-victory-too-early/code/victory-detector.ts delete mode 100644 courses/learn-harness-engineering/docs/ru/lectures/lecture-09-why-agents-declare-victory-too-early/index.md delete mode 100644 courses/learn-harness-engineering/docs/ru/lectures/lecture-10-why-end-to-end-testing-changes-results/code/architecture-rules.md delete mode 100644 courses/learn-harness-engineering/docs/ru/lectures/lecture-10-why-end-to-end-testing-changes-results/code/e2e-runner.ts delete mode 100644 courses/learn-harness-engineering/docs/ru/lectures/lecture-10-why-end-to-end-testing-changes-results/code/index.md delete mode 100644 courses/learn-harness-engineering/docs/ru/lectures/lecture-10-why-end-to-end-testing-changes-results/code/review-feedback-to-rule.md delete mode 100644 courses/learn-harness-engineering/docs/ru/lectures/lecture-10-why-end-to-end-testing-changes-results/index.md delete mode 100644 courses/learn-harness-engineering/docs/ru/lectures/lecture-11-why-observability-belongs-inside-the-harness/code/evaluator-rubric.md delete mode 100644 courses/learn-harness-engineering/docs/ru/lectures/lecture-11-why-observability-belongs-inside-the-harness/code/index.md delete mode 100644 courses/learn-harness-engineering/docs/ru/lectures/lecture-11-why-observability-belongs-inside-the-harness/code/runtime-logger.ts delete mode 100644 courses/learn-harness-engineering/docs/ru/lectures/lecture-11-why-observability-belongs-inside-the-harness/code/sprint-contract.md delete mode 100644 courses/learn-harness-engineering/docs/ru/lectures/lecture-11-why-observability-belongs-inside-the-harness/index.md delete mode 100644 courses/learn-harness-engineering/docs/ru/lectures/lecture-12-why-every-session-must-leave-a-clean-state/code/benchmark-comparison-template.md delete mode 100644 courses/learn-harness-engineering/docs/ru/lectures/lecture-12-why-every-session-must-leave-a-clean-state/code/benchmark-runner.ts delete mode 100644 courses/learn-harness-engineering/docs/ru/lectures/lecture-12-why-every-session-must-leave-a-clean-state/code/cleanup-loop.md delete mode 100644 courses/learn-harness-engineering/docs/ru/lectures/lecture-12-why-every-session-must-leave-a-clean-state/code/cleanup-scanner.ts delete mode 100644 courses/learn-harness-engineering/docs/ru/lectures/lecture-12-why-every-session-must-leave-a-clean-state/code/index.md delete mode 100644 courses/learn-harness-engineering/docs/ru/lectures/lecture-12-why-every-session-must-leave-a-clean-state/index.md delete mode 100644 courses/learn-harness-engineering/docs/ru/projects/index.md delete mode 100644 courses/learn-harness-engineering/docs/ru/projects/project-01-baseline-vs-minimal-harness/index.md delete mode 100644 courses/learn-harness-engineering/docs/ru/projects/project-02-agent-readable-workspace/index.md delete mode 100644 courses/learn-harness-engineering/docs/ru/projects/project-03-multi-session-continuity/index.md delete mode 100644 courses/learn-harness-engineering/docs/ru/projects/project-04-incremental-indexing/index.md delete mode 100644 courses/learn-harness-engineering/docs/ru/projects/project-05-grounded-qa-verification/index.md delete mode 100644 courses/learn-harness-engineering/docs/ru/projects/project-06-runtime-observability-and-debugging/index.md delete mode 100644 courses/learn-harness-engineering/docs/ru/resources/index.md delete mode 100644 courses/learn-harness-engineering/docs/ru/resources/openai-advanced/index.md delete mode 100644 courses/learn-harness-engineering/docs/ru/resources/openai-advanced/repo-template/AGENTS.md delete mode 100644 courses/learn-harness-engineering/docs/ru/resources/openai-advanced/repo-template/ARCHITECTURE.md delete mode 100644 courses/learn-harness-engineering/docs/ru/resources/openai-advanced/repo-template/docs/DESIGN.md delete mode 100644 courses/learn-harness-engineering/docs/ru/resources/openai-advanced/repo-template/docs/FRONTEND.md delete mode 100644 courses/learn-harness-engineering/docs/ru/resources/openai-advanced/repo-template/docs/PLANS.md delete mode 100644 courses/learn-harness-engineering/docs/ru/resources/openai-advanced/repo-template/docs/PRODUCT_SENSE.md delete mode 100644 courses/learn-harness-engineering/docs/ru/resources/openai-advanced/repo-template/docs/QUALITY_SCORE.md delete mode 100644 courses/learn-harness-engineering/docs/ru/resources/openai-advanced/repo-template/docs/RELIABILITY.md delete mode 100644 courses/learn-harness-engineering/docs/ru/resources/openai-advanced/repo-template/docs/SECURITY.md delete mode 100644 courses/learn-harness-engineering/docs/ru/resources/openai-advanced/repo-template/docs/design-docs/core-beliefs.md delete mode 100644 courses/learn-harness-engineering/docs/ru/resources/openai-advanced/repo-template/docs/design-docs/index.md delete mode 100644 courses/learn-harness-engineering/docs/ru/resources/openai-advanced/repo-template/docs/exec-plans/active/index.md delete mode 100644 courses/learn-harness-engineering/docs/ru/resources/openai-advanced/repo-template/docs/exec-plans/completed/index.md delete mode 100644 courses/learn-harness-engineering/docs/ru/resources/openai-advanced/repo-template/docs/exec-plans/tech-debt-tracker.md delete mode 100644 courses/learn-harness-engineering/docs/ru/resources/openai-advanced/repo-template/docs/generated/db-schema.md delete mode 100644 courses/learn-harness-engineering/docs/ru/resources/openai-advanced/repo-template/docs/product-specs/index.md delete mode 100644 courses/learn-harness-engineering/docs/ru/resources/openai-advanced/repo-template/docs/product-specs/new-user-onboarding.md delete mode 100644 courses/learn-harness-engineering/docs/ru/resources/openai-advanced/repo-template/docs/references/design-system-reference-llms.txt delete mode 100644 courses/learn-harness-engineering/docs/ru/resources/openai-advanced/repo-template/docs/references/nixpacks-llms.txt delete mode 100644 courses/learn-harness-engineering/docs/ru/resources/openai-advanced/repo-template/docs/references/uv-llms.txt delete mode 100644 courses/learn-harness-engineering/docs/ru/resources/openai-advanced/repo-template/index.md delete mode 100644 courses/learn-harness-engineering/docs/ru/resources/openai-advanced/sops/chrome-devtools-validation-loop.md delete mode 100644 courses/learn-harness-engineering/docs/ru/resources/openai-advanced/sops/encode-knowledge-into-repo.md delete mode 100644 courses/learn-harness-engineering/docs/ru/resources/openai-advanced/sops/index.md delete mode 100644 courses/learn-harness-engineering/docs/ru/resources/openai-advanced/sops/layered-domain-architecture.md delete mode 100644 courses/learn-harness-engineering/docs/ru/resources/openai-advanced/sops/observability-feedback-loop.md delete mode 100644 courses/learn-harness-engineering/docs/ru/resources/reference/coding-agent-startup-flow.md delete mode 100644 courses/learn-harness-engineering/docs/ru/resources/reference/index.md delete mode 100644 courses/learn-harness-engineering/docs/ru/resources/reference/initializer-agent-playbook.md delete mode 100644 courses/learn-harness-engineering/docs/ru/resources/reference/method-map.md delete mode 100644 courses/learn-harness-engineering/docs/ru/resources/reference/prompt-calibration.md delete mode 100644 courses/learn-harness-engineering/docs/ru/resources/templates/AGENTS.md delete mode 100644 courses/learn-harness-engineering/docs/ru/resources/templates/CLAUDE.md delete mode 100644 courses/learn-harness-engineering/docs/ru/resources/templates/claude-progress.md delete mode 100644 courses/learn-harness-engineering/docs/ru/resources/templates/clean-state-checklist.md delete mode 100644 courses/learn-harness-engineering/docs/ru/resources/templates/evaluator-rubric.md delete mode 100644 courses/learn-harness-engineering/docs/ru/resources/templates/feature_list.json delete mode 100644 courses/learn-harness-engineering/docs/ru/resources/templates/index.md delete mode 100755 courses/learn-harness-engineering/docs/ru/resources/templates/init.sh delete mode 100644 courses/learn-harness-engineering/docs/ru/resources/templates/quality-document.md delete mode 100644 courses/learn-harness-engineering/docs/ru/resources/templates/session-handoff.md delete mode 100644 courses/learn-harness-engineering/docs/ru/skills/index.md delete mode 100644 courses/learn-harness-engineering/docs/uz/GLOSSARY.md delete mode 100644 courses/learn-harness-engineering/docs/uz/STYLE-GUIDE.md delete mode 100644 courses/learn-harness-engineering/docs/uz/index.md delete mode 100644 courses/learn-harness-engineering/docs/uz/lectures/lecture-01-why-capable-agents-still-fail/code/failure-pattern-demo.ts delete mode 100644 courses/learn-harness-engineering/docs/uz/lectures/lecture-01-why-capable-agents-still-fail/code/failure-signals-checklist.md delete mode 100644 courses/learn-harness-engineering/docs/uz/lectures/lecture-01-why-capable-agents-still-fail/code/index.md delete mode 100644 courses/learn-harness-engineering/docs/uz/lectures/lecture-01-why-capable-agents-still-fail/code/underspecified-task.md delete mode 100644 courses/learn-harness-engineering/docs/uz/lectures/lecture-01-why-capable-agents-still-fail/index.md delete mode 100644 courses/learn-harness-engineering/docs/uz/lectures/lecture-02-what-a-harness-actually-is/code/harness-components.md delete mode 100644 courses/learn-harness-engineering/docs/uz/lectures/lecture-02-what-a-harness-actually-is/code/harness-vs-no-harness.ts delete mode 100644 courses/learn-harness-engineering/docs/uz/lectures/lecture-02-what-a-harness-actually-is/code/index.md delete mode 100644 courses/learn-harness-engineering/docs/uz/lectures/lecture-02-what-a-harness-actually-is/code/minimal-harness-loop.ts delete mode 100644 courses/learn-harness-engineering/docs/uz/lectures/lecture-02-what-a-harness-actually-is/index.md delete mode 100644 courses/learn-harness-engineering/docs/uz/lectures/lecture-03-why-the-repository-must-become-the-system-of-record/code/index.md delete mode 100644 courses/learn-harness-engineering/docs/uz/lectures/lecture-03-why-the-repository-must-become-the-system-of-record/code/repo-knowledge-layout.txt delete mode 100644 courses/learn-harness-engineering/docs/uz/lectures/lecture-03-why-the-repository-must-become-the-system-of-record/code/repo-reader.ts delete mode 100644 courses/learn-harness-engineering/docs/uz/lectures/lecture-03-why-the-repository-must-become-the-system-of-record/code/system-of-record-checklist.md delete mode 100644 courses/learn-harness-engineering/docs/uz/lectures/lecture-03-why-the-repository-must-become-the-system-of-record/index.md delete mode 100644 courses/learn-harness-engineering/docs/uz/lectures/lecture-04-why-one-giant-instruction-file-fails/code/AGENTS-short.md delete mode 100644 courses/learn-harness-engineering/docs/uz/lectures/lecture-04-why-one-giant-instruction-file-fails/code/anti-patterns.md delete mode 100644 courses/learn-harness-engineering/docs/uz/lectures/lecture-04-why-one-giant-instruction-file-fails/code/index.md delete mode 100644 courses/learn-harness-engineering/docs/uz/lectures/lecture-04-why-one-giant-instruction-file-fails/code/split-vs-monolithic.ts delete mode 100644 courses/learn-harness-engineering/docs/uz/lectures/lecture-04-why-one-giant-instruction-file-fails/index.md delete mode 100644 courses/learn-harness-engineering/docs/uz/lectures/lecture-05-why-long-running-tasks-lose-continuity/code/continuity-checklist.md delete mode 100644 courses/learn-harness-engineering/docs/uz/lectures/lecture-05-why-long-running-tasks-lose-continuity/code/index.md delete mode 100644 courses/learn-harness-engineering/docs/uz/lectures/lecture-05-why-long-running-tasks-lose-continuity/code/session-handoff.md delete mode 100644 courses/learn-harness-engineering/docs/uz/lectures/lecture-05-why-long-running-tasks-lose-continuity/code/session-simulator.ts delete mode 100644 courses/learn-harness-engineering/docs/uz/lectures/lecture-05-why-long-running-tasks-lose-continuity/index.md delete mode 100644 courses/learn-harness-engineering/docs/uz/lectures/lecture-06-why-initialization-needs-its-own-phase/code/index.md delete mode 100644 courses/learn-harness-engineering/docs/uz/lectures/lecture-06-why-initialization-needs-its-own-phase/code/init-check.ts delete mode 100644 courses/learn-harness-engineering/docs/uz/lectures/lecture-06-why-initialization-needs-its-own-phase/code/init.sh delete mode 100644 courses/learn-harness-engineering/docs/uz/lectures/lecture-06-why-initialization-needs-its-own-phase/code/initializer-output-checklist.md delete mode 100644 courses/learn-harness-engineering/docs/uz/lectures/lecture-06-why-initialization-needs-its-own-phase/index.md delete mode 100644 courses/learn-harness-engineering/docs/uz/lectures/lecture-07-why-agents-overreach-and-under-finish/code/index.md delete mode 100644 courses/learn-harness-engineering/docs/uz/lectures/lecture-07-why-agents-overreach-and-under-finish/code/next-task-template.md delete mode 100644 courses/learn-harness-engineering/docs/uz/lectures/lecture-07-why-agents-overreach-and-under-finish/code/scope-surface-example.md delete mode 100644 courses/learn-harness-engineering/docs/uz/lectures/lecture-07-why-agents-overreach-and-under-finish/code/scope-tracker.ts delete mode 100644 courses/learn-harness-engineering/docs/uz/lectures/lecture-07-why-agents-overreach-and-under-finish/index.md delete mode 100644 courses/learn-harness-engineering/docs/uz/lectures/lecture-08-why-feature-lists-are-harness-primitives/code/feature-list-validator.ts delete mode 100644 courses/learn-harness-engineering/docs/uz/lectures/lecture-08-why-feature-lists-are-harness-primitives/code/feature_list.json delete mode 100644 courses/learn-harness-engineering/docs/uz/lectures/lecture-08-why-feature-lists-are-harness-primitives/code/index.md delete mode 100644 courses/learn-harness-engineering/docs/uz/lectures/lecture-08-why-feature-lists-are-harness-primitives/code/pass-gate-policy.md delete mode 100644 courses/learn-harness-engineering/docs/uz/lectures/lecture-08-why-feature-lists-are-harness-primitives/index.md delete mode 100644 courses/learn-harness-engineering/docs/uz/lectures/lecture-09-why-agents-declare-victory-too-early/code/clean-state-checklist.md delete mode 100644 courses/learn-harness-engineering/docs/uz/lectures/lecture-09-why-agents-declare-victory-too-early/code/index.md delete mode 100644 courses/learn-harness-engineering/docs/uz/lectures/lecture-09-why-agents-declare-victory-too-early/code/victory-detector.ts delete mode 100644 courses/learn-harness-engineering/docs/uz/lectures/lecture-09-why-agents-declare-victory-too-early/index.md delete mode 100644 courses/learn-harness-engineering/docs/uz/lectures/lecture-10-why-end-to-end-testing-changes-results/code/architecture-rules.md delete mode 100644 courses/learn-harness-engineering/docs/uz/lectures/lecture-10-why-end-to-end-testing-changes-results/code/e2e-runner.ts delete mode 100644 courses/learn-harness-engineering/docs/uz/lectures/lecture-10-why-end-to-end-testing-changes-results/code/index.md delete mode 100644 courses/learn-harness-engineering/docs/uz/lectures/lecture-10-why-end-to-end-testing-changes-results/code/review-feedback-to-rule.md delete mode 100644 courses/learn-harness-engineering/docs/uz/lectures/lecture-10-why-end-to-end-testing-changes-results/index.md delete mode 100644 courses/learn-harness-engineering/docs/uz/lectures/lecture-11-why-observability-belongs-inside-the-harness/code/evaluator-rubric.md delete mode 100644 courses/learn-harness-engineering/docs/uz/lectures/lecture-11-why-observability-belongs-inside-the-harness/code/index.md delete mode 100644 courses/learn-harness-engineering/docs/uz/lectures/lecture-11-why-observability-belongs-inside-the-harness/code/runtime-logger.ts delete mode 100644 courses/learn-harness-engineering/docs/uz/lectures/lecture-11-why-observability-belongs-inside-the-harness/code/sprint-contract.md delete mode 100644 courses/learn-harness-engineering/docs/uz/lectures/lecture-11-why-observability-belongs-inside-the-harness/index.md delete mode 100644 courses/learn-harness-engineering/docs/uz/lectures/lecture-12-why-every-session-must-leave-a-clean-state/code/benchmark-comparison-template.md delete mode 100644 courses/learn-harness-engineering/docs/uz/lectures/lecture-12-why-every-session-must-leave-a-clean-state/code/benchmark-runner.ts delete mode 100644 courses/learn-harness-engineering/docs/uz/lectures/lecture-12-why-every-session-must-leave-a-clean-state/code/cleanup-loop.md delete mode 100644 courses/learn-harness-engineering/docs/uz/lectures/lecture-12-why-every-session-must-leave-a-clean-state/code/cleanup-scanner.ts delete mode 100644 courses/learn-harness-engineering/docs/uz/lectures/lecture-12-why-every-session-must-leave-a-clean-state/code/index.md delete mode 100644 courses/learn-harness-engineering/docs/uz/lectures/lecture-12-why-every-session-must-leave-a-clean-state/index.md delete mode 100644 courses/learn-harness-engineering/docs/uz/projects/index.md delete mode 100644 courses/learn-harness-engineering/docs/uz/projects/project-01-baseline-vs-minimal-harness/index.md delete mode 100644 courses/learn-harness-engineering/docs/uz/projects/project-02-agent-readable-workspace/index.md delete mode 100644 courses/learn-harness-engineering/docs/uz/projects/project-03-multi-session-continuity/index.md delete mode 100644 courses/learn-harness-engineering/docs/uz/projects/project-04-incremental-indexing/index.md delete mode 100644 courses/learn-harness-engineering/docs/uz/projects/project-05-grounded-qa-verification/index.md delete mode 100644 courses/learn-harness-engineering/docs/uz/projects/project-06-runtime-observability-and-debugging/index.md delete mode 100644 courses/learn-harness-engineering/docs/uz/resources/index.md delete mode 100644 courses/learn-harness-engineering/docs/uz/resources/openai-advanced/index.md delete mode 100644 courses/learn-harness-engineering/docs/uz/resources/openai-advanced/repo-template/AGENTS.md delete mode 100644 courses/learn-harness-engineering/docs/uz/resources/openai-advanced/repo-template/ARCHITECTURE.md delete mode 100644 courses/learn-harness-engineering/docs/uz/resources/openai-advanced/repo-template/docs/DESIGN.md delete mode 100644 courses/learn-harness-engineering/docs/uz/resources/openai-advanced/repo-template/docs/FRONTEND.md delete mode 100644 courses/learn-harness-engineering/docs/uz/resources/openai-advanced/repo-template/docs/PLANS.md delete mode 100644 courses/learn-harness-engineering/docs/uz/resources/openai-advanced/repo-template/docs/PRODUCT_SENSE.md delete mode 100644 courses/learn-harness-engineering/docs/uz/resources/openai-advanced/repo-template/docs/QUALITY_SCORE.md delete mode 100644 courses/learn-harness-engineering/docs/uz/resources/openai-advanced/repo-template/docs/RELIABILITY.md delete mode 100644 courses/learn-harness-engineering/docs/uz/resources/openai-advanced/repo-template/docs/SECURITY.md delete mode 100644 courses/learn-harness-engineering/docs/uz/resources/openai-advanced/repo-template/docs/design-docs/core-beliefs.md delete mode 100644 courses/learn-harness-engineering/docs/uz/resources/openai-advanced/repo-template/docs/design-docs/index.md delete mode 100644 courses/learn-harness-engineering/docs/uz/resources/openai-advanced/repo-template/docs/exec-plans/active/index.md delete mode 100644 courses/learn-harness-engineering/docs/uz/resources/openai-advanced/repo-template/docs/exec-plans/completed/index.md delete mode 100644 courses/learn-harness-engineering/docs/uz/resources/openai-advanced/repo-template/docs/exec-plans/tech-debt-tracker.md delete mode 100644 courses/learn-harness-engineering/docs/uz/resources/openai-advanced/repo-template/docs/generated/db-schema.md delete mode 100644 courses/learn-harness-engineering/docs/uz/resources/openai-advanced/repo-template/docs/product-specs/index.md delete mode 100644 courses/learn-harness-engineering/docs/uz/resources/openai-advanced/repo-template/docs/product-specs/new-user-onboarding.md delete mode 100644 courses/learn-harness-engineering/docs/uz/resources/openai-advanced/repo-template/docs/references/design-system-reference-llms.txt delete mode 100644 courses/learn-harness-engineering/docs/uz/resources/openai-advanced/repo-template/docs/references/nixpacks-llms.txt delete mode 100644 courses/learn-harness-engineering/docs/uz/resources/openai-advanced/repo-template/docs/references/uv-llms.txt delete mode 100644 courses/learn-harness-engineering/docs/uz/resources/openai-advanced/repo-template/index.md delete mode 100644 courses/learn-harness-engineering/docs/uz/resources/openai-advanced/sops/chrome-devtools-validation-loop.md delete mode 100644 courses/learn-harness-engineering/docs/uz/resources/openai-advanced/sops/encode-knowledge-into-repo.md delete mode 100644 courses/learn-harness-engineering/docs/uz/resources/openai-advanced/sops/index.md delete mode 100644 courses/learn-harness-engineering/docs/uz/resources/openai-advanced/sops/layered-domain-architecture.md delete mode 100644 courses/learn-harness-engineering/docs/uz/resources/openai-advanced/sops/observability-feedback-loop.md delete mode 100644 courses/learn-harness-engineering/docs/uz/resources/reference/coding-agent-startup-flow.md delete mode 100644 courses/learn-harness-engineering/docs/uz/resources/reference/index.md delete mode 100644 courses/learn-harness-engineering/docs/uz/resources/reference/initializer-agent-playbook.md delete mode 100644 courses/learn-harness-engineering/docs/uz/resources/reference/method-map.md delete mode 100644 courses/learn-harness-engineering/docs/uz/resources/reference/prompt-calibration.md delete mode 100644 courses/learn-harness-engineering/docs/uz/resources/templates/AGENTS.md delete mode 100644 courses/learn-harness-engineering/docs/uz/resources/templates/CLAUDE.md delete mode 100644 courses/learn-harness-engineering/docs/uz/resources/templates/claude-progress.md delete mode 100644 courses/learn-harness-engineering/docs/uz/resources/templates/clean-state-checklist.md delete mode 100644 courses/learn-harness-engineering/docs/uz/resources/templates/evaluator-rubric.md delete mode 100644 courses/learn-harness-engineering/docs/uz/resources/templates/feature_list.json delete mode 100644 courses/learn-harness-engineering/docs/uz/resources/templates/index.md delete mode 100755 courses/learn-harness-engineering/docs/uz/resources/templates/init.sh delete mode 100644 courses/learn-harness-engineering/docs/uz/resources/templates/quality-document.md delete mode 100644 courses/learn-harness-engineering/docs/uz/resources/templates/session-handoff.md delete mode 100644 courses/learn-harness-engineering/docs/uz/skills/index.md delete mode 100644 courses/learn-harness-engineering/docs/vi/index.md delete mode 100644 courses/learn-harness-engineering/docs/vi/lectures/lecture-01-why-capable-agents-still-fail/code/failure-pattern-demo.ts delete mode 100644 courses/learn-harness-engineering/docs/vi/lectures/lecture-01-why-capable-agents-still-fail/code/failure-signals-checklist.md delete mode 100644 courses/learn-harness-engineering/docs/vi/lectures/lecture-01-why-capable-agents-still-fail/code/index.md delete mode 100644 courses/learn-harness-engineering/docs/vi/lectures/lecture-01-why-capable-agents-still-fail/code/underspecified-task.md delete mode 100644 courses/learn-harness-engineering/docs/vi/lectures/lecture-01-why-capable-agents-still-fail/index.md delete mode 100644 courses/learn-harness-engineering/docs/vi/lectures/lecture-02-what-a-harness-actually-is/code/harness-components.md delete mode 100644 courses/learn-harness-engineering/docs/vi/lectures/lecture-02-what-a-harness-actually-is/code/harness-vs-no-harness.ts delete mode 100644 courses/learn-harness-engineering/docs/vi/lectures/lecture-02-what-a-harness-actually-is/code/index.md delete mode 100644 courses/learn-harness-engineering/docs/vi/lectures/lecture-02-what-a-harness-actually-is/code/minimal-harness-loop.ts delete mode 100644 courses/learn-harness-engineering/docs/vi/lectures/lecture-02-what-a-harness-actually-is/index.md delete mode 100644 courses/learn-harness-engineering/docs/vi/lectures/lecture-03-why-the-repository-must-become-the-system-of-record/code/index.md delete mode 100644 courses/learn-harness-engineering/docs/vi/lectures/lecture-03-why-the-repository-must-become-the-system-of-record/code/repo-knowledge-layout.txt delete mode 100644 courses/learn-harness-engineering/docs/vi/lectures/lecture-03-why-the-repository-must-become-the-system-of-record/code/repo-reader.ts delete mode 100644 courses/learn-harness-engineering/docs/vi/lectures/lecture-03-why-the-repository-must-become-the-system-of-record/code/system-of-record-checklist.md delete mode 100644 courses/learn-harness-engineering/docs/vi/lectures/lecture-03-why-the-repository-must-become-the-system-of-record/index.md delete mode 100644 courses/learn-harness-engineering/docs/vi/lectures/lecture-04-why-one-giant-instruction-file-fails/code/AGENTS-short.md delete mode 100644 courses/learn-harness-engineering/docs/vi/lectures/lecture-04-why-one-giant-instruction-file-fails/code/anti-patterns.md delete mode 100644 courses/learn-harness-engineering/docs/vi/lectures/lecture-04-why-one-giant-instruction-file-fails/code/index.md delete mode 100644 courses/learn-harness-engineering/docs/vi/lectures/lecture-04-why-one-giant-instruction-file-fails/code/split-vs-monolithic.ts delete mode 100644 courses/learn-harness-engineering/docs/vi/lectures/lecture-04-why-one-giant-instruction-file-fails/index.md delete mode 100644 courses/learn-harness-engineering/docs/vi/lectures/lecture-05-why-long-running-tasks-lose-continuity/code/continuity-checklist.md delete mode 100644 courses/learn-harness-engineering/docs/vi/lectures/lecture-05-why-long-running-tasks-lose-continuity/code/index.md delete mode 100644 courses/learn-harness-engineering/docs/vi/lectures/lecture-05-why-long-running-tasks-lose-continuity/code/session-handoff.md delete mode 100644 courses/learn-harness-engineering/docs/vi/lectures/lecture-05-why-long-running-tasks-lose-continuity/code/session-simulator.ts delete mode 100644 courses/learn-harness-engineering/docs/vi/lectures/lecture-05-why-long-running-tasks-lose-continuity/index.md delete mode 100644 courses/learn-harness-engineering/docs/vi/lectures/lecture-06-why-initialization-needs-its-own-phase/code/index.md delete mode 100644 courses/learn-harness-engineering/docs/vi/lectures/lecture-06-why-initialization-needs-its-own-phase/code/init-check.ts delete mode 100644 courses/learn-harness-engineering/docs/vi/lectures/lecture-06-why-initialization-needs-its-own-phase/code/init.sh delete mode 100644 courses/learn-harness-engineering/docs/vi/lectures/lecture-06-why-initialization-needs-its-own-phase/code/initializer-output-checklist.md delete mode 100644 courses/learn-harness-engineering/docs/vi/lectures/lecture-06-why-initialization-needs-its-own-phase/index.md delete mode 100644 courses/learn-harness-engineering/docs/vi/lectures/lecture-07-why-agents-overreach-and-under-finish/code/index.md delete mode 100644 courses/learn-harness-engineering/docs/vi/lectures/lecture-07-why-agents-overreach-and-under-finish/code/next-task-template.md delete mode 100644 courses/learn-harness-engineering/docs/vi/lectures/lecture-07-why-agents-overreach-and-under-finish/code/scope-surface-example.md delete mode 100644 courses/learn-harness-engineering/docs/vi/lectures/lecture-07-why-agents-overreach-and-under-finish/code/scope-tracker.ts delete mode 100644 courses/learn-harness-engineering/docs/vi/lectures/lecture-07-why-agents-overreach-and-under-finish/index.md delete mode 100644 courses/learn-harness-engineering/docs/vi/lectures/lecture-08-why-feature-lists-are-harness-primitives/code/feature-list-validator.ts delete mode 100644 courses/learn-harness-engineering/docs/vi/lectures/lecture-08-why-feature-lists-are-harness-primitives/code/feature_list.json delete mode 100644 courses/learn-harness-engineering/docs/vi/lectures/lecture-08-why-feature-lists-are-harness-primitives/code/index.md delete mode 100644 courses/learn-harness-engineering/docs/vi/lectures/lecture-08-why-feature-lists-are-harness-primitives/code/pass-gate-policy.md delete mode 100644 courses/learn-harness-engineering/docs/vi/lectures/lecture-08-why-feature-lists-are-harness-primitives/index.md delete mode 100644 courses/learn-harness-engineering/docs/vi/lectures/lecture-09-why-agents-declare-victory-too-early/code/clean-state-checklist.md delete mode 100644 courses/learn-harness-engineering/docs/vi/lectures/lecture-09-why-agents-declare-victory-too-early/code/index.md delete mode 100644 courses/learn-harness-engineering/docs/vi/lectures/lecture-09-why-agents-declare-victory-too-early/code/victory-detector.ts delete mode 100644 courses/learn-harness-engineering/docs/vi/lectures/lecture-09-why-agents-declare-victory-too-early/index.md delete mode 100644 courses/learn-harness-engineering/docs/vi/lectures/lecture-10-why-end-to-end-testing-changes-results/code/architecture-rules.md delete mode 100644 courses/learn-harness-engineering/docs/vi/lectures/lecture-10-why-end-to-end-testing-changes-results/code/e2e-runner.ts delete mode 100644 courses/learn-harness-engineering/docs/vi/lectures/lecture-10-why-end-to-end-testing-changes-results/code/index.md delete mode 100644 courses/learn-harness-engineering/docs/vi/lectures/lecture-10-why-end-to-end-testing-changes-results/code/review-feedback-to-rule.md delete mode 100644 courses/learn-harness-engineering/docs/vi/lectures/lecture-10-why-end-to-end-testing-changes-results/index.md delete mode 100644 courses/learn-harness-engineering/docs/vi/lectures/lecture-11-why-observability-belongs-inside-the-harness/code/evaluator-rubric.md delete mode 100644 courses/learn-harness-engineering/docs/vi/lectures/lecture-11-why-observability-belongs-inside-the-harness/code/index.md delete mode 100644 courses/learn-harness-engineering/docs/vi/lectures/lecture-11-why-observability-belongs-inside-the-harness/code/runtime-logger.ts delete mode 100644 courses/learn-harness-engineering/docs/vi/lectures/lecture-11-why-observability-belongs-inside-the-harness/code/sprint-contract.md delete mode 100644 courses/learn-harness-engineering/docs/vi/lectures/lecture-11-why-observability-belongs-inside-the-harness/index.md delete mode 100644 courses/learn-harness-engineering/docs/vi/lectures/lecture-12-why-every-session-must-leave-a-clean-state/code/benchmark-comparison-template.md delete mode 100644 courses/learn-harness-engineering/docs/vi/lectures/lecture-12-why-every-session-must-leave-a-clean-state/code/benchmark-runner.ts delete mode 100644 courses/learn-harness-engineering/docs/vi/lectures/lecture-12-why-every-session-must-leave-a-clean-state/code/cleanup-loop.md delete mode 100644 courses/learn-harness-engineering/docs/vi/lectures/lecture-12-why-every-session-must-leave-a-clean-state/code/cleanup-scanner.ts delete mode 100644 courses/learn-harness-engineering/docs/vi/lectures/lecture-12-why-every-session-must-leave-a-clean-state/code/index.md delete mode 100644 courses/learn-harness-engineering/docs/vi/lectures/lecture-12-why-every-session-must-leave-a-clean-state/index.md delete mode 100644 courses/learn-harness-engineering/docs/vi/projects/index.md delete mode 100644 courses/learn-harness-engineering/docs/vi/projects/project-01-baseline-vs-minimal-harness/index.md delete mode 100644 courses/learn-harness-engineering/docs/vi/projects/project-02-agent-readable-workspace/index.md delete mode 100644 courses/learn-harness-engineering/docs/vi/projects/project-03-multi-session-continuity/index.md delete mode 100644 courses/learn-harness-engineering/docs/vi/projects/project-04-incremental-indexing/index.md delete mode 100644 courses/learn-harness-engineering/docs/vi/projects/project-05-grounded-qa-verification/index.md delete mode 100644 courses/learn-harness-engineering/docs/vi/projects/project-06-runtime-observability-and-debugging/index.md delete mode 100644 courses/learn-harness-engineering/docs/vi/resources/index.md delete mode 100644 courses/learn-harness-engineering/docs/vi/resources/openai-advanced/index.md delete mode 100644 courses/learn-harness-engineering/docs/vi/resources/openai-advanced/repo-template/AGENTS.md delete mode 100644 courses/learn-harness-engineering/docs/vi/resources/openai-advanced/repo-template/ARCHITECTURE.md delete mode 100644 courses/learn-harness-engineering/docs/vi/resources/openai-advanced/repo-template/docs/DESIGN.md delete mode 100644 courses/learn-harness-engineering/docs/vi/resources/openai-advanced/repo-template/docs/FRONTEND.md delete mode 100644 courses/learn-harness-engineering/docs/vi/resources/openai-advanced/repo-template/docs/PLANS.md delete mode 100644 courses/learn-harness-engineering/docs/vi/resources/openai-advanced/repo-template/docs/PRODUCT_SENSE.md delete mode 100644 courses/learn-harness-engineering/docs/vi/resources/openai-advanced/repo-template/docs/QUALITY_SCORE.md delete mode 100644 courses/learn-harness-engineering/docs/vi/resources/openai-advanced/repo-template/docs/RELIABILITY.md delete mode 100644 courses/learn-harness-engineering/docs/vi/resources/openai-advanced/repo-template/docs/SECURITY.md delete mode 100644 courses/learn-harness-engineering/docs/vi/resources/openai-advanced/repo-template/docs/design-docs/core-beliefs.md delete mode 100644 courses/learn-harness-engineering/docs/vi/resources/openai-advanced/repo-template/docs/design-docs/index.md delete mode 100644 courses/learn-harness-engineering/docs/vi/resources/openai-advanced/repo-template/docs/exec-plans/active/index.md delete mode 100644 courses/learn-harness-engineering/docs/vi/resources/openai-advanced/repo-template/docs/exec-plans/completed/index.md delete mode 100644 courses/learn-harness-engineering/docs/vi/resources/openai-advanced/repo-template/docs/exec-plans/tech-debt-tracker.md delete mode 100644 courses/learn-harness-engineering/docs/vi/resources/openai-advanced/repo-template/docs/generated/db-schema.md delete mode 100644 courses/learn-harness-engineering/docs/vi/resources/openai-advanced/repo-template/docs/product-specs/index.md delete mode 100644 courses/learn-harness-engineering/docs/vi/resources/openai-advanced/repo-template/docs/product-specs/new-user-onboarding.md delete mode 100644 courses/learn-harness-engineering/docs/vi/resources/openai-advanced/repo-template/docs/references/design-system-reference-llms.txt delete mode 100644 courses/learn-harness-engineering/docs/vi/resources/openai-advanced/repo-template/docs/references/nixpacks-llms.txt delete mode 100644 courses/learn-harness-engineering/docs/vi/resources/openai-advanced/repo-template/docs/references/uv-llms.txt delete mode 100644 courses/learn-harness-engineering/docs/vi/resources/openai-advanced/repo-template/index.md delete mode 100644 courses/learn-harness-engineering/docs/vi/resources/openai-advanced/sops/chrome-devtools-validation-loop.md delete mode 100644 courses/learn-harness-engineering/docs/vi/resources/openai-advanced/sops/encode-knowledge-into-repo.md delete mode 100644 courses/learn-harness-engineering/docs/vi/resources/openai-advanced/sops/index.md delete mode 100644 courses/learn-harness-engineering/docs/vi/resources/openai-advanced/sops/layered-domain-architecture.md delete mode 100644 courses/learn-harness-engineering/docs/vi/resources/openai-advanced/sops/observability-feedback-loop.md delete mode 100644 courses/learn-harness-engineering/docs/vi/resources/reference/coding-agent-startup-flow.md delete mode 100644 courses/learn-harness-engineering/docs/vi/resources/reference/index.md delete mode 100644 courses/learn-harness-engineering/docs/vi/resources/reference/initializer-agent-playbook.md delete mode 100644 courses/learn-harness-engineering/docs/vi/resources/reference/method-map.md delete mode 100644 courses/learn-harness-engineering/docs/vi/resources/reference/prompt-calibration.md delete mode 100644 courses/learn-harness-engineering/docs/vi/resources/templates/AGENTS.md delete mode 100644 courses/learn-harness-engineering/docs/vi/resources/templates/CLAUDE.md delete mode 100644 courses/learn-harness-engineering/docs/vi/resources/templates/claude-progress.md delete mode 100644 courses/learn-harness-engineering/docs/vi/resources/templates/clean-state-checklist.md delete mode 100644 courses/learn-harness-engineering/docs/vi/resources/templates/evaluator-rubric.md delete mode 100644 courses/learn-harness-engineering/docs/vi/resources/templates/feature_list.json delete mode 100644 courses/learn-harness-engineering/docs/vi/resources/templates/index.md delete mode 100755 courses/learn-harness-engineering/docs/vi/resources/templates/init.sh delete mode 100644 courses/learn-harness-engineering/docs/vi/resources/templates/quality-document.md delete mode 100644 courses/learn-harness-engineering/docs/vi/resources/templates/session-handoff.md delete mode 100644 courses/learn-harness-engineering/docs/vi/skills/index.md delete mode 100644 courses/learn-harness-engineering/docs/zh-TW/index.md delete mode 100644 courses/learn-harness-engineering/docs/zh-TW/lectures/lecture-01-why-capable-agents-still-fail/code/failure-pattern-demo.ts delete mode 100644 courses/learn-harness-engineering/docs/zh-TW/lectures/lecture-01-why-capable-agents-still-fail/code/failure-signals-checklist.md delete mode 100644 courses/learn-harness-engineering/docs/zh-TW/lectures/lecture-01-why-capable-agents-still-fail/code/index.md delete mode 100644 courses/learn-harness-engineering/docs/zh-TW/lectures/lecture-01-why-capable-agents-still-fail/code/underspecified-task.md delete mode 100644 courses/learn-harness-engineering/docs/zh-TW/lectures/lecture-01-why-capable-agents-still-fail/index.md delete mode 100644 courses/learn-harness-engineering/docs/zh-TW/lectures/lecture-02-what-a-harness-actually-is/code/harness-components.md delete mode 100644 courses/learn-harness-engineering/docs/zh-TW/lectures/lecture-02-what-a-harness-actually-is/code/harness-vs-no-harness.ts delete mode 100644 courses/learn-harness-engineering/docs/zh-TW/lectures/lecture-02-what-a-harness-actually-is/code/index.md delete mode 100644 courses/learn-harness-engineering/docs/zh-TW/lectures/lecture-02-what-a-harness-actually-is/code/minimal-harness-loop.ts delete mode 100644 courses/learn-harness-engineering/docs/zh-TW/lectures/lecture-02-what-a-harness-actually-is/index.md delete mode 100644 courses/learn-harness-engineering/docs/zh-TW/lectures/lecture-03-why-the-repository-must-become-the-system-of-record/code/index.md delete mode 100644 courses/learn-harness-engineering/docs/zh-TW/lectures/lecture-03-why-the-repository-must-become-the-system-of-record/code/repo-knowledge-layout.txt delete mode 100644 courses/learn-harness-engineering/docs/zh-TW/lectures/lecture-03-why-the-repository-must-become-the-system-of-record/code/repo-reader.ts delete mode 100644 courses/learn-harness-engineering/docs/zh-TW/lectures/lecture-03-why-the-repository-must-become-the-system-of-record/code/system-of-record-checklist.md delete mode 100644 courses/learn-harness-engineering/docs/zh-TW/lectures/lecture-03-why-the-repository-must-become-the-system-of-record/index.md delete mode 100644 courses/learn-harness-engineering/docs/zh-TW/lectures/lecture-04-why-one-giant-instruction-file-fails/code/AGENTS-short.md delete mode 100644 courses/learn-harness-engineering/docs/zh-TW/lectures/lecture-04-why-one-giant-instruction-file-fails/code/anti-patterns.md delete mode 100644 courses/learn-harness-engineering/docs/zh-TW/lectures/lecture-04-why-one-giant-instruction-file-fails/code/index.md delete mode 100644 courses/learn-harness-engineering/docs/zh-TW/lectures/lecture-04-why-one-giant-instruction-file-fails/code/split-vs-monolithic.ts delete mode 100644 courses/learn-harness-engineering/docs/zh-TW/lectures/lecture-04-why-one-giant-instruction-file-fails/index.md delete mode 100644 courses/learn-harness-engineering/docs/zh-TW/lectures/lecture-05-why-long-running-tasks-lose-continuity/code/continuity-checklist.md delete mode 100644 courses/learn-harness-engineering/docs/zh-TW/lectures/lecture-05-why-long-running-tasks-lose-continuity/code/index.md delete mode 100644 courses/learn-harness-engineering/docs/zh-TW/lectures/lecture-05-why-long-running-tasks-lose-continuity/code/session-handoff.md delete mode 100644 courses/learn-harness-engineering/docs/zh-TW/lectures/lecture-05-why-long-running-tasks-lose-continuity/code/session-simulator.ts delete mode 100644 courses/learn-harness-engineering/docs/zh-TW/lectures/lecture-05-why-long-running-tasks-lose-continuity/index.md delete mode 100644 courses/learn-harness-engineering/docs/zh-TW/lectures/lecture-06-why-initialization-needs-its-own-phase/code/index.md delete mode 100644 courses/learn-harness-engineering/docs/zh-TW/lectures/lecture-06-why-initialization-needs-its-own-phase/code/init-check.ts delete mode 100644 courses/learn-harness-engineering/docs/zh-TW/lectures/lecture-06-why-initialization-needs-its-own-phase/code/init.sh delete mode 100644 courses/learn-harness-engineering/docs/zh-TW/lectures/lecture-06-why-initialization-needs-its-own-phase/code/initializer-output-checklist.md delete mode 100644 courses/learn-harness-engineering/docs/zh-TW/lectures/lecture-06-why-initialization-needs-its-own-phase/index.md delete mode 100644 courses/learn-harness-engineering/docs/zh-TW/lectures/lecture-07-why-agents-overreach-and-under-finish/code/index.md delete mode 100644 courses/learn-harness-engineering/docs/zh-TW/lectures/lecture-07-why-agents-overreach-and-under-finish/code/next-task-template.md delete mode 100644 courses/learn-harness-engineering/docs/zh-TW/lectures/lecture-07-why-agents-overreach-and-under-finish/code/scope-surface-example.md delete mode 100644 courses/learn-harness-engineering/docs/zh-TW/lectures/lecture-07-why-agents-overreach-and-under-finish/code/scope-tracker.ts delete mode 100644 courses/learn-harness-engineering/docs/zh-TW/lectures/lecture-07-why-agents-overreach-and-under-finish/index.md delete mode 100644 courses/learn-harness-engineering/docs/zh-TW/lectures/lecture-08-why-feature-lists-are-harness-primitives/code/feature-list-validator.ts delete mode 100644 courses/learn-harness-engineering/docs/zh-TW/lectures/lecture-08-why-feature-lists-are-harness-primitives/code/feature_list.json delete mode 100644 courses/learn-harness-engineering/docs/zh-TW/lectures/lecture-08-why-feature-lists-are-harness-primitives/code/index.md delete mode 100644 courses/learn-harness-engineering/docs/zh-TW/lectures/lecture-08-why-feature-lists-are-harness-primitives/code/pass-gate-policy.md delete mode 100644 courses/learn-harness-engineering/docs/zh-TW/lectures/lecture-08-why-feature-lists-are-harness-primitives/index.md delete mode 100644 courses/learn-harness-engineering/docs/zh-TW/lectures/lecture-09-why-agents-declare-victory-too-early/code/clean-state-checklist.md delete mode 100644 courses/learn-harness-engineering/docs/zh-TW/lectures/lecture-09-why-agents-declare-victory-too-early/code/index.md delete mode 100644 courses/learn-harness-engineering/docs/zh-TW/lectures/lecture-09-why-agents-declare-victory-too-early/code/victory-detector.ts delete mode 100644 courses/learn-harness-engineering/docs/zh-TW/lectures/lecture-09-why-agents-declare-victory-too-early/index.md delete mode 100644 courses/learn-harness-engineering/docs/zh-TW/lectures/lecture-10-why-end-to-end-testing-changes-results/code/architecture-rules.md delete mode 100644 courses/learn-harness-engineering/docs/zh-TW/lectures/lecture-10-why-end-to-end-testing-changes-results/code/e2e-runner.ts delete mode 100644 courses/learn-harness-engineering/docs/zh-TW/lectures/lecture-10-why-end-to-end-testing-changes-results/code/index.md delete mode 100644 courses/learn-harness-engineering/docs/zh-TW/lectures/lecture-10-why-end-to-end-testing-changes-results/code/review-feedback-to-rule.md delete mode 100644 courses/learn-harness-engineering/docs/zh-TW/lectures/lecture-10-why-end-to-end-testing-changes-results/index.md delete mode 100644 courses/learn-harness-engineering/docs/zh-TW/lectures/lecture-11-why-observability-belongs-inside-the-harness/code/evaluator-rubric.md delete mode 100644 courses/learn-harness-engineering/docs/zh-TW/lectures/lecture-11-why-observability-belongs-inside-the-harness/code/index.md delete mode 100644 courses/learn-harness-engineering/docs/zh-TW/lectures/lecture-11-why-observability-belongs-inside-the-harness/code/runtime-logger.ts delete mode 100644 courses/learn-harness-engineering/docs/zh-TW/lectures/lecture-11-why-observability-belongs-inside-the-harness/code/sprint-contract.md delete mode 100644 courses/learn-harness-engineering/docs/zh-TW/lectures/lecture-11-why-observability-belongs-inside-the-harness/index.md delete mode 100644 courses/learn-harness-engineering/docs/zh-TW/lectures/lecture-12-why-every-session-must-leave-a-clean-state/code/benchmark-comparison-template.md delete mode 100644 courses/learn-harness-engineering/docs/zh-TW/lectures/lecture-12-why-every-session-must-leave-a-clean-state/code/benchmark-runner.ts delete mode 100644 courses/learn-harness-engineering/docs/zh-TW/lectures/lecture-12-why-every-session-must-leave-a-clean-state/code/cleanup-loop.md delete mode 100644 courses/learn-harness-engineering/docs/zh-TW/lectures/lecture-12-why-every-session-must-leave-a-clean-state/code/cleanup-scanner.ts delete mode 100644 courses/learn-harness-engineering/docs/zh-TW/lectures/lecture-12-why-every-session-must-leave-a-clean-state/code/index.md delete mode 100644 courses/learn-harness-engineering/docs/zh-TW/lectures/lecture-12-why-every-session-must-leave-a-clean-state/index.md delete mode 100644 courses/learn-harness-engineering/docs/zh-TW/projects/index.md delete mode 100644 courses/learn-harness-engineering/docs/zh-TW/projects/project-01-baseline-vs-minimal-harness/index.md delete mode 100644 courses/learn-harness-engineering/docs/zh-TW/projects/project-02-agent-readable-workspace/index.md delete mode 100644 courses/learn-harness-engineering/docs/zh-TW/projects/project-03-multi-session-continuity/index.md delete mode 100644 courses/learn-harness-engineering/docs/zh-TW/projects/project-04-incremental-indexing/index.md delete mode 100644 courses/learn-harness-engineering/docs/zh-TW/projects/project-05-grounded-qa-verification/index.md delete mode 100644 courses/learn-harness-engineering/docs/zh-TW/projects/project-06-runtime-observability-and-debugging/index.md delete mode 100644 courses/learn-harness-engineering/docs/zh-TW/resources/index.md delete mode 100644 courses/learn-harness-engineering/docs/zh-TW/resources/openai-advanced/index.md delete mode 100644 courses/learn-harness-engineering/docs/zh-TW/resources/openai-advanced/repo-template/AGENTS.md delete mode 100644 courses/learn-harness-engineering/docs/zh-TW/resources/openai-advanced/repo-template/ARCHITECTURE.md delete mode 100644 courses/learn-harness-engineering/docs/zh-TW/resources/openai-advanced/repo-template/docs/DESIGN.md delete mode 100644 courses/learn-harness-engineering/docs/zh-TW/resources/openai-advanced/repo-template/docs/FRONTEND.md delete mode 100644 courses/learn-harness-engineering/docs/zh-TW/resources/openai-advanced/repo-template/docs/PLANS.md delete mode 100644 courses/learn-harness-engineering/docs/zh-TW/resources/openai-advanced/repo-template/docs/PRODUCT_SENSE.md delete mode 100644 courses/learn-harness-engineering/docs/zh-TW/resources/openai-advanced/repo-template/docs/QUALITY_SCORE.md delete mode 100644 courses/learn-harness-engineering/docs/zh-TW/resources/openai-advanced/repo-template/docs/RELIABILITY.md delete mode 100644 courses/learn-harness-engineering/docs/zh-TW/resources/openai-advanced/repo-template/docs/SECURITY.md delete mode 100644 courses/learn-harness-engineering/docs/zh-TW/resources/openai-advanced/repo-template/docs/design-docs/core-beliefs.md delete mode 100644 courses/learn-harness-engineering/docs/zh-TW/resources/openai-advanced/repo-template/docs/design-docs/index.md delete mode 100644 courses/learn-harness-engineering/docs/zh-TW/resources/openai-advanced/repo-template/docs/exec-plans/active/index.md delete mode 100644 courses/learn-harness-engineering/docs/zh-TW/resources/openai-advanced/repo-template/docs/exec-plans/completed/index.md delete mode 100644 courses/learn-harness-engineering/docs/zh-TW/resources/openai-advanced/repo-template/docs/exec-plans/tech-debt-tracker.md delete mode 100644 courses/learn-harness-engineering/docs/zh-TW/resources/openai-advanced/repo-template/docs/generated/db-schema.md delete mode 100644 courses/learn-harness-engineering/docs/zh-TW/resources/openai-advanced/repo-template/docs/product-specs/index.md delete mode 100644 courses/learn-harness-engineering/docs/zh-TW/resources/openai-advanced/repo-template/docs/product-specs/new-user-onboarding.md delete mode 100644 courses/learn-harness-engineering/docs/zh-TW/resources/openai-advanced/repo-template/docs/references/design-system-reference-llms.txt delete mode 100644 courses/learn-harness-engineering/docs/zh-TW/resources/openai-advanced/repo-template/docs/references/nixpacks-llms.txt delete mode 100644 courses/learn-harness-engineering/docs/zh-TW/resources/openai-advanced/repo-template/docs/references/uv-llms.txt delete mode 100644 courses/learn-harness-engineering/docs/zh-TW/resources/openai-advanced/repo-template/index.md delete mode 100644 courses/learn-harness-engineering/docs/zh-TW/resources/openai-advanced/sops/chrome-devtools-validation-loop.md delete mode 100644 courses/learn-harness-engineering/docs/zh-TW/resources/openai-advanced/sops/encode-knowledge-into-repo.md delete mode 100644 courses/learn-harness-engineering/docs/zh-TW/resources/openai-advanced/sops/index.md delete mode 100644 courses/learn-harness-engineering/docs/zh-TW/resources/openai-advanced/sops/layered-domain-architecture.md delete mode 100644 courses/learn-harness-engineering/docs/zh-TW/resources/openai-advanced/sops/observability-feedback-loop.md delete mode 100644 courses/learn-harness-engineering/docs/zh-TW/resources/reference/coding-agent-startup-flow.md delete mode 100644 courses/learn-harness-engineering/docs/zh-TW/resources/reference/index.md delete mode 100644 courses/learn-harness-engineering/docs/zh-TW/resources/reference/initializer-agent-playbook.md delete mode 100644 courses/learn-harness-engineering/docs/zh-TW/resources/reference/method-map.md delete mode 100644 courses/learn-harness-engineering/docs/zh-TW/resources/reference/prompt-calibration.md delete mode 100644 courses/learn-harness-engineering/docs/zh-TW/resources/templates/AGENTS.md delete mode 100644 courses/learn-harness-engineering/docs/zh-TW/resources/templates/CLAUDE.md delete mode 100644 courses/learn-harness-engineering/docs/zh-TW/resources/templates/claude-progress.md delete mode 100644 courses/learn-harness-engineering/docs/zh-TW/resources/templates/clean-state-checklist.md delete mode 100644 courses/learn-harness-engineering/docs/zh-TW/resources/templates/evaluator-rubric.md delete mode 100644 courses/learn-harness-engineering/docs/zh-TW/resources/templates/feature_list.json delete mode 100644 courses/learn-harness-engineering/docs/zh-TW/resources/templates/index.md delete mode 100755 courses/learn-harness-engineering/docs/zh-TW/resources/templates/init.sh delete mode 100644 courses/learn-harness-engineering/docs/zh-TW/resources/templates/quality-document.md delete mode 100644 courses/learn-harness-engineering/docs/zh-TW/resources/templates/session-handoff.md delete mode 100644 courses/learn-harness-engineering/docs/zh-TW/skills/index.md delete mode 100644 courses/learn-harness-engineering/docs/zh/index.md delete mode 100644 courses/learn-harness-engineering/docs/zh/lectures/lecture-01-why-capable-agents-still-fail/code/failure-pattern-demo.ts delete mode 100644 courses/learn-harness-engineering/docs/zh/lectures/lecture-01-why-capable-agents-still-fail/code/failure-signals-checklist.md delete mode 100644 courses/learn-harness-engineering/docs/zh/lectures/lecture-01-why-capable-agents-still-fail/code/index.md delete mode 100644 courses/learn-harness-engineering/docs/zh/lectures/lecture-01-why-capable-agents-still-fail/code/underspecified-task.md delete mode 100644 courses/learn-harness-engineering/docs/zh/lectures/lecture-01-why-capable-agents-still-fail/index.md delete mode 100644 courses/learn-harness-engineering/docs/zh/lectures/lecture-02-what-a-harness-actually-is/code/harness-components.md delete mode 100644 courses/learn-harness-engineering/docs/zh/lectures/lecture-02-what-a-harness-actually-is/code/harness-vs-no-harness.ts delete mode 100644 courses/learn-harness-engineering/docs/zh/lectures/lecture-02-what-a-harness-actually-is/code/index.md delete mode 100644 courses/learn-harness-engineering/docs/zh/lectures/lecture-02-what-a-harness-actually-is/code/minimal-harness-loop.ts delete mode 100644 courses/learn-harness-engineering/docs/zh/lectures/lecture-02-what-a-harness-actually-is/index.md delete mode 100644 courses/learn-harness-engineering/docs/zh/lectures/lecture-03-why-the-repository-must-become-the-system-of-record/code/index.md delete mode 100644 courses/learn-harness-engineering/docs/zh/lectures/lecture-03-why-the-repository-must-become-the-system-of-record/code/repo-knowledge-layout.txt delete mode 100644 courses/learn-harness-engineering/docs/zh/lectures/lecture-03-why-the-repository-must-become-the-system-of-record/code/repo-reader.ts delete mode 100644 courses/learn-harness-engineering/docs/zh/lectures/lecture-03-why-the-repository-must-become-the-system-of-record/code/system-of-record-checklist.md delete mode 100644 courses/learn-harness-engineering/docs/zh/lectures/lecture-03-why-the-repository-must-become-the-system-of-record/index.md delete mode 100644 courses/learn-harness-engineering/docs/zh/lectures/lecture-04-why-one-giant-instruction-file-fails/code/AGENTS-short.md delete mode 100644 courses/learn-harness-engineering/docs/zh/lectures/lecture-04-why-one-giant-instruction-file-fails/code/anti-patterns.md delete mode 100644 courses/learn-harness-engineering/docs/zh/lectures/lecture-04-why-one-giant-instruction-file-fails/code/index.md delete mode 100644 courses/learn-harness-engineering/docs/zh/lectures/lecture-04-why-one-giant-instruction-file-fails/code/split-vs-monolithic.ts delete mode 100644 courses/learn-harness-engineering/docs/zh/lectures/lecture-04-why-one-giant-instruction-file-fails/index.md delete mode 100644 courses/learn-harness-engineering/docs/zh/lectures/lecture-05-why-long-running-tasks-lose-continuity/code/continuity-checklist.md delete mode 100644 courses/learn-harness-engineering/docs/zh/lectures/lecture-05-why-long-running-tasks-lose-continuity/code/index.md delete mode 100644 courses/learn-harness-engineering/docs/zh/lectures/lecture-05-why-long-running-tasks-lose-continuity/code/session-handoff.md delete mode 100644 courses/learn-harness-engineering/docs/zh/lectures/lecture-05-why-long-running-tasks-lose-continuity/code/session-simulator.ts delete mode 100644 courses/learn-harness-engineering/docs/zh/lectures/lecture-05-why-long-running-tasks-lose-continuity/index.md delete mode 100644 courses/learn-harness-engineering/docs/zh/lectures/lecture-06-why-initialization-needs-its-own-phase/code/index.md delete mode 100644 courses/learn-harness-engineering/docs/zh/lectures/lecture-06-why-initialization-needs-its-own-phase/code/init-check.ts delete mode 100644 courses/learn-harness-engineering/docs/zh/lectures/lecture-06-why-initialization-needs-its-own-phase/code/init.sh delete mode 100644 courses/learn-harness-engineering/docs/zh/lectures/lecture-06-why-initialization-needs-its-own-phase/code/initializer-output-checklist.md delete mode 100644 courses/learn-harness-engineering/docs/zh/lectures/lecture-06-why-initialization-needs-its-own-phase/index.md delete mode 100644 courses/learn-harness-engineering/docs/zh/lectures/lecture-07-why-agents-overreach-and-under-finish/code/index.md delete mode 100644 courses/learn-harness-engineering/docs/zh/lectures/lecture-07-why-agents-overreach-and-under-finish/code/next-task-template.md delete mode 100644 courses/learn-harness-engineering/docs/zh/lectures/lecture-07-why-agents-overreach-and-under-finish/code/scope-surface-example.md delete mode 100644 courses/learn-harness-engineering/docs/zh/lectures/lecture-07-why-agents-overreach-and-under-finish/code/scope-tracker.ts delete mode 100644 courses/learn-harness-engineering/docs/zh/lectures/lecture-07-why-agents-overreach-and-under-finish/index.md delete mode 100644 courses/learn-harness-engineering/docs/zh/lectures/lecture-08-why-feature-lists-are-harness-primitives/code/feature-list-validator.ts delete mode 100644 courses/learn-harness-engineering/docs/zh/lectures/lecture-08-why-feature-lists-are-harness-primitives/code/feature_list.json delete mode 100644 courses/learn-harness-engineering/docs/zh/lectures/lecture-08-why-feature-lists-are-harness-primitives/code/index.md delete mode 100644 courses/learn-harness-engineering/docs/zh/lectures/lecture-08-why-feature-lists-are-harness-primitives/code/pass-gate-policy.md delete mode 100644 courses/learn-harness-engineering/docs/zh/lectures/lecture-08-why-feature-lists-are-harness-primitives/index.md delete mode 100644 courses/learn-harness-engineering/docs/zh/lectures/lecture-09-why-agents-declare-victory-too-early/code/clean-state-checklist.md delete mode 100644 courses/learn-harness-engineering/docs/zh/lectures/lecture-09-why-agents-declare-victory-too-early/code/index.md delete mode 100644 courses/learn-harness-engineering/docs/zh/lectures/lecture-09-why-agents-declare-victory-too-early/code/victory-detector.ts delete mode 100644 courses/learn-harness-engineering/docs/zh/lectures/lecture-09-why-agents-declare-victory-too-early/index.md delete mode 100644 courses/learn-harness-engineering/docs/zh/lectures/lecture-10-why-end-to-end-testing-changes-results/code/architecture-rules.md delete mode 100644 courses/learn-harness-engineering/docs/zh/lectures/lecture-10-why-end-to-end-testing-changes-results/code/e2e-runner.ts delete mode 100644 courses/learn-harness-engineering/docs/zh/lectures/lecture-10-why-end-to-end-testing-changes-results/code/index.md delete mode 100644 courses/learn-harness-engineering/docs/zh/lectures/lecture-10-why-end-to-end-testing-changes-results/code/review-feedback-to-rule.md delete mode 100644 courses/learn-harness-engineering/docs/zh/lectures/lecture-10-why-end-to-end-testing-changes-results/index.md delete mode 100644 courses/learn-harness-engineering/docs/zh/lectures/lecture-11-why-observability-belongs-inside-the-harness/code/evaluator-rubric.md delete mode 100644 courses/learn-harness-engineering/docs/zh/lectures/lecture-11-why-observability-belongs-inside-the-harness/code/index.md delete mode 100644 courses/learn-harness-engineering/docs/zh/lectures/lecture-11-why-observability-belongs-inside-the-harness/code/runtime-logger.ts delete mode 100644 courses/learn-harness-engineering/docs/zh/lectures/lecture-11-why-observability-belongs-inside-the-harness/code/sprint-contract.md delete mode 100644 courses/learn-harness-engineering/docs/zh/lectures/lecture-11-why-observability-belongs-inside-the-harness/index.md delete mode 100644 courses/learn-harness-engineering/docs/zh/lectures/lecture-12-why-every-session-must-leave-a-clean-state/code/benchmark-comparison-template.md delete mode 100644 courses/learn-harness-engineering/docs/zh/lectures/lecture-12-why-every-session-must-leave-a-clean-state/code/benchmark-runner.ts delete mode 100644 courses/learn-harness-engineering/docs/zh/lectures/lecture-12-why-every-session-must-leave-a-clean-state/code/cleanup-loop.md delete mode 100644 courses/learn-harness-engineering/docs/zh/lectures/lecture-12-why-every-session-must-leave-a-clean-state/code/cleanup-scanner.ts delete mode 100644 courses/learn-harness-engineering/docs/zh/lectures/lecture-12-why-every-session-must-leave-a-clean-state/code/index.md delete mode 100644 courses/learn-harness-engineering/docs/zh/lectures/lecture-12-why-every-session-must-leave-a-clean-state/index.md delete mode 100644 courses/learn-harness-engineering/docs/zh/projects/index.md delete mode 100644 courses/learn-harness-engineering/docs/zh/projects/project-01-baseline-vs-minimal-harness/index.md delete mode 100644 courses/learn-harness-engineering/docs/zh/projects/project-02-agent-readable-workspace/index.md delete mode 100644 courses/learn-harness-engineering/docs/zh/projects/project-03-multi-session-continuity/index.md delete mode 100644 courses/learn-harness-engineering/docs/zh/projects/project-04-incremental-indexing/index.md delete mode 100644 courses/learn-harness-engineering/docs/zh/projects/project-05-grounded-qa-verification/index.md delete mode 100644 courses/learn-harness-engineering/docs/zh/projects/project-06-runtime-observability-and-debugging/index.md delete mode 100644 courses/learn-harness-engineering/docs/zh/resources/index.md delete mode 100644 courses/learn-harness-engineering/docs/zh/resources/openai-advanced/index.md delete mode 100644 courses/learn-harness-engineering/docs/zh/resources/openai-advanced/repo-template/AGENTS.md delete mode 100644 courses/learn-harness-engineering/docs/zh/resources/openai-advanced/repo-template/ARCHITECTURE.md delete mode 100644 courses/learn-harness-engineering/docs/zh/resources/openai-advanced/repo-template/docs/DESIGN.md delete mode 100644 courses/learn-harness-engineering/docs/zh/resources/openai-advanced/repo-template/docs/FRONTEND.md delete mode 100644 courses/learn-harness-engineering/docs/zh/resources/openai-advanced/repo-template/docs/PLANS.md delete mode 100644 courses/learn-harness-engineering/docs/zh/resources/openai-advanced/repo-template/docs/PRODUCT_SENSE.md delete mode 100644 courses/learn-harness-engineering/docs/zh/resources/openai-advanced/repo-template/docs/QUALITY_SCORE.md delete mode 100644 courses/learn-harness-engineering/docs/zh/resources/openai-advanced/repo-template/docs/RELIABILITY.md delete mode 100644 courses/learn-harness-engineering/docs/zh/resources/openai-advanced/repo-template/docs/SECURITY.md delete mode 100644 courses/learn-harness-engineering/docs/zh/resources/openai-advanced/repo-template/docs/design-docs/core-beliefs.md delete mode 100644 courses/learn-harness-engineering/docs/zh/resources/openai-advanced/repo-template/docs/design-docs/index.md delete mode 100644 courses/learn-harness-engineering/docs/zh/resources/openai-advanced/repo-template/docs/exec-plans/active/index.md delete mode 100644 courses/learn-harness-engineering/docs/zh/resources/openai-advanced/repo-template/docs/exec-plans/completed/index.md delete mode 100644 courses/learn-harness-engineering/docs/zh/resources/openai-advanced/repo-template/docs/exec-plans/tech-debt-tracker.md delete mode 100644 courses/learn-harness-engineering/docs/zh/resources/openai-advanced/repo-template/docs/generated/db-schema.md delete mode 100644 courses/learn-harness-engineering/docs/zh/resources/openai-advanced/repo-template/docs/product-specs/index.md delete mode 100644 courses/learn-harness-engineering/docs/zh/resources/openai-advanced/repo-template/docs/product-specs/new-user-onboarding.md delete mode 100644 courses/learn-harness-engineering/docs/zh/resources/openai-advanced/repo-template/docs/references/design-system-reference-llms.txt delete mode 100644 courses/learn-harness-engineering/docs/zh/resources/openai-advanced/repo-template/docs/references/nixpacks-llms.txt delete mode 100644 courses/learn-harness-engineering/docs/zh/resources/openai-advanced/repo-template/docs/references/uv-llms.txt delete mode 100644 courses/learn-harness-engineering/docs/zh/resources/openai-advanced/repo-template/index.md delete mode 100644 courses/learn-harness-engineering/docs/zh/resources/openai-advanced/sops/chrome-devtools-validation-loop.md delete mode 100644 courses/learn-harness-engineering/docs/zh/resources/openai-advanced/sops/encode-knowledge-into-repo.md delete mode 100644 courses/learn-harness-engineering/docs/zh/resources/openai-advanced/sops/index.md delete mode 100644 courses/learn-harness-engineering/docs/zh/resources/openai-advanced/sops/layered-domain-architecture.md delete mode 100644 courses/learn-harness-engineering/docs/zh/resources/openai-advanced/sops/observability-feedback-loop.md delete mode 100644 courses/learn-harness-engineering/docs/zh/resources/reference/coding-agent-startup-flow.md delete mode 100644 courses/learn-harness-engineering/docs/zh/resources/reference/index.md delete mode 100644 courses/learn-harness-engineering/docs/zh/resources/reference/initializer-agent-playbook.md delete mode 100644 courses/learn-harness-engineering/docs/zh/resources/reference/method-map.md delete mode 100644 courses/learn-harness-engineering/docs/zh/resources/reference/prompt-calibration.md delete mode 100644 courses/learn-harness-engineering/docs/zh/resources/templates/AGENTS.md delete mode 100644 courses/learn-harness-engineering/docs/zh/resources/templates/CLAUDE.md delete mode 100644 courses/learn-harness-engineering/docs/zh/resources/templates/claude-progress.md delete mode 100644 courses/learn-harness-engineering/docs/zh/resources/templates/clean-state-checklist.md delete mode 100644 courses/learn-harness-engineering/docs/zh/resources/templates/evaluator-rubric.md delete mode 100644 courses/learn-harness-engineering/docs/zh/resources/templates/feature_list.json delete mode 100644 courses/learn-harness-engineering/docs/zh/resources/templates/index.md delete mode 100755 courses/learn-harness-engineering/docs/zh/resources/templates/init.sh delete mode 100644 courses/learn-harness-engineering/docs/zh/resources/templates/quality-document.md delete mode 100644 courses/learn-harness-engineering/docs/zh/resources/templates/session-handoff.md delete mode 100644 courses/learn-harness-engineering/docs/zh/skills/index.md create mode 100644 docs/.vitepress/course-catalog.mjs create mode 100644 docs/.vitepress/course-registry.mjs create mode 100644 docs/.vitepress/course-routes.mjs create mode 100644 docs/.vitepress/theme/components/CourseCatalogHome.vue create mode 100644 docs/index.md create mode 100644 scripts/course-source-config.mjs create mode 100644 scripts/sync-course-docs.mjs diff --git a/.gitignore b/.gitignore index d86c77a7..22387dd8 100644 --- a/.gitignore +++ b/.gitignore @@ -25,6 +25,10 @@ pnpm-debug.log* .vitepress/dist/ docs/.vitepress/cache/ docs/.vitepress/dist/ +docs/.vitepress/generated-course-data.mjs +docs/.vitepress/featured-course.json +courses/ +docs/courses/ *.tsbuildinfo artifacts/ dist/ diff --git a/README.md b/README.md index 75fd7271..31c2d472 100644 --- a/README.md +++ b/README.md @@ -1,82 +1,59 @@ # course-builder -Whitelabel VitePress course builder with multi-course manifest support. +Reusable VitePress course builder. The repository contains the builder only; course source content can live outside the repo and is mirrored into `docs/courses/` during local dev and build. -This repository is no longer only the `Learn Harness Engineering` course. It now contains: +## How it works -- a reusable VitePress course platform -- one active course selected at build time -- multiple course manifests under `courses/` -- isolated course content under `courses//docs` +- external course source directory: `../course-builder-courses` by default +- override source directory: `COURSE_SOURCE_DIR=/absolute/or/relative/path` +- manifest contract per course: `/course.manifest.mjs` +- markdown content per course: `/docs//...` +- generated mirror used by VitePress: `docs/courses//` +- generated runtime registry: `docs/.vitepress/generated-course-data.mjs` -## Current structure +The published site exposes: -- active course selector: [docs/.vitepress/active-course.mjs](/Users/matheuspuppe/Desktop/estudo/course-builder/docs/.vitepress/active-course.mjs:1) -- active-course wrapper: [docs/.vitepress/course.manifest.mjs](/Users/matheuspuppe/Desktop/estudo/course-builder/docs/.vitepress/course.manifest.mjs:1) -- builder shell: [docs/.vitepress/config.mts](/Users/matheuspuppe/Desktop/estudo/course-builder/docs/.vitepress/config.mts:1) -- course manifests: - - [courses/learn-harness-engineering/course.manifest.mjs](/Users/matheuspuppe/Desktop/estudo/course-builder/courses/learn-harness-engineering/course.manifest.mjs:1) - - [courses/agent-ops-bootcamp/course.manifest.mjs](/Users/matheuspuppe/Desktop/estudo/course-builder/courses/agent-ops-bootcamp/course.manifest.mjs:1) -- course content roots: - - [courses/learn-harness-engineering/docs](/Users/matheuspuppe/Desktop/estudo/course-builder/courses/learn-harness-engineering/docs:1) - - [courses/agent-ops-bootcamp/docs](/Users/matheuspuppe/Desktop/estudo/course-builder/courses/agent-ops-bootcamp/docs:1) +- root catalog at `/` +- one route tree per course at `/courses///...` ## Commands ```bash npm install +npm run courses:sync npm test -npm run docs:build npm run docs:dev +npm run docs:build ``` -To inspect and switch courses: +Useful course commands: ```bash npm run course:list -npm run course:new -- my-new-course "My New Course" -npm run course:activate -- learn-harness-engineering -npm run course:activate -- agent-ops-bootcamp +npm run course:new -- my-course "My Course" +npm run course:activate -- my-course ``` -## How it works - -Each course manifest defines: - -- `site` -- `brand` -- `theme` -- `locales` -- `curriculum` -- `homeByLocale` - -The VitePress layer reads the active manifest and points `srcDir` at that course's own `docs` directory. Nav, sidebar, branding, homepage, and markdown content all come from the active course. - -## Creating a new course - -1. Create `courses//course.manifest.mjs`. -2. Or scaffold both manifesto and docs with `npm run course:new -- "Course Title"`. -3. Create `courses//docs/` if you did not use the scaffold command. -4. Define the course `site`, `brand`, `theme`, `locales`, `curriculum`, and `homeByLocale`. -5. Add markdown content under `courses//docs//...` to match the curriculum. -6. Switch to it with `npm run course:activate -- `. - -## Current limitations - -- only one course is active per build -- the current manifest format is code-first, not CMS-driven -- only one course is active per build - -## Validation - -The repo currently validates: - -- manifest loading -- branding token generation -- locale-aware navigation generation -- alternate course manifest loading -- full VitePress production build +## External course layout + +Each external course directory should look like: + +```text +course-builder-courses/ + my-course/ + course.manifest.mjs + docs/ + index.md + en/ + index.md + lectures/ + projects/ + resources/ + skills/ +``` ## Notes -The second course, `agent-ops-bootcamp`, exists mainly as an architectural proof that the platform can host distinct branding and curriculum in the same repo. +- `courses/` inside this repo is ignored and should not be used as the source of truth. +- `docs/courses/` and generated registry files are local build artifacts and are ignored. +- `npm test`, `npm run docs:dev`, and `npm run docs:build` sync external courses before running. diff --git a/WHITELABEL.md b/WHITELABEL.md index c0796828..7ce58039 100644 --- a/WHITELABEL.md +++ b/WHITELABEL.md @@ -1,54 +1,44 @@ # Whitelabel Guide -O projeto agora está estruturado para suportar múltiplos cursos com um curso ativo definido pela camada VitePress. - -## Estrutura atual - -- curso ativo: [docs/.vitepress/active-course.mjs](/Users/matheuspuppe/Desktop/estudo/course-builder/docs/.vitepress/active-course.mjs:1) -- manifesto do curso atual: [courses/learn-harness-engineering/course.manifest.mjs](/Users/matheuspuppe/Desktop/estudo/course-builder/courses/learn-harness-engineering/course.manifest.mjs:1) -- conteúdo do curso atual: [courses/learn-harness-engineering/docs](/Users/matheuspuppe/Desktop/estudo/course-builder/courses/learn-harness-engineering/docs:1) -- wrappers compatíveis com a camada antiga: - - [docs/.vitepress/course.config.mjs](/Users/matheuspuppe/Desktop/estudo/course-builder/docs/.vitepress/course.config.mjs:1) - - [docs/.vitepress/course-content.mjs](/Users/matheuspuppe/Desktop/estudo/course-builder/docs/.vitepress/course-content.mjs:1) - - [docs/.vitepress/course-curriculum.mjs](/Users/matheuspuppe/Desktop/estudo/course-builder/docs/.vitepress/course-curriculum.mjs:1) - - [docs/.vitepress/course-locales.mjs](/Users/matheuspuppe/Desktop/estudo/course-builder/docs/.vitepress/course-locales.mjs:1) - -## O que já está desacoplado - -- branding visual do site -- config base do VitePress -- manifesto de locales e labels de UI -- currículo por locale -- conteúdo da homepage por locale -- conteúdo markdown por curso -- seleção do curso ativo - -## Como criar outro curso - -1. Crie uma pasta `courses//`. -2. Crie `courses//docs/`. -3. Copie [courses/learn-harness-engineering/course.manifest.mjs](/Users/matheuspuppe/Desktop/estudo/course-builder/courses/learn-harness-engineering/course.manifest.mjs:1) para o novo slug. -4. Edite no manifesto: - - `site.title` - - `site.description` - - `site.base` - - `site.repoTreeUrl` - - `brand.logo` - - `theme.*` - - `locales` - - `curriculum` - - `homeByLocale` -5. Adicione o conteúdo real em `courses//docs//` para bater com o currículo do novo curso. -6. Ative o curso. - -Ou use os comandos: - -- `npm run course:list` -- `npm run course:new -- "Course Title"` -- `npm run course:activate -- ` - -## Limitações atuais - -- o manifesto do curso atual ainda concentra muito conteúdo inline -- ainda existe apenas um curso ativo por build -- o builder ainda depende de manifests em código, não de uma camada editorial +O repositório agora é apenas o builder. Os cursos devem ficar fora dele e entrar no site por sincronização. + +## Estrutura + +- builder VitePress: `docs/.vitepress/` +- catálogo inicial: `docs/index.md` +- espelho gerado dos cursos: `docs/courses/` +- configuração da fonte externa: `scripts/course-source-config.mjs` +- sincronização: `scripts/sync-course-docs.mjs` + +## Fonte dos cursos + +Por padrão, o builder procura cursos em: + +```bash +../course-builder-courses +``` + +Você pode trocar isso com: + +```bash +COURSE_SOURCE_DIR=../outro-diretorio npm run docs:dev +``` + +Cada curso externo precisa ter: + +1. `course.manifest.mjs` +2. `docs/` +3. conteúdo por locale compatível com o currículo definido no manifest + +## Fluxo + +1. Crie ou edite o curso fora deste repo. +2. Rode `npm run courses:sync`. +3. Rode `npm run docs:dev` ou `npm run docs:build`. +4. Se quiser mudar o destaque do catálogo, rode `npm run course:activate -- `. + +## Observações + +- `courses/` neste repo está no `.gitignore`. +- `docs/courses/` e `docs/.vitepress/generated-course-data.mjs` são artefatos gerados. +- O site continua servindo múltiplos cursos em `/courses///...`. diff --git a/courses/agent-ops-bootcamp/course.manifest.mjs b/courses/agent-ops-bootcamp/course.manifest.mjs deleted file mode 100644 index 1e0dc6ac..00000000 --- a/courses/agent-ops-bootcamp/course.manifest.mjs +++ /dev/null @@ -1,183 +0,0 @@ -const courseManifest = { - site: { - title: "Agent Ops Bootcamp", - description: - "A compact course on operating, debugging, and shipping agentic workflows in production teams.", - base: "/course-builder/", - repoTreeUrl: "https://github.com/puppe1990/course-builder/tree/main", - }, - brand: { - logo: 'data:image/svg+xml;utf8,', - }, - theme: { - fontStylesheets: [ - "https://fonts.googleapis.com/css2?family=Newsreader:opsz,wght@6..72,400;6..72,600&family=Plus+Jakarta+Sans:wght@400;500;600;700&family=JetBrains+Mono:wght@400;500&display=swap", - ], - typography: { - body: "'Plus Jakarta Sans', ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif", - heading: - "'Newsreader', ui-serif, Georgia, Cambria, 'Times New Roman', Times, serif", - mono: "'JetBrains Mono', 'Fira Code', ui-monospace, SFMono-Regular, Menlo, Consolas, monospace", - mermaid: "'Plus Jakarta Sans', sans-serif", - }, - layout: { - maxWidth: "1280px", - sidebarWidth: "280px", - cardRadius: "14px", - }, - effects: { - featureShadow: "0 18px 48px rgba(11, 110, 79, 0.14)", - featureShadowDark: "0 18px 48px rgba(0, 0, 0, 0.4)", - }, - colors: { - light: { - bg: "#F6FBF8", - bgAlt: "#ECF5F0", - bgElevated: "#FFFFFF", - bgSoft: "#ECF5F0", - text1: "#142019", - text2: "#355244", - text3: "#5B7669", - brand1: "#0B6E4F", - brand2: "#095A41", - brand3: "#074734", - brandSoft: "rgba(11, 110, 79, 0.12)", - divider: "rgba(8, 32, 20, 0.1)", - sidebarBg: "#ECF5F0", - navBg: "#F6FBF8", - }, - dark: { - bg: "#0E1712", - bgAlt: "#0A120D", - bgElevated: "#17221C", - bgSoft: "#0A120D", - text1: "#E7F2EC", - text2: "#B7CEC1", - text3: "#87A294", - brand1: "#39A37F", - brand2: "#2A8B6A", - brand3: "#1E7054", - brandSoft: "rgba(57, 163, 127, 0.16)", - divider: "rgba(255, 255, 255, 0.12)", - sidebarBg: "#0A120D", - navBg: "#0E1712", - }, - }, - }, - locales: [ - { - key: "en", - label: "English", - lang: "en", - labels: { - lectures: "Modules", - projects: "Labs", - resources: "Toolkit", - skills: "Playbooks", - resourceLibrary: "Toolkit", - tryHarness: "Open Templates ↗", - }, - }, - ], - curriculum: { - en: { - lectures: [ - { text: "Welcome", link: "/en/" }, - { - text: "Operate Agents with Guardrails", - link: "/en/lectures/operate-agents-with-guardrails/", - }, - { - text: "Debug Long-Running Workflows", - link: "/en/lectures/debug-long-running-workflows/", - }, - { - text: "Ship with Runtime Feedback", - link: "/en/lectures/ship-with-runtime-feedback/", - }, - ], - projects: [ - { text: "Overview", link: "/en/projects/" }, - { text: "Incident Drill", link: "/en/projects/incident-drill/" }, - { text: "Release Checklist", link: "/en/projects/release-checklist/" }, - ], - resources: [ - { text: "Overview", link: "/en/resources/" }, - { text: "Runbooks", link: "/en/resources/templates/" }, - { text: "Reference", link: "/en/resources/reference/" }, - { text: "Advanced Ops", link: "/en/resources/openai-advanced/" }, - ], - skills: [{ text: "Playbooks", link: "/en/skills/" }], - }, - }, - homeByLocale: { - en: { - hero: { - title: "Welcome to Agent Ops Bootcamp", - intro: [ - "Agent Ops Bootcamp focuses on how teams run, observe, and correct agentic systems under production constraints.", - "The course is intentionally compact: a few modules, a few labs, and a toolkit you can adapt to your own operating model.", - ], - references: [ - { - label: "Operational feedback loops", - href: "https://openai.com/index/harness-engineering/", - }, - ], - }, - sections: { - start: "Start here", - mechanism: "Operating loop", - learn: "You will practice", - next: "Next moves", - }, - startText: - "This sample course exists to prove the platform can host a second curriculum with different naming, visual identity, and positioning.", - cards: [ - { - title: "Modules", - body: "Short theory units about operating agents safely.", - href: "./lectures/operate-agents-with-guardrails/", - }, - { - title: "Labs", - body: "Hands-on operational drills.", - href: "./projects/", - }, - { - title: "Toolkit", - body: "Runbooks and reusable templates.", - href: "./resources/", - }, - ], - mechanismText: - "Production agent work improves when runtime feedback and verification are part of the operating loop.", - mechanismMermaid: `graph LR - A["Task"] --> B["Run"] - B --> C{"Observe"} - C -->|Issues| D["Correct"] - D --> B - C -->|Stable| E["Ship"]`, - learnItems: [ - "Define operator-facing guardrails.", - "Treat logs and runtime feedback as first-class inputs.", - "Run lightweight drills before real incidents.", - ], - nextIntro: "The sample path is intentionally small:", - nextSteps: [ - { - title: "Module 1", - description: "Start with guardrails.", - href: "./lectures/operate-agents-with-guardrails/", - }, - { - title: "Lab 1", - description: "Run an incident drill.", - href: "./projects/incident-drill/", - }, - ], - }, - }, -}; - -export default courseManifest; diff --git a/courses/agent-ops-bootcamp/docs/en/index.md b/courses/agent-ops-bootcamp/docs/en/index.md deleted file mode 100644 index af6b0fd4..00000000 --- a/courses/agent-ops-bootcamp/docs/en/index.md +++ /dev/null @@ -1 +0,0 @@ - diff --git a/courses/agent-ops-bootcamp/docs/en/lectures/debug-long-running-workflows/index.md b/courses/agent-ops-bootcamp/docs/en/lectures/debug-long-running-workflows/index.md deleted file mode 100644 index 2fade37f..00000000 --- a/courses/agent-ops-bootcamp/docs/en/lectures/debug-long-running-workflows/index.md +++ /dev/null @@ -1,14 +0,0 @@ -# Debug Long-Running Workflows - -Long-running agents rarely fail in one obvious place. They drift, loop, or lose context across steps. - -## What to inspect - -- Execution logs -- State transitions -- Retry behavior -- Handoff boundaries - -## Debugging principle - -Treat every long-running workflow as an observable system, not a black box prompt. diff --git a/courses/agent-ops-bootcamp/docs/en/lectures/operate-agents-with-guardrails/index.md b/courses/agent-ops-bootcamp/docs/en/lectures/operate-agents-with-guardrails/index.md deleted file mode 100644 index 793c8492..00000000 --- a/courses/agent-ops-bootcamp/docs/en/lectures/operate-agents-with-guardrails/index.md +++ /dev/null @@ -1,13 +0,0 @@ -# Operate Agents with Guardrails - -Agent systems need explicit operating boundaries. This module shows how teams define ownership, stop conditions, and escalation paths before runtime issues become incidents. - -## Core rules - -- Define who can trigger the workflow. -- Define what the agent can change without approval. -- Define when the system must pause and ask for human review. - -## Outcome - -By the end of this module, you should have a lightweight guardrail policy that can be attached to any agent workflow. diff --git a/courses/agent-ops-bootcamp/docs/en/lectures/ship-with-runtime-feedback/index.md b/courses/agent-ops-bootcamp/docs/en/lectures/ship-with-runtime-feedback/index.md deleted file mode 100644 index d94203d6..00000000 --- a/courses/agent-ops-bootcamp/docs/en/lectures/ship-with-runtime-feedback/index.md +++ /dev/null @@ -1,14 +0,0 @@ -# Ship with Runtime Feedback - -Shipping agent systems safely requires a feedback loop between runtime behavior and product decisions. - -## Runtime inputs - -- Error classes -- Human overrides -- Repeated retries -- Failed postconditions - -## Practice - -Turn the most common runtime failures into explicit operating rules and release checks. diff --git a/courses/agent-ops-bootcamp/docs/en/projects/incident-drill/index.md b/courses/agent-ops-bootcamp/docs/en/projects/incident-drill/index.md deleted file mode 100644 index 3dd162b8..00000000 --- a/courses/agent-ops-bootcamp/docs/en/projects/incident-drill/index.md +++ /dev/null @@ -1,7 +0,0 @@ -# Incident Drill - -Simulate a production issue where an agent keeps retrying a task with stale context. - -## Deliverable - -Write the operator checklist your team should follow in the first ten minutes of the incident. diff --git a/courses/agent-ops-bootcamp/docs/en/projects/index.md b/courses/agent-ops-bootcamp/docs/en/projects/index.md deleted file mode 100644 index 0810eea1..00000000 --- a/courses/agent-ops-bootcamp/docs/en/projects/index.md +++ /dev/null @@ -1,3 +0,0 @@ -# Labs - -Use these labs to pressure-test your operating model before a real release. diff --git a/courses/agent-ops-bootcamp/docs/en/projects/release-checklist/index.md b/courses/agent-ops-bootcamp/docs/en/projects/release-checklist/index.md deleted file mode 100644 index d8bd10ad..00000000 --- a/courses/agent-ops-bootcamp/docs/en/projects/release-checklist/index.md +++ /dev/null @@ -1,8 +0,0 @@ -# Release Checklist - -Before enabling a workflow in production, verify: - -- the trigger is intentional, -- the stop conditions are explicit, -- the logs are reviewable, -- the fallback path is owned by a human. diff --git a/courses/agent-ops-bootcamp/docs/en/resources/index.md b/courses/agent-ops-bootcamp/docs/en/resources/index.md deleted file mode 100644 index 23ca95d9..00000000 --- a/courses/agent-ops-bootcamp/docs/en/resources/index.md +++ /dev/null @@ -1,3 +0,0 @@ -# Toolkit - -This toolkit is the operational layer of the course: compact templates, references, and reusable docs. diff --git a/courses/agent-ops-bootcamp/docs/en/resources/openai-advanced/index.md b/courses/agent-ops-bootcamp/docs/en/resources/openai-advanced/index.md deleted file mode 100644 index 284800ed..00000000 --- a/courses/agent-ops-bootcamp/docs/en/resources/openai-advanced/index.md +++ /dev/null @@ -1,3 +0,0 @@ -# Advanced Ops - -Use this area for deeper operational patterns once the core course flow is stable. diff --git a/courses/agent-ops-bootcamp/docs/en/resources/reference/index.md b/courses/agent-ops-bootcamp/docs/en/resources/reference/index.md deleted file mode 100644 index 552622c1..00000000 --- a/courses/agent-ops-bootcamp/docs/en/resources/reference/index.md +++ /dev/null @@ -1,3 +0,0 @@ -# Reference - -Use this section for operating heuristics, terminology, and small checklists that support the labs. diff --git a/courses/agent-ops-bootcamp/docs/en/resources/templates/index.md b/courses/agent-ops-bootcamp/docs/en/resources/templates/index.md deleted file mode 100644 index 80b4c8f7..00000000 --- a/courses/agent-ops-bootcamp/docs/en/resources/templates/index.md +++ /dev/null @@ -1,9 +0,0 @@ -# Runbooks - -## Minimal runbook template - -1. Trigger -2. Expected outcome -3. Failure signals -4. Human escalation point -5. Cleanup steps diff --git a/courses/agent-ops-bootcamp/docs/en/skills/index.md b/courses/agent-ops-bootcamp/docs/en/skills/index.md deleted file mode 100644 index df86ceb5..00000000 --- a/courses/agent-ops-bootcamp/docs/en/skills/index.md +++ /dev/null @@ -1,7 +0,0 @@ -# Playbooks - -Playbooks are compact action guides for common operational situations: - -- rollout review -- runtime incident response -- postmortem capture diff --git a/courses/agent-ops-bootcamp/docs/index.md b/courses/agent-ops-bootcamp/docs/index.md deleted file mode 100644 index 261535fd..00000000 --- a/courses/agent-ops-bootcamp/docs/index.md +++ /dev/null @@ -1,14 +0,0 @@ ---- -layout: page ---- - - - -
-

Loading...

-
diff --git a/courses/learn-harness-engineering/course.manifest.mjs b/courses/learn-harness-engineering/course.manifest.mjs deleted file mode 100644 index 3488494b..00000000 --- a/courses/learn-harness-engineering/course.manifest.mjs +++ /dev/null @@ -1,674 +0,0 @@ -const brandLogo = - 'data:image/svg+xml;utf8,'; - -const defaultHome = { - hero: { - title: "Welcome to Learn Harness Engineering", - intro: [ - "Learn Harness Engineering is a course dedicated to the engineering of AI coding agents. We have deeply studied and synthesized the most advanced Harness Engineering theories and practices in the industry.", - "Through systematic environment design, state management, verification, and control systems, this course teaches you how to make agentic coding tools like Codex and Claude Code truly reliable.", - ], - references: [ - { - label: - "OpenAI: Harness engineering: leveraging Codex in an agent-first world", - href: "https://openai.com/index/harness-engineering/", - }, - { - label: "Anthropic: Effective harnesses for long-running agents", - href: "https://www.anthropic.com/engineering/effective-harnesses-for-long-running-agents", - }, - { - label: - "Anthropic: Harness design for long-running application development", - href: "https://www.anthropic.com/engineering/harness-design-long-running-apps", - }, - { - label: "Awesome Harness Engineering", - href: "https://github.com/walkinglabs/awesome-harness-engineering", - }, - ], - }, - sections: { - start: "Get started", - mechanism: "The Core Mechanism of a Harness", - learn: "What you will learn", - next: "Next steps", - }, - startText: - "Choose your learning path to get started. The course is divided into theoretical lectures, hands-on projects, and a copy-ready resource library.", - cards: [ - { - title: "Lectures", - body: "Understand why strong models still fail and learn the theory behind effective harnesses.", - href: "./lectures/lecture-01-why-capable-agents-still-fail/", - }, - { - title: "Projects", - body: "Hands-on practice building a reliable agentic environment from scratch.", - href: "./projects/", - }, - { - title: "Resource Library", - body: "Copy-ready templates (AGENTS.md, feature_list.json) to use in your own repositories.", - href: "./resources/", - }, - ], - mechanismText: - 'A harness does not "make the model smarter"; it establishes a closed-loop working system for the model.', - mechanismMermaid: `graph TD - A["Clear Objective
AGENTS.md"] --> B("Initialization
init.sh") - B --> C{"Run Tasks
AI Agent"} - C -->|Encounter Issues| D["Runtime Feedback
CLI / Logs"] - D -->|Auto-fix| C - C -->|Code Completed| E{"Verify & QA
Test suite"} - E -->|Failed| D - E -->|Passed| F["Cleanup & Handoff
claude-progress.md"] - - classDef primary fill:#D95C41,stroke:#C14E36,color:#fff,font-weight:bold; - classDef process fill:#F4F3EE,stroke:#D1D1D1,color:#1A1A1A; - classDef check fill:#EAE8E1,stroke:#B3B3B3,color:#1A1A1A; - - class A,F primary; - class B,D process; - class C,E check;`, - learnItems: [ - "Constrain agent behavior with explicit rules and boundaries.", - "Maintain context across long-running, multi-session tasks.", - "Stop agents from declaring victory too early.", - "Verify work using full-pipeline tests and self-reflection.", - "Make runtime observable and debuggable.", - ], - nextIntro: - "Once you understand the core concepts, these guides help you go deeper:", - nextSteps: [ - { - title: "Lecture 01: Why Capable Agents Still Fail", - description: "Start with the theory behind harness engineering.", - href: "./lectures/lecture-01-why-capable-agents-still-fail/", - }, - { - title: "Project 01: Baseline vs Minimal Harness", - description: "Walk through your first real task.", - href: "./projects/project-01-baseline-vs-minimal-harness/", - }, - { - title: "Templates", - description: "Grab the minimal harness pack for your own projects.", - href: "./resources/templates/", - }, - ], -}; - -const localeEntries = [ - { - key: "en", - label: "English", - lang: "en", - labels: { - lectures: "Lectures", - projects: "Projects", - resources: "Library", - skills: "Skills", - resourceLibrary: "Resource Library", - tryHarness: "Try Harness ↗", - }, - }, - { - key: "zh", - label: "简体中文", - lang: "zh-CN", - labels: { - lectures: "讲义", - projects: "项目", - resources: "资料库", - skills: "技能", - resourceLibrary: "资料库", - tryHarness: "Try Harness ↗", - prev: "上一篇", - next: "下一篇", - lastUpdated: "最后更新于", - returnToTop: "回到顶部", - sidebarMenu: "菜单", - darkModeSwitch: "主题", - lightModeSwitchTitle: "切换到浅色模式", - darkModeSwitchTitle: "切换到深色模式", - }, - }, - { - key: "zh-TW", - label: "繁體中文", - lang: "zh-TW", - labels: { - lectures: "講義", - projects: "專案", - resources: "資源庫", - skills: "技能", - resourceLibrary: "資源庫", - tryHarness: "Try Harness ↗", - prev: "上一篇", - next: "下一篇", - lastUpdated: "最後更新於", - returnToTop: "回到頂部", - sidebarMenu: "選單", - darkModeSwitch: "主題", - lightModeSwitchTitle: "切換到淺色模式", - darkModeSwitchTitle: "切換到深色模式", - }, - }, - { - key: "ja", - label: "日本語", - lang: "ja-JP", - labels: { - lectures: "講義", - projects: "プロジェクト", - resources: "リソース", - skills: "スキル", - resourceLibrary: "リソースライブラリ", - tryHarness: "Try Harness ↗", - outline: "このページ", - prev: "前へ", - next: "次へ", - lastUpdated: "最終更新", - returnToTop: "トップへ戻る", - sidebarMenu: "メニュー", - darkModeSwitch: "テーマ", - lightModeSwitchTitle: "ライトモードに切り替え", - darkModeSwitchTitle: "ダークモードに切り替え", - }, - }, - { - key: "es", - label: "Español", - lang: "es-ES", - labels: { - lectures: "Lecciones", - projects: "Proyectos", - resources: "Biblioteca", - skills: "Skills", - resourceLibrary: "Biblioteca de recursos", - tryHarness: "Try Harness ↗", - outline: "En esta página", - prev: "Anterior", - next: "Siguiente", - lastUpdated: "Última actualización", - returnToTop: "Volver arriba", - sidebarMenu: "Menú", - darkModeSwitch: "Tema", - lightModeSwitchTitle: "Cambiar a tema claro", - darkModeSwitchTitle: "Cambiar a tema oscuro", - }, - }, - { - key: "fr", - label: "Français", - lang: "fr-FR", - labels: { - lectures: "Cours", - projects: "Projets", - resources: "Bibliothèque", - skills: "Skills", - resourceLibrary: "Bibliothèque de ressources", - tryHarness: "Try Harness ↗", - outline: "Sur cette page", - prev: "Précédent", - next: "Suivant", - lastUpdated: "Dernière mise à jour", - returnToTop: "Retour en haut", - sidebarMenu: "Menu", - darkModeSwitch: "Thème", - lightModeSwitchTitle: "Passer au thème clair", - darkModeSwitchTitle: "Passer au thème sombre", - }, - }, - { - key: "de", - label: "Deutsch", - lang: "de-DE", - labels: { - lectures: "Lektionen", - projects: "Projekte", - resources: "Bibliothek", - skills: "Skills", - resourceLibrary: "Ressourcenbibliothek", - tryHarness: "Try Harness ↗", - outline: "Auf dieser Seite", - prev: "Zurück", - next: "Weiter", - lastUpdated: "Zuletzt aktualisiert", - returnToTop: "Nach oben", - sidebarMenu: "Menü", - darkModeSwitch: "Theme", - lightModeSwitchTitle: "Zum hellen Theme wechseln", - darkModeSwitchTitle: "Zum dunklen Theme wechseln", - }, - }, - { - key: "ar", - label: "العربية", - lang: "ar-SA", - labels: { - lectures: "المحاضرات", - projects: "المشاريع", - resources: "المكتبة", - skills: "المهارات", - resourceLibrary: "مكتبة الموارد", - tryHarness: "Try Harness ↗", - outline: "في هذه الصفحة", - prev: "السابق", - next: "التالي", - lastUpdated: "آخر تحديث", - returnToTop: "العودة إلى الأعلى", - sidebarMenu: "القائمة", - darkModeSwitch: "السمة", - lightModeSwitchTitle: "التبديل إلى السمة الفاتحة", - darkModeSwitchTitle: "التبديل إلى السمة الداكنة", - }, - }, - { - key: "vi", - label: "Tiếng Việt", - lang: "vi-VN", - labels: { - lectures: "Bài giảng", - projects: "Dự án", - resources: "Tài nguyên", - skills: "Kỹ năng", - resourceLibrary: "Thư viện Tài nguyên", - tryHarness: "Try Harness ↗", - prev: "Trang trước", - next: "Trang sau", - lastUpdated: "Cập nhật lần cuối", - returnToTop: "Trở lên trên cùng", - sidebarMenu: "Menu", - darkModeSwitch: "Giao diện", - lightModeSwitchTitle: "Chuyển sang chế độ sáng", - darkModeSwitchTitle: "Chuyển sang chế độ tối", - }, - }, - { - key: "ko", - label: "한국어", - lang: "ko", - labels: { - lectures: "강의", - projects: "프로젝트", - resources: "리소스 모음", - skills: "스킬", - resourceLibrary: "리소스 모음", - tryHarness: "Try Harness ↗", - outline: "이 페이지에서", - prev: "이전", - next: "다음", - lastUpdated: "마지막 업데이트", - returnToTop: "맨 위로", - sidebarMenu: "메뉴", - darkModeSwitch: "테마", - lightModeSwitchTitle: "라이트 모드로 전환", - darkModeSwitchTitle: "다크 모드로 전환", - }, - }, - { - key: "uz", - label: "Oʻzbek", - lang: "uz", - labels: { - lectures: "Maʼruzalar", - projects: "Loyihalar", - resources: "Kutubxona", - skills: "Malakalar", - resourceLibrary: "Resurslar kutubxonasi", - tryHarness: "Harness'ni sinash ↗", - outline: "Ushbu sahifada", - prev: "Oldingi", - next: "Keyingi", - lastUpdated: "Oxirgi yangilanish", - returnToTop: "Yuqoriga qaytish", - sidebarMenu: "Menyu", - darkModeSwitch: "Mavzu", - lightModeSwitchTitle: "Yorugʻ rejimga oʻtish", - darkModeSwitchTitle: "Qorongʻi rejimga oʻtish", - }, - }, - { - key: "ru", - label: "Русский", - lang: "ru", - labels: { - lectures: "Лекции", - projects: "Проекты", - resources: "Материалы", - skills: "Скиллы", - resourceLibrary: "Материалы", - tryHarness: "Try Harness ↗", - outline: "На этой странице", - prev: "Предыдущая", - next: "Следующая", - lastUpdated: "Последнее обновление", - returnToTop: "Наверх", - sidebarMenu: "Меню", - darkModeSwitch: "Тема", - lightModeSwitchTitle: "Включить светлую тему", - darkModeSwitchTitle: "Включить тёмную тему", - }, - }, -]; - -const sourceItemsByLocale = { - en: { - lectures: [ - { text: "Welcome", link: "/en/" }, - { - text: "Why Capable Agents Still Fail", - link: "/en/lectures/lecture-01-why-capable-agents-still-fail/", - }, - { - text: "What a Harness Actually Is", - link: "/en/lectures/lecture-02-what-a-harness-actually-is/", - }, - { - text: "Why the Repository Must Become the System of Record", - link: "/en/lectures/lecture-03-why-the-repository-must-become-the-system-of-record/", - }, - { - text: "Why One Giant Instruction File Fails", - link: "/en/lectures/lecture-04-why-one-giant-instruction-file-fails/", - }, - { - text: "Why Long-Running Tasks Lose Continuity", - link: "/en/lectures/lecture-05-why-long-running-tasks-lose-continuity/", - }, - { - text: "Why Initialization Needs Its Own Phase", - link: "/en/lectures/lecture-06-why-initialization-needs-its-own-phase/", - }, - { - text: "Why Agents Overreach and Under-Finish", - link: "/en/lectures/lecture-07-why-agents-overreach-and-under-finish/", - }, - { - text: "Why Feature Lists Are Harness Primitives", - link: "/en/lectures/lecture-08-why-feature-lists-are-harness-primitives/", - }, - { - text: "Why Agents Declare Victory Too Early", - link: "/en/lectures/lecture-09-why-agents-declare-victory-too-early/", - }, - { - text: "Why End-to-End Testing Changes Results", - link: "/en/lectures/lecture-10-why-end-to-end-testing-changes-results/", - }, - { - text: "Why Observability Belongs Inside the Harness", - link: "/en/lectures/lecture-11-why-observability-belongs-inside-the-harness/", - }, - { - text: "Why Every Session Must Leave a Clean State", - link: "/en/lectures/lecture-12-why-every-session-must-leave-a-clean-state/", - }, - ], - projects: [ - { text: "Welcome", link: "/en/projects/" }, - { - text: "Prompt-Only vs. Rules-First", - link: "/en/projects/project-01-baseline-vs-minimal-harness/", - }, - { - text: "Agent-Readable Workspace", - link: "/en/projects/project-02-agent-readable-workspace/", - }, - { - text: "Multi-Session Continuity", - link: "/en/projects/project-03-multi-session-continuity/", - }, - { - text: "Runtime Feedback and Scope Control", - link: "/en/projects/project-04-incremental-indexing/", - }, - { - text: "Self-Verification and Role Separation", - link: "/en/projects/project-05-grounded-qa-verification/", - }, - { - text: "Complete Harness (Capstone)", - link: "/en/projects/project-06-runtime-observability-and-debugging/", - }, - ], - resources: [ - { text: "Overview", link: "/en/resources/" }, - { text: "English Templates", link: "/en/resources/templates/" }, - { text: "English Reference", link: "/en/resources/reference/" }, - { text: "Advanced Pack", link: "/en/resources/openai-advanced/" }, - ], - skills: [{ text: "Skills Overview", link: "/en/skills/" }], - }, - ko: { - lectures: [ - { text: "환영합니다", link: "/ko/" }, - { - text: "유능한 에이전트가 여전히 실패하는 이유", - link: "/ko/lectures/lecture-01-why-capable-agents-still-fail/", - }, - { - text: "하네스란 무엇인가", - link: "/ko/lectures/lecture-02-what-a-harness-actually-is/", - }, - { - text: "저장소가 시스템 오브 레코드(SoR)가 되어야 하는 이유", - link: "/ko/lectures/lecture-03-why-the-repository-must-become-the-system-of-record/", - }, - { - text: "거대한 단일 지시 파일이 실패하는 이유", - link: "/ko/lectures/lecture-04-why-one-giant-instruction-file-fails/", - }, - { - text: "장기 작업이 연속성을 잃는 이유", - link: "/ko/lectures/lecture-05-why-long-running-tasks-lose-continuity/", - }, - { - text: "초기화가 별도 단계여야 하는 이유", - link: "/ko/lectures/lecture-06-why-initialization-needs-its-own-phase/", - }, - { - text: "에이전트가 과도하게 손대고 끝맺지 못하는 이유", - link: "/ko/lectures/lecture-07-why-agents-overreach-and-under-finish/", - }, - { - text: "기능 목록이 하네스의 기본 단위인 이유", - link: "/ko/lectures/lecture-08-why-feature-lists-are-harness-primitives/", - }, - { - text: "에이전트가 너무 일찍 완료를 선언하는 이유", - link: "/ko/lectures/lecture-09-why-agents-declare-victory-too-early/", - }, - { - text: "엔드투엔드 테스트가 결과를 바꾸는 이유", - link: "/ko/lectures/lecture-10-why-end-to-end-testing-changes-results/", - }, - { - text: "관측 가능성이 하네스 안에 있어야 하는 이유", - link: "/ko/lectures/lecture-11-why-observability-belongs-inside-the-harness/", - }, - { - text: "모든 세션이 클린 상태로 끝나야 하는 이유", - link: "/ko/lectures/lecture-12-why-every-session-must-leave-a-clean-state/", - }, - ], - projects: [ - { text: "환영합니다", link: "/ko/projects/" }, - { - text: "프롬프트 단독 vs 규칙 우선", - link: "/ko/projects/project-01-baseline-vs-minimal-harness/", - }, - { - text: "에이전트가 읽을 수 있는 작업 공간", - link: "/ko/projects/project-02-agent-readable-workspace/", - }, - { - text: "다중 세션 연속성", - link: "/ko/projects/project-03-multi-session-continuity/", - }, - { - text: "런타임 피드백과 범위 제어", - link: "/ko/projects/project-04-incremental-indexing/", - }, - { - text: "자기 검증과 역할 분리", - link: "/ko/projects/project-05-grounded-qa-verification/", - }, - { - text: "완성형 하네스(캡스톤)", - link: "/ko/projects/project-06-runtime-observability-and-debugging/", - }, - ], - resources: [ - { text: "리소스 모음 개요", link: "/ko/resources/" }, - { text: "한국어 템플릿", link: "/ko/resources/templates/" }, - { text: "한국어 레퍼런스", link: "/ko/resources/reference/" }, - { text: "고급 리소스 팩", link: "/ko/resources/openai-advanced/" }, - ], - skills: [{ text: "스킬 개요", link: "/ko/skills/" }], - }, -}; - -const homeByLocale = { - en: defaultHome, - ko: { - ...defaultHome, - hero: { - title: "Learn Harness Engineering에 오신 것을 환영합니다", - intro: [ - "Learn Harness Engineering은 AI 코딩 에이전트의 엔지니어링에 집중하는 강의입니다. 업계에서 가장 앞선 하네스 엔지니어링 이론과 실천을 깊이 정리했습니다.", - "체계적인 환경 설계, 상태 관리, 검증, 제어 시스템을 통해 Codex와 Claude Code 같은 도구를 실제로 신뢰할 수 있게 만드는 방법을 다룹니다.", - ], - references: defaultHome.hero.references, - }, - sections: { - start: "시작하기", - mechanism: "하네스의 핵심 메커니즘", - learn: "이 강의에서 배우는 것", - next: "다음 단계", - }, - startText: - "학습 경로를 선택해 시작하세요. 강의는 이론 강의, 실습 프로젝트, 즉시 사용 가능한 리소스 모음으로 구성됩니다.", - cards: [ - { - title: "강의(Lectures)", - body: "강력한 모델이 왜 여전히 실패하는지 이해하고, 효과적인 하네스의 이론을 배웁니다.", - href: "./lectures/lecture-01-why-capable-agents-still-fail/", - }, - { - title: "프로젝트(Projects)", - body: "믿을 수 있는 에이전트 환경을 처음부터 직접 만들어 보는 실습입니다.", - href: "./projects/", - }, - { - title: "리소스 모음(Resource Library)", - body: "여러분의 저장소에서 바로 쓸 수 있는 복사용 템플릿입니다.", - href: "./resources/", - }, - ], - mechanismText: - '하네스는 모델을 "더 똑똑하게" 만드는 것이 아니라, 모델을 위한 닫힌 루프 작업 시스템을 만듭니다.', - learnItems: [ - "명시적인 규칙과 경계로 에이전트 동작을 제약합니다.", - "장시간·다중 세션 작업에서도 컨텍스트를 유지합니다.", - "에이전트가 너무 빨리 완료를 선언하지 못하게 합니다.", - "전체 파이프라인 테스트와 자기 점검으로 결과물을 검증합니다.", - "런타임을 관측·디버깅 가능하게 만듭니다.", - ], - nextIntro: - "핵심 개념이 익숙해졌다면, 다음 자료로 한 걸음 더 들어가 보세요.", - nextSteps: [ - { - title: "강의 01: 유능한 에이전트가 여전히 실패하는 이유", - description: "하네스 엔지니어링의 이론적 출발점입니다.", - href: "./lectures/lecture-01-why-capable-agents-still-fail/", - }, - { - title: "프로젝트 01: 베이스라인 vs 미니멀 하네스", - description: "첫 실제 과제를 처음부터 진행해 봅니다.", - href: "./projects/project-01-baseline-vs-minimal-harness/", - }, - { - title: "템플릿(Templates)", - description: "미니멀 하네스 팩을 자신의 프로젝트에 바로 적용해 보세요.", - href: "./resources/templates/", - }, - ], - }, -}; - -const courseManifest = { - site: { - title: "Learn Harness Engineering", - description: - "A project-based course on designing the environments, state, verification, and control systems that make Codex and Claude Code reliable.", - base: "/course-builder/", - repoTreeUrl: "https://github.com/puppe1990/course-builder/tree/main", - }, - brand: { - logo: brandLogo, - }, - theme: { - fontStylesheets: [ - "https://fonts.googleapis.com/css2?family=Fraunces:opsz,wght@9..144,400;9..144,500;9..144,600&family=Instrument+Sans:wght@400;500;600;700&family=JetBrains+Mono:wght@400;500;600&display=swap", - ], - typography: { - body: "'Instrument Sans', ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif", - heading: - "'Fraunces', ui-serif, Georgia, Cambria, 'Times New Roman', Times, serif", - mono: "'JetBrains Mono', 'Fira Code', ui-monospace, SFMono-Regular, Menlo, Consolas, monospace", - mermaid: "'Instrument Sans', sans-serif", - }, - layout: { - maxWidth: "1376px", - sidebarWidth: "296px", - cardRadius: "12px", - }, - effects: { - featureShadow: "0 18px 48px rgba(217, 92, 65, 0.12)", - featureShadowDark: "0 18px 48px rgba(0, 0, 0, 0.35)", - }, - colors: { - light: { - bg: "#FAF9F5", - bgAlt: "#F4F3EE", - bgElevated: "#FFFFFF", - bgSoft: "#F4F3EE", - text1: "#1A1A1A", - text2: "#4A4A4A", - text3: "#757575", - brand1: "#D95C41", - brand2: "#C14E36", - brand3: "#A8412B", - brandSoft: "rgba(217, 92, 65, 0.1)", - divider: "rgba(0, 0, 0, 0.08)", - sidebarBg: "#F4F3EE", - navBg: "#FAF9F5", - }, - dark: { - bg: "#1A1A1A", - bgAlt: "#141414", - bgElevated: "#242424", - bgSoft: "#141414", - text1: "#E5E5E5", - text2: "#B3B3B3", - text3: "#808080", - brand1: "#E07A64", - brand2: "#D95C41", - brand3: "#C14E36", - brandSoft: "rgba(224, 122, 100, 0.15)", - divider: "rgba(255, 255, 255, 0.1)", - sidebarBg: "#141414", - navBg: "#1A1A1A", - }, - }, - }, - locales: localeEntries, - curriculum: sourceItemsByLocale, - homeByLocale, -}; - -export default courseManifest; diff --git a/courses/learn-harness-engineering/docs/ar/index.md b/courses/learn-harness-engineering/docs/ar/index.md deleted file mode 100644 index fca4de61..00000000 --- a/courses/learn-harness-engineering/docs/ar/index.md +++ /dev/null @@ -1,73 +0,0 @@ -# مرحبًا بك في Learn Harness Engineering - -Learn Harness Engineering هو مساق مخصص لهندسة وكلاء البرمجة بالذكاء الاصطناعي. درسنا و لخصنا مجموعة من أكثر نظريات وممارسات Harness Engineering تقدمًا في الصناعة. تشمل مراجعنا الأساسية: -- [OpenAI: Harness engineering: leveraging Codex in an agent-first world](https://openai.com/index/harness-engineering/) -- [Anthropic: Effective harnesses for long-running agents](https://www.anthropic.com/engineering/effective-harnesses-for-long-running-agents) -- [Anthropic: Harness design for long-running application development](https://www.anthropic.com/engineering/harness-design-long-running-apps) -- [Awesome Harness Engineering](https://github.com/walkinglabs/awesome-harness-engineering) - -من خلال تصميم البيئة، وإدارة الحالة، والتحقق، وأنظمة التحكم بطريقة منهجية، يوضح هذا المساق كيف تجعل أدوات البرمجة الوكيلية مثل Codex و Claude Code موثوقة فعليًا. ستتعلم كيف تبني الميزات، وتصلح الأخطاء، وتؤتمت مهام التطوير عبر تقييد مساعد البرمجة بالذكاء الاصطناعي بقواعد وحدود صريحة. - -## ابدأ - -اختر مسار التعلم المناسب لك. ينقسم المساق إلى محاضرات نظرية، ومشاريع عملية، ومكتبة موارد جاهزة للنسخ. - - - -## الآلية الأساسية للـ harness - -الـ harness لا "يجعل النموذج أذكى"؛ بل ينشئ **نظام عمل** مغلق الحلقة للنموذج. يمكنك فهم التدفق الأساسي من خلال هذا المخطط: - -```mermaid -graph TD - A["Clear Objective
AGENTS.md"] --> B("Initialization
init.sh") - B --> C{"Run Tasks
AI Agent"} - C -->|Encounter Issues| D["Runtime Feedback
CLI / Logs"] - D -->|Auto-fix| C - C -->|Code Completed| E{"Verify & QA
Test suite"} - E -->|Failed| D - E -->|Passed| F["Cleanup & Handoff
claude-progress.md"] - - classDef primary fill:#D95C41,stroke:#C14E36,color:#fff,font-weight:bold; - classDef process fill:#F4F3EE,stroke:#D1D1D1,color:#1A1A1A; - classDef check fill:#EAE8E1,stroke:#B3B3B3,color:#1A1A1A; - - class A,F primary; - class B,D process; - class C,E check; -``` - -## ما الذي ستتعلمه - -هذه بعض المفاهيم الأساسية التي ستتقنها: - -
    -
  • تقييد سلوك الوكيل بقواعد وحدود صريحة.
  • -
  • الحفاظ على السياق عبر المهام الطويلة ومتعددة الجلسات.
  • -
  • منع الوكلاء من إعلان النجاح مبكرًا.
  • -
  • التحقق من العمل باستخدام اختبارات pipeline كاملة ومراجعة ذاتية.
  • -
  • جعل runtime قابلًا للملاحظة وأسهل في التصحيح.
  • -
- -## الخطوات التالية - -بعد فهم المفاهيم الأساسية، تساعدك هذه الأدلة على التعمق: - - diff --git a/courses/learn-harness-engineering/docs/ar/lectures/lecture-01-why-capable-agents-still-fail/code/failure-pattern-demo.ts b/courses/learn-harness-engineering/docs/ar/lectures/lecture-01-why-capable-agents-still-fail/code/failure-pattern-demo.ts deleted file mode 100644 index 311e665b..00000000 --- a/courses/learn-harness-engineering/docs/ar/lectures/lecture-01-why-capable-agents-still-fail/code/failure-pattern-demo.ts +++ /dev/null @@ -1,170 +0,0 @@ -/** - * failure-pattern-demo.ts - * - * Simulates the 4-step failure pattern that capable agents fall into: - * 1. Incomplete context - * 2. Locally reasonable changes - * 3. No global verification - * 4. Premature completion - * - * Run: npx tsx docs/lectures/lecture-01-why-capable-agents-still-fail/code/failure-pattern-demo.ts - */ - -// --------------------------------------------------------------------------- -// Types -// --------------------------------------------------------------------------- - -interface StepState { - step: number; - name: string; - contextAvailable: string[]; - contextMissing: string[]; - actionTaken: string; - localOutcome: string; - globalImpact: string; - completed: boolean; -} - -// --------------------------------------------------------------------------- -// Simulated "model" -- a simple decision function that bases its output -// solely on whatever context it has been given. -// --------------------------------------------------------------------------- - -function modelDecide(context: string[], task: string): string { - const has = (s: string) => context.some((c) => c.includes(s)); - - // The task is to "add a search endpoint to the API". - // Correct answer requires knowing about auth middleware and rate limiting. - - if (!has("auth")) { - return "Created new route handler /search without authentication checks"; - } - if (!has("rate-limit")) { - return "Added search route with auth but forgot rate limiting"; - } - if (!has("test-standards")) { - return "Implemented search with auth and rate-limit, but no tests"; - } - return "Fully implemented search endpoint with auth, rate-limit, and tests"; -} - -// --------------------------------------------------------------------------- -// Failure simulation -// --------------------------------------------------------------------------- - -function simulateFailurePattern(): StepState[] { - const steps: StepState[] = []; - - // ---- Step 1: Incomplete Context ---- - const step1Context = ["project structure", "route definitions"]; - const step1Missing = ["auth middleware", "rate-limiting policy", "test standards"]; - const step1Decision = modelDecide(step1Context, "add search endpoint"); - - steps.push({ - step: 1, - name: "Incomplete Context", - contextAvailable: step1Context, - contextMissing: step1Missing, - actionTaken: step1Decision, - localOutcome: "Looks good -- route compiles, returns data", - globalImpact: "Missing auth means unauthenticated access to search", - completed: false, - }); - - // ---- Step 2: Locally Reasonable Changes ---- - // The agent adds auth after a hint, but still lacks other context. - const step2Context = [...step1Context, "auth middleware"]; - const step2Missing = ["rate-limiting policy", "test standards"]; - const step2Decision = modelDecide(step2Context, "add search endpoint"); - - steps.push({ - step: 2, - name: "Locally Reasonable Changes", - contextAvailable: step2Context, - contextMissing: step2Missing, - actionTaken: step2Decision, - localOutcome: "Route has auth -- looks complete locally", - globalImpact: "No rate limiting means the endpoint can be abused", - completed: false, - }); - - // ---- Step 3: No Global Verification ---- - const step3Context = [...step2Context, "rate-limiting policy"]; - const step3Missing = ["test standards"]; - const step3Decision = modelDecide(step3Context, "add search endpoint"); - - steps.push({ - step: 3, - name: "No Global Verification", - contextAvailable: step3Context, - contextMissing: step3Missing, - actionTaken: step3Decision, - localOutcome: "Feature appears fully implemented", - globalImpact: "No tests -- regression risk, violates project standards", - completed: false, - }); - - // ---- Step 4: Premature Completion ---- - steps.push({ - step: 4, - name: "Premature Completion", - contextAvailable: step3Context, - contextMissing: step3Missing, - actionTaken: 'Agent outputs: "Done. Added search endpoint."', - localOutcome: "Agent is satisfied, task marked complete", - globalImpact: "Task is incomplete -- missing tests, no E2E verification", - completed: true, - }); - - return steps; -} - -// --------------------------------------------------------------------------- -// Comparison table -// --------------------------------------------------------------------------- - -function printComparisonTable(steps: StepState[]): void { - console.log("\n" + "=".repeat(90)); - console.log(" FAILURE PATTERN DEMO -- 4 Steps to a Broken Deliverable"); - console.log("=".repeat(90)); - - for (const s of steps) { - console.log(`\n Step ${s.step}: ${s.name}`); - console.log(" " + "-".repeat(60)); - console.log(` Context available : ${s.contextAvailable.join(", ")}`); - console.log(` Context missing : ${s.contextMissing.join(", ") || "(none)"}`); - console.log(` Action taken : ${s.actionTaken}`); - console.log(` Local outcome : ${s.localOutcome}`); - console.log(` Global impact : ${s.globalImpact}`); - console.log(` Marked complete? : ${s.completed ? "YES (premature)" : "No"}`); - } - - // Summary comparison - console.log("\n" + "=".repeat(90)); - console.log(" COMPARISON: What the agent saw vs. what was actually needed"); - console.log("=".repeat(90)); - - const totalRequired = ["project structure", "route definitions", "auth middleware", "rate-limiting policy", "test standards"]; - const finalAvailable = steps[steps.length - 1].contextAvailable; - - const header = "| Criterion | Available | Missing | Status |"; - const sep = "|------------------------|-----------|---------|----------|"; - console.log("\n" + header); - console.log(sep); - - for (const item of totalRequired) { - const avail = finalAvailable.includes(item); - const row = `| ${item.padEnd(23)}| ${(avail ? "Yes" : "No").padEnd(10)}| ${(!avail ? "Yes" : "").padEnd(8)}| ${(avail ? "OK" : "GAP").padEnd(9)}|`; - console.log(row); - } - - const gapCount = totalRequired.filter((i) => !finalAvailable.includes(i)).length; - console.log("\n Result: Agent completed with " + gapCount + " of " + totalRequired.length + " context items missing."); - console.log(" This is the core failure pattern: each step looked reasonable in isolation.\n"); -} - -// --------------------------------------------------------------------------- -// Run -// --------------------------------------------------------------------------- - -printComparisonTable(simulateFailurePattern()); diff --git a/courses/learn-harness-engineering/docs/ar/lectures/lecture-01-why-capable-agents-still-fail/code/failure-signals-checklist.md b/courses/learn-harness-engineering/docs/ar/lectures/lecture-01-why-capable-agents-still-fail/code/failure-signals-checklist.md deleted file mode 100644 index 38b41a4e..00000000 --- a/courses/learn-harness-engineering/docs/ar/lectures/lecture-01-why-capable-agents-still-fail/code/failure-signals-checklist.md +++ /dev/null @@ -1,9 +0,0 @@ -# قائمة فحص إشارات الفشل - -استخدم هذه القائمة عند مراجعة نتيجة تشغيل harness ضعيف. - -- هل سأل الوكيل أو استنتج بشكل خاطئ كيفية تشغيل التطبيق؟ -- هل أنشأ أدلة أو تجريدات لا تتطابق مع المنتج المقصود؟ -- هل توقف بعد بناء هيكل UI مرئي بدون سير عمل كامل؟ -- هل ترك ملاحظات أو منتجات تساعد تشغيلاً مستقبلياً على الاستمرار؟ -- هل يمكن لجلسة جديدة فهم ما حدث في أقل من خمس دقائق؟ diff --git a/courses/learn-harness-engineering/docs/ar/lectures/lecture-01-why-capable-agents-still-fail/code/index.md b/courses/learn-harness-engineering/docs/ar/lectures/lecture-01-why-capable-agents-still-fail/code/index.md deleted file mode 100644 index 4622ef52..00000000 --- a/courses/learn-harness-engineering/docs/ar/lectures/lecture-01-why-capable-agents-still-fail/code/index.md +++ /dev/null @@ -1,7 +0,0 @@ -# كود المحاضرة 01 - -استخدم هذا المجلد لأمثلة صغيرة توضح: - -- نموذج قوي يفشل في بيئة ضعيفة -- إعداد مستودع غير محدد بشكل كافٍ -- حلقات تغذية راجعة مفقودة diff --git a/courses/learn-harness-engineering/docs/ar/lectures/lecture-01-why-capable-agents-still-fail/code/underspecified-task.md b/courses/learn-harness-engineering/docs/ar/lectures/lecture-01-why-capable-agents-still-fail/code/underspecified-task.md deleted file mode 100644 index 187e22b7..00000000 --- a/courses/learn-harness-engineering/docs/ar/lectures/lecture-01-why-capable-agents-still-fail/code/underspecified-task.md +++ /dev/null @@ -1,18 +0,0 @@ -# مثال على مهمة غير محددة بشكل كافٍ - -بناء تطبيق سطح مكتب لقاعدة المعرفة مع إجابات أسئلة بالذكاء الاصطناعي. - -القيود: - -- لم يتم تحديد أي قيود -- لم يتم إعطاء أمر تشغيل -- لم يتم توفير إرشادات هيكل المجلدات -- لم يتم تعريف نموذج بيانات -- لا توجد معايير إكمال صريحة - -النتائج النموذجية لهذا النوع من prompt: - -- الوكيل يخترع هيكلاً بشكل مخصص -- التطبيق قد يُترجم لكنه لا يبدأ بشكل متسق -- الواجهة قد تظهر قبل أن يكون هناك مسار استيراد/استعلام قابل للاستخدام -- الوكيل يتوقف غالباً بعد نجاح تجميلي diff --git a/courses/learn-harness-engineering/docs/ar/lectures/lecture-01-why-capable-agents-still-fail/index.md b/courses/learn-harness-engineering/docs/ar/lectures/lecture-01-why-capable-agents-still-fail/index.md deleted file mode 100644 index f0a6188b..00000000 --- a/courses/learn-harness-engineering/docs/ar/lectures/lecture-01-why-capable-agents-still-fail/index.md +++ /dev/null @@ -1,117 +0,0 @@ -[中文版本 →](../../../zh/lectures/lecture-01-why-capable-agents-still-fail/) - -> أمثلة الكود: [code/](https://github.com/walkinglabs/learn-harness-engineering/blob/main/docs/ar/lectures/lecture-01-why-capable-agents-still-fail/code/) -> مشروع عملي: [المشروع 01. الاعتماد على المطالبة فقط مقابل القواعد أولًا](./../../projects/project-01-baseline-vs-minimal-harness/index.md) - -# المحاضرة 01. النماذج القوية لا تعني تنفيذًا موثوقًا - -تظن أنك خبير بما يكفي في عالم الذكاء الاصطناعي: اشتراك Claude Pro، مفتاح GPT-4o API، وأرقام لوحة SWE-bench محفوظة في ذاكرتك. ثم في يوم ما تسلم مشروعًا حقيقيًا إلى وكيل ذكاء اصطناعي وأنت واثق تمامًا. النتيجة؟ يضيف ميزة لكنه يكسر الاختبارات، يصلح عيبًا لكنه يضيف عيبين جديدين، يعمل لمدة 20 دقيقة ثم يعلن بفخر "تم" - وعندما تنظر إلى الكود تكتشف أنه ليس ما طلبته إطلاقًا. - -غريزتك الأولى؟ "هذا النموذج ليس جيدًا بما يكفي. حان وقت الترقية." تمهل. قبل أن تمد يدك إلى محفظتك، فكّر في احتمال أن المشكلة ليست في النموذج أصلًا. - -لننظر إلى بعض الأرقام. بحلول أواخر عام 2025، كانت أقوى وكلاء البرمجة على SWE-bench Verified تحقق تقريبًا 50-60%. وهذا على مهام مختارة بعناية، مع أوصاف Issues واضحة وحالات اختبار موجودة. انقل الأمر إلى بيئة تطويرك اليومية - متطلبات غامضة، لا اختبارات جاهزة، وقواعد عمل ضمنية مبعثرة في كل مكان - وستنخفض هذه النسبة أكثر. - -لكن خلف هذه الأرقام توجد حقيقة غير بديهية. - -## الحصان نفسه، مصائر مختلفة - -أجرت Anthropic تجربة مضبوطة. المطالبة نفسها ("ابنِ صانع ألعاب ريترو ثنائية الأبعاد")، والنموذج نفسه (Opus 4.5). التشغيل الأول: عارٍ بلا دعم - 20 دقيقة، 9 دولارات، والميزات الأساسية للعبة لم تعمل إطلاقًا. التشغيل الثاني: Harness كامل (مخطط + مولد + مقيم في بنية من ثلاثة وكلاء) - 6 ساعات، 200 دولار، وأصبحت اللعبة قابلة للعب. - -لم يغيروا النموذج. بقي Opus 4.5 هو Opus 4.5. ما تغيّر هو العدة الكاملة للفرس. - -مقال OpenAI لعام 2025 عن هندسة الـ Harness يقولها بوضوح: ينتقل Codex في مستودع مجهز جيدًا بالـ Harness من "غير موثوق" إلى "موثوق". لاحظ الصياغة - ليست "أفضل قليلًا"، بل انتقال نوعي. مثل حصان أصيل: يمكنك ركوبه بلا عدة مناسبة، لكنك لن تذهب بعيدًا، ولن تتحرك بسرعة، والسقوط لن يكون مفاجأة. الـ Harness هو تلك العدة الكاملة - **كل ما في البنية الهندسية خارج أوزان النموذج.** - -## أين تتعطل الوكلاء فعليًا - -إذًا، ما الذي يسوء تحديدًا؟ - -الأكثر شيوعًا: أنت لم تعرّف المهمة بوضوح. تقول "أضف ميزة بحث"، وفهم الوكيل يختلف تمامًا عن فهمك - البحث في ماذا؟ نص كامل أم بيانات منظمة؟ هل توجد صفحات؟ هل توجد تظليلات للنتائج؟ لم تحدد ذلك، فيخمن الوكيل. التخمين الصحيح حظ؛ أما التخمين الخاطئ فيكلف إصلاحه أكثر مما كان سيكلفه التحديد من البداية. الأمر يشبه دخول مطعم وقولك للطاهي: "أريد سمكًا" - هل سيأتي مطهوًا ببطء، أم على البخار، أم في قدر ساخن؟ الأمر كله متروك للمصادفة. - -حتى عندما تحدد المطلوب، لدى المشروع أعراف معمارية ضمنية لا يعرفها الوكيل. فريقك اعتمد صيغة SQLAlchemy 2.0، لكن الوكيل يكتب افتراضيًا بصيغة 1.x. كل نقاط API يجب أن تستخدم مصادقة OAuth 2.0، لكن هذه القاعدة موجودة فقط في رأسك وفي رسالة Slack من ثلاثة أشهر. الوكيل لا يرى ذلك - ليس لأنه لا يريد الالتزام، بل لأنه حرفيًا لا يعرف أن هذه القواعد موجودة. - -والبيئة نفسها قد تكون فخًا. بيئة تطوير ناقصة، اعتماديات مفقودة، إصدارات أدوات خاطئة. يحرق الوكيل جزءًا ثمينًا من نافذة السياق على أخطاء `pip install` وتعارضات إصدار Node بدلًا من حل مهمتك الفعلية. كأنك تستأجر نجارًا ماهرًا ثم تنسى أن توفر له مطرقة ومسامير ومنضدة عمل مستوية - مهما كانت مهارته، لن يستطيع إنجاز العمل. - -والأكثر شيوعًا من ذلك: لا توجد طريقة للتحقق. لا اختبارات، لا Lint، أو لم تُبلّغ أوامر التحقق إلى الوكيل. يكتب الوكيل الكود، ينظر إليه، يقرر أنه جيد، ثم يقول "تم". هذا يشبه أن تطلب من طالب تسليم واجب بلا نموذج إجابة - يظن أنه أجاب بشكل صحيح، لكن عند التصحيح تجد الأخطاء متراكمة. لاحظت Anthropic أيضًا ظاهرة مثيرة: عندما تشعر الوكلاء أن السياق يوشك على النفاد، تندفع لإنهاء المهمة، وتتخطى التحقق، وتختار حلًا بسيطًا بدل الحل الأفضل. يسمون ذلك "context anxiety" - وهو شبيه بما يحدث عندما تدرك أن وقت الامتحان أوشك على الانتهاء فتبدأ بتخمين إجابات الأسئلة المتبقية. - -المهام الطويلة الممتدة عبر جلسات أسوأ أيضًا: كل الاكتشافات من الجلسة السابقة تضيع، وكل جلسة جديدة تضطر إلى إعادة استكشاف بنية المشروع وفهم تنظيم الكود. الوكلاء بلا حالة مستمرة ترتفع معدلات فشلها بحدة في المهام التي تتجاوز 30 دقيقة. - -## مصطلحات أساسية - -مع هذه السيناريوهات في الذهن، لم تعد هذه المفاهيم مجرد مصطلحات: - -- **Capability Gap**: الفجوة الكبيرة بين أداء النموذج على المعايير القياسية وأدائه في المهام الحقيقية. معدل نجاح 50-60% على SWE-bench Verified يعني أن قرابة نصف القضايا الحقيقية لا تُحل. -- **Harness**: كل ما هو خارج النموذج - التعليمات، الأدوات، البيئة، إدارة الحالة، وتغذية التحقق الراجعة. إن لم يكن من أوزان النموذج فهو جزء من الـ Harness. هذه هي "العدة الكاملة" التي نتحدث عنها. -- **Harness-Induced Failure**: لدى النموذج قدرة كافية، لكن بيئة التنفيذ فيها عيوب بنيوية. تجربة Anthropic المضبوطة أثبتت ذلك بالفعل. -- **Verification Gap**: الفجوة بين ثقة الوكيل في مخرجاته والصحة الفعلية. يقول الوكيل "انتهيت" وهو لم ينتهِ - وهذا هو نمط الفشل الأكثر شيوعًا. -- **Diagnostic Loop**: نفّذ، لاحظ الفشل، انسبه إلى طبقة Harness محددة، أصلح تلك الطبقة، ثم نفّذ مرة أخرى. هذه هي المنهجية الأساسية لهندسة الـ Harness. -- **Definition of Done**: مجموعة شروط قابلة للتحقق آليًا - الاختبارات تمر، الـ lint نظيف، وفحوصات الأنواع ناجحة. من دون Definition of Done صريحة، سيخترع الوكيل تعريفه الخاص. - -## عندما تفشل الأمور، أصلح الـ Harness أولًا - -المبدأ الأساسي: **عندما تفشل الأمور، لا تستبدل النموذج أولًا - افحص الـ Harness.** إذا نجح النموذج نفسه في مهام مشابهة ومهيكلة جيدًا، فافترض أن المشكلة في الـ Harness. مثل سيارة تعطلت: لا تفترض فورًا أن المحرك هو السبب. افحص أولًا إن كان الوقود قد نفد. - -خطوات عملية: - -**انسب كل فشل إلى طبقة محددة.** لا تقل فقط "النموذج سيئ". اسأل: هل كانت المهمة غير واضحة؟ هل كان السياق غير كافٍ؟ هل لم تكن هناك وسائل تحقق؟ اربط كل فشل بإحدى طبقات الفشل الخمس: مواصفة المهمة، تزويد السياق، بيئة التنفيذ، تغذية التحقق الراجعة، وإدارة الحالة. هذه طبقات تشخيص عملية، وليست المصطلحات الستة في المسرد أعلاه. ابنِ هذه العادة، وستجد أن عبارة "النموذج ليس جيدًا بما يكفي" تظهر أقل فأقل في سجلاتك. - -**اكتب Definition of Done صريحة لكل مهمة.** لا تقل "أضف ميزة بحث". قل: -``` -Completion criteria: -- New endpoint GET /api/search?q=xxx -- Supports pagination, default 20 items -- Results include highlighted snippets -- All new code passes pytest -- Type checking passes (mypy --strict) -``` - -**أنشئ ملف AGENTS.md.** ضعه في جذر المستودع ليخبر الوكيل بمكدس التقنية، والأعراف المعمارية، وأوامر التحقق الخاصة بالمشروع. هذه هي الخطوة الأولى في هندسة الـ Harness، وهي أعلى خطوة عائدًا على الجهد يمكنك اتخاذها. قد يكون ملف `AGENTS.md` واحد أكثر فاعلية من الترقية إلى نموذج أغلى - وهذا ليس مزاحًا. - -**ابنِ حلقة تشخيص.** لا تتعامل مع الفشل على أنه "النموذج غبي مرة أخرى". تعامل معه كإشارة إلى وجود عيب في الـ Harness. في كل فشل: حدد الطبقة، أصلحها، وتأكد ألا يتكرر الفشل بالطريقة نفسها. بعد بضع جولات، يصبح الـ Harness أقوى ويستقر أداء الوكيل. مثل إصلاح الطريق: كل حفرة تملؤها تجعل المقطع التالي أكثر سلاسة. - -**قِس التحسن.** احتفظ بسجل بسيط: هل نجحت كل مهمة أم فشلت، وأي طبقة سببت الفشل؟ بعد عدة جولات سترى أي طبقة هي عنق الزجاجة - ركز طاقتك هناك. - -## تجربة المليون سطر - -أجرت OpenAI في 2025 تجربة جريئة: استخدام Codex لبناء منتج داخلي كامل من مستودع Git فارغ. بعد خمسة أشهر، احتوى المستودع على نحو مليون سطر كود - منطق تطبيق، بنية تحتية، أدوات، توثيق، وأدوات تطوير داخلية - كلها مولدة بواسطة الوكلاء. قاد ثلاثة مهندسين Codex، وفتحوا ودمجوا نحو 1,500 Pull Request. بمعدل 3.5 Pull Request لكل شخص يوميًا. - -القيد الأساسي: **البشر لا يكتبون الكود مباشرة أبدًا.** لم تكن هذه حيلة، بل صُممت لإجبار الفريق على فهم ما يتغير عندما لا تكون وظيفة المهندس الأساسية هي كتابة الكود، بل تصميم البيئات، وصياغة النية، وبناء حلقات التغذية الراجعة. - -كان التقدم في البداية أبطأ من المتوقع. ليس لأن Codex لم يكن قادرًا، بل لأن البيئة لم تكن مكتملة بما يكفي - كان الوكيل يفتقد الأدوات، والتجريدات، والبنى الداخلية اللازمة للتقدم نحو أهداف عالية المستوى. أصبح عمل المهندسين: تقسيم الأهداف الكبيرة إلى لبنات صغيرة (تصميم، كود، مراجعة، اختبار)، وترك الوكيل يركبها، ثم استخدام تلك اللبنات لفتح مهام أكثر تعقيدًا. عندما يفشل شيء، لم يكن الحل تقريبًا "حاول بجهد أكبر"، بل "ما القدرة التي يفتقدها الوكيل، وكيف نجعلها مفهومة وقابلة للتنفيذ؟" - -تثبت هذه التجربة مباشرة الأطروحة المركزية لهذه المحاضرة: **النموذج نفسه ينتج مخرجات مختلفة جوهريًا في بيئة عارية مقارنة ببيئة ذات Harness كامل.** النموذج لم يتغير. البيئة تغيرت. - -> المصدر: [OpenAI: Harness engineering: leveraging Codex in an agent-first world](https://openai.com/index/harness-engineering/) - -## مثال أقرب إلى الواقع - -استخدم فريق Claude Sonnet لإضافة نقطة API جديدة إلى تطبيق ويب Python متوسط الحجم (FastAPI + PostgreSQL + Redis، نحو 15,000 سطر كود). - -في البداية أعطوه جملة واحدة فقط: "add user preferences endpoints under `/api/v2/users`." النتيجة؟ استهلك الوكيل 40% من نافذة السياق في استكشاف بنية المستودع، وأنتج كودًا يبدو معقولًا لكنه لا يتبع أنماط معالجة الأخطاء في المشروع، واستخدم صيغة SQLAlchemy قديمة، وأعلن الانتهاء رغم أن نقطة النهاية كانت تحتوي على أخطاء تشغيل. اضطرت الجلسة التالية إلى إعادة كل عمل الاكتشاف. - -لاحقًا أضافوا `AGENTS.md` (يصف معمارية المشروع وإصدارات مكدس التقنية)، وأوامر تحقق صريحة (`pytest tests/api/v2/ && python -m mypy src/`)، وسجلات قرارات معمارية. نجح النموذج نفسه في الجولات الثلاث المستقلة كلها، مع كفاءة سياق أفضل بنحو 60%. - -لم يغيروا النموذج. غيّروا الـ Harness. - -## الخلاصات الأساسية - -- قدرة النموذج وموثوقية التنفيذ شيئان مختلفان. حتى الحصان الأصيل يحتاج إلى عدة جيدة. -- عندما تفشل الأمور، افحص الـ Harness أولًا ثم النموذج. تبديل النماذج هو الخيار الأغلى - وغالبًا ليست المشكلة في النموذج أصلًا. -- كل فشل إشارة: لدى الـ Harness عيب بنيوي. اعثر عليه وأصلحه. -- خمس طبقات دفاع: مواصفة المهمة، تزويد السياق، بيئة التنفيذ، تغذية التحقق الراجعة، وإدارة الحالة. افحصها بشكل منهجي، كما يستبعد الطبيب الأسباب الأكثر شيوعًا أولًا. -- قد يكون ملف `AGENTS.md` واحد أكثر فاعلية من الترقية إلى نموذج أغلى. بجدية. - -## قراءات إضافية - -- [OpenAI: Harness Engineering — Leveraging Codex in an Agent-First World](https://openai.com/index/harness-engineering/) -- [Anthropic: Effective Harnesses for Long-Running Agents](https://www.anthropic.com/engineering/effective-harnesses-for-long-running-agents) -- [HumanLayer: Skill Issue — Harness Engineering for Coding Agents](https://humanlayer.dev/articles/harness-engineering-for-coding-agents/) -- [SWE-bench Leaderboard](https://www.swebench.com/) -- [Thoughtworks Technology Radar: Harness Engineering](https://www.thoughtworks.com/radar) - -## تمارين - -1. **تجربة مقارنة**: اختر قاعدة كود تعرفها جيدًا ومهمة تعديل غير تافهة. شغّل الوكيل أولًا بلا دعم Harness وسجل الإخفاقات. ثم أضف `AGENTS.md` يحتوي على أوامر تحقق صريحة وشغّل الوكيل نفسه مرة أخرى. قارن النتائج، وانسب كل فشل إلى إحدى طبقات الدفاع الخمس. - -2. **قياس Verification Gap**: اختر 5 مهام برمجية. بعد كل مهمة، سجل هل يدعي الوكيل أنه انتهى، ثم تحقق من الصحة الفعلية باختبارات مستقلة. احسب نسبة الحالات التي يقول فيها الوكيل إنه انتهى بينما لم ينتهِ فعليًا - هذه هي Verification Gap لديك. ثم فكّر: ما أوامر التحقق التي ستخفض هذه النسبة؟ - -3. **تدريب حلقة التشخيص**: ابحث عن مهمة يفشل فيها الوكيل مرارًا في مشروعك. شغّلها مرة واحدة وسجل الفشل. انسبه إلى إحدى الطبقات الخمس. أصلح تلك الطبقة. شغّل مرة أخرى. كرر ذلك من ثلاث إلى خمس جولات، وسجل التحسن في كل مرة. diff --git a/courses/learn-harness-engineering/docs/ar/lectures/lecture-02-what-a-harness-actually-is/code/harness-components.md b/courses/learn-harness-engineering/docs/ar/lectures/lecture-02-what-a-harness-actually-is/code/harness-components.md deleted file mode 100644 index 6e0519ec..00000000 --- a/courses/learn-harness-engineering/docs/ar/lectures/lecture-02-what-a-harness-actually-is/code/harness-components.md +++ /dev/null @@ -1,21 +0,0 @@ -# مثال على مكونات الـ Harness - -لوكيل برمجة يعمل في مستودع محلي: - -- النموذج (Model): - الـ LLM نفسه - -- الـ Harness: - - system prompt - - AGENTS.md - - أداة bash - - أدوات قراءة/كتابة الملفات - - وصول git - - نظام الملفات المحلي - - سكربتات التشغيل - - أوامر الاختبار - - stop hooks - - فحوصات lint - - حلقة التقييم (evaluator loop) - -إذا غيّرت أيًا من مكونات الـ harness أعلاه، فقد غيّرت الوكيل الفعّال. diff --git a/courses/learn-harness-engineering/docs/ar/lectures/lecture-02-what-a-harness-actually-is/code/harness-vs-no-harness.ts b/courses/learn-harness-engineering/docs/ar/lectures/lecture-02-what-a-harness-actually-is/code/harness-vs-no-harness.ts deleted file mode 100644 index d856ba36..00000000 --- a/courses/learn-harness-engineering/docs/ar/lectures/lecture-02-what-a-harness-actually-is/code/harness-vs-no-harness.ts +++ /dev/null @@ -1,180 +0,0 @@ -/** - * harness-vs-no-harness.ts - * - * Side-by-side comparison of the same task executor running with and without - * a harness. The harness version adds explicit rules, verification steps, - * and stop conditions. - * - * Run: npx tsx docs/lectures/lecture-02-what-a-harness-actually-is/code/harness-vs-no-harness.ts - */ - -// --------------------------------------------------------------------------- -// Types & helpers -// --------------------------------------------------------------------------- - -interface TaskResult { - name: string; - passed: boolean; - durationMs: number; - issues: string[]; -} - -function pad(s: string, len: number): string { - return s.length >= len ? s : s + " ".repeat(len - s.length); -} - -// --------------------------------------------------------------------------- -// Simulated task executor -// --------------------------------------------------------------------------- - -/** A simple task that can succeed or fail depending on rules. */ -type Task = { - name: string; - requiresAuth: boolean; - hasTests: boolean; - withinScope: boolean; -}; - -const tasks: Task[] = [ - { name: "Add search endpoint", requiresAuth: true, hasTests: false, withinScope: true }, - { name: "Add delete endpoint", requiresAuth: true, hasTests: true, withinScope: true }, - { name: "Refactor auth middleware", requiresAuth: true, hasTests: true, withinScope: false }, - { name: "Add health check", requiresAuth: false, hasTests: true, withinScope: true }, - { name: "Add rate limiter", requiresAuth: true, hasTests: false, withinScope: true }, -]; - -// --------------------------------------------------------------------------- -// Run WITHOUT harness -- just execute every task, no checks -// --------------------------------------------------------------------------- - -function runWithoutHarness(taskList: Task[]): TaskResult[] { - const results: TaskResult[] = []; - - for (const t of taskList) { - const start = performance.now(); - const issues: string[] = []; - - // The "agent" does the work and calls it done. - let passed = true; - - // Problems that go undetected without a harness - if (t.requiresAuth && !t.hasTests) { - issues.push("No tests for auth-protected endpoint"); - // Agent doesn't notice -- marks as pass anyway - } - if (!t.withinScope) { - issues.push("Task is outside current scope"); - // Agent doesn't notice - } - - const duration = performance.now() - start; - results.push({ name: t.name, passed, durationMs: duration, issues }); - } - - return results; -} - -// --------------------------------------------------------------------------- -// Run WITH harness -- rules, verification, stop conditions -// --------------------------------------------------------------------------- - -function runWithHarness(taskList: Task[]): TaskResult[] { - const rules = { - requireTestsForAuth: true, - enforceScope: true, - }; - - const results: TaskResult[] = []; - - for (const t of taskList) { - const start = performance.now(); - const issues: string[] = []; - let passed = true; - - // Rule: auth endpoints must have tests - if (rules.requireTestsForAuth && t.requiresAuth && !t.hasTests) { - issues.push("BLOCKED: Auth-protected endpoint missing tests"); - passed = false; - } - - // Rule: stay in scope - if (rules.enforceScope && !t.withinScope) { - issues.push("BLOCKED: Task outside active scope -- skipped"); - passed = false; - } - - // Verification: re-check after execution - if (passed && !t.hasTests) { - issues.push("WARNING: No tests exist for this change"); - } - - const duration = performance.now() - start; - results.push({ name: t.name, passed, durationMs: duration, issues }); - } - - return results; -} - -// --------------------------------------------------------------------------- -// Reporting -// --------------------------------------------------------------------------- - -function printComparison( - noHarness: TaskResult[], - withHarness: TaskResult[] -): void { - console.log("\n" + "=".repeat(80)); - console.log(" HARNESS vs NO-HARNESS COMPARISON"); - console.log("=".repeat(80)); - - const header = `| ${pad("Task", 28)}| ${pad("No-Harness", 12)}| ${pad("Issues(NH)", 35)}| ${pad("Harness", 12)}| ${pad("Issues(H)", 35)}|`; - const sep = `|${"-".repeat(30)}|${"-".repeat(14)}|${"-".repeat(37)}|${"-".repeat(14)}|${"-".repeat(37)}|`; - - console.log("\n" + header); - console.log(sep); - - for (let i = 0; i < noHarness.length; i++) { - const nh = noHarness[i]; - const wh = withHarness[i]; - const nhPass = nh.passed ? "PASS" : "FAIL"; - const whPass = wh.passed ? "PASS" : "FAIL"; - const nhIssue = nh.issues[0] ?? "(none)"; - const whIssue = wh.issues[0] ?? "(none)"; - - console.log( - `| ${pad(nh.name, 28)}| ${pad(nhPass, 12)}| ${pad(nhIssue, 35)}| ${pad(whPass, 12)}| ${pad(whIssue, 35)}|` - ); - } - - // Metrics - const nhPassed = noHarness.filter((r) => r.passed).length; - const whPassed = withHarness.filter((r) => r.passed).length; - const nhIssues = noHarness.reduce((sum, r) => sum + r.issues.length, 0); - const whIssues = withHarness.reduce((sum, r) => sum + r.issues.length, 0); - - console.log("\n" + "=".repeat(80)); - console.log(" SUMMARY METRICS"); - console.log("=".repeat(80)); - - const metricsHeader = `| ${pad("Metric", 30)}| ${pad("No Harness", 15)}| ${pad("With Harness", 15)}|`; - const metricsSep = `|${"-".repeat(32)}|${"-".repeat(17)}|${"-".repeat(17)}|`; - console.log("\n" + metricsHeader); - console.log(metricsSep); - console.log(`| ${pad("Tasks passed", 30)}| ${pad(String(nhPassed) + "/" + noHarness.length, 15)}| ${pad(String(whPassed) + "/" + withHarness.length, 15)}|`); - console.log(`| ${pad("Issues detected", 30)}| ${pad(String(nhIssues), 15)}| ${pad(String(whIssues), 15)}|`); - console.log(`| ${pad("False positives (passed but flawed)", 30)}| ${pad(String(nhPassed - truePassCount(noHarness)), 15)}| ${pad(String(whPassed - truePassCount(withHarness)), 15)}|`); - - console.log("\n The harness catches problems the no-harness run silently ignores."); - console.log(" Without a harness, every task 'passes' even when it shouldn't.\n"); -} - -/** Count tasks that actually pass (have tests and are in scope). */ -function truePassCount(results: TaskResult[]): number { - return results.filter((r) => r.passed && r.issues.length === 0).length; -} - -// --------------------------------------------------------------------------- -// Run -// --------------------------------------------------------------------------- - -printComparison(runWithoutHarness(tasks), runWithHarness(tasks)); diff --git a/courses/learn-harness-engineering/docs/ar/lectures/lecture-02-what-a-harness-actually-is/code/index.md b/courses/learn-harness-engineering/docs/ar/lectures/lecture-02-what-a-harness-actually-is/code/index.md deleted file mode 100644 index 1bbd6d3e..00000000 --- a/courses/learn-harness-engineering/docs/ar/lectures/lecture-02-what-a-harness-actually-is/code/index.md +++ /dev/null @@ -1,8 +0,0 @@ -# كود المحاضرة 02 - -استخدم هذا المجلد لأمثلة صغيرة تميّز بين: - -- سلوك النموذج -- سلوك الـ harness -- إعدادات تعتمد على prompt فقط -- إعدادات وكلاء مدعومة بالبيئة diff --git a/courses/learn-harness-engineering/docs/ar/lectures/lecture-02-what-a-harness-actually-is/code/minimal-harness-loop.ts b/courses/learn-harness-engineering/docs/ar/lectures/lecture-02-what-a-harness-actually-is/code/minimal-harness-loop.ts deleted file mode 100644 index e56cddd8..00000000 --- a/courses/learn-harness-engineering/docs/ar/lectures/lecture-02-what-a-harness-actually-is/code/minimal-harness-loop.ts +++ /dev/null @@ -1,36 +0,0 @@ -type Message = { - role: "user" | "assistant"; - content: string; -}; - -type ToolResult = { - ok: boolean; - output: string; -}; - -function runTool(name: string, input: string): ToolResult { - if (name === "read_file") { - return { ok: true, output: `contents of ${input}` }; - } - - return { ok: false, output: `unknown tool: ${name}` }; -} - -export function minimalHarness(messages: Message[]) { - const nextAction = { - tool: "read_file", - input: "README.md" - }; - - const result = runTool(nextAction.tool, nextAction.input); - - return { - messages: [ - ...messages, - { - role: "assistant", - content: `Tool ${nextAction.tool} returned: ${result.output}` - } - ] - }; -} diff --git a/courses/learn-harness-engineering/docs/ar/lectures/lecture-02-what-a-harness-actually-is/index.md b/courses/learn-harness-engineering/docs/ar/lectures/lecture-02-what-a-harness-actually-is/index.md deleted file mode 100644 index cb85336e..00000000 --- a/courses/learn-harness-engineering/docs/ar/lectures/lecture-02-what-a-harness-actually-is/index.md +++ /dev/null @@ -1,109 +0,0 @@ -[中文版本 →](../../../zh/lectures/lecture-02-what-a-harness-actually-is/) - -> أمثلة الكود: [code/](https://github.com/walkinglabs/learn-harness-engineering/blob/main/docs/ar/lectures/lecture-02-what-a-harness-actually-is/code/) -> مشروع عملي: [Project 01. Prompt-only vs. rules-first](./../../projects/project-01-baseline-vs-minimal-harness/index.md) - -# المحاضرة 02. ما معنى harness فعليًا - -تُستخدم كلمة "harness" بكثرة في أوساط وكلاء البرمجة بالذكاء الاصطناعي، لكن بصراحة، معظم الناس يقصدون "ملف prompt" عندما يقولون harness. هذا ليس harness. الأمر أشبه بفتح مطعم ليس فيه سوى المكونات — لا موقد، ولا سكاكين، ولا وصفات، ولا خطة لتقديم الأطباق. هذا ليس مطعمًا. هذا ثلاجة. - -تقدم لك هذه المحاضرة تعريفًا دقيقًا وقابلًا للتطبيق لمفهوم harness. ليس تجريدًا أكاديميًا، بل إطار عمل يمكنك استخدامه اليوم: يتكون harness من خمسة أنظمة فرعية، لكل منها مسؤوليات واضحة ومعايير تقييم محددة. - -## ابدأ بتشبيه - -تخيل أنك مهندس تم تعيينه حديثًا وأُلقيت في مشروع بدون أي توثيق. لا README، ولا تعليقات في الكود، ولا أحد يخبرك كيف تشغّل الاختبارات، وإعدادات CI مدفونة في مكان ما. هل يمكنك كتابة كود جيد؟ ربما — إذا كنت ذكيًا بما فيه الكفاية وصبورًا بما فيه الكفاية. لكنك ستقضي وقتًا هائلًا في "اكتشاف ما يدور حوله هذا المشروع" بدلاً من "حل المشكلة." - -يواجه وكيل الذكاء الاصطناعي نفس الموقف تمامًا. والأمر أسوأ — يمكنك على الأقل سؤال زميل. الوكيل لا يمكنه رؤية سوى الملفات التي تضعها أمامه والأوامر التي يمكنه تنفيذها. لا يمكنه أن يربت على كتف أحدهم ويسأل "مرحبًا، أي إصدار من ORM يستخدمه هذا المشروع؟" - -صاغت OpenAI المبدأ الأساسي بأن "المستودع هو المواصفة" — يجب أن يكون كل السياق الضروري في المستودع، مُقدمًا من خلال ملفات تعليمات منظمة، وأوامر تحقق صريحة، وتنظيم واضح للأدلة. توثيق Anthropic للوكلاء طويلي التشغيل يُؤكد على استمرارية الحالة، ومسارات الاسترداد الصريحة، وتتبع التقدم المنظم. الشركتان تركزان على جوانب مختلفة، لكنهما تقولان نفس الشيء: **كل شيء في البنية التحتية الهندسية خارج النموذج يحدد مقدار ما يتحقق فعليًا من قدرات النموذج.** - -انظر إلى بعض الأدوات التي تعرفها بالفعل: - -**Claude Code** يجسد تفكير harness. يقرأ `CLAUDE.md` من مستودعك (رف الوصفات)، ويمكنه تشغيل أوامر shell (حامل السكاكين)، وينفذ في بيئتك المحلية (الموقد)، ويحافظ على سجل الجلسة (محطة التحضير)، ويمكنه تشغيل الاختبارات ورؤية النتائج (نافذة فحص الجودة). لكن إذا لم تخبره كيف يشغّل الاختبارات، فنافذة فحص الجودة معطلة — لا أحد يعرف ما إذا كان الطبق ناضجًا بالكامل. - -**Cursor** يتبع منطقًا مشابهًا. ملف `.cursorrules` الخاص به هو رف الوصفات، والطرفية هي حامل السكاكين، ويقرأ هيكل مشروعك وإعدادات lint كالموقد. لكن إدارة حالة Cursor ضعيفة نسبيًا — أغلق IDE وأعد فتحه، والسياق السابق اختفى. - -**Codex** (وكيل البرمجة من OpenAI) يستخدم git worktrees لعزل بيئة تشغيل كل مهمة، مقترنًا بمكدس مراقبة محلي (سجلات، مقاييس، تتبعات)، بحيث يتم التحقق من كل تغيير في بيئة مستقلة. في المستودعات التي تحتوي على `AGENTS.md` وأوامر تحقق واضحة، فإنه يؤدي بشكل أفضل بكثير من المستودعات "العارية". - -**AutoGPT** هي قصة تحذيرية — غياب إدارة الحالة المنظمة يؤدي إلى تراكم السياق في المهام الطويلة، وغياب آليات التغذية الراجعة الدقيقة يجعل الوكيل يدور في حلقة مفرغة. كثير من الناس يقولون إن AutoGPT "لا يعمل"، لكن في الحقيقة إن harness الخاص بـ AutoGPT هو الذي لا يعمل — أعطِ طباخًا موقدًا معطلًا وحتى أفضل المكونات لن تُنتج وجبة. - -## المفاهيم الأساسية - -- **ما هو harness**: كل شيء في البنية التحتية الهندسية خارج أوزان النموذج. تُلخص OpenAI الوظيفة الأساسية للمهندس في ثلاثة أشياء: تصميم البيئات، والتعبير عن النوايا، وبناء حلقات التغذية الراجعة. تسمي Anthropic Claude Agent SDK الخاص بها "harness وكيل للأغراض العامة." -- **المستودع هو المصدر الوحيد للحقيقة**: أي شيء لا يستطيع الوكيل رؤيته، لجميع الأغراض العملية، غير موجود. تتعامل OpenAI مع المستودع بصفته "سجل النظام" — يجب أن يعيش فيه كل السياق الضروري، من خلال ملفات منظمة وتنظيم واضح للأدلة. -- **أعطِ خريطة، لا دليلًا**: تجربة OpenAI — يجب أن يكون `AGENTS.md` صفحة فهرس، لا موسوعة. حوالي 100 سطر كافية. إذا لم تكف، قسّمها إلى دليل `docs/` ودع الوكيل يقرأ عند الحاجة. -- **قيّد، لا تُدارة بالتفصيل**: harness الجيد يستخدم قواعد قابلة للتنفيذ لتقييد الوكيل، بدلاً من تعداد التعليمات واحدة تلو الأخرى. تقول OpenAI "فرض الثوابت، لا تُدارة التنفيذ"؛ ووجدت Anthropic أن الوكلاء يمدحون عملهم بثقة، والحل هو فصل "من يقوم بالعمل" عن "من يتحقق من العمل." -- **أزل المكونات واحدًا تلو الآخر**: لقياس المساهمة الحدية لكل مكون من مكونات harness، أزلها واحدًا تلو الآخر وانظر أي إزالة تسبب أكبر انخفاض في الأداء. استخدمت Anthropic هذه الطريقة ووجدت أنه مع قوة النماذج، تتوقف بعض المكونات عن كونها حرجة — لكن جديدة تظهر دائمًا. - -## نموذج harness ذو الأنظمة الفرعية الخمسة - -نعود إلى تشبيه المطبخ. المطبخ الكامل يحتوي على خمس مناطق وظيفية، وharness يحتوي على خمسة أنظمة فرعية: - -```mermaid -flowchart LR - Rules["Project rules
AGENTS.md / CLAUDE.md"] --> Agent["AI Agent"] - State["Progress and git
PROGRESS.md / commits"] --> Agent - Agent --> Tools["Tools
shell / files / tests"] - Tools --> Env["Runtime
deps / services / versions"] - Env --> Checks["Check results
test / lint / build"] - Checks --> Agent -``` - -**نظام التعليمات (رف الوصفات)**: أنشئ `AGENTS.md` (أو `CLAUDE.md`) يحتوي على نظرة عامة على المشروع والغرض منه (جملة واحدة)، حزمة التقنيات والإصدارات (Python 3.11، FastAPI 0.100+، PostgreSQL 15)، أوامر التشغيل الأولى (`make setup`، `make test`)، القيود الصارمة غير القابلة للتفاوض ("جميع واجهات API يجب أن تستخدم OAuth 2.0")، وروابط إلى توثيق أكثر تفصيلًا. - -**نظام الأدوات (حامل السكاكين)**: تأكد من أن الوكيل لديه وصول كافٍ للأدوات. لا تعطل shell بحجة "الأمان" — إذا لم يستطع الوكيل حتى تشغيل `pip install`، فكيف يُفترض أن يعمل؟ لكن لا تفتح كل شيء أيضًا — اتبع مبادئ الامتياز الأقل. - -**نظام البيئة (الموقد)**: اجعل حالة البيئة واصفة لنفسها. استخدم `pyproject.toml` أو `package.json` لتثبيت التبعيات، و`.nvmrc` أو `.python-version` لإصدارات وقت التشغيل، وDocker أو devcontainers لضمان قابلية التكرار. - -**نظام الحالة (محطة التحضير)**: المهام الطويلة تحتاج إلى تتبع التقدم. استخدم ملف `PROGRESS.md` بسيط يسجل: ما تم إنجازه، ما قيد التنفيذ، ما المحظور. حدّثه قبل نهاية كل جلسة، واقرأه عند بدء الجلسة التالية. - -**نظام التغذية الراجعة (نافذة فحص الجودة)**: هذا هو النظام الفرعي ذو أعلى عائد على الاستثمار. اذكر أوامر التحقق صراحةً في `AGENTS.md`: -``` -Verification commands: -- Tests: pytest tests/ -x -- Type check: mypy src/ --strict -- Lint: ruff check src/ -- Full verification: make check (includes all above) -``` - -غياب أي نظام فرعي يشبه غياب منطقة وظيفية في المطبخ — لا يزال بإمكانك الطبخ، لكن الأمر دائمًا محرج. - -**قياس قيمة مكونات harness**: استخدم "التحكم المتساوي للنموذج." أبقِ النموذج ثابتًا، وأزل الأنظمة الفرعية واحدًا تلو الآخر، وقِس أي إزالة تسبب أكبر انخفاض في الأداء. أكبر انخفاض يحدد المكون صاحب أعلى مساهمة حدية في تلك المهمة، وليس بالضرورة عنق الزجاجة. لتحديد عنق الزجاجة الحقيقي، اجمع هذه التجربة مع سجلات الفشل ونسب الأسباب: مهمة غير واضحة، سياق غير كافٍ، بيئة غير قابلة لإعادة الإنتاج، غياب تغذية التحقق الراجعة، أو انقطاع إدارة الحالة. - -## قصة فريق حقيقية - -استخدم فريق GPT-4o على تطبيق واجهة أمامية بـ TypeScript + React (~20,000 سطر كود). مرّوا بأربع مراحل — بشكل أساسي إضافة معدات المطبخ قطعة قطعة: - -**المرحلة 1 — مطبخ فارغ**: فقط وصف مشروع أساسي في README. نجحت محاولة واحدة من أصل 5 (20%). الأعطال الرئيسية: اختيار مدير حزم خاطئ (npm مقابل yarn)، عدم اتباع اصطلاحات تسمية المكونات، عدم القدرة على تشغيل الاختبارات. - -**المرحلة 2 — رف الوصفات مُركّب**: أُضيف `AGENTS.md` بإصدارات حزمة التقنيات، واصطلاحات التسمية، والقرارات المعمارية الرئيسية. ارتفع معدل النجاح إلى 60%. كانت الأعطال المتبقية أساسًا مشاكل بيئية وتحقق مفقود. - -**المرحلة 3 — نافذة فحص الجودة مفتوحة**: أُدرجت أوامر التحقق في `AGENTS.md`: `yarn test && yarn lint && yarn build`. ارتفع معدل النجاح إلى 80%. - -**المرحلة 4 — محطة التحضير جاهزة**: أُدخلت قوالب ملفات التقدم حيث سجّل الوكلاء العمل المكتمل وغير المكتمل في كل تشغيل. استقر معدل النجاح عند 80-100%. - -أربع تكرارات، النموذج لم يتغير إطلاقًا، ومعدل النجاح ارتفع من 20% إلى ما يقارب 100%. هذه هي قوة هندسة harness. لم تشترِ مكونات أغلى — لقد نظّمت المطبخ بشكل صحيح فحسب. - -## الخلاصات الأساسية - -- Harness = تعليمات + أدوات + بيئة + حالة + تغذية راجعة. خمسة أنظمة فرعية، مثل المناطق الوظيفية الخمس للمطبخ — جميعها أساسية. -- إذا لم تكن أوزان النموذج، فهي harness. إن harness الخاص بك يحدد مقدار ما يتحقق من قدرات النموذج. -- من بين الأنظمة الفرعية الخمسة، عادةً ما يكون نظام التغذية الراجعة هو الأقل استثمارًا والأعلى عائدًا. اجعل أوامر التحقق صحيحة أولاً — نافذة فحص الجودة هي الترقية الأكثر جدوى بالاستثمار. -- استخدم "التحكم المتساوي للنموذج" لقياس المساهمة الحدية لكل نظام فرعي؛ واستخدم سجلات الفشل ونسب الأسباب لتحديد عنق الزجاجة الحقيقي. -- harness يتآكل مثل الكود. راجع بانتظام، وسدد ديون harness كما تسدد الديون التقنية. - -## قراءات إضافية - -- [OpenAI: Harness Engineering](https://openai.com/index/harness-engineering/) -- [Anthropic: Effective Harnesses for Long-Running Agents](https://www.anthropic.com/engineering/effective-harnesses-for-long-running-agents) -- [HumanLayer: Harness Engineering for Coding Agents](https://humanlayer.dev/articles/harness-engineering-for-coding-agents/) -- [SWE-agent: Agent-Computer Interfaces](https://github.com/princeton-nlp/SWE-agent) -- [Thoughtworks: Harness Engineering on Technology Radar](https://www.thoughtworks.com/radar) - -## تمارين - -1. **تدقيق harness الخماسي**: خذ مشروعًا تستخدم فيه وكيل ذكاء اصطناعي وقم بتدقيق كامل باستخدام إطار العمل الخماسي. قيّم كل نظام فرعي من 1 إلى 5. اعثر على النظام الفرعي الأقل تقييمًا، واقضِ 30 دقيقة في تحسينه، ثم لاحظ التغيير في أداء الوكيل. - -2. **تجربة التحكم المتساوي للنموذج**: اختر نموذجًا واحدًا ومهمة صعبة واحدة. أزل التعليمات بالتتابع (احذف AGENTS.md)، وأزل التغذية الراجعة (لا تقدم أوامر تحقق)، وأزل الحالة (بدون ملفات تقدم) — أزل واحدًا فقط في كل مرة وقِس انخفاض الأداء. بناءً على النتائج، رتّب أهمية الأنظمة الفرعية لمشروعك. - -3. **تحليل الأفعال**: اعثر على سيناريو في مشروعك حيث الوكيل "يريد أن يفعل شيئًا لكنه لا يستطيع" (مثلًا، يعرف أنه يجب استخدام استعلامات معلمّة لكنه لا يعرف أنماط ORM في مشروعك). حلل هل هذه فجوة تنفيذ (لا يعرف كيف) أم فجوة تقييم (لا يعرف ما إذا كان صحيحًا)، ثم صمّم تحسينًا لـ harness لسدّها. diff --git a/courses/learn-harness-engineering/docs/ar/lectures/lecture-03-why-the-repository-must-become-the-system-of-record/code/index.md b/courses/learn-harness-engineering/docs/ar/lectures/lecture-03-why-the-repository-must-become-the-system-of-record/code/index.md deleted file mode 100644 index b3acc338..00000000 --- a/courses/learn-harness-engineering/docs/ar/lectures/lecture-03-why-the-repository-must-become-the-system-of-record/code/index.md +++ /dev/null @@ -1,7 +0,0 @@ -# كود المحاضرة 03 - -استخدم هذا المجلد لأمثلة عن: - -- هياكل المستودعات القابلة للقراءة بواسطة الوكلاء -- الوثائق كسجل مرجعي للنظام (system of record) -- الموضع السيئ مقابل الجيد للمعرفة diff --git a/courses/learn-harness-engineering/docs/ar/lectures/lecture-03-why-the-repository-must-become-the-system-of-record/code/repo-knowledge-layout.txt b/courses/learn-harness-engineering/docs/ar/lectures/lecture-03-why-the-repository-must-become-the-system-of-record/code/repo-knowledge-layout.txt deleted file mode 100644 index 45315dac..00000000 --- a/courses/learn-harness-engineering/docs/ar/lectures/lecture-03-why-the-repository-must-become-the-system-of-record/code/repo-knowledge-layout.txt +++ /dev/null @@ -1,13 +0,0 @@ -AGENTS.md -docs/ - ARCHITECTURE.md - PRODUCT.md - RELIABILITY.md - references/ - electron.md - sqlite.md - plans/ - active/ - completed/ -src/ -scripts/ diff --git a/courses/learn-harness-engineering/docs/ar/lectures/lecture-03-why-the-repository-must-become-the-system-of-record/code/repo-reader.ts b/courses/learn-harness-engineering/docs/ar/lectures/lecture-03-why-the-repository-must-become-the-system-of-record/code/repo-reader.ts deleted file mode 100644 index 46f6a62a..00000000 --- a/courses/learn-harness-engineering/docs/ar/lectures/lecture-03-why-the-repository-must-become-the-system-of-record/code/repo-reader.ts +++ /dev/null @@ -1,189 +0,0 @@ -/** - * repo-reader.ts - * - * Reads a directory structure and scores it on discoverability. - * Checks for: AGENTS.md, docs/, architecture docs, feature tracking, - * handoff files, and other signals of a repository that serves as the - * system of record. - * - * Usage: - * npx tsx docs/lectures/lecture-03.../code/repo-reader.ts [path] - * (defaults to current working directory if no path given) - * - * Run: npx tsx docs/lectures/lecture-03-why-the-repository-must-become-the-system-of-record/code/repo-reader.ts - */ - -import * as fs from "node:fs"; -import * as path from "node:path"; - -// --------------------------------------------------------------------------- -// Scoring criteria -// --------------------------------------------------------------------------- - -interface Check { - name: string; - description: string; - maxPoints: number; - check: (dir: string) => { points: number; found: string[]; missing: string[] }; -} - -const checks: Check[] = [ - { - name: "AGENTS.md / CLAUDE.md", - description: "Agent-readable instruction file at repo root", - maxPoints: 15, - check: (dir) => { - const candidates = ["AGENTS.md", "CLAUDE.md", ".claude/CLAUDE.md"]; - const found = candidates.filter((f) => fs.existsSync(path.join(dir, f))); - return { points: found.length > 0 ? 15 : 0, found, missing: found.length > 0 ? [] : candidates }; - }, - }, - { - name: "Documentation directory", - description: "Dedicated docs/ or documentation/ directory", - maxPoints: 10, - check: (dir) => { - const candidates = ["docs", "documentation", "doc"]; - const found = candidates.filter((f) => { - const p = path.join(dir, f); - return fs.existsSync(p) && fs.statSync(p).isDirectory(); - }); - return { points: found.length > 0 ? 10 : 0, found, missing: found.length > 0 ? [] : ["docs/"] }; - }, - }, - { - name: "Architecture documentation", - description: "Files describing system architecture", - maxPoints: 15, - check: (dir) => { - const patterns = [ - "architecture.md", "ARCHITECTURE.md", "docs/architecture.md", - "docs/architecture/", "design.md", "DESIGN.md", - ]; - const found = patterns.filter((f) => fs.existsSync(path.join(dir, f))); - return { points: found.length > 0 ? 15 : 0, found, missing: found.length > 0 ? [] : ["architecture.md or docs/architecture/"] }; - }, - }, - { - name: "Feature tracking", - description: "Feature list or task tracking file", - maxPoints: 15, - check: (dir) => { - const patterns = [ - "feature_list.json", "features.md", "FEATURES.md", - "docs/features.md", "tasks.json", "TODO.md", - ]; - const found = patterns.filter((f) => fs.existsSync(path.join(dir, f))); - return { points: found.length > 0 ? 15 : 0, found, missing: found.length > 0 ? [] : ["feature_list.json or features.md"] }; - }, - }, - { - name: "Handoff / session continuity", - description: "Files for multi-session continuity", - maxPoints: 15, - check: (dir) => { - const patterns = [ - "HANDOFF.md", "handoff.md", "SESSION_NOTES.md", - "docs/handoff.md", ".handoff", "PROGRESS.md", - ]; - const found = patterns.filter((f) => fs.existsSync(path.join(dir, f))); - return { points: found.length > 0 ? 15 : 0, found, missing: found.length > 0 ? [] : ["HANDOFF.md or PROGRESS.md"] }; - }, - }, - { - name: "Testing structure", - description: "Test directory or test configuration", - maxPoints: 10, - check: (dir) => { - const patterns = ["test", "tests", "__tests__", "spec"]; - const found = patterns.filter((f) => { - const p = path.join(dir, f); - return fs.existsSync(p) && fs.statSync(p).isDirectory(); - }); - return { points: found.length > 0 ? 10 : 0, found, missing: found.length > 0 ? [] : ["test/ or tests/"] }; - }, - }, - { - name: "Configuration files", - description: "Project config (package.json, tsconfig, etc.)", - maxPoints: 10, - check: (dir) => { - const patterns = ["package.json", "tsconfig.json", "pyproject.toml", "Cargo.toml", "go.mod"]; - const found = patterns.filter((f) => fs.existsSync(path.join(dir, f))); - return { points: found.length > 0 ? 10 : 0, found, missing: found.length > 0 ? [] : ["package.json or equivalent"] }; - }, - }, - { - name: "README", - description: "Root README file", - maxPoints: 10, - check: (dir) => { - const patterns = ["README.md", "README.rst", "README.txt", "README"]; - const found = patterns.filter((f) => fs.existsSync(path.join(dir, f))); - return { points: found.length > 0 ? 10 : 0, found, missing: found.length > 0 ? [] : ["README.md"] }; - }, - }, -]; - -// --------------------------------------------------------------------------- -// Report generation -// --------------------------------------------------------------------------- - -function pad(s: string, len: number): string { - return s.length >= len ? s : s + " ".repeat(len - s.length); -} - -function scoreRepo(targetDir: string): void { - const resolved = path.resolve(targetDir); - - if (!fs.existsSync(resolved)) { - console.error(` Error: Directory not found: ${resolved}`); - process.exit(1); - } - - console.log("\n" + "=".repeat(80)); - console.log(" REPOSITORY DISCOVERABILITY SCORE"); - console.log("=".repeat(80)); - console.log(` Target: ${resolved}\n`); - - const header = `| ${pad("Criterion", 30)}| ${pad("Points", 8)}| ${pad("Status", 10)}| Details`; - const sep = `|${"-".repeat(32)}|${"-".repeat(10)}|${"-".repeat(12)}|${"-".repeat(30)}`; - console.log(header); - console.log(sep); - - let totalScore = 0; - let maxScore = 0; - - for (const check of checks) { - const result = check.check(resolved); - totalScore += result.points; - maxScore += check.maxPoints; - const status = result.points > 0 ? "PASS" : "FAIL"; - const detail = result.found.length > 0 ? result.found.join(", ") : result.missing[0]; - - console.log(`| ${pad(check.name, 30)}| ${pad(result.points + "/" + check.maxPoints, 8)}| ${pad(status, 10)}| ${detail}`); - } - - // Final score - console.log("\n" + "-".repeat(80)); - console.log(` TOTAL SCORE: ${totalScore} / ${maxScore} (${Math.round((totalScore / maxScore) * 100)}%)`); - - // Grade - const pct = totalScore / maxScore; - let grade: string; - if (pct >= 0.9) grade = "A -- Repository is a strong system of record"; - else if (pct >= 0.7) grade = "B -- Good foundation, minor gaps"; - else if (pct >= 0.5) grade = "C -- Partial structure, significant gaps"; - else if (pct >= 0.3) grade = "D -- Minimal structure, hard for agents to navigate"; - else grade = "F -- Repository lacks discoverability signals"; - - console.log(` GRADE: ${grade}`); - console.log("=".repeat(80) + "\n"); -} - -// --------------------------------------------------------------------------- -// Main -// --------------------------------------------------------------------------- - -const target = process.argv[2] || process.cwd(); -scoreRepo(target); diff --git a/courses/learn-harness-engineering/docs/ar/lectures/lecture-03-why-the-repository-must-become-the-system-of-record/code/system-of-record-checklist.md b/courses/learn-harness-engineering/docs/ar/lectures/lecture-03-why-the-repository-must-become-the-system-of-record/code/system-of-record-checklist.md deleted file mode 100644 index 50f41a4a..00000000 --- a/courses/learn-harness-engineering/docs/ar/lectures/lecture-03-why-the-repository-must-become-the-system-of-record/code/system-of-record-checklist.md +++ /dev/null @@ -1,11 +0,0 @@ -# قائمة فحص سجل النظام (System of Record) - -هل يمكن لوكيل جديد اكتشاف ما يلي من المستودع وحده؟ - -- ما المنتج الذي يتم بناؤه؟ -- ماذا يجب أن يفعل التطبيق للمستخدمين؟ -- كيف ينظّم الكود المصدري؟ -- كيف يبدأ التطبيق؟ -- كيف يتم فحص الحالة الصحية؟ -- ما العمل الجاري حاليًا؟ -- ما هي معايير الجودة المهمة؟ diff --git a/courses/learn-harness-engineering/docs/ar/lectures/lecture-03-why-the-repository-must-become-the-system-of-record/index.md b/courses/learn-harness-engineering/docs/ar/lectures/lecture-03-why-the-repository-must-become-the-system-of-record/index.md deleted file mode 100644 index c36de9d2..00000000 --- a/courses/learn-harness-engineering/docs/ar/lectures/lecture-03-why-the-repository-must-become-the-system-of-record/index.md +++ /dev/null @@ -1,137 +0,0 @@ -[中文版本 →](../../../zh/lectures/lecture-03-why-the-repository-must-become-the-system-of-record/) - -> أمثلة الكود: [code/](https://github.com/walkinglabs/learn-harness-engineering/blob/main/docs/ar/lectures/lecture-03-why-the-repository-must-become-the-system-of-record/code/) -> مشروع عملي: [المشروع 02. مساحة عمل قابلة للقراءة بواسطة الوكلاء](./../../projects/project-02-agent-readable-workspace/index.md) - -# المحاضرة 03. اجعل المستودع مصدر الحقيقة الوحيد - -قرارات المعمارية في فريقك مبعثرة بين Confluence وSlack وJira ورؤوس بعض المهندسين الكبار. بالنسبة للبشر، هذا يعمل بالكاد: يمكنك سؤال زميل، أو البحث في سجل المحادثات، أو التنقيب في الوثائق. وإذا فشل كل شيء، يمكنك الإمساك بشخص في غرفة الاستراحة. لكن بالنسبة إلى وكيل ذكاء اصطناعي، فإن المعلومات غير الموجودة في المستودع لا وجود لها ببساطة. - -هذا ليس مبالغة. فكّر في مدخلات الوكيل الفعلية: مطالبات النظام ووصف المهمة، محتوى الملفات من المستودع، ومخرجات تنفيذ الأدوات. هذا كل شيء. سجل Slack، وتذاكر Jira، وصفحات Confluence، وقرار المعمارية الذي ناقشته مع زميل على القهوة بعد ظهر الجمعة - لا يرى الوكيل شيئًا من ذلك. لا يستطيع "الذهاب ليسأل شخصًا" ولا "البحث في سجل المحادثة". إنه مهندس محبوس داخل المستودع؛ كل ما هو خارجه لا يعرف عنه شيئًا. - -إذًا يصبح السؤال: هل ستعطي هذا المهندس خريطة جيدة؟ - -## ما الذي يجب أن يكون على الخريطة - -تقول OpenAI ذلك بصراحة: **المعلومات التي لا توجد في المستودع لا توجد بالنسبة للوكيل.** يسمون هذا مبدأ "repo as spec" - أي أن المستودع نفسه هو وثيقة المواصفات ذات السلطة الأعلى. - -توثيق Anthropic عن الوكلاء طويلة التشغيل يردد الفكرة نفسها: الحالة المستمرة شرط ضروري لاستمرارية المهام الطويلة. القدرة على استعادة المعرفة عبر الجلسات تحدد مباشرة معدلات نجاح المهام. وهذه الحالة يجب أن توجد في المستودع، لأنه التخزين الوحيد المستقر والقابل للوصول لدى الوكيل. - -قد تفكر: "فريقنا صغير، والمعرفة في رؤوس الجميع، والأمور تسير جيدًا." صحيح بالنسبة للبشر. لكن إذا كنت تستخدم وكيلًا، فاقبل هذه الحقيقة: الوكيل لا يستطيع سؤال الناس. كل ما يحتاج إلى معرفته يجب أن يُكتب ويوضع حيث يستطيع العثور عليه. - -ليس الأمر عن "كتابة المزيد من الوثائق". بل عن "وضع معلومات القرار في المكان الصحيح". ملف `ARCHITECTURE.md` من 50 سطرًا داخل `src/api/` أكثر فائدة بعشرة آلاف مرة من وثيقة تصميم من 500 صفحة في Confluence لا يصونها أحد. يشبه الأمر خريطة مكتب مرسومة يدويًا ومثبتة على مكتبك مقابل مخطط معماري جميل مقفل في خزانة ملفات: الأولى أمامك عندما تحتاجها؛ أما الثانية فقد تكون أفضل تقنيًا لكنها عديمة الفائدة في اللحظة. - -## وضوح المعرفة - -```mermaid -flowchart LR - Slack["قواعد في Slack"] --> Write["اكتبها داخل ملفات المستودع
AGENTS.md / ARCHITECTURE.md / PROGRESS.md"] - Confluence["قواعد في Confluence"] --> Write - Heads["قواعد في رؤوس الناس"] --> Write - Jira["قواعد في تذاكر Jira"] --> Write - Write --> Repo["ملفات المستودع"] - Repo --> Agent["جلسة وكيل جديدة
تقرأ المستودع مباشرة"] - Warning["إذا لم تكن القاعدة في المستودع،
فلن يراها الوكيل"] --> Agent -``` - -كيف تختبر إن كانت خريطتك جيدة بما يكفي؟ نفّذ "اختبار البدء البارد": افتح جلسة وكيل جديدة تمامًا تستخدم محتوى المستودع فقط، وانظر هل تستطيع الإجابة عن خمسة أسئلة أساسية: - -```mermaid -flowchart TB - Q1["ما هذا النظام؟"] --> A1["AGENTS.md / README"] - Q2["كيف هو منظم؟"] --> A2["ARCHITECTURE.md / وثائق الوحدات"] - Q3["كيف أشغله؟"] --> A3["Makefile / init.sh / package scripts"] - Q4["كيف أتحقق منه؟"] --> A4["أوامر test و lint و check"] - Q5["أين نحن الآن؟"] --> A5["PROGRESS.md / feature list / git history"] - - A1 --> Ready["يمكن لجلسة جديدة بدء العمل
دون سؤال إنسان"] - A2 --> Ready - A3 --> Ready - A4 --> Ready - A5 --> Ready -``` - -إذا لم تستطع الإجابة، ففي الخريطة فراغات. حيث تكون الخريطة فارغة، يخمّن الوكيل؛ التخمينات الخاطئة تصبح عيوبًا، وكثرة التخمين تستهلك السياق. وكل جلسة جديدة تخمّن من جديد. تكلفة التخمين دائمًا أعلى من تكلفة رسم الخريطة جيدًا من البداية. - -## المفاهيم الأساسية - -- **Knowledge Visibility Gap**: نسبة معرفة المشروع الكلية غير الموجودة في المستودع. كلما كبرت الفجوة، ارتفع معدل فشل الوكيل. كم من المعرفة الضمنية عن هذا المشروع يعيش في رأسك؟ احسبها كلها، ثم انظر كم دخل إلى المستودع - الفرق هو فجوة الوضوح. -- **System of Record**: مستودع الكود كمصدر رسمي لقرارات المشروع، وقيود المعمارية، وحالة التنفيذ، ومعايير التحقق. الكلمة الأخيرة للمستودع، ولا شيء آخر يُحسب. مثل خريطة تحدد "الطريق مغلق" - لن تسلك ذلك الطريق. لكن إذا كانت تلك المعلومة موجودة فقط في رأس شخص ما، فستضطر إلى سؤاله كل مرة. -- **Cold-Start Test**: الأسئلة الخمسة أعلاه. عدد الإجابات الممكنة يوضح مدى اكتمال خريطتك. -- **Discovery Cost**: مقدار ميزانية السياق التي يحرقها الوكيل للعثور على معلومة أساسية داخل المستودع. كلما كانت المعلومة مخفية أكثر، ارتفعت تكلفة الاكتشاف، وبقيت ميزانية أقل للمهمة الحقيقية. إخفاء معلومة حرجة في README على عمق عشرة مجلدات يشبه قفل مطفأة الحريق داخل خزنة في القبو: هي موجودة، لكنك لا تجدها عند الحاجة. -- **Knowledge Decay Rate**: نسبة مدخلات المعرفة التي تصبح قديمة لكل وحدة زمن. خروج الوثائق عن التزامن مع الكود هو العدو الأكبر - أسوأ من عدم وجود وثائق أصلًا. -- **ACID Analogy**: تطبيق مبادئ معاملات قواعد البيانات (Atomicity، Consistency، Isolation، Durability) على إدارة حالة الوكلاء. سنفصل ذلك أدناه. - -## كيف ترسم خريطة جيدة - -**المبدأ 1: المعرفة تعيش بجانب الكود.** قاعدة عن مصادقة نقاط API يجب أن تكون بجانب كود API، لا مدفونة في وثيقة عالمية ضخمة. ضع مستندًا قصيرًا في كل مجلد وحدة يشرح مسؤوليات الوحدة، وواجهاتها، وقيودها الخاصة. مثل لافتات رفوف المكتبة: إذا أردت كتب التاريخ، تذهب مباشرة إلى الرف المكتوب عليه "History". لا حاجة للبحث في المكتبة كلها. - -**المبدأ 2: استخدم ملف دخول موحدًا.** `AGENTS.md` (أو `CLAUDE.md`) هو صفحة هبوط الوكيل. لا يحتاج إلى احتواء كل المعلومات، لكنه يجب أن يسمح للوكيل بالإجابة بسرعة عن ثلاثة أسئلة: "ما هذا المشروع؟"، "كيف أشغله؟"، و"كيف أتحقق منه؟". 50-100 سطر كافية. - -**المبدأ 3: الحد الأدنى، لكن مكتمل.** يجب أن يكون لكل قطعة معرفة حالة استخدام واضحة. إذا لم يؤثر حذف قاعدة على جودة قرارات الوكيل، فلا ينبغي أن توجد تلك القاعدة. لكن كل سؤال في اختبار البدء البارد يجب أن تكون له إجابة. هذا توازن دقيق: لا كثير، ولا قليل، بل ما يكفي. - -**المبدأ 4: حدّث مع الكود.** اربط تحديثات المعرفة بتغييرات الكود. أبسط نهج: ضع وثائق المعمارية في مجلد الوحدة المقابلة. عندما تعدّل الكود، ترى الوثيقة طبيعيًا. بعد تغييرات الكود، يمكن لـ CI تذكيرك بالتحقق مما إذا كانت الوثائق تحتاج تحديثًا. - -**هيكل مستودع ملموس**: - -``` -project/ -├── AGENTS.md # Entry: project overview, run commands, hard constraints -├── src/ -│ ├── api/ -│ │ ├── ARCHITECTURE.md # API layer architecture decisions -│ │ └── ... -│ ├── db/ -│ │ ├── CONSTRAINTS.md # Database operation hard constraints -│ │ └── ... -│ └── ... -├── PROGRESS.md # Current progress: done, in-progress, blocked -└── Makefile # Standardized commands: setup, test, lint, check -``` - -## إدارة حالة الوكيل بمبادئ ACID - -تأتي هذه الاستعارة من إدارة معاملات قواعد البيانات. قد تظن أنها تعقيد زائد، لكنها تمنحك إطارًا عمليًا جدًا: - -- **Atomicity**: كل "عملية منطقية" (مثل "إضافة endpoint جديد وتحديث الاختبارات") تحصل على commit واحد في git. إذا فشلت في المنتصف، استخدم `git stash` للتراجع. الكل أو لا شيء - لا يوجد "نصف منجز". -- **Consistency**: عرّف شروط تحقق لـ "حالة متسقة": كل الاختبارات تنجح، وlint لا يبلغ عن أخطاء. يشغّل الوكيل التحقق بعد كل عملية؛ الحالات الوسيطة غير المتسقة لا تُسجّل في commit. مثل تحويل بنكي: لا يمكنك الخصم دون إضافة الرصيد. -- **Isolation**: عندما تعمل عدة وكلاء بالتوازي، صمم ملفات الحالة لتجنب race conditions. نهج بسيط: يستخدم كل وكيل ملف تقدم خاصًا به، أو تُستخدم فروع git للعزل. لا يستطيع طاهيان تتبيل القدر نفسه في الوقت نفسه - من يتحمل المسؤولية إذا أصبح مالحًا أكثر من اللازم؟ -- **Durability**: معرفة المشروع الحرجة تعيش في ملفات متتبعة بـ git. يمكن للحالة المؤقتة أن تبقى في ذاكرة الجلسة، لكن المعرفة العابرة للجلسات يجب أن تُحفظ في ملفات. ما في رأسك لا يُحسب - ما هو مكتوب فقط يُحسب. - -## قصة تحول حقيقية - -كان فريق يصون منصة تجارة إلكترونية تضم نحو 30 خدمة مصغرة. قرارات المعمارية (بروتوكولات الاتصال بين الخدمات، استراتيجيات اتساق البيانات، قواعد إصدار API) كانت مبعثرة بين Confluence (قديم جزئيًا)، وSlack (يصعب البحث فيه)، ورؤوس بعض المهندسين الكبار (لا يتوسع)، وتعليقات كود متفرقة (غير منهجية). - -بعد إدخال وكلاء الذكاء الاصطناعي، احتاجت 70% من المهام إلى تدخل بشري. كان كل فشل تقريبًا يتضمن انتهاك الوكيل لقيد ضمني من نوع "الجميع يعرفه لكن لا أحد كتبه". يشبه موظفًا جديدًا لم يخبره أحد "يجب أن تنشر طلب الغداء في محادثة المجموعة" - يخمّن خطأ، يُوبَّخ، لكن بعد التوبيخ لا يكتب أحد القاعدة. - -نفذ الفريق تحولًا: -1. أنشأ `AGENTS.md` في جذر المستودع مع نظرة عامة على المشروع، وإصدارات مكدس التقنية، والقيود العالمية الصارمة -2. أضاف `ARCHITECTURE.md` في كل مجلد خدمة مصغرة يصف المسؤوليات، والواجهات، والاعتماديات -3. أنشأ `CONSTRAINTS.md` مركزيًا يتضمن القيود الصارمة بلغة "MUST/MUST NOT" صريحة -4. أضاف `PROGRESS.md` في كل مجلد خدمة لتتبع حالة العمل الحالية - -بعد التحول، استطاع الوكيل نفسه الإجابة عن كل أسئلة المشروع الأساسية عند البدء البارد، وتحسنت جودة إنجاز المهام بوضوح. - -## الخلاصات الأساسية - -- المعرفة غير الموجودة في المستودع لا توجد بالنسبة للوكيل. وضع القرارات الحرجة في المستودع هو أبسط استثمار في الـ Harness - ارسم خريطة جيدة كي لا تضل الطريق. -- استخدم "اختبار البدء البارد" لتقييم جودة المستودع: هل تستطيع جلسة جديدة الإجابة عن خمسة أسئلة أساسية باستخدام محتوى المستودع فقط؟ -- يجب أن تكون المعرفة قريبة من الكود، في حدها الأدنى لكنها مكتملة، ومحدثة مع الكود. ليس الأمر عن كتابة المزيد من الوثائق، بل عن وضع المعلومات في المكان الصحيح. -- استخدم مبادئ ACID لحالة الوكلاء: commits ذرية، تحقق من الاتساق، عزل التوازي، ومعرفة حرجة مستدامة. -- تدهور المعرفة هو العدو الأكبر. الوثائق غير المتزامنة مع الكود أخطر من غياب الوثائق - فهي ترسل الوكيل في الاتجاه الخطأ بينما يظن أنه على صواب. - -## قراءات إضافية - -- [OpenAI: Harness Engineering](https://openai.com/index/harness-engineering/) -- [Anthropic: Effective Harnesses for Long-Running Agents](https://www.anthropic.com/engineering/effective-harnesses-for-long-running-agents) -- [Infrastructure as Code — Martin Fowler](https://martinfowler.com/bliki/InfrastructureAsCode.html) -- [ADR: Architecture Decision Records](https://adr.github.io/) -- [The Twelve-Factor App](https://12factor.net/) - -## تمارين - -1. **اختبار البدء البارد**: افتح جلسة وكيل جديدة تمامًا في مشروعك (لا سياق شفهي، محتوى المستودع فقط). اسأله خمسة أسئلة: ما هذا النظام؟ كيف هو منظم؟ كيف أشغله؟ كيف أتحقق منه؟ ما التقدم الحالي؟ سجل ما لا يستطيع الإجابة عنه، ثم حسّن المستودع حتى يستطيع. - -2. **قياس تحويل المعرفة إلى خارج الرأس**: اسرد كل القرارات والقيود المهمة لعمل التطوير في مشروعك. علّم كل واحدة بأنها داخل المستودع أو خارجه. احسب Knowledge Visibility Gap لديك (النسبة خارج المستودع). ضع خطة لخفضها إلى أقل من 10%. - -3. **تقييم ACID**: قيّم إدارة الحالة في مشروعك باستخدام استعارة ACID في هذه المحاضرة. Atomicity - هل يمكن التراجع عن عمليات الوكيل بشكل نظيف؟ Consistency - هل يوجد تحقق من "حالة متسقة"؟ Isolation - هل تتداخل الوكلاء المتزامنة مع بعضها؟ Durability - هل كل المعرفة العابرة للجلسات محفوظة؟ diff --git a/courses/learn-harness-engineering/docs/ar/lectures/lecture-04-why-one-giant-instruction-file-fails/code/AGENTS-short.md b/courses/learn-harness-engineering/docs/ar/lectures/lecture-04-why-one-giant-instruction-file-fails/code/AGENTS-short.md deleted file mode 100644 index 79da6996..00000000 --- a/courses/learn-harness-engineering/docs/ar/lectures/lecture-04-why-one-giant-instruction-file-fails/code/AGENTS-short.md +++ /dev/null @@ -1,15 +0,0 @@ -# AGENTS.md - -## ابدأ هنا - -- اقرأ `docs/ARCHITECTURE.md` -- اقرأ `docs/PRODUCT.md` -- استخدم `npm run dev` لتشغيل التطبيق -- استخدم `npm run check` قبل اعتبار العمل مكتملًا - -## قواعد صارمة - -- لا تُغيّر حدود main/preload/renderer في Electron بدون قراءة - `docs/ARCHITECTURE.md` -- لا تُعلن عن مكتمل بدون تحقق (verification) -- اترك حالة نظيفة (clean state) للجلسة التالية diff --git a/courses/learn-harness-engineering/docs/ar/lectures/lecture-04-why-one-giant-instruction-file-fails/code/anti-patterns.md b/courses/learn-harness-engineering/docs/ar/lectures/lecture-04-why-one-giant-instruction-file-fails/code/anti-patterns.md deleted file mode 100644 index 1632c577..00000000 --- a/courses/learn-harness-engineering/docs/ar/lectures/lecture-04-why-one-giant-instruction-file-fails/code/anti-patterns.md +++ /dev/null @@ -1,7 +0,0 @@ -# أنماط مضادة في ملفات التعليمات - -- وضع كل معرفة المستودع في ملف واحد -- تكرار نفس القاعدة في أماكن متعددة -- ترميز قواعد قديمة لا يراجعها أحد -- كتابة تعليمات شرطية محددة جدًا لدرجة نادرًا ما تنطبق -- تضمين أدوات طويلة في سياق التشغيل diff --git a/courses/learn-harness-engineering/docs/ar/lectures/lecture-04-why-one-giant-instruction-file-fails/code/index.md b/courses/learn-harness-engineering/docs/ar/lectures/lecture-04-why-one-giant-instruction-file-fails/code/index.md deleted file mode 100644 index 64e46d81..00000000 --- a/courses/learn-harness-engineering/docs/ar/lectures/lecture-04-why-one-giant-instruction-file-fails/code/index.md +++ /dev/null @@ -1,7 +0,0 @@ -# كود المحاضرة 04 - -استخدم هذا المجلد لأمثلة عن: - -- ملفات التعليمات الأحادية (monolithic) -- نقاط الدخول المختصرة -- أنماط الكشف التدريجي (progressive disclosure) diff --git a/courses/learn-harness-engineering/docs/ar/lectures/lecture-04-why-one-giant-instruction-file-fails/code/split-vs-monolithic.ts b/courses/learn-harness-engineering/docs/ar/lectures/lecture-04-why-one-giant-instruction-file-fails/code/split-vs-monolithic.ts deleted file mode 100644 index 59a96328..00000000 --- a/courses/learn-harness-engineering/docs/ar/lectures/lecture-04-why-one-giant-instruction-file-fails/code/split-vs-monolithic.ts +++ /dev/null @@ -1,209 +0,0 @@ -/** - * split-vs-monolithic.ts - * - * Creates a monolithic instruction file (~200 lines) and then shows how - * splitting into 4 focused files dramatically reduces the context needed - * for any single query. Simulates an "agent" searching for a specific rule - * and measures how many lines it must read in each approach. - * - * Run: npx tsx docs/lectures/lecture-04-why-one-giant-instruction-file-fails/code/split-vs-monolithic.ts - */ - -// --------------------------------------------------------------------------- -// Simulated monolithic instruction file (200 lines of rules) -// --------------------------------------------------------------------------- - -const monolithicInstructions: { lineNumber: number; section: string; content: string }[] = []; - -// Section 1: Project Overview (lines 1-50) -for (let i = 1; i <= 50; i++) { - monolithicInstructions.push({ - lineNumber: i, - section: "Project Overview", - content: i === 25 ? "This project uses React 18 with TypeScript strict mode." : `Overview detail line ${i}`, - }); -} - -// Section 2: Code Style Rules (lines 51-100) -for (let i = 51; i <= 100; i++) { - monolithicInstructions.push({ - lineNumber: i, - section: "Code Style", - content: - i === 72 - ? "RULE: Always use explicit return types on exported functions." - : i === 78 - ? "RULE: Use const assertions for immutable arrays." - : `Style rule detail line ${i}`, - }); -} - -// Section 3: Testing Standards (lines 101-150) -for (let i = 101; i <= 150; i++) { - monolithicInstructions.push({ - lineNumber: i, - section: "Testing", - content: - i === 120 - ? "RULE: Every new endpoint must have integration tests." - : i === 135 - ? "RULE: Test files must mirror the source file structure." - : `Testing detail line ${i}`, - }); -} - -// Section 4: Deployment Rules (lines 151-200) -for (let i = 151; i <= 200; i++) { - monolithicInstructions.push({ - lineNumber: i, - section: "Deployment", - content: - i === 175 - ? "RULE: Never deploy on Fridays. Deploy window is Tue-Thu 10am-3pm." - : `Deployment detail line ${i}`, - }); -} - -// --------------------------------------------------------------------------- -// Split instruction files (4 focused files) -// --------------------------------------------------------------------------- - -const splitInstructions: Record = { - "01-project-overview.md": monolithicInstructions.filter((l) => l.section === "Project Overview"), - "02-code-style.md": monolithicInstructions.filter((l) => l.section === "Code Style"), - "03-testing.md": monolithicInstructions.filter((l) => l.section === "Testing"), - "04-deployment.md": monolithicInstructions.filter((l) => l.section === "Deployment"), -}; - -// --------------------------------------------------------------------------- -// Simulated queries -- the agent needs to find specific rules -// --------------------------------------------------------------------------- - -interface Query { - description: string; - targetRule: string; - relevantSection: string; -} - -const queries: Query[] = [ - { - description: "Find the rule about return types", - targetRule: "explicit return types", - relevantSection: "Code Style", - }, - { - description: "Find the deployment window rule", - targetRule: "deploy on Fridays", - relevantSection: "Deployment", - }, - { - description: "Find the integration test rule", - targetRule: "integration tests", - relevantSection: "Testing", - }, - { - description: "Find the test file structure rule", - targetRule: "mirror the source file", - relevantSection: "Testing", - }, -]; - -// --------------------------------------------------------------------------- -// Search simulation -// --------------------------------------------------------------------------- - -function searchMonolithic(query: Query): { linesRead: number; found: boolean } { - // Agent must scan from the top, reading each line until it finds the rule. - // In the worst case it reads all lines. - let linesRead = 0; - let found = false; - - for (const line of monolithicInstructions) { - linesRead++; - if (line.content.toLowerCase().includes(query.targetRule.toLowerCase())) { - found = true; - break; - } - } - - return { linesRead, found }; -} - -function searchSplit(query: Query): { linesRead: number; found: boolean; fileAccessed: string } { - // Agent knows which file to look in based on the section. - // It only reads lines from that one file. - const fileMap: Record = { - "Project Overview": "01-project-overview.md", - "Code Style": "02-code-style.md", - Testing: "03-testing.md", - Deployment: "04-deployment.md", - }; - - const targetFile = fileMap[query.relevantSection]; - const lines = splitInstructions[targetFile]; - let linesRead = 0; - let found = false; - - for (const line of lines) { - linesRead++; - if (line.content.toLowerCase().includes(query.targetRule.toLowerCase())) { - found = true; - break; - } - } - - return { linesRead, found, fileAccessed: targetFile }; -} - -// --------------------------------------------------------------------------- -// Reporting -// --------------------------------------------------------------------------- - -function pad(s: string, len: number): string { - return s.length >= len ? s : s + " ".repeat(len - s.length); -} - -function run(): void { - console.log("\n" + "=".repeat(90)); - console.log(" MONOLITHIC vs SPLIT INSTRUCTION FILES"); - console.log("=".repeat(90)); - - console.log("\n Monolithic file: 1 file, " + monolithicInstructions.length + " lines total"); - console.log(" Split files: 4 files, ~" + Math.round(monolithicInstructions.length / 4) + " lines each\n"); - - const header = `| ${pad("Query", 42)}| ${pad("Monolithic (lines)", 20)}| ${pad("Split (lines)", 15)}| ${pad("File Accessed", 22)}| Savings`; - const sep = `|${"-".repeat(44)}|${"-".repeat(22)}|${"-".repeat(17)}|${"-".repeat(24)}|${"-".repeat(10)}`; - console.log(header); - console.log(sep); - - let totalMono = 0; - let totalSplit = 0; - - for (const q of queries) { - const mono = searchMonolithic(q); - const split = searchSplit(q); - totalMono += mono.linesRead; - totalSplit += split.linesRead; - - const savings = Math.round(((mono.linesRead - split.linesRead) / mono.linesRead) * 100); - console.log( - `| ${pad(q.description, 42)}| ${pad(String(mono.linesRead), 20)}| ${pad(String(split.linesRead), 15)}| ${pad(split.fileAccessed, 22)}| ${savings}%` - ); - } - - console.log(sep); - const avgMono = Math.round(totalMono / queries.length); - const avgSplit = Math.round(totalSplit / queries.length); - console.log( - `| ${pad("AVERAGE", 42)}| ${pad(String(avgMono), 20)}| ${pad(String(avgSplit), 15)}| ${pad("(targeted file)", 22)}| ${Math.round(((avgMono - avgSplit) / avgMono) * 100)}%` - ); - - console.log("\n" + "=".repeat(90)); - console.log(" KEY INSIGHT"); - console.log("=".repeat(90)); - console.log(" With a monolithic file, the agent must scan up to " + monolithicInstructions.length + " lines for every query."); - console.log(" With split files, it reads only the relevant " + Math.round(monolithicInstructions.length / 4) + "-line file."); - console.log(" This means less context window usage, fewer hallucinations, and faster execution.\n"); -} - -run(); diff --git a/courses/learn-harness-engineering/docs/ar/lectures/lecture-04-why-one-giant-instruction-file-fails/index.md b/courses/learn-harness-engineering/docs/ar/lectures/lecture-04-why-one-giant-instruction-file-fails/index.md deleted file mode 100644 index b112f8ac..00000000 --- a/courses/learn-harness-engineering/docs/ar/lectures/lecture-04-why-one-giant-instruction-file-fails/index.md +++ /dev/null @@ -1,132 +0,0 @@ -[中文版本 →](../../../zh/lectures/lecture-04-why-one-giant-instruction-file-fails/) - -> أمثلة الكود: [code/](https://github.com/walkinglabs/learn-harness-engineering/blob/main/docs/ar/lectures/lecture-04-why-one-giant-instruction-file-fails/code/) -> مشروع عملي: [المشروع 02. مساحة عمل قابلة للقراءة بواسطة الوكلاء](./../../projects/project-02-agent-readable-workspace/index.md) - -# المحاضرة 04. قسّم التعليمات عبر ملفات - -أخذت هندسة الـ Harness بجدية. أنشأت ملف `AGENTS.md` ووضعت فيه كل قاعدة، وكل قيد، وكل درس تعلمته يخطر في بالك. بعد شهر أصبح الملف 300 سطر، وبعد شهرين 450 سطرًا، وبعد ثلاثة أشهر 600 سطر. ثم لاحظت أن أداء الوكيل يزداد سوءًا: في إصلاح عيب بسيط، يستهلك الوكيل قدرًا كبيرًا من السياق في معالجة تعليمات نشر غير ذات صلة؛ قيد أمني حرج مدفون في السطر 300 يتم تجاهله تمامًا؛ وثلاث قواعد متناقضة لأسلوب الكود تجعل الوكيل يختار واحدة عشوائيًا كل مرة. - -هذا هو فخ "ملف التعليمات العملاق". يشبه حقيبة سفر محشوة أكثر من اللازم: كل شيء يبدو مفيدًا، فتضغطه داخلها حتى توشك السحّابة أن تنفجر. للعثور على قطعة ملابس بديلة، عليك إفراغ الحقيبة كلها. تحمل حقيبة ممتلئة، لكنك تستخدم ربما ثلث ما بداخلها فقط. - -## الحلقة المفرغة في الجذر - -الحلقة المفرغة الأكثر شيوعًا تسير هكذا: يخطئ الوكيل، فتقول "أضف قاعدة تمنع هذا"، وتضيفها إلى `AGENTS.md`، فتعمل مؤقتًا، ثم يخطئ الوكيل خطأ آخر، فتضيف قاعدة أخرى، وتكرر ذلك، حتى يتضخم الملف خارج السيطرة. - -ليس هذا ذنبك. إنها استجابة طبيعية جدًا: إضافة قاعدة كلما حدث خطأ تبدو منطقية، مثل رمي شيء إضافي في حقيبتك كلما خرجت من المنزل "للاحتياط". لكن الأثر التراكمي كارثي. لنرَ ما الذي يتعطل تحديدًا. - -**ميزانية السياق تُلتهم.** نافذة السياق لدى الوكيل محدودة. لنفترض أن لدى وكيلك نافذة 200K token (حجم شائع في Claude). قد يستهلك ملف تعليمات متضخم 10-20K token. يبدو أن هناك مساحة كثيرة؟ لكن المهمة المعقدة قد تحتاج إلى قراءة عشرات ملفات المصدر، ومخرجات الأدوات تستهلك سياقًا أيضًا، وسجل المحادثة يتراكم. عندما يحتاج الوكيل إلى فهم الكود، تكون الميزانية ضيقة بالفعل، مثل حقيبة مليئة بأشياء "للاحتياط" ولا يبقى فيها مكان للحاسوب المحمول. - -**الضياع في المنتصف.** أثبت بحث "Lost in the Middle" (Liu et al., 2023) بوضوح أن نماذج اللغة الكبيرة تستخدم المعلومات الموجودة في منتصف النصوص الطويلة بفاعلية أقل بكثير من المعلومات في البداية أو النهاية. ملف `AGENTS.md` لديك طوله 600 سطر، وفي السطر 300 توجد عبارة "كل استعلامات قاعدة البيانات يجب أن تستخدم استعلامات مهيكلة بالمعاملات" - هذا قيد أمني صارم. لكنه مدفون في المنتصف، ومن المرجح جدًا أن يتجاهله الوكيل. مثل زجاجة واقي الشمس في قاع حقيبة محشوة: تعرف أنها موجودة، تبحث ثلاث مرات، لا تجدها، ثم تشتري واحدة أخرى. - -**تضارب الأولويات.** يخلط الملف بين قيود غير قابلة للتفاوض ("never use eval()")، وإرشادات تصميم مهمة ("prefer functional style")، ودرس تاريخي محدد ("تم إصلاح تسرب ذاكرة في WebSocket الأسبوع الماضي، انتبه للأنماط المشابهة"). لهذه القواعد الثلاث مستويات أهمية مختلفة تمامًا، لكنها تبدو متطابقة داخل الملف. لا يملك الوكيل إشارة موثوقة للتمييز بينها، مثل جواز السفر وكابل الشحن مختلطين في الحقيبة بلا طريقة لمعرفة أيهما أكثر إلحاحًا. - -**تدهور الصيانة.** الملفات الكبيرة صعبة الصيانة بطبيعتها. نادرًا ما تُحذف التعليمات القديمة، لأن عواقب الحذف غير واضحة ("ربما يعتمد شيء ما على هذه القاعدة")، بينما تبدو إضافة تعليمات جديدة مجانية. النتيجة: الملف يكبر فقط ولا يصغر، ونسبة الإشارة إلى الضجيج تهبط باستمرار. هذا بالضبط تراكم الدين التقني، لكن في التعليمات. - -**تراكم التناقضات.** التعليمات المضافة في أوقات مختلفة تبدأ بالتناقض: واحدة تقول "استخدم TypeScript strict mode"، وأخرى تقول "بعض ملفات legacy تسمح بأنواع any". يختار الوكيل عشوائيًا أيهما يتبع كل مرة. مثل أن تقول لك أمك "ارتدِ ملابس دافئة" ويقول لك أبوك "لا تلبس كثيرًا"، فتقف عند الباب لا تعرف لمن تستمع. - -## المفاهيم الأساسية - -- **Instruction Bloat**: عندما يشغل ملف التعليمات أكثر من 10-15% من نافذة السياق، يبدأ بمزاحمة الميزانية المخصصة لقراءة الكود والتفكير في المهمة. قد يستهلك `AGENTS.md` من 600 سطر 10,000-20,000 token، أي 8-15% من نافذة 128K قبل أن يبدأ الوكيل أصلًا. -- **Lost in the Middle Effect**: أثبت بحث Liu et al. عام 2023 أن نماذج اللغة الكبيرة تستخدم المعلومات في منتصف النصوص الطويلة بفاعلية أقل بكثير من المعلومات في البداية أو النهاية. قيد حرج مدفون في السطر 300 من ملف 600 سطر لديه احتمال عالٍ أن يُتجاهل عمليًا. -- **Instruction Signal-to-Noise Ratio (SNR)**: نسبة التعليمات في ملف ما التي ترتبط بالمهمة الحالية. إجبار الوكيل على قراءة 50 سطرًا من تعليمات النشر أثناء إصلاح عيب يعني SNR منخفضًا. -- **Routing File**: ملف دخول قصير تتمثل وظيفته الأساسية في توجيه الوكيل إلى مستندات أكثر تفصيلًا، لا احتواء كل شيء بنفسه. 50-200 سطر كافية. -- **Progressive Disclosure**: أعطِ معلومات عامة أولًا، ثم التفاصيل عند الحاجة. تصميم Harness جيد يشبه تصميم واجهة جيدًا: لا ترمِ كل الخيارات على المستخدم دفعة واحدة. -- **Priority Ambiguity**: عندما تظهر كل التعليمات بالشكل نفسه وفي المكان نفسه، لا يستطيع الوكيل التمييز بين القيود الصارمة غير القابلة للتفاوض والإرشادات اللينة. - -## بنية التعليمات - -```mermaid -flowchart LR - Mono["ملف AGENTS.md واحد من 600 سطر"] --> MonoLoad["حتى إصلاح عيب صغير
يجب أن يقرأ قواعد النشر والملاحظات القديمة"] - MonoLoad --> MonoRisk["القواعد المهمة المدفونة في المنتصف
يسهل تفويتها"] - - Router["AGENTS.md قصير"] --> Topics["حمّل مستندات API / DB / testing
فقط عندما تحتاجها المهمة"] - Topics --> RoutedResult["يبقى سياق أكثر لقراءة الكود
والتحقق"] -``` - -```mermaid -flowchart TB - File["ملف تعليمات من 600 سطر"] --> Top["القسم العلوي
quick start + قيود صارمة"] - File --> Mid["القسم الأوسط
قاعدة أمنية حرجة في السطر 300"] - File --> Bot["القسم السفلي
قائمة تحقق ختامية صريحة"] - Top --> Seen["احتمال عالٍ للتذكر"] - Bot --> Seen - Mid --> Missed["احتمال عالٍ للتخفيف أو التفويت"] -``` - -## كيفية التقسيم - -المبدأ الأساسي: أبقِ المعلومات المطلوبة كثيرًا في المتناول، وضع المعلومات المطلوبة أحيانًا في مكان منفصل، واترك ما لن تستخدمه أبدًا. - -يبقى ملف الدخول `AGENTS.md` بين 50 و200 سطر، ويحتوي فقط على العناصر الأكثر استخدامًا: نظرة عامة على المشروع (جملة أو جملتان)، أوامر التشغيل الأولى (`make setup && make test`)، قيود عالمية صارمة (لا أكثر من 15 قاعدة غير قابلة للتفاوض)، وروابط إلى مستندات موضوعية (وصف من سطر واحد + شرط التطبيق). - -```markdown -# AGENTS.md - -## نظرة عامة على المشروع -Python 3.11 FastAPI backend, PostgreSQL 15 database. - -## Quick Start -- Install: `make setup` -- Test: `make test` -- Full verification: `make check` - -## Hard Constraints -- All APIs must use OAuth 2.0 authentication -- All database queries must use SQLAlchemy 2.0 syntax -- All PRs must pass pytest + mypy --strict + ruff check - -## Topic Docs -- [API Design Patterns](docs/api-patterns.md) — Required reading when adding endpoints -- [Database Rules](docs/database-rules.md) — Required when modifying database operations -- [Testing Standards](docs/testing-standards.md) — Reference when writing tests -``` - -كل مستند موضوعي يكون بين 50 و150 سطرًا، منظمًا حسب الموضوع داخل `docs/` أو بجانب الوحدة المقابلة. يقرأه الوكيل فقط عند الحاجة. مثل مكعبات التنظيم داخل حقيبة السفر: الملابس في مكعب، أدوات العناية في آخر، الشواحن في ثالث. العثور على شيء لا يتطلب إفراغ الحقيبة كلها. - -بعض المعلومات أفضل مكان لها داخل الكود مباشرة: تعريفات الأنواع، تعليقات الواجهات، وشرح ملفات الإعداد. يراها الوكيل طبيعيًا عند قراءة الكود، فلا حاجة لتكرارها في التعليمات. - -ينبغي أن يكون لكل تعليمة مصدر ("لماذا أُضيفت هذه القاعدة؟")، وشرط تطبيق ("متى تكون مطلوبة؟")، وشرط انتهاء ("في أي ظروف يمكن حذفها؟"). راجع بانتظام، واحذف المدخلات القديمة والمكررة والمتناقضة. أدِر تعليماتك كما تدير اعتماديات الكود: الاعتماديات غير المستخدمة يجب حذفها، وإلا فهي تبطئ النظام فقط. - -إذا كان لا بد من وجود تعليمة في ملف الدخول، فضعها في الأعلى أو الأسفل، لا في المنتصف. يخبرنا تأثير "lost in the middle" أن نماذج اللغة تستخدم المعلومات في الأطراف أفضل بكثير من المركز. لكن النهج الأفضل هو نقل التعليمات إلى مستندات موضوعية تُحمّل عند الطلب. - -تدعم OpenAI وAnthropic ضمنيًا نهج التقسيم. تقول OpenAI إن ملفات الدخول يجب أن تكون "قصيرة وموجهة للتوجيه"، وتقول Anthropic إن معلومات التحكم في الوكلاء طويلة التشغيل يجب أن تكون "موجزة وعالية الأولوية". كلاهما يقول الشيء نفسه: لا تحشر كل شيء في ملف واحد. الحقيبة تحتاج تنظيمًا، لا ضغطًا بالقوة. - -## مثال من الواقع - -تضخم ملف `AGENTS.md` لدى فريق SaaS من 50 سطرًا إلى 600. كان يخلط إصدارات مكدس التقنية، ومعايير الكود، وملاحظات إصلاح عيوب تاريخية، وأدلة استخدام API، وإجراءات النشر، وتفضيلات شخصية لأعضاء الفريق - حقيبة كاملة توشك أن تنفجر. - -بدأ أداء الوكيل يتدهور بوضوح: أثناء إصلاحات عيوب بسيطة، كان يستهلك سياقًا كبيرًا في معالجة تعليمات نشر غير ذات صلة؛ القيد الأمني "كل استعلامات قاعدة البيانات يجب أن تستخدم استعلامات مهيكلة بالمعاملات" كان مدفونًا في السطر 300 ويُتجاهل كثيرًا؛ ثلاث قواعد متناقضة لأسلوب الكود سببت سلوكًا عشوائيًا. - -نفذ الفريق "إعادة تنظيم للحقيبة": -1. تقليص `AGENTS.md` إلى 80 سطرًا: فقط نظرة عامة على المشروع، أوامر التشغيل، و15 قيدًا عالميًا صارمًا -2. إنشاء مستندات موضوعية: `docs/api-patterns.md` (120 سطرًا)، `docs/database-rules.md` (60 سطرًا)، `docs/testing-standards.md` (80 سطرًا) -3. إضافة روابط المستندات الموضوعية في ملف التوجيه -4. تحويل الملاحظات التاريخية إلى اختبارات أو حذفها - -بعد إعادة التنظيم، ارتفعت نسبة نجاح مجموعة المهام نفسها من 45% إلى 72%. وارتفع الالتزام بالقيد الأمني من 60% إلى 95%، لأنه انتقل من منتصف الملف إلى أعلى ملف التوجيه، ولم يعد "ضائعًا في المنتصف". - -## الخلاصات الأساسية - -- "إضافة قاعدة" مسكن قصير الأمد وسم طويل الأمد. قبل إضافة قاعدة، اسأل: هل ستكون أفضل في مستند موضوعي؟ لا تواصل حشو الحقيبة. -- ملف الدخول موجّه وليس موسوعة. 50-200 سطر مع نظرة عامة، قيود صارمة، وروابط فقط. -- استفد من تأثير "lost in the middle": المعلومات المهمة في الأعلى أو الأسفل؛ المعلومات الأقل أهمية تنتقل إلى مستندات موضوعية. -- أدِر تضخم التعليمات مثل الدين التقني. مراجعات دورية؛ كل تعليمة تحتاج مصدرًا، وشرط تطبيق، وشرط انتهاء. -- بعد التقسيم يتحسن SNR، ويصرف الوكيل ميزانية سياق أكثر على المهمة الفعلية بدل معالجة تعليمات غير ذات صلة. - -## قراءات إضافية - -- [OpenAI: Harness Engineering](https://openai.com/index/harness-engineering/) -- [Anthropic: Effective Harnesses for Long-Running Agents](https://www.anthropic.com/engineering/effective-harnesses-for-long-running-agents) -- [Lost in the Middle: How Language Models Use Long Contexts](https://arxiv.org/abs/2307.03172) -- [HumanLayer: Harness Engineering for Coding Agents](https://humanlayer.dev/articles/harness-engineering-for-coding-agents/) -- [Nielsen Norman Group: Progressive Disclosure](https://www.nngroup.com/articles/progressive-disclosure/) - -## تمارين - -1. **تدقيق SNR**: خذ ملف تعليمات الدخول الحالي لديك واسرد كل التعليمات. اختر 5 أنواع شائعة من المهام وحدد ما إذا كانت كل تعليمة ذات صلة بتلك المهمة. احسب SNR لكل نوع مهمة. التعليمات التي تكون ضجيجًا لمعظم المهام يجب نقلها إلى مستندات موضوعية. - -2. **إعادة تنظيم Progressive Disclosure**: إذا كان لديك ملف تعليمات يتجاوز 300 سطر، فقسّمه إلى: (a) ملف توجيه أقل من 100 سطر، (b) 3-5 مستندات موضوعية. شغّل مجموعة المهام نفسها (5 على الأقل) قبل وبعد، وقارن نسب النجاح. - -3. **التحقق من Lost in the middle**: في ملف تعليمات طويل، ضع قيدًا حرجًا في الأعلى، والمنتصف، والأسفل على التوالي، وشغّل مجموعة المهام نفسها في كل مرة (5 تشغيلات على الأقل لكل موضع). راقب إن كان هناك فرق في معدل الالتزام. قد يفاجئك مدى قوة تأثير الموضع. diff --git a/courses/learn-harness-engineering/docs/ar/lectures/lecture-05-why-long-running-tasks-lose-continuity/code/continuity-checklist.md b/courses/learn-harness-engineering/docs/ar/lectures/lecture-05-why-long-running-tasks-lose-continuity/code/continuity-checklist.md deleted file mode 100644 index 36d2122a..00000000 --- a/courses/learn-harness-engineering/docs/ar/lectures/lecture-05-why-long-running-tasks-lose-continuity/code/continuity-checklist.md +++ /dev/null @@ -1,6 +0,0 @@ -# قائمة فحص الاستمرارية - -- هل يمكن لوكيل جديد تحديد العمل الأخير في أقل من خمس دقائق؟ -- هل مسار التشغيل المستقر الحالي موثق؟ -- هل العمل غير المنتهي محدد بوضوح؟ -- هل المهمة التالية الأفضل مرئية بدون قراءة سجلات الدردشة القديمة؟ diff --git a/courses/learn-harness-engineering/docs/ar/lectures/lecture-05-why-long-running-tasks-lose-continuity/code/index.md b/courses/learn-harness-engineering/docs/ar/lectures/lecture-05-why-long-running-tasks-lose-continuity/code/index.md deleted file mode 100644 index c5c88243..00000000 --- a/courses/learn-harness-engineering/docs/ar/lectures/lecture-05-why-long-running-tasks-lose-continuity/code/index.md +++ /dev/null @@ -1,7 +0,0 @@ -# كود المحاضرة 05 - -استخدم هذا المجلد لأمثلة عن: - -- مهام متعددة الجلسات معطلة -- مفقودات制品 الاستمرارية -- أنماط استعادة الاستمرارية diff --git a/courses/learn-harness-engineering/docs/ar/lectures/lecture-05-why-long-running-tasks-lose-continuity/code/session-handoff.md b/courses/learn-harness-engineering/docs/ar/lectures/lecture-05-why-long-running-tasks-lose-continuity/code/session-handoff.md deleted file mode 100644 index 74802185..00000000 --- a/courses/learn-harness-engineering/docs/ar/lectures/lecture-05-why-long-running-tasks-lose-continuity/code/session-handoff.md +++ /dev/null @@ -1,17 +0,0 @@ -# مثال على تسليم الجلسة - -## المكتمل - -- إضافة دعم استيراد Markdown -- إضافة قائمة مستندات أساسية في العارض - -## المعطل أو غير المُتحقق - -- الاستيراد ينجح لملفات `.md` لكنه يفشل لملفات `.txt` الكبيرة -- التطبيق يبدأ، لكن عرض التفاصيل لم يتم توصيله - -## الخطوة التالية الأفضل - -- إصلاح مسار استيراد `.txt` -- التحقق من الاستيراد من البداية للنهاية -- ثم إضافة لوحة تفاصيل المستند diff --git a/courses/learn-harness-engineering/docs/ar/lectures/lecture-05-why-long-running-tasks-lose-continuity/code/session-simulator.ts b/courses/learn-harness-engineering/docs/ar/lectures/lecture-05-why-long-running-tasks-lose-continuity/code/session-simulator.ts deleted file mode 100644 index 69f9c3e2..00000000 --- a/courses/learn-harness-engineering/docs/ar/lectures/lecture-05-why-long-running-tasks-lose-continuity/code/session-simulator.ts +++ /dev/null @@ -1,162 +0,0 @@ -/** - * session-simulator.ts - * - * Simulates two sessions working on a multi-step task. - * Run 1: No handoff file -- Session B starts from scratch and duplicates work. - * Run 2: With handoff file -- Session B picks up where Session A left off. - * - * Run: npx tsx docs/lectures/lecture-05-why-long-running-tasks-lose-continuity/code/session-simulator.ts - */ - -// --------------------------------------------------------------------------- -// Types -// --------------------------------------------------------------------------- - -interface TaskStep { - id: number; - name: string; - durationMs: number; -} - -interface SessionResult { - session: string; - stepsCompleted: number; - totalDurationMs: number; - duplicatedSteps: number; - output: string[]; -} - -// --------------------------------------------------------------------------- -// Task definition -// --------------------------------------------------------------------------- - -const taskSteps: TaskStep[] = [ - { id: 1, name: "Read project structure", durationMs: 50 }, - { id: 2, name: "Understand existing auth module", durationMs: 80 }, - { id: 3, name: "Design new search endpoint", durationMs: 60 }, - { id: 4, name: "Implement search endpoint", durationMs: 100 }, - { id: 5, name: "Write integration tests", durationMs: 70 }, - { id: 6, name: "Update documentation", durationMs: 40 }, -]; - -// --------------------------------------------------------------------------- -// Simulated session executor -// --------------------------------------------------------------------------- - -/** Simulate a session doing work. */ -function runSession( - sessionName: string, - startStep: number, - endStep: number -): SessionResult { - const output: string[] = []; - let totalDuration = 0; - - for (let i = startStep; i <= endStep; i++) { - const step = taskSteps.find((s) => s.id === i)!; - totalDuration += step.durationMs; - output.push(` [${sessionName}] Step ${step.id}: ${step.name} (${step.durationMs}ms)`); - } - - return { - session: sessionName, - stepsCompleted: endStep - startStep + 1, - totalDurationMs: totalDuration, - duplicatedSteps: 0, - output, - }; -} - -// --------------------------------------------------------------------------- -// Run 1: No handoff file -// --------------------------------------------------------------------------- - -function simulateNoHandoff(): { sessionA: SessionResult; sessionB: SessionResult } { - // Session A does steps 1-3 before timing out - const sessionA = runSession("Session A", 1, 3); - - // Session B has no context, starts from scratch - const sessionB = runSession("Session B", 1, 6); - sessionB.duplicatedSteps = 3; // Redoes steps 1-3 that A already did - - return { sessionA, sessionB }; -} - -// --------------------------------------------------------------------------- -// Run 2: With handoff file -// --------------------------------------------------------------------------- - -function simulateWithHandoff(): { sessionA: SessionResult; sessionB: SessionResult } { - // Session A does steps 1-3 and writes a handoff file - const sessionA = runSession("Session A", 1, 3); - - // Session B reads handoff, starts from step 4 - const sessionB = runSession("Session B", 4, 6); - sessionB.duplicatedSteps = 0; - - return { sessionA, sessionB }; -} - -// --------------------------------------------------------------------------- -// Reporting -// --------------------------------------------------------------------------- - -function pad(s: string, len: number): string { - return s.length >= len ? s : s + " ".repeat(len - s.length); -} - -function printRun(title: string, result: { sessionA: SessionResult; sessionB: SessionResult }): void { - console.log("\n" + "=".repeat(80)); - console.log(` ${title}`); - console.log("=".repeat(80)); - - console.log("\n Session A output:"); - for (const line of result.sessionA.output) { - console.log(line); - } - - console.log("\n Session B output:"); - for (const line of result.sessionB.output) { - console.log(line); - } - - console.log("\n Session B notes:"); - if (result.sessionB.duplicatedSteps > 0) { - console.log(` - Redid ${result.sessionB.duplicatedSteps} steps that Session A already completed`); - console.log(` - Wasted ${result.sessionB.totalDurationMs - (taskSteps.slice(3).reduce((s, t) => s + t.durationMs, 0))}ms on duplicate work`); - } else { - console.log(" - Read handoff file, continued from step 4"); - console.log(" - No duplicate work performed"); - } -} - -function printComparison(): void { - const noHandoff = simulateNoHandoff(); - const withHandoff = simulateWithHandoff(); - - printRun("RUN 1: NO HANDOFF FILE", noHandoff); - printRun("RUN 2: WITH HANDOFF FILE", withHandoff); - - // Comparison table - console.log("\n" + "=".repeat(80)); - console.log(" COMPARISON TABLE"); - console.log("=".repeat(80) + "\n"); - - const header = `| ${pad("Metric", 35)}| ${pad("No Handoff", 15)}| ${pad("With Handoff", 15)}|`; - const sep = `|${"-".repeat(37)}|${"-".repeat(17)}|${"-".repeat(17)}|`; - console.log(header); - console.log(sep); - - const noTotal = noHandoff.sessionA.totalDurationMs + noHandoff.sessionB.totalDurationMs; - const withTotal = withHandoff.sessionA.totalDurationMs + withHandoff.sessionB.totalDurationMs; - - console.log(`| ${pad("Session A steps completed", 35)}| ${pad(String(noHandoff.sessionA.stepsCompleted), 15)}| ${pad(String(withHandoff.sessionA.stepsCompleted), 15)}|`); - console.log(`| ${pad("Session B steps completed", 35)}| ${pad(String(noHandoff.sessionB.stepsCompleted), 15)}| ${pad(String(withHandoff.sessionB.stepsCompleted), 15)}|`); - console.log(`| ${pad("Duplicated steps", 35)}| ${pad(String(noHandoff.sessionB.duplicatedSteps), 15)}| ${pad(String(withHandoff.sessionB.duplicatedSteps), 15)}|`); - console.log(`| ${pad("Total work (ms)", 35)}| ${pad(String(noTotal), 15)}| ${pad(String(withTotal), 15)}|`); - console.log(`| ${pad("Time saved by handoff", 35)}| ${pad("-", 15)}| ${pad(String(noTotal - withTotal) + "ms", 15)}|`); - - console.log("\n A handoff file eliminates duplicate work and ensures continuity across sessions.\n"); -} - -printComparison(); diff --git a/courses/learn-harness-engineering/docs/ar/lectures/lecture-05-why-long-running-tasks-lose-continuity/index.md b/courses/learn-harness-engineering/docs/ar/lectures/lecture-05-why-long-running-tasks-lose-continuity/index.md deleted file mode 100644 index 2299d4cd..00000000 --- a/courses/learn-harness-engineering/docs/ar/lectures/lecture-05-why-long-running-tasks-lose-continuity/index.md +++ /dev/null @@ -1,180 +0,0 @@ -[中文版本 →](../../../zh/lectures/lecture-05-why-long-running-tasks-lose-continuity/) - -> أمثلة الكود: [code/](https://github.com/walkinglabs/learn-harness-engineering/blob/main/docs/ar/lectures/lecture-05-why-long-running-tasks-lose-continuity/code/) -> مشروع عملي: [Project 03. Multi-session continuity](./../../projects/project-03-multi-session-continuity/index.md) - -# المحاضرة 05. حافظ على السياق عبر الجلسات - -تطلب من Claude Code تنفيذ ميزة كاملة. يعمل لمدة 30 دقيقة، وينجز معظم العمل، لكن السياق ينفد. تبدأ جلسة جديدة للاستمرار — وتكتشف أنه لا يتذكر ما القرارات التي اتُخذت في المرة السابقة، ولا لماذا خُيرت الخيار A على الخيار B، ولا أي الملفات عدّلت بالفعل، ولا في أي حالة تكون الاختبارات. يقضي 15 دقيقة في إعادة استكشاف المشروع، وقد يكون غير متسق مع النهج السابق. - -تخيل لو كنت حرفيًا ينسى كل شيء كل صباح عند الاستيقاظ. سيتعين عليك إعادة التعرف على موقع البناء بالكامل — أي جدار نصف مكتمل، ولماذا اختيرت الطوب الأحمر بدلاً من الأزرق، وأين وصلت مسارات السباكة. والأخطر، قد تقتلع نافذة رُكّبت بالفعل أمس، ببساطة لأنك لم تتذكر أنه تم إنجازها. - -هذا بالضبات ما يواجهه وكلاء البرمجة بالذكاء الاصطناعي في المهام العابرة للجلسات. تشرح هذه المحاضرة لماذا "يفقد الوكلاء الوعي" خلال المهام الطويلة، وكيف يمكن لاستمرارية الحالة المنظمة أن تجعلهم كحرفي يحتفظ بمذكرات يومية موثوقة — لا يزال مصابًا بفقدان الذاكرة، لكن المذكرة تتذكر كل شيء. - -## نوافذ السياق: ليست بلا حدود - -نوافذ السياق محدودة. هذه المشكلة لا تُحل بترقية النماذج — حتى لو نمت أحجام النوافذ إلى مليون token، فإن المهام المعقدة ستستنزفها. لأن الوكلاء لا يُولّدون الكود فحسب؛ بل يفهمون قواعد الكود، ويتابعون تاريخ قراراتهم، ويعالجون مخرجات الأدوات، ويحافظون على سياق المحادثة. كل هذه المعلومات تنمو أسرع من توسع النافذة. - -مشكلة أعمق: المعلومات التي يُنتجها الوكيل ليست متساوية الأهمية. خطوات الاستدلال الوسيطة تحتوي على "السبب" وراء القرارات — لماذا خُير الخيار B على A، ولماذا هذه المكتبة بدلاً من تلك، ولماذا تُركي تحسين معين. المخرجات النهائية تحتوي فقط على "الماذا" — الكود نفسه. استراتيجيات الضغط عادةً تحافظ على الأخيرة وتفقد الأولى. الجلسة التالية ترى الكود لكنها لا تعرف لماذا كُتب بهذه الطريقة، وقد "تُحسّن" قرار تصميم متعمد بعيدًا. - -اكتشفت Anthropic شيئًا مذهلًا في أبحاثها حول الوكلاء طويلي التشغيل: عندما يحس الوكلاء بنفاد السياق، يُظهرون سلوك "التقارب المبكر" — الاستعجال لإنهاء العمل الحالي، أو تخطي خطوات التحقق، أو اختيار حل بسيط بدلاً من الحل الأمثل. الأمر أشبه بإدراك أن الوقت ينفد في الامتحان والتخمين السريع في الأسئلة المتبقية متعددة الخيارات. تسمي Anthropic هذا "قلق السياق." - -## تدفق استمرارية الجلسة - -بدون مخرجات الاستمرارية، كل جلسة جديدة كارثة: - -```mermaid -flowchart LR - S1["Session 1
feature is half done"] --> End1["Context is nearly full
session ends"] - End1 --> S2["Session 2 starts fresh"] - S2 --> Guess["Re-read folders, rerun tests,
guess why the code was written this way"] - Guess --> Drift["Work gets repeated
and recovery is slow"] -``` - -مع مخرجات الاستمرارية، يمكن للجلسات الجديدة الاستمرار بسرعة: - -```mermaid -flowchart LR - Work["Session 1 work"] --> Progress["PROGRESS.md
done / in progress / next step"] - Work --> Decisions["DECISIONS.md
why this approach was chosen"] - Work --> Verify["Verification notes
which tests pass and fail"] - Work --> Commit["Git checkpoint
exact repo state"] - - Progress --> Rebuild["Session 2 rebuild"] - Decisions --> Rebuild - Verify --> Rebuild - Commit --> Rebuild - - Rebuild --> Resume["New session picks up quickly"] -``` - -## المفاهيم الأساسية - -- **نوافذ السياق محدودة**: مهما كان حجم النافذة المُعلن عنه (128K، 200K، 1M)، المهام الطويلة ستستنزفها في النهاية. بعد الاستنفاد، يلزم إما ضغط (فقدان معلومات) أو إعادة تعيين (جلسة جديدة). كلاهما يفقد شيئًا ما. -- **مخرجات الاستمرارية**: ملفات حالة محفوظة تتيح لجلسة جديدة استئناف ما توقف عنده الجلسة السابقة بدون لبس. الشكل الأساسي: سجل تقدم + سجل تحقق + إجراءات تالية. مذكرة ذلك الحرفي. -- **تكلفة إعادة البناء**: الوقت الذي تحتاجه جلسة جديدة للوصول إلى حالة قابلة للتنفيذ. harness الجيد يمكن أن يضغط تكلفة إعادة البناء من 15 دقيقة إلى 3 دقائق. -- **الانحراف**: الفجوة بين فهم الوكيل والحالة الفعلية لمستودع الكود. كل حد جلسة يُدخل انحرافًا؛ بدون تحكم، يتراكم. -- **قلق السياق**: ظاهرة رصدتها Anthropic — يُظهر الوكلاء سلوك التقارب المبكر عند اقترابهم من حدود السياق المتوقعة، منهين المهام مبكرًا لتجنب فقدان المعلومات. إنه قلق موارد غير عقلاني. -- **الضغط مقابل إعادة التعيين**: الضغط يُلخص السياق داخل نفس الجلسة (يحافظ على "الماذا"، قد يفقد "السبب")؛ إعادة التعيين تفتح جلسة جديدة تعيد البناء من المخرجات المحفوظة (نظيفة لكنها تعتمد على اكتمال المخرجات). - -## ماذا يحدث عندما تنقطع الاستمرارية - -الجلسة السابقة أنفقت جزءًا كبيرًا من ميزانية السياق في تحليل ثلاثة أساليب واختيار الخيار B. وكيل هذه الجلسة لا يعرف عن ذلك التحليل وقد يعيد اتخاذ القرار بناءً على معلومات غير مكتملة — ربما يختار الخيار A. مثل الحرفي المصاب بفقدان الذاكرة الذي لا يتذكر لماذا اختير الطوب الأحمر، ينظر إلى الأزرق اليوم ويظنه أجمل، ويهدم جدار أمس ليبنيه من جديد. - -الأسوأ من ذلك هو العمل المكرر. الوكيل غير متأكد مما إذا كان عمل معين قد أُنجز بالفعل فيقوم به مرة أخرى. أو الأسوأ — يُنجز نصفه، ويكتشف تعارضًا مع التنفيذ الموجود، ويضطر لإعادة العمل. في موقع بناء، لا يمكن لفريقين بناء نفس الجدار في وقت واحد — لكن بدون سجلات تقدم، الطاقم الجديد لا يعلم أن أحدهم يعمل عليه بالفعل. - -عبر عدة جلسات، قد يكون اتجاه التنفيذ قد انحرف بصمت عن المتطلبات الأصلية. كل جلسة جديدة لديها فهم مختلف قليلًا لأهداف المشروع. مثل لعبة التلفاز — بعد أن يمرر عشرة أشخاص الرسالة، "أحضر لي قهوة" قد تصبح "اشترِ لي آلة قهوة." - -هناك أيضًا فجوة التحقق. نتائج تحقق الجلسة السابقة (أي اختبارات نجحت، أيها فشلت، ولماذا فشلت) لم تُسجّل. الجلسة الجديدة عليها إعادة تشغيل كل التحقق لفهم الحالة الحالية. كل جلسة تعيد التشخيص من الصفر، في كل مرة تُهدر سياق ثمين. - -كلتا الشركتين OpenAI وAnthropic تؤكدان على استمرارية الحالة المنظمة في توثيقهما. مقال OpenAI حول هندسة harness يتعامل مع المستودع بصفته "سجل عمليات" — يجب أن تترك نتائج كل عملية دليلًا قابلًا للتتبع في المستودع. توثيق Anthropic للوكلاء طويلي التشغيل يوصي تحديدًا بـ "ملفات التسليم" — مستندات منظمة تحتوي على الحالة الحالية، والمشاكل المعروفة، والإجراءات التالية. - -## مذكرة للحرفي المصاب بفقدان الذاكرة - -النهج الأساسي: **عامل الوكيل كمهندس عبقري مصاب بفقدان الذاكرة.** قبل أن "ينهي دوامه"، يجب أن يكتب المعلومات الحاسمة حتى يتمكن وكيل "المناوبة" التالي من الاستمرار بسرعة. - -**الأداة 1: ملف التقدم (PROGRESS.md).** أقدم مخرجات الاستمرارية — جوهر المذكرة: - -```markdown -# Project Progress - -## Current State -- Latest commit: abc1234 (feat: add user preferences endpoint) -- Test status: 42/43 passing (test_pagination_edge_case failing) -- Lint: passing - -## Completed -- [x] User model and database migration -- [x] Basic CRUD endpoints -- [x] Auth middleware integration - -## In Progress -- [ ] Pagination feature (90% - edge case test failing) - -## Known Issues -- test_pagination_edge_case returns 500 on empty result sets -- Need to confirm whether deleted users should appear in listings - -## Next Steps -1. Fix pagination edge case bug -2. Add "include deleted users" query parameter -3. Update API documentation -``` - -**الأداة 2: سجل القرارات (DECISIONS.md).** سجّل قرارات التصميم المهمة وأسبابها. لا حاجة لمستندات تصميم مفصلة — فقط "أي قرار، لماذا، متى" — الملاحظات في المذكرة: - -```markdown -# Design Decisions - -## 2024-01-15: Use Redis for user preferences caching -- Reason: High read frequency (every API call), small data size -- Rejected alternative: PostgreSQL materialized view (high change frequency makes maintenance cost not worthwhile) -- Constraint: Cache TTL of 5 minutes, active invalidation on write -``` - -**الأداة 3: git commits كنقاط فحص.** Commit بعد إكمال كل وحدة عمل ذرية. يجب أن تشرح رسائل Commit ما تم إنجازه ولماذا. هذه لقطات حالة مجانية ومُصدَّرة تلقائيًا. - -**الأداة 4: init.sh أو تدفق تهيئة harness.** حدد في `AGENTS.md` إجراءات "تسجيل الحضور" و"انتهاء الدوام": - -```markdown -## At session start (clock in) -1. Read PROGRESS.md for current state -2. Read DECISIONS.md for important decisions -3. Run make check to confirm repo is in consistent state -4. Continue from PROGRESS.md "Next Steps" section - -## Before session end (clock out) -1. Update PROGRESS.md -2. Run make check to confirm consistent state -3. Commit all completed work -``` - -**الاستراتيجية المختلطة**: ليست كل مهمة تحتاج إعادة تعيين السياق. المهام القصيرة (أقل من 30 دقيقة) يمكن إنجازها ضمن جلسة واحدة. المهام الطويلة (التي تمتد عبر جلسات) يجب أن تستخدم ملفات التقدم وسجلات القرارات للاستمرارية. معيار القرار: إذا كانت المهمة تحتاج أكثر من 60% من النافذة، ابدأ في تحضير التسليم. - -### تعمق في قلق السياق - -كشفت أبحاث Anthropic في مارس 2026 مزيدًا من مظاهر قلق السياق المحددة: على Sonnet 4.5، عندما يقترب السياق من حد النافذة، يُظهر الوكيل سلوك "التقارب المبكر" القوي. الأمر أشبه بإدراك أن الوقت قد انتهى تقريبًا في الامتحان والملء السريع بإجابات عشوائية. - -استراتيجيتان تعالجان هذا: - -**الضغط**: تلخيص المحادثة المبكرة داخل نفس الجلسة. الميزة: يحافظ على الاستمرارية، الوكيل يمكنه رؤية "الماذا." العيب: "السبب" غالبًا يُفقد في الملخصات — لماذا خُير الخيار B على A، ولماذا تُركي تحسين معين. الأكثر أهمية، الضغط لا يُزيل قلق السياق — الوكيل يعرف أن السياق كان كبيرًا يومًا ما، ونفسيًا لا يزال يميل إلى الاستعجال في الإنهاء. - -**إعادة تعيين السياق**: مسح السياق بالكامل، وفتح جلسة جديدة، وإعادة البناء من المخرجات المحفوظة. الميزة: حالة ذهنية نظيفة — الجلسة الجديدة ليس لديها قلق "الوقت ينفد." العيب: يعتمد على اكتمال مخرجات التسليم. إذا كانت المذكرة تفتقر إلى معلومات حاسمة، قد تُهدر الجلسة الجديدة وقتًا في اتجاه خاطئ. - -بيانات Anthropic الفعلية: على Sonnet 4.5، قلق السياق شديد بما يكفي لأن الضغط وحده غير كافٍ — تصبح إعادة تعيين السياق مكونًا حاسمًا في تصميم harness. لكن على Opus 4.5، هذا السلوك يقل كثيرًا، ويمكن للضغط إدارة السياق بدون الاعتماد على إعادة التعيين. هذا يعني: **تصميم harness يحتاج إلى فهم محدد للنموذج المستهدف، لا قالبًا واحدًا يناسب الجميع.** - -> المصدر: [Anthropic: Harness design for long-running application development](https://www.anthropic.com/engineering/harness-design-long-running-apps) - -## مثال من الواقع - -كُلّف وكيل بتنفيذ نظام مدونة مع مصادقة مستخدمين — 12 نقطة ميزة، وقُدّر أنها تحتاج 5 جلسات. - -**الخط الأساسي بدون المذكرة**: الجلسة 1 نفّذت نموذج المستخدم والمسارات الأساسية. الجلسة 2 بدأت بدون أن يتذكر الوكيل عقد واجهة middleware المصادقة، وقضى ~15 دقيقة في استنتاج نية التصميم السابقة. بحلول الجلسة 3، الانحراف المتراكم جعل الوكيل يبدأ في إعادة تنفيذ ميزات مكتملة بالفعل. بحلول الجلسة 5، المستودع يحتوي على الكثير من الكود المكرر لكن ميزة المصادقة الأساسية لم تجتز اختبارات شاملة بعد. فقط 7 من 12 نقطة ميزة أُنجزت، 3 منها بمشاكل صحة خفية. مثل الحرفي الذي لا يكتب في مذكرته أبدًا — بحلول اليوم الخامس، موقع البناء في حالة فوضى، بعض الجدران بُنيت مرتين، وبعضها الذي كان يجب بناؤه لم يُبدأ. - -**مع المذكرة**: باستخدام ملفات التقدم، وسجلات القرارات، وسجلات التحقق، ونقاط فحص git. تُحدَّث تقارير الحالة تلقائيًا عند نهاية كل جلسة. تكلفة إعادة بناء الجلسة 2 انخفضت إلى ~3 دقائق. بحلول الجلسة 5، أُنجزت كل نقاط الميزة الـ 12 وتحققت منها. - -مقارنة كمية: وقت إعادة البناء انخفض ~78%، ومعدل إكمال الميزات من 58% إلى 100%، ومعدل العيوب الخفية من 43% إلى 8%. الحرفي لا يزال مصابًا بفقدان الذاكرة، لكن مع المذكرة، كل يوم يبدأ من حيث توقف أمس، لا من الصفر. - -## الخلاصات الأساسية - -- نوافذ السياق مورد محدود. المهام الطويلة ستمتد عبر جلسات، والجلسات ستفقد معلومات — مثل الحرفي الذي ينسى كل يوم، هذا واقع موضوعي. -- الحل ليس نوافذ أكبر — بل استمرارية حالة أفضل. ملفات التقدم + سجلات القرارات + نقاط فحص git — أعطِ الحرفي المصاب بفقدان الذاكرة مذكرة موثوقة. -- عامل الوكيل كمهندس مصاب بفقدان الذاكرة: قبل "انتهاء الدوام"، اكتب ما تم إنجازه، ولماذا، وما التالي. -- تكلفة إعادة البناء هي المقياس الأساسي. harness الجيد يجب أن يوصل الجلسات الجديدة إلى حالة قابلة للتنفيذ خلال 3 دقائق. -- الاستراتيجية المختلطة: مهام قصيرة ضمن الجلسات، مهام طويلة مع مخرجات منظمة للاستمرارية. - -## قراءات إضافية - -- [Anthropic: Effective Harnesses for Long-Running Agents](https://www.anthropic.com/engineering/effective-harnesses-for-long-running-agents) -- [OpenAI: Harness Engineering](https://openai.com/index/harness-engineering/) -- [Lost in the Middle: How Language Models Use Long Contexts](https://arxiv.org/abs/2307.03172) -- [Claude Code Documentation](https://docs.anthropic.com/ar/docs/claude-code) -- [HumanLayer: Harness Engineering for Coding Agents](https://humanlayer.dev/articles/harness-engineering-for-coding-agents/) - -## تمارين - -1. **قياس فقدان الاستمرارية**: اختر مهمة تطوير تحتاج 3 جلسات على الأقل. بدون تقديم أي مخرجات استمرارية، سجّل عند بدء كل جلسة كم سياق يُنفق في "اكتشاف ما حدث في المرة السابقة." بعد كل جلسة، أنشئ ملف تقدم ودع الجلسة التالية تبدأ منه. قارن تكاليف إعادة البناء مع وبدون ملفات التقدم. - -2. **تصميم قالب تسليم**: صمّم قالب تسليم بسيط بأربعة حقول: حالة المستودع (commit hash)، حالة وقت التشغيل (معدل نجاح الاختبارات)، العوائق، الإجراءات التالية. دع جلسة وكيل جديدة تمامًا تستعيد حالة المشروع باستخدام هذا القالب فقط. سجّل الالتباسات التي واجهتها أثناء الاستعادة، وكرّر لتحسين القالب. - -3. **تجربة الاستراتيجية المختلطة**: في مهمة تطوير من 5 جلسات، قارن ثلاث استراتيجيات: (a) دائمًا ابدأ جلسات جديدة + ملفات تقدم، (b) أنجز قدر الممكن في جلسة واحدة (ضغط السياق)، (c) استراتيجية مختلطة (مهام قصيرة ضمن الجلسة، مهام طويلة عبر جلسات + ملفات تقدم). قارن وقت إعادة البناء، ومعدل إكمال الميزات، وثبات القرارات. diff --git a/courses/learn-harness-engineering/docs/ar/lectures/lecture-06-why-initialization-needs-its-own-phase/code/index.md b/courses/learn-harness-engineering/docs/ar/lectures/lecture-06-why-initialization-needs-its-own-phase/code/index.md deleted file mode 100644 index 9f5c15f2..00000000 --- a/courses/learn-harness-engineering/docs/ar/lectures/lecture-06-why-initialization-needs-its-own-phase/code/index.md +++ /dev/null @@ -1,8 +0,0 @@ -# كود المحاضرة 06 - -استخدم هذا المجلد لأمثلة عن: - -- مخرجات التهيئة -- سكربتات التهيئة -- ملفات التقدم -- هيكلة التشغيل الأول diff --git a/courses/learn-harness-engineering/docs/ar/lectures/lecture-06-why-initialization-needs-its-own-phase/code/init-check.ts b/courses/learn-harness-engineering/docs/ar/lectures/lecture-06-why-initialization-needs-its-own-phase/code/init-check.ts deleted file mode 100644 index 52dcd985..00000000 --- a/courses/learn-harness-engineering/docs/ar/lectures/lecture-06-why-initialization-needs-its-own-phase/code/init-check.ts +++ /dev/null @@ -1,296 +0,0 @@ -/** - * init-check.ts - * - * Programmatically checks initialization prerequisites: - * - Node.js version - * - Dependencies installed (node_modules exists) - * - Data directory exists - * - Config files present - * - * Simulates running with and without an explicit init phase, - * showing how missing prerequisites cause silent failures later. - * - * Run: npx tsx docs/lectures/lecture-06-why-initialization-needs-its-own-phase/code/init-check.ts - */ - -import * as fs from "node:fs"; -import * as path from "node:path"; -import * as childProcess from "node:child_process"; - -// --------------------------------------------------------------------------- -// Types -// --------------------------------------------------------------------------- - -interface CheckItem { - name: string; - category: string; - check: () => { pass: boolean; detail: string }; - impactIfMissing: string; -} - -interface CheckResult { - name: string; - category: string; - passed: boolean; - detail: string; - impactIfMissing: string; -} - -// --------------------------------------------------------------------------- -// Checks -// --------------------------------------------------------------------------- - -function createChecks(targetDir: string): CheckItem[] { - return [ - { - name: "Node.js version >= 18", - category: "Runtime", - check: () => { - try { - const version = process.version; // e.g. "v20.11.0" - const major = parseInt(version.replace("v", "").split(".")[0], 10); - return { - pass: major >= 18, - detail: `Detected: ${version} (need >= v18)`, - }; - } catch { - return { pass: false, detail: "Could not detect Node.js version" }; - } - }, - impactIfMissing: "TypeScript features and built-in APIs unavailable", - }, - { - name: "package.json exists", - category: "Config", - check: () => { - const p = path.join(targetDir, "package.json"); - const exists = fs.existsSync(p); - return { - pass: exists, - detail: exists ? `Found: ${p}` : "Not found in target directory", - }; - }, - impactIfMissing: "Cannot install dependencies or run scripts", - }, - { - name: "Dependencies installed (node_modules)", - category: "Dependencies", - check: () => { - const p = path.join(targetDir, "node_modules"); - const exists = fs.existsSync(p); - return { - pass: exists, - detail: exists ? `Found: ${p}` : "node_modules/ not found -- run npm install", - }; - }, - impactIfMissing: "All imports fail at runtime", - }, - { - name: "TypeScript available", - category: "Toolchain", - check: () => { - try { - const result = childProcess.execSync("npx tsc --version", { - encoding: "utf-8", - timeout: 10000, - stdio: ["pipe", "pipe", "pipe"], - }).trim(); - return { pass: true, detail: `Detected: ${result}` }; - } catch { - return { pass: false, detail: "TypeScript not available via npx" }; - } - }, - impactIfMissing: "Cannot compile TypeScript files", - }, - { - name: "tsconfig.json exists", - category: "Config", - check: () => { - const p = path.join(targetDir, "tsconfig.json"); - const exists = fs.existsSync(p); - return { - pass: exists, - detail: exists ? `Found: ${p}` : "Not found", - }; - }, - impactIfMissing: "TypeScript compiler uses defaults, may not match project needs", - }, - { - name: "Source directory exists", - category: "Structure", - check: () => { - const candidates = ["src", "lib", "app"]; - const found = candidates.filter((d) => { - const p = path.join(targetDir, d); - return fs.existsSync(p) && fs.statSync(p).isDirectory(); - }); - return { - pass: found.length > 0, - detail: found.length > 0 ? `Found: ${found.join(", ")}` : "No src/ lib/ or app/ directory found", - }; - }, - impactIfMissing: "Agent cannot locate source files to modify", - }, - { - name: "Test directory exists", - category: "Structure", - check: () => { - const candidates = ["test", "tests", "__tests__", "spec"]; - const found = candidates.filter((d) => { - const p = path.join(targetDir, d); - return fs.existsSync(p) && fs.statSync(p).isDirectory(); - }); - return { - pass: found.length > 0, - detail: found.length > 0 ? `Found: ${found.join(", ")}` : "No test directory found", - }; - }, - impactIfMissing: "Agent cannot find or run existing tests", - }, - { - name: "Git repository initialized", - category: "Version Control", - check: () => { - const gitDir = path.join(targetDir, ".git"); - const exists = fs.existsSync(gitDir); - return { - pass: exists, - detail: exists ? "Git repository detected" : "No .git directory found", - }; - }, - impactIfMissing: "No rollback capability, no change history", - }, - ]; -} - -// --------------------------------------------------------------------------- -// Simulation: with and without init phase -// --------------------------------------------------------------------------- - -interface SimResult { - scenario: string; - checksRun: boolean; - failuresBeforeWork: number; - workAttempted: boolean; - workSucceeded: boolean; - timeWastedMs: number; -} - -function simulateWithoutInit(): SimResult { - // Agent skips init, goes straight to work. - // Encounters failures one at a time as it hits missing prerequisites. - const checks = createChecks(process.cwd()); - let failures = 0; - let timeWasted = 0; - - for (const check of checks) { - const result = check.check(); - if (!result.pass) { - failures++; - timeWasted += 200; // Agent spends time discovering each missing piece - } - } - - return { - scenario: "NO INIT PHASE", - checksRun: false, - failuresBeforeWork: 0, // Didn't check upfront - workAttempted: true, - workSucceeded: failures === 0, - timeWastedMs: timeWasted, - }; -} - -function simulateWithInit(): SimResult { - // Agent runs init phase first, discovers all issues upfront. - const checks = createChecks(process.cwd()); - let failures = 0; - - for (const check of checks) { - if (!check.check().pass) { - failures++; - } - } - - return { - scenario: "WITH INIT PHASE", - checksRun: true, - failuresBeforeWork: failures, - workAttempted: failures === 0, - workSucceeded: failures === 0, - timeWastedMs: 0, - }; -} - -// --------------------------------------------------------------------------- -// Reporting -// --------------------------------------------------------------------------- - -function pad(s: string, len: number): string { - return s.length >= len ? s : s + " ".repeat(len - s.length); -} - -function run(): void { - const targetDir = process.cwd(); - console.log("\n" + "=".repeat(80)); - console.log(" INITIALIZATION PREREQUISITE CHECK"); - console.log("=".repeat(80)); - console.log(` Target: ${targetDir}\n`); - - const checks = createChecks(targetDir); - const results: CheckResult[] = checks.map((c) => { - const r = c.check(); - return { - name: c.name, - category: c.category, - passed: r.pass, - detail: r.detail, - impactIfMissing: c.impactIfMissing, - }; - }); - - // Detailed report - const header = `| ${pad("Check", 35)}| ${pad("Category", 12)}| ${pad("Status", 6)}| Detail`; - const sep = `|${"-".repeat(37)}|${"-".repeat(14)}|${"-".repeat(8)}|${"-".repeat(40)}`; - console.log(header); - console.log(sep); - - for (const r of results) { - const status = r.passed ? "PASS" : "FAIL"; - console.log(`| ${pad(r.name, 35)}| ${pad(r.category, 12)}| ${pad(status, 6)}| ${r.detail}`); - } - - const passCount = results.filter((r) => r.passed).length; - const failCount = results.filter((r) => !r.passed).length; - - console.log("\n" + "-".repeat(80)); - console.log(` Results: ${passCount} passed, ${failCount} failed out of ${results.length} checks`); - - if (failCount > 0) { - console.log("\n FAILED CHECKS -- Impact if not resolved:"); - for (const r of results.filter((r) => !r.passed)) { - console.log(` - ${r.name}: ${r.impactIfMissing}`); - } - } - - // Comparison: with vs without init - const noInit = simulateWithoutInit(); - const withInit = simulateWithInit(); - - console.log("\n" + "=".repeat(80)); - console.log(" INIT PHASE COMPARISON"); - console.log("=".repeat(80) + "\n"); - - const cHeader = `| ${pad("Metric", 35)}| ${pad("No Init Phase", 18)}| ${pad("With Init Phase", 18)}|`; - const cSep = `|${"-".repeat(37)}|${"-".repeat(20)}|${"-".repeat(20)}|`; - console.log(cHeader); - console.log(cSep); - console.log(`| ${pad("Prerequisites checked upfront", 35)}| ${pad("No", 18)}| ${pad("Yes", 18)}|`); - console.log(`| ${pad("Work attempted despite issues", 35)}| ${pad(String(noInit.workAttempted), 18)}| ${pad(String(withInit.workAttempted), 18)}|`); - console.log(`| ${pad("Time wasted discovering issues late", 35)}| ${pad(noInit.timeWastedMs + "ms", 18)}| ${pad(withInit.timeWastedMs + "ms", 18)}|`); - console.log(`| ${pad("Work succeeded", 35)}| ${pad(String(noInit.workSucceeded), 18)}| ${pad(String(withInit.workSucceeded), 18)}|`); - - console.log("\n An explicit init phase catches problems before they waste time.\n"); -} - -run(); diff --git a/courses/learn-harness-engineering/docs/ar/lectures/lecture-06-why-initialization-needs-its-own-phase/code/init.sh b/courses/learn-harness-engineering/docs/ar/lectures/lecture-06-why-initialization-needs-its-own-phase/code/init.sh deleted file mode 100644 index 2c6e1b0c..00000000 --- a/courses/learn-harness-engineering/docs/ar/lectures/lecture-06-why-initialization-needs-its-own-phase/code/init.sh +++ /dev/null @@ -1,10 +0,0 @@ -#!/usr/bin/env bash -set -euo pipefail - -echo "[init] installing dependencies" -npm install - -echo "[init] starting the docs site is optional" -echo "[init] use npm run docs:dev for course docs" - -echo "[init] project-specific startup would go here" diff --git a/courses/learn-harness-engineering/docs/ar/lectures/lecture-06-why-initialization-needs-its-own-phase/code/initializer-output-checklist.md b/courses/learn-harness-engineering/docs/ar/lectures/lecture-06-why-initialization-needs-its-own-phase/code/initializer-output-checklist.md deleted file mode 100644 index 41c8224b..00000000 --- a/courses/learn-harness-engineering/docs/ar/lectures/lecture-06-why-initialization-needs-its-own-phase/code/initializer-output-checklist.md +++ /dev/null @@ -1,7 +0,0 @@ -# قائمة فحص مخرجات التهيئة - -- هل يوجد أمر تشغيل معياري؟ -- هل يوجد أمر تحقق معياري؟ -- هل يوجد أول مُنتج تقدم (progress artifact)؟ -- هل يوجد أول التزام (commit) مستقر؟ -- هل يوجد سطح ميزات مرئي للجلسات اللاحقة؟ diff --git a/courses/learn-harness-engineering/docs/ar/lectures/lecture-06-why-initialization-needs-its-own-phase/index.md b/courses/learn-harness-engineering/docs/ar/lectures/lecture-06-why-initialization-needs-its-own-phase/index.md deleted file mode 100644 index 8d135eee..00000000 --- a/courses/learn-harness-engineering/docs/ar/lectures/lecture-06-why-initialization-needs-its-own-phase/index.md +++ /dev/null @@ -1,157 +0,0 @@ -[中文版本 →](../../../zh/lectures/lecture-06-why-initialization-needs-its-own-phase/) - -> أمثلة الكود: [code/](https://github.com/walkinglabs/learn-harness-engineering/blob/main/docs/ar/lectures/lecture-06-why-initialization-needs-its-own-phase/code/) -> مشروع عملي: [Project 03. Multi-session continuity](./../../projects/project-03-multi-session-continuity/index.md) - -# المحاضرة 06. هيئ قبل كل جلسة وكيل - -تبدأ جلسة وكيل جديدة وتقول "أضف ميزة بحث." يقفز مباشرة إلى البرمجة — حماس مُعجب. بعد 20 دقيقة يكتشف أن إطار الاختبار غير مضبوط بشكل صحيح، يقضي 10 دقائق أخرى في إصلاح ذلك، ثم تنسيق نص ترحيل قاعدة البيانات خاطئ، المزيد من العبث. ميزة البحث تُضاف في النهاية، لكن الجلسة كلها كانت غير فعالة — معظم الوقت ذهب إلى "اكتشاف كيف يعمل هذا المشروع" بدلاً من كتابة ميزة البحث. - -النهج الأفضل: قبل السماح للوكيل ببدء العمل، استخدم مرحلة منفصلة لتجهيز البيئة الأساسية، واجعل أوامر التحقق ناجحة، وهيكل المشروع مفهومًا. الأمر أشهر ببناء منزل — لا تصب الأساس وترفع الجدران في وقت واحد. إذا فعلت، ترتفع الجدران قبل أن يتصلب الأساس، ويجب هدم المبنى بالكامل والبدء من جديد. صب الأساس أولًا، دعه يتصلب، ثم ابنِ الجدران — نظيف وفعال. - -تشرح هذه المحاضرة لماذا يجب أن تكون التهيئة مرحلة منفصلة، لا مختلطة بالتنفيذ. - -## الأساس والجدران: وظيفتان مختلفتان جذريًا - -التهيئة والتنفيذ لهما أهداف تحسين مختلفة تمامًا. مرحلة التنفيذ تُحسِّن من: زيادة كمية وجودة الميزات المُتحقَقة. مرحلة التهيئة تُحسِّن من: زيادة موثوقية وكفاءة كل التنفيذ اللاحق. - -عندما تخلط التهيئة والتنفيذ، يواجه الوكيل مشكلة تحسين متعددة الأهداف — بناء البنية التحتية وكتابة كود الميزات في وقت واحد. بدون تحديد أولويات صريح، يميل الوكيل طبيعيًا نحو كتابة الكود (لأن ذلك مخرج مرئي مباشر) مع التضحية بالبنية التحتية (لأن قيمتها تظهر فقط في الجلسات اللاحقة). الأمر أشبه بإخبار طاقم بناء بصب الأساس ورفع الجدران في وقت واحد — سيسرعون على الأرجح لرفع الجدران لأن الجدران مرئية ويمكن إثباتها. لكن المنزل ذو الأساس السيء سيواجه مشاكل نظامية لاحقًا. - -## دورة حياة التهيئة - -```mermaid -flowchart TB - subgraph Wrong["Mixed session (wrong)"] - W1["Start feature work immediately"] --> W2["Discover env and test gaps mid-task"] - W2 --> W3["Accumulate unverified code"] - W3 --> W4["Next session must rediscover project state"] - end - - subgraph Right["Dedicated initialization (right)"] - R1["Session 1: environment runnable"] --> R2["Example test passing"] - R2 --> R3["Bootstrap contract + task list written"] - R3 --> R4["Clean checkpoint committed"] - R4 --> R5["Later sessions start directly on verified tasks"] - end -``` - -## ماذا يحدث عند خلطهما - -المشكلة الأكثر مباشرة: الأساس لا يتصلب بشكل صحيح. الوكيل ينفق 80% من جهده على كود الميزات و20% في إعداد بعض البنية التحتية بشكل عابر. إطار الاختبار مضبوط لكن لم يُتحقق منه قط، وقواعد lint محددة لكنها متساهلة جدًا، ولم يُنشأ ملف تقدم. هذه العيوب ليست واضحة في الجلسة الأولى (لأن الوكيل لا يزال يتذكر ما فعله)، لكنها تظهر في الجلسة الثانية — الوكيل الجديد لا يعرف كيف يُشغّل، أو يختبر، أو أين تقف الأمور. أساس رديء، مبنى مهتز. - -تكلفة أخفى هي "التراكم غير المُتحقَق" — كود الميزات المكتوب قبل ضبط إطار الاختبار هو كود بدون تحقق. عندما تعود أخيرًا لإضافة اختبارات لهذا الكود، قد تكتشف أن التصميم كان خاطئًا من البداية — لو كنت تعرف، لطبّقته بشكل مختلف. مثل تبليط سطح خرسانة رطبة — عندما تكتشف أن الأرضية غير مستوية، يجب نزع كل البلاط وإعادة العمل. - -ميزانية الجلسة تُهدر أيضًا. عمل التهيئة (ضبط البيئات، إعداد الاختبارات، فهم هيكل المشروع) يستهلك ميزانية كبيرة، تاركًا أقل للتنفيذ الفعلي للميزات. النتيجة: الجلسة الأولى تُنجز نصف الميزات فقط، والجلسة الثانية عليها البدء من جديد في فهم المشروع. ميزانية أُنفقت على الأساس، لكن الأساس ليس متينًا أيضًا — لم يتحقق أي هدف. - -المشكلة الأكثر تجاهلًا هي ألغام الافتراضات الضمنية. القرارات التي يتخذها الوكيل أثناء التهيئة (أي إطار اختبار، كيف تنظيم الأدلة، إدارة التبعيات) — إذا لم تُسجَّل صراحةً، لا يمكن للجلسات اللاحقة فهم هذه الخيارات. الأسوأ، أن الجلسات اللاحقة قد تتخذ خيارات متناقضة. طاقم البناء الأول استخدم أساسًا خرسانيًا، والطاقم الثاني لا يعرف وغرس فيه أوتادًا خشبية — الأساس يتشقق. - -أبحاث Anthropic لتطوير التطبيقات طويلة التشغيل توصي صراحةً بفصل التهيئة عن التنفيذ. بياناتهم التجريبية: المشاريع التي استخدمت مرحلة تهيئة مخصصة أظهرت معدلات إكمال ميزات أعلى بنسبة 31% في سيناريوهات الجلسات المتعددة مقارنة بالأساليب المختلطة. الرؤية الأساسية — الوقت المستثمر في مرحلة التهيئة يُسترد بالكامل في الجلسات الـ 3-4 التالية. كلما كان الأساس أصلب، ارتفعت الجدران أسرع. - -دليل OpenAI Codex لهندسة harness يُؤكد أيضًا على مبدأ "المستودع كسجل عمليات" — أَسِّس هيكل عمليات واضحًا من التشغيل الأول، وإلا ستضطر كل جلسة جديدة إلى استنتاج اصطلاحات المشروع من جديد. - -## المفاهيم الأساسية - -- **مرحلة التهيئة**: المرحلة الأولى في دورة حياة الوكيل — بدون تنفيذ ميزات، فقط إعداد المتطلبات المسبقة لكل مراحل التنفيذ اللاحقة. المخرج ليس كودًا، بل بنية تحتية. -- **عقد الإقلاع**: الشروط التي يمكن بموجبها تشغيل المشروع بوضوح بواسطة جلسة وكيل جديدة — يمكن البدء، يمكن الاختبار، يمكن رؤية التقدم، يمكن استئناف الخطوات التالية. أربعة شروط، جميعها مطلوبة. -- **البدء البارد مقابل البدء الدافئ**: البدء البارد هو من دليل فارغ حيث يجب على الوكيل تخمين هيكل المشروع؛ البدء الدافئ هو من قالب أو مشروع موجود حيث البنية التحتية جاهزة بالفعل. البدء الدافئ يتفوق بكثير على البدء البارد — مثل بدء العمل في موقع به مياه جارية وكهرباء مقابل البدء من أرض قاحلة. -- **جاهزية التسليم**: المشروع في حالة تسمح في أي لحظة لوكيل جديد بتسلمه. لا حاجة لشرح شفهي — فقط محتويات المستودع. -- **الوقت حتى أول تحقق**: الوقت من بداية المشروع حتى اجتياز أول نقطة ميزة للتحقق. هذا هو المقياس الأساسي لقياس كفاءة التهيئة. -- **قابلية الاستخدام اللاحقة**: أفضل مقياس لجودة التهيئة — نسبة الجلسات اللاحقة التي يمكنها تنفيذ المهام بنجاح دون الاعتماد على معرفة ضمنية. - -## كيف تُنجز التهيئة بشكل صحيح - -**عامل التهيئة كمرحلة مخصصة.** الجلسة الأولى تُنجز التهيئة فقط — بدون أي كود ميزات أعمال. التهيئة تُنتج: - -**1. بيئة قابلة للتشغيل.** المشروع يبدأ، التبعيات مثبتة، لا مشاكل بيئية. الأساس مَصوب، بدون شقوق. - -**2. إطار اختبار قابل للتحقق.** اختبار مثال واحد على الأقل يجتاز بنجاح. هذا يثبت أن إطار الاختبار نفسه مضبوط بشكل صحيح — مثل إقامة عمود على الأساس لإثبات أنه يحمل الوزن. - -**3. مستند عقد الإقلاع.** مستند واضح يخبر الجلسات اللاحقة: -```markdown -# Initialization Contract - -## Start Commands -- Install dependencies: `make setup` -- Start dev server: `make dev` -- Run tests: `make test` -- Full verification: `make check` - -## Current State -- All dependencies installed and locked -- Test framework configured (Vitest + React Testing Library) -- Example test passing (1/1) -- Lint rules configured (ESLint + Prettier) - -## Project Structure -- src/ — Source code -- src/components/ — React components -- src/api/ — API client -- tests/ — Test files -``` - -**4. تقسيم المهام.** قسّم المشروع بالكامل إلى قائمة مهام مرتبة، كل مهمة بمعايير قبول واضحة: -```markdown -# Task Breakdown - -## Task 1: User Authentication Basics -- Implement JWT auth middleware -- Add login/register endpoints -- Acceptance: pytest tests/test_auth.py all passing - -## Task 2: User Profile Page -- Implement user profile CRUD -- Add profile edit form -- Acceptance: pytest tests/test_profile.py all passing - -## Task 3: Search Feature -- ... -``` - -**5. Git commit كنقطة فحص.** بعد اكتمال التهيئة، commit نقطة فحص نظيفة. كل العمل اللاحق يبدأ من هذه النقطة. - -**استراتيجية البدء الدافئ**: لا تبدأ من دليل فارغ. استخدم قالب مشروع (create-react-app، fastapi-template، إلخ) لإعداد هيكل أدلة قياسي، وتكوين تبعيات، وإطار اختبار مسبقًا. اخبز خطوات التهيئة الشائعة في القالب، واترك فقط عمل التهيئة الخاص بالمشروع. مثل بدء العمل في موقع به مياه جارية وكهرباء — أفضل ب ten thousand مرة من البدء من أرض قاحلة. - -**معايير اكتمال التهيئة**: ليس "كم كود كُتب"، بل هل شروط عقد الإقلاع الأربعة محققة — يمكن البدء، يمكن الاختبار، يمكن رؤية التقدم، يمكن استئناف الخطوات التالية. استخدم قائمة الفحص هذه للتحقق من التهيئة: - -```markdown -## Initialization Acceptance Checklist -- [ ] `make setup` succeeds from scratch -- [ ] `make test` has at least one passing test -- [ ] A new agent session can answer "how to run" and "how to test" from repo contents alone -- [ ] Task breakdown file exists with at least 3 tasks -- [ ] Everything committed to git -``` - -## مثال من الواقع - -أسلوبا تهيئة لمشروع واجهة أمامية بـ React: - -**الأسلوب المختلط (صب الأساس ورفع الجدران في وقت واحد)**: الوكيل أنشأ هيكل المشروع ونفّذ الميزة الأولى في وقت واحد في الجلسة 1. عند نهاية الجلسة، المستودع يحتوي على كود قابل للتشغيل لكن: بدون توثيق صريح لأوامر البدء/الاختبار، وبدون ملف تتبع تقدم، وبدون تقسيم مهام. الجلسة 2 قضت ~20 دقيقة في استنتاج هيكل المشروع وإطار الاختبار وعملية البناء — مثل طاقم بناء جديد يصل إلى موقع، لا يعرف مدى تقدم الأساس أو أين مسارات السباكة، يضطر لحفر ثقوب واحدة تلو الأخرى للاكتشاف. - -**التهيئة المخصصة (الأساس أولًا)**: الجلسة 1 أنجزت التهيئة فقط — أنشأت هيكل الأدلة من قالب، وضبطت إطار الاختبار (Vitest + React Testing Library)، وكتبت وتحققت من اختبار مثال واحد، وأنشأت مستند عقد الإقلاع وملف تقسيم المهام، وcommit نقطة فحص أولية. وقت إعادة بناء الجلسة 2 كان أقل من 3 دقائق، وبدأت العمل مباشرة من قائمة المهام — الطاقم يصل، يلقي نظرة على المخطط، ويعرف بالضبط من أين يستأنف. - -مقارنة دورة المشروع الكاملة: إجمالي وقت إعادة البناء (عبر كل الجلسات) للأسلوب المختلط كان ~60% أكثر من أسلوب التهيئة المخصصة. الـ 20 دقيقة الإضافية المُنفقة على التهيئة استُردت مرات عديدة في الجلسات اللاحقة. مثل الأساس المتين الذي يجعل الجدران ترتفع أسرع — البطيء سريع. - -## الخلاصات الأساسية - -- التهيئة والتنفيذ لهما أهداف تحسين مختلفة — خلطهما يسحب كليهما للأسفل. صب الأساس أولًا، ثم ابنِ الجدران. -- مخرجات التهيئة ليست كودًا، بل بنية تحتية: بيئة قابلة للتشغيل، اختبارات قابلة للتحقق، عقد إقلاع، تقسيم مهام. -- تحقق من التهيئة بشروط عقد الإقلاع الأربعة: يمكن البدء، يمكن الاختبار، يمكن رؤية التقدم، يمكن استئناف الخطوات التالية. -- البدء الدافئ يتفوق على البدء البارد. استخدم قوالب المشاريع لإعداد بنية تحتية قياسية مسبقًا. -- الوقت المستثمر في التهيئة يُسترد بالكامل في الجلسات الـ 3-4 التالية. هذا ليس تكلفة إضافية — بل استثمار مسبق. كلما كان الأساس أصلب، ارتفع المبنى أسرع. - -## قراءات إضافية - -- [Anthropic: Effective Harnesses for Long-Running Agents](https://www.anthropic.com/engineering/effective-harnesses-for-long-running-agents) -- [OpenAI: Harness Engineering](https://openai.com/index/harness-engineering/) -- [HumanLayer: Harness Engineering for Coding Agents](https://humanlayer.dev/articles/harness-engineering-for-coding-agents/) -- [Infrastructure as Code — Martin Fowler](https://martinfowler.com/bliki/InfrastructureAsCode.html) -- [SWE-agent: Agent-Computer Interfaces](https://github.com/princeton-nlp/SWE-agent) - -## تمارين - -1. **تصميم عقد الإقلاع**: اكتب عقد إقلاع كامل لمشروع تعمل على تطويره. ثم افتح جلسة وكيل جديدة تمامًا، وأره فقط محتويات المستودع (بدون سياق شفهي)، واجعله يحاول بدء المشروع، وتشغيل الاختبارات، وفهم التقدم الحالي. سجّل كل مشكلة يواجهها — كل واحدة تقابل بندًا مفقودًا في عقد الإقلاع الخاص بك. - -2. **تجربة مقارنة**: اختر مشروعًا جديدًا متوسط التعقيد. الأسلوب A: دع الوكيل يُهيئ وينفذ أول تنفيذ في وقت واحد. الأسلوب B: اقضِ جلسة واحدة في التهيئة المخصصة، وابدأ التنفيذ في الجلسة 2. بعد 4 جلسات، قارن: الوقت حتى أول تحقق، وتكلفة إعادة البناء، ومعدل إكمال الميزات. - -3. **قائمة فحص قبول التهيئة**: صمّم قائمة فحص قبول تهيئة لمشروعك. اجعل جلسة وكيل جديدة تنفذ كل بند في قائمة الفحص وسجّل أيها ينجح وأيها يفشل. البنود الفاشلة هي حيث يحتاج harness الخاص بك إلى تقوية. diff --git a/courses/learn-harness-engineering/docs/ar/lectures/lecture-07-why-agents-overreach-and-under-finish/code/index.md b/courses/learn-harness-engineering/docs/ar/lectures/lecture-07-why-agents-overreach-and-under-finish/code/index.md deleted file mode 100644 index 7ee37d66..00000000 --- a/courses/learn-harness-engineering/docs/ar/lectures/lecture-07-why-agents-overreach-and-under-finish/code/index.md +++ /dev/null @@ -1,8 +0,0 @@ -# كود المحاضرة 07 - -استخدم هذا المجلد لأمثلة عن: - -- إخفاقات التنفيذ الواحد -- prompts مهام كبيرة الحجم -- تشكيل المهام التدريجي -- أسطح الميزات المنظمة diff --git a/courses/learn-harness-engineering/docs/ar/lectures/lecture-07-why-agents-overreach-and-under-finish/code/next-task-template.md b/courses/learn-harness-engineering/docs/ar/lectures/lecture-07-why-agents-overreach-and-under-finish/code/next-task-template.md deleted file mode 100644 index b0f9ccf7..00000000 --- a/courses/learn-harness-engineering/docs/ar/lectures/lecture-07-why-agents-overreach-and-under-finish/code/next-task-template.md +++ /dev/null @@ -1,6 +0,0 @@ -# قالب المهمة التالية - -- الميزة ذات الأولوية القصوى الحالية: -- لماذا هذه الميزة هي التالية: -- ما الذي يُعتبر ناجحًا: -- ما الذي يجب عدم تغييره خلال هذه الخطوة: diff --git a/courses/learn-harness-engineering/docs/ar/lectures/lecture-07-why-agents-overreach-and-under-finish/code/scope-surface-example.md b/courses/learn-harness-engineering/docs/ar/lectures/lecture-07-why-agents-overreach-and-under-finish/code/scope-surface-example.md deleted file mode 100644 index cd5916d9..00000000 --- a/courses/learn-harness-engineering/docs/ar/lectures/lecture-07-why-agents-overreach-and-under-finish/code/scope-surface-example.md +++ /dev/null @@ -1,17 +0,0 @@ -# مثال على سطح النطاق - -المهمة: - -- إضافة الفهرسة إلى تطبيق المعرفة Electron - -شكل نطاق سيء: - -- "تنفيذ الفهرسة" - -شكل نطاق أفضل: - -- تحليل المستندات المستوردة -- تقسيم المستندات إلى أجزاء -- حفظ بيانات الأجزاء الوصفية -- عرض حالة الفهرسة في واجهة المستخدم -- إضافة إجراء إعادة الفهرسة diff --git a/courses/learn-harness-engineering/docs/ar/lectures/lecture-07-why-agents-overreach-and-under-finish/code/scope-tracker.ts b/courses/learn-harness-engineering/docs/ar/lectures/lecture-07-why-agents-overreach-and-under-finish/code/scope-tracker.ts deleted file mode 100644 index c587a1dc..00000000 --- a/courses/learn-harness-engineering/docs/ar/lectures/lecture-07-why-agents-overreach-and-under-finish/code/scope-tracker.ts +++ /dev/null @@ -1,151 +0,0 @@ -/** - * scope-tracker.ts - * - * Reads a feature list and a change log. Enforces single-active-feature - * policy. Given a log of changes, flags any changes outside the active - * feature scope. Demonstrates how scope drift happens and how the tracker - * catches it. - * - * Run: npx tsx docs/lectures/lecture-07-why-agents-overreach-and-under-finish/code/scope-tracker.ts - */ - -// --------------------------------------------------------------------------- -// Types -// --------------------------------------------------------------------------- - -interface Feature { - id: string; - name: string; - status: "active" | "pending" | "done"; -} - -interface ChangeLogEntry { - step: number; - file: string; - description: string; - featureId: string; // The feature this change claims to belong to -} - -// --------------------------------------------------------------------------- -// Sample data -// --------------------------------------------------------------------------- - -const features: Feature[] = [ - { id: "F-001", name: "Search endpoint", status: "active" }, - { id: "F-002", name: "Delete endpoint", status: "pending" }, - { id: "F-003", name: "Rate limiting", status: "pending" }, - { id: "F-004", name: "User dashboard", status: "pending" }, -]; - -// A realistic change log where the agent gradually drifts from the active feature -const changeLog: ChangeLogEntry[] = [ - { step: 1, file: "src/routes/search.ts", description: "Add search route handler", featureId: "F-001" }, - { step: 2, file: "src/routes/search.ts", description: "Add query parameter validation", featureId: "F-001" }, - { step: 3, file: "src/routes/search.ts", description: "Add search results pagination", featureId: "F-001" }, - { step: 4, file: "src/routes/delete.ts", description: "Add delete route handler", featureId: "F-002" }, // DRIFT - { step: 5, file: "src/middleware/rate-limit.ts", description: "Add rate limiter middleware", featureId: "F-003" }, // DRIFT - { step: 6, file: "src/routes/search.ts", description: "Integrate rate limiter into search", featureId: "F-001" }, - { step: 7, file: "src/dashboard/ui.tsx", description: "Create dashboard layout component", featureId: "F-004" }, // DRIFT - { step: 8, file: "src/routes/search.ts", description: "Add search response formatting", featureId: "F-001" }, - { step: 9, file: "src/routes/delete.ts", description: "Add delete confirmation logic", featureId: "F-002" }, // DRIFT - { step: 10, file: "src/routes/search.ts", description: "Add search tests", featureId: "F-001" }, -]; - -// --------------------------------------------------------------------------- -// Scope tracker -// --------------------------------------------------------------------------- - -interface ScopeCheckResult { - step: number; - file: string; - description: string; - featureId: string; - inScope: boolean; - activeFeature: string; -} - -function trackScope( - featureList: Feature[], - changes: ChangeLogEntry[] -): ScopeCheckResult[] { - const activeFeatures = featureList.filter((f) => f.status === "active"); - const activeIds = new Set(activeFeatures.map((f) => f.id)); - const activeNames = activeFeatures.map((f) => f.name).join(", "); - - return changes.map((change) => ({ - step: change.step, - file: change.file, - description: change.description, - featureId: change.featureId, - inScope: activeIds.has(change.featureId), - activeFeature: activeNames, - })); -} - -// --------------------------------------------------------------------------- -// Reporting -// --------------------------------------------------------------------------- - -function pad(s: string, len: number): string { - return s.length >= len ? s : s + " ".repeat(len - s.length); -} - -function run(): void { - const results = trackScope(features, changeLog); - - console.log("\n" + "=".repeat(100)); - console.log(" SCOPE TRACKER -- Single Active Feature Enforcement"); - console.log("=".repeat(100)); - - console.log("\n Active feature: " + features.filter((f) => f.status === "active").map((f) => `${f.id} (${f.name})`).join(", ")); - console.log(" Pending features: " + features.filter((f) => f.status === "pending").map((f) => `${f.id} (${f.name})`).join(", ")); - - // Detailed change log - console.log("\n" + "-".repeat(100)); - const header = `| ${pad("Step", 5)}| ${pad("File", 35)}| ${pad("Description", 40)}| ${pad("Feature", 8)}| ${pad("In Scope", 10)}|`; - const sep = `|${"-".repeat(7)}|${"-".repeat(37)}|${"-".repeat(42)}|${"-".repeat(10)}|${"-".repeat(12)}|`; - console.log(header); - console.log(sep); - - let inScopeCount = 0; - let driftCount = 0; - - for (const r of results) { - const scopeLabel = r.inScope ? "OK" : "DRIFT"; - if (r.inScope) inScopeCount++; - else driftCount++; - - const marker = r.inScope ? " " : ">>"; - console.log(`${marker}| ${pad(String(r.step), 5)}| ${pad(r.file, 35)}| ${pad(r.description, 40)}| ${pad(r.featureId, 8)}| ${pad(scopeLabel, 10)}|`); - } - - // Summary - console.log("\n" + "=".repeat(100)); - console.log(" SCOPE DRIFT SUMMARY"); - console.log("=".repeat(100) + "\n"); - - const sHeader = `| ${pad("Metric", 40)}| ${pad("Value", 15)}|`; - const sSep = `|${"-".repeat(42)}|${"-".repeat(17)}|`; - console.log(sHeader); - console.log(sSep); - console.log(`| ${pad("Total changes", 40)}| ${pad(String(results.length), 15)}|`); - console.log(`| ${pad("Changes within active scope (F-001)", 40)}| ${pad(String(inScopeCount), 15)}|`); - console.log(`| ${pad("Changes outside active scope (DRIFT)", 40)}| ${pad(String(driftCount), 15)}|`); - console.log(`| ${pad("Features touched (total)", 40)}| ${pad(String(new Set(results.map((r) => r.featureId)).size), 15)}|`); - - // Drift detail - const driftFeatures = [...new Set(results.filter((r) => !r.inScope).map((r) => r.featureId))]; - if (driftFeatures.length > 0) { - console.log("\n DRIFTED FEATURES:"); - for (const fid of driftFeatures) { - const feat = features.find((f) => f.id === fid); - const driftChanges = results.filter((r) => r.featureId === fid); - console.log(` ${fid} (${feat?.name}): ${driftChanges.length} unauthorized changes`); - } - } - - console.log("\n Without a scope tracker, the agent silently worked on " + driftFeatures.length + " unrelated features."); - console.log(" The tracker catches this drift and enforces the single-active-feature policy.\n"); -} - -run(); diff --git a/courses/learn-harness-engineering/docs/ar/lectures/lecture-07-why-agents-overreach-and-under-finish/index.md b/courses/learn-harness-engineering/docs/ar/lectures/lecture-07-why-agents-overreach-and-under-finish/index.md deleted file mode 100644 index c00c9f1a..00000000 --- a/courses/learn-harness-engineering/docs/ar/lectures/lecture-07-why-agents-overreach-and-under-finish/index.md +++ /dev/null @@ -1,134 +0,0 @@ -[中文版本 →](../../../zh/lectures/lecture-07-why-agents-overreach-and-under-finish/) - -> أمثلة الكود: [code/](https://github.com/walkinglabs/learn-harness-engineering/blob/main/docs/ar/lectures/lecture-07-why-agents-overreach-and-under-finish/code/) -> مشروع عملي: [Project 04. Runtime feedback and scope control](./../../projects/project-04-incremental-indexing/index.md) - -# المحاضرة 07. ارسم حدود مهمة واضحة للوكلاء - -تطلب من Claude Code "إضافة مصادقة المستخدم إلى هذا المشروع"، فيبدأ بتعديل مخطط قاعدة البيانات، وكتابة المسارات، وتغيير مكونات الواجهة الأمامية، و — أثناء وجوده في ذلك — إعادة هيكلة middleware معالجة الأخطاء. بعد ساعتين تتفقد: 12 ملفًا معدّلًا، 800 سطر كود جديد، ولا ميزة واحدة تعمل من البداية للنهاية. - -العض أكثر مما تستطيع مضغه — هذا القول ينطبق على وكلاء الذكاء الاصطناعي بشكل خاص. الوكلاء يولدون بدافع "فعل القليل الإضافي" — يرون أشياء مرتبطة فيتعاملون معها في الطريق، مثل شخص يذهب إلى السوبرماركت لزجاجة صلصة صويا ويخرج يدفع عربة كاملة. المشكلة هي أن البشر الذين يشترون أكثر مما ينب مضيعون للمال فقط؛ الوكلاء الذين يفعلون أشياء كثيرة في وقت واحد يعني أنه لا شيء يُنجز بشكل صحيح. - -مقال مدونة Anthropic الهندسية "Effective harnesses for long-running agents" يوضح: عندما تكون prompts واسعة جدًا، يميل الوكلاء إلى "بدء أشياء متعددة في وقت واحد" بدلاً من "إنهاء شيء واحد أولًا." ممارسات OpenAI الهندسية لـ Codex وجدت نفس الشيء — المهام بدون ضوابط نطاق صريحة تشهد انهيارًا في معدلات الإكمال. هذه ليست مشكلة نموذج — إنها مشكلة harness. أنت لم ترسم الحدود. - -## الانتباه مورد محدود - -هذه ليست استعارة — إنها رياضيات. افترض أن سعة سياق الوكيل هي C ويُنشط k مهمة في وقت واحد. كل مهمة تحصل على متوسط C/k من موارد الاستدلال. عندما ينخفض C/k تحت الحد الأدنى المطلوب لإكمال مهمة واحدة، لا شيء يُنجز. معدتك بحجم محدد فقط — ضع عشر فطائر في وقت واحد ولن تهضمها جميعًا، بل ستحصل على عشر حالات عسر هضم. - -السلوك الفعلي لـ Claude Code يوضح. اطلب منه "إضافة تسجيل مستخدم" وقد: - -1. ينشئ نموذج User -2. يكتب مسار التسجيل -3. يلاحظ أنه يحتاج تحقق بريد إلكتروني، فيضيف خدمة بريد -4. يرى أن كلمات المرور تحتاج تجزئة، فيجلب bcrypt -5. يلاحظ أن معالجة الأخطاء غير متسقة، فيُعيد هيكلة middleware الأخطاء العام -6. يرى أن هيكل ملف الاختبار فوضوي، فيُعيد تنظيم الدليل - -بعد ست خطوات، كل واحدة نصف مكتملة. بدون تحقق شامل، وارتباط معقد بين الكود غير المكتمل، والجلسة التالية التي ستلتقط القطع ستكون ضائعة تمامًا. مثل شخص يطبخ ستة أطباق في وقت واحد — كل طبق في المقلاة لكن لم يُقدم أي منها. جميعها تحترق. - -البيانات التجريبية من Anthropic تدعم هذا مباشرة: الوكلاء الذين يستخدمون استراتيجية "الخطوة التالية الصغيرة" (ما يعادل WIP=1) يُظهرون معدل إكمال مهام أعلى بنسبة 37% من الوكلاء الذين يستخدمون prompts واسعة. والأكثر إثارة للاهتمام، أن عدد أسطر الكود التي يُنتجها الوكلاء مرتبط ارتباطًا سلبيًا ضعيفًا مع إكمال الميزات الفعلي — كود أكثر مكتوب، ميزات أقل مُنجزة. العض أكثر مما تستطيع مضغه، مُثبت بالبيانات. - -## سير عمل WIP=1 - -```mermaid -flowchart LR - Queue["Feature queue"] --> Pick["Pick exactly one task"] - Pick --> Active["Only one active item"] - Active --> Verify["Run end-to-end verification"] - Verify -->|pass| Commit["Commit and unlock next task"] - Verify -->|fail| Active - Commit --> Queue -``` - -```mermaid -flowchart TB - Budget["Available reasoning budget = C"] --> One["WIP = 1
C / 1 per task"] - Budget --> Many["WIP = 5
C / 5 per task"] - - One --> Finish["One feature reaches passing state"] - Many --> Partial["Five partial implementations"] - Partial --> VCR["Low verified completion rate
high rework next session"] -``` - -## المفاهيم الأساسية - -- **التجاوز (Overreach)**: الوكيل يُنشط مهامًا أكثر من المثالي في جلسة واحدة. إنه قابل للقياس — فعل 5 ميزات مع 0 نجحت شاملة هو تجاوز. -- **عدم الإكمال (Under-finish)**: نسبة المهام التي تجتاز التحقق الشامل، من إجمالي المهام المُنشطة، تنخفض عن الحد. كود مكتوب لكن اختبارات لا تنجح هو عدم إكمال. -- **حد WIP (حد العمل الجاري)**: من منهجية Kanban. الفكرة الأساسية: حدد عدد المهام الجارية في وقت واحد. للوكلاء، WIP=1 هو الإعداد الافتراضي الأكثر أمانًا — أنهِ واحدة قبل بدء التالية. مثل بوفيه — لا تُكدّس طبقك، أنهِ طبقًا ثم عد للطبق التالي. -- **دليل الإكمال**: الشرط القابل للتحقق الذي يجب أن تلبيه المهمة للانتقال من "قيد التنفيذ" إلى "مكتملة." بدون هذا، يستبدل الوكلاء "الكود يبدو جيدًا" بـ "السلوك يجتاز الاختبارات." -- **سطح النطاق**: هيكل DAG حيث كل عقدة هي وحدة عمل والحواف هي التبعيات. الحالات محدودة بأربعة: not_started، active، blocked، passing. -- **ضغط الإكمال**: القوة المقيدة التي يمارسها harness من خلال حدود WIP ومتطلبات دليل الإكمال، مُجبرًا الوكيل على إنهاء المهمة الحالية قبل بدء مهمة جديدة. - -## التجاوز وعدم الإكمال متعايشان - -هاتان المشكلتان ليستا مستقلتين — تُضخّم كل منهما الأخرى. التجاوز يُخفّف الانتباه، والانتباه المُخفّض يسبب عدم الإكمال، والكود نصف المكتوب المتبقي يزيد من تعقيد النظام، مما يزيد من دفع التجاوز في المهمة التالية. حلقة مفرغة. - -بلغة Kanban: قانون ليتل يخبرنا أن L = lambda * W. إذا كان العمل الجاري L مرتفعًا جدًا (فعل أشياء كثيرة في وقت واحد)، فإن زمن التسليم W لكل مهمة يزداد حتمًا. للوكلاء، هذا يعني أن كل ميزة تستغرق وقتًا أطول من البداية حتى الإكمال المُتحقَق، واحتمالية الفشل تنمو. - -هذه مشكلة قديمة في عالم البشر أيضًا — وثّق Steve McConnell في *Rapid Development* أن زحف النطاق هو السبب الرئيس لفشل المشاريع. لكن البشر لديهم على الأقل حدس "لقد أنجزت ما يكفي." الوكلاء ليس لديهم شيء. توليد الفكرة التالية يكلف النموذج تقريبًا صفر tokens إضافية — كتابة "دعني أصلح هذا أيضًا وأنا هنا" بالكاد يُسجَّل — لكن كل تعديل إضافي يُخفّف انتباه الوكيل. مثل بوفيه كل طبق إضافي فيه تكلفة حدية قريبة من الصفر، لكن معدتك بسعة محدودة. - -## كيف تفعل ذلك بشكل صحيح - -### 1. فرض WIP=1 - -هذه هي الطريقة الأكثر مباشرة وفعالية. في harness الخاص بك، أخبر الوكيل صراحةً: **مهمة واحدة فقط مسموح بها في حالة "active" في أي وقت.** في `CLAUDE.md` الخاص بـ Claude Code أو `AGENTS.md` الخاص بـ Codex، اكتب: - -``` -## Work Rules -- Work on one feature at a time -- Only start the next feature after the current one passes end-to-end verification -- Don't "also refactor" feature B while implementing feature A -``` - -مثل الأكل في بوفيه — طبق واحد في كل مرة، أنهِه قبل العود للمزيد. - -### 2. حدد دليل إكمال صريح لكل مهمة - -الإنجاز ليس "الكود مكتوب" — بل "التحقق من السلوك ناجح." في قائمة الميزات الخاصة بك، كل إدخال يحتاج أمر تحقق: - -``` -F01: User Registration - Verification: curl -X POST /api/register -d '{"email":"test@example.com","password":"123456"}' | jq .status == 201 - State: passing -``` - -### 3. أخرج سطح النطاق إلى ملف خارجي - -استخدم ملفًا قابلًا للقراءة آليًا (JSON أو Markdown) لتسجيل حالات كل المهام. أي جلسة جديدة يمكنها قراءة هذا الملف والمعرفة فورًا: أي مهمة نشطة؟ ما السلوك الذي يُحسب كمنجز؟ أي عمليات تحقق نجحت؟ - -### 4. راقب معدل الإكمال المُتحقَق - -يجب على harness تتبع VCR (معدل الإكمال المُتحقَق) = مهام مُتحقَقة / مهام مُنشطة باستمرار. احظر تنشيط مهام جديدة عندما يكون VCR < 1.0. - -## حالة من الواقع - -مشروع REST API مع 8 ميزات، استراتيجيتان مقارنتان: - -**وضع البوفيه (غير مقيد)**: الوكيل يُنشط 5 ميزات في وقت واحد في الجلسة 1. يُنتج ~800 سطر عبر 12 ملفًا. معدل نجاح اختبار شامل: 20% — فقط تسجيل المستخدم يعمل. الميزات الأربع الأخرى: مخطط قاعدة بيانات مُنشأ لكن ينقصه منطق التحقق، مسارات محددة لكنها تُعيد تنسيقات استجابة خاطئة. مثل شخص يطبخ ستة أطباق في وقت واحد، واحد فقط بالكاد صالح للأكل. بنهاية الجلسة 3، فقط 3 من 8 ميزات مكتملة. - -**وضع الطبق الواحد (WIP=1)**: الوكيل يعمل على تسجيل المستخدم فقط في الجلسة 1. يُنتج ~200 سطر عبر 4 ملفات. اختبارات شاملة: 100% ناجحة. يُنفذ commit لتطبيق نظيف ومُتحقَق. بنهاية الجلسة 4، 7 من 8 ميزات مكتملة (الثامنة محجوبة بتبعية خارجية). - -النتيجة: كود إجمالي أقل (800 مقابل 1200 سطر) لكن كود أكثر فعالية. معدل الإكمال: 87.5% مقابل 37.5%. خذ قضمة واحدة في كل مرة، وتأكل فعليًا أكثر. - -## الخلاصات الأساسية - -- **WIP=1 هو الإعداد الافتراضي الآمن لـ harness الوكلاء** — أنهِ واحدة، ثم ابدأ التالية؛ لا تحاول التوازي. لا يمكنك أن تسمن في قضمة واحدة. -- **دليل الإكمال يجب أن يكون قابلًا للتنفيذ** — "الكود يبدو جيدًا" لا يُحسب؛ "curl يُعيد 201" نعم. -- **سطح النطاق يجب أن يُخرَج كملف** — ليس فقط مذكورًا في المحادثة، بل مسجّلًا بتنسيق قابل للقراءة آليًا في المستودع. -- **التجاوز وعدم الإكمال متعايشان** — حل أحدهما يحل الآخر. -- **"افعل أقل لكن أنهِ" دائمًا يتفوق على "افعل أكثر لكن اترك نصفه غير مكتمل"** — أسطر كود الوكيل ومعدل إكمال الميزات مرتبطان سلبيًا. الجودة دائمًا تتفوق على الكمية. - -## قراءات إضافية - -- [Effective harnesses for long-running agents - Anthropic](https://www.anthropic.com/engineering/effective-harnesses-for-long-running-agents) — مدونة Anthropic الهندسية، مناقشة تفصيلية لاستراتيجية "الخطوة التالية الصغيرة" -- [Harness Engineering - OpenAI](https://openai.com/index/harness-engineering/) — المعالجة الكاملة لـ OpenAI لهندسة harness -- [Kanban: Successful Evolutionary Change - David Anderson](https://www.goodreads.com/book/show/1070822.Kanban) — المصدر الكلاسيكي حول حدود WIP -- [Rapid Development - Steve McConnell](https://www.goodreads.com/book/show/125171.Rapid_Development) — بيانات تجريبية حول زحف النطاق بصفته السبب الرئيس لفشل المشاريع - -## تمارين - -1. **تنوي المهام**: اختر متطلبًا واسعًا (مثلًا، "نفّذ نظام إدارة مستخدمين") وقسّمه إلى 5 وحدات عمل ذرية على الأقل. لكل وحدة، حدد: (a) وصف سلوك واحد، (b) أمر تحقق قابل للتنفيذ، (c) التبعيات. تحقق مما إذا كان التحلل يلبي قيد WIP=1. - -2. **تجربة مقارنة**: شغّل نفس المشروع مرتين — مرة بدون قيود، ومرة مع WIP=1 مفروض. قارن: معدل الإكمال المُتحقَق، وإجمالي أسطر الكود، ونسبة الكود الفعال. - -3. **تدقيق دليل الإكمال**: راجع مخرجات تشغيل وكيل حديث، وصنّف كل تغيير كود بصفته "سلوك مكتمل،" أو "سلوك غير مكتمل،" أو "هيكل أولي." أضف أوامر تحقق مفقودة لكل سلوك غير مكتمل. diff --git a/courses/learn-harness-engineering/docs/ar/lectures/lecture-08-why-feature-lists-are-harness-primitives/code/feature-list-validator.ts b/courses/learn-harness-engineering/docs/ar/lectures/lecture-08-why-feature-lists-are-harness-primitives/code/feature-list-validator.ts deleted file mode 100644 index 52d6a2b9..00000000 --- a/courses/learn-harness-engineering/docs/ar/lectures/lecture-08-why-feature-lists-are-harness-primitives/code/feature-list-validator.ts +++ /dev/null @@ -1,218 +0,0 @@ -/** - * feature-list-validator.ts - * - * Reads a feature_list.json, validates its schema, and checks for features - * marked "pass" without verification evidence. Outputs a structured report. - * Can run against any project directory that has a feature_list.json. - * - * Usage: - * npx tsx docs/lectures/lecture-08.../code/feature-list-validator.ts [path-to-dir] - * (defaults to the directory containing this script) - * - * Run: npx tsx docs/lectures/lecture-08-why-feature-lists-are-harness-primitives/code/feature-list-validator.ts - */ - -import * as fs from "node:fs"; -import * as path from "node:path"; - -// --------------------------------------------------------------------------- -// Types -// --------------------------------------------------------------------------- - -interface FeatureEntry { - id?: string; - category?: string; - description?: string; - verification?: string[]; - passes?: boolean; - // Allow unknown fields for flexibility - [key: string]: unknown; -} - -interface ValidationResult { - featureId: string; - schemaValid: boolean; - schemaErrors: string[]; - hasVerification: boolean; - markedPassWithoutEvidence: boolean; - passes: boolean; - verificationCount: number; -} - -// --------------------------------------------------------------------------- -// Schema validation -// --------------------------------------------------------------------------- - -function validateSchema(entry: FeatureEntry, index: number): string[] { - const errors: string[] = []; - const label = entry.id ?? `entry ${index + 1}`; - - if (!entry.id || typeof entry.id !== "string") { - errors.push(`[${label}] Missing or invalid 'id' field`); - } - if (!entry.category || typeof entry.category !== "string") { - errors.push(`[${label}] Missing or invalid 'category' field`); - } - if (!entry.description || typeof entry.description !== "string") { - errors.push(`[${label}] Missing or invalid 'description' field`); - } - if (entry.verification !== undefined && !Array.isArray(entry.verification)) { - errors.push(`[${label}] 'verification' must be an array if present`); - } - if (entry.passes !== undefined && typeof entry.passes !== "boolean") { - errors.push(`[${label}] 'passes' must be a boolean if present`); - } - - return errors; -} - -// --------------------------------------------------------------------------- -// Evidence validation -// --------------------------------------------------------------------------- - -function checkEvidence(entry: FeatureEntry): { - hasVerification: boolean; - markedPassWithoutEvidence: boolean; -} { - const hasVerification = Array.isArray(entry.verification) && entry.verification.length > 0; - const markedPassWithoutEvidence = entry.passes === true && !hasVerification; - - return { hasVerification, markedPassWithoutEvidence }; -} - -// --------------------------------------------------------------------------- -// Process feature list -// --------------------------------------------------------------------------- - -function processFeatureList(entries: FeatureEntry[]): ValidationResult[] { - return entries.map((entry, index) => { - const schemaErrors = validateSchema(entry, index); - const evidence = checkEvidence(entry); - - return { - featureId: (entry.id as string) ?? `entry-${index + 1}`, - schemaValid: schemaErrors.length === 0, - schemaErrors, - hasVerification: evidence.hasVerification, - markedPassWithoutEvidence: evidence.markedPassWithoutEvidence, - passes: entry.passes === true, - verificationCount: Array.isArray(entry.verification) ? entry.verification.length : 0, - }; - }); -} - -// --------------------------------------------------------------------------- -// Reporting -// --------------------------------------------------------------------------- - -function pad(s: string, len: number): string { - return s.length >= len ? s : s + " ".repeat(len - s.length); -} - -function run(): void { - // Resolve target directory - const scriptDir = path.dirname(new URL(import.meta.url).pathname); - const targetDir = process.argv[2] - ? path.resolve(process.argv[2]) - : scriptDir; - - const filePath = path.join(targetDir, "feature_list.json"); - - console.log("\n" + "=".repeat(90)); - console.log(" FEATURE LIST VALIDATOR"); - console.log("=".repeat(90)); - console.log(` Reading: ${filePath}\n`); - - if (!fs.existsSync(filePath)) { - console.error(` ERROR: feature_list.json not found at ${filePath}`); - console.error(" Usage: npx tsx feature-list-validator.ts [path-to-directory-containing-feature_list.json]\n"); - process.exit(1); - } - - let entries: FeatureEntry[]; - try { - const raw = fs.readFileSync(filePath, "utf-8"); - entries = JSON.parse(raw); - } catch (err) { - console.error(` ERROR: Could not parse feature_list.json: ${err}`); - process.exit(1); - } - - if (!Array.isArray(entries)) { - console.error(" ERROR: feature_list.json must contain a JSON array at the top level."); - process.exit(1); - } - - // For demo purposes, also validate an extended test set - const demoEntries: FeatureEntry[] = [ - ...entries, - { - id: "qna-002", - category: "import", - description: "User can import a PDF document.", - verification: ["Upload a PDF file", "Verify it appears in the document list"], - passes: true, - }, - { - id: "qna-003", - category: "grounded_qa", - description: "System hallucination rate is below 5%.", - verification: [], // Empty -- no evidence - passes: true, // Marked as pass WITHOUT evidence - }, - { - id: "missing-fields", - // Missing 'category' and 'description' - passes: true, - } as FeatureEntry, - ]; - - const results = processFeatureList(demoEntries); - - // Print report - const header = `| ${pad("Feature ID", 14)}| ${pad("Schema", 8)}| ${pad("Passes", 7)}| ${pad("Verifications", 14)}| ${pad("Evidence?", 12)}| Notes`; - const sep = `|${"-".repeat(16)}|${"-".repeat(10)}|${"-".repeat(9)}|${"-".repeat(16)}|${"-".repeat(14)}|${"-".repeat(30)}`; - console.log(header); - console.log(sep); - - for (const r of results) { - const schemaLabel = r.schemaValid ? "OK" : "INVALID"; - const passesLabel = r.passes ? "PASS" : "FAIL"; - const evidenceLabel = r.hasVerification ? "Present" : "MISSING"; - const notes = r.markedPassWithoutEvidence - ? "FLAGGED: passes without evidence!" - : r.schemaErrors.length > 0 - ? r.schemaErrors[0] - : ""; - - const marker = r.markedPassWithoutEvidence ? ">>" : r.schemaValid ? " " : "!!"; - console.log( - `${marker}| ${pad(r.featureId, 14)}| ${pad(schemaLabel, 8)}| ${pad(passesLabel, 7)}| ${pad(String(r.verificationCount), 14)}| ${pad(evidenceLabel, 12)}| ${notes}` - ); - } - - // Summary - const total = results.length; - const schemaOk = results.filter((r) => r.schemaValid).length; - const passing = results.filter((r) => r.passes).length; - const flagged = results.filter((r) => r.markedPassWithoutEvidence).length; - const withEvidence = results.filter((r) => r.hasVerification).length; - - console.log("\n" + "-".repeat(90)); - console.log(" SUMMARY"); - console.log("-".repeat(90)); - console.log(` Total features: ${total}`); - console.log(` Schema valid: ${schemaOk}/${total}`); - console.log(` Marked as passing: ${passing}/${total}`); - console.log(` With verification evidence: ${withEvidence}/${total}`); - console.log(` Flagged (pass without evidence): ${flagged}`); - - if (flagged > 0) { - console.log(`\n WARNING: ${flagged} feature(s) marked as "pass" without any verification evidence.`); - console.log(" These features need verification before they can be trusted.\n"); - } else { - console.log("\n All passing features have verification evidence. Feature list is healthy.\n"); - } -} - -run(); diff --git a/courses/learn-harness-engineering/docs/ar/lectures/lecture-08-why-feature-lists-are-harness-primitives/code/feature_list.json b/courses/learn-harness-engineering/docs/ar/lectures/lecture-08-why-feature-lists-are-harness-primitives/code/feature_list.json deleted file mode 100644 index 8b973e31..00000000 --- a/courses/learn-harness-engineering/docs/ar/lectures/lecture-08-why-feature-lists-are-harness-primitives/code/feature_list.json +++ /dev/null @@ -1,15 +0,0 @@ -[ - { - "id": "qna-001", - "category": "grounded_qa", - "description": "User can ask a question about an imported document and receive an answer with visible citations.", - "verification": [ - "Import a markdown document", - "Open the Q&A panel", - "Ask a question about known content", - "Verify the answer is returned", - "Verify one or more citations are displayed" - ], - "passes": false - } -] diff --git a/courses/learn-harness-engineering/docs/ar/lectures/lecture-08-why-feature-lists-are-harness-primitives/code/index.md b/courses/learn-harness-engineering/docs/ar/lectures/lecture-08-why-feature-lists-are-harness-primitives/code/index.md deleted file mode 100644 index a1641a6a..00000000 --- a/courses/learn-harness-engineering/docs/ar/lectures/lecture-08-why-feature-lists-are-harness-primitives/code/index.md +++ /dev/null @@ -1,8 +0,0 @@ -# كود المحاضرة 08 - -استخدم هذا المجلد لأمثلة عن: - -- بوابات حالة النجاح (pass-state gating) -- التحقق من البداية للنهاية -- معايير إكمال ضعيفة مقابل قوية -- أمثلة حلقة التقييم diff --git a/courses/learn-harness-engineering/docs/ar/lectures/lecture-08-why-feature-lists-are-harness-primitives/code/pass-gate-policy.md b/courses/learn-harness-engineering/docs/ar/lectures/lecture-08-why-feature-lists-are-harness-primitives/code/pass-gate-policy.md deleted file mode 100644 index cdd0830c..00000000 --- a/courses/learn-harness-engineering/docs/ar/lectures/lecture-08-why-feature-lists-are-harness-primitives/code/pass-gate-policy.md +++ /dev/null @@ -1,8 +0,0 @@ -# سياسة بوابة النجاح - -لا يمكن للميزة الانتقال من `passes: false` إلى `passes: true` إلا عندما: - -- يكون سير العمل المتوقع قد تم تنفيذه -- يكون دليل النجاح مسجلاً -- لا يوجد خطأ حاظر في المسار المختبر -- التنفيذ لا يترك التطبيق في حالة معطلة أو غامضة diff --git a/courses/learn-harness-engineering/docs/ar/lectures/lecture-08-why-feature-lists-are-harness-primitives/index.md b/courses/learn-harness-engineering/docs/ar/lectures/lecture-08-why-feature-lists-are-harness-primitives/index.md deleted file mode 100644 index 77aadade..00000000 --- a/courses/learn-harness-engineering/docs/ar/lectures/lecture-08-why-feature-lists-are-harness-primitives/index.md +++ /dev/null @@ -1,137 +0,0 @@ -[中文版本 →](../../../zh/lectures/lecture-08-why-feature-lists-are-harness-primitives/) - -> أمثلة الكود: [code/](https://github.com/walkinglabs/learn-harness-engineering/blob/main/docs/ar/lectures/lecture-08-why-feature-lists-are-harness-primitives/code/) -> مشروع عملي: [Project 04. Runtime feedback and scope control](./../../projects/project-04-incremental-indexing/index.md) - -# المحاضرة 08. استخدم قوائم الميزات لتقييد الوكيل - -تطلب من agent بناء موقع تجارة إلكترونية. بعد أن ينتهي، يخبرك "انتهيت". تنظر إلى الكود — مصادقة المستخدم تعمل، لكن زر الدفع في سلة التسوق لا يفعل شيئًا، وتدفق الدفع غير متصل على الإطلاق. المشكلة: لم تخبره أبدًا ما يعنيه "انتهيت"، لذا استخدم معياره الخاص — "كتبت الكثير من الكود ويبدو مكتملًا إلى حد ما." - -قوائم الميزات، في نظر الكثير من الناس، هي مجرد مذكرة — اكتب الأشياء حتى لا تنساها، ثم ألقِها جانبًا. لكن في عالم harness، قائمة الميزات ليست مذكرة للبشر — إنها العمود الفقري لـ harness بالكامل. يعتمد المجدول عليها لاختيار المهام، ويعتمد المدقق عليها للحكم على الاكتمال، ويعتمد مُعدّ التسليم عليها لإنشاء الملخصات. اكسر العمود الفقري والجسم كله يشل. - -تشدد كل من Anthropic و OpenAI: **يجب إخراج القطع الأثرية (artifacts) إلى الخارج.** يجب أن تعيش حالة الميزة في ملف قابل للقراءة آليًا في المستودع، وليس في نص محادثة غير منظم. - -## الوكلاء لا يعرفون ما يعنيه "انتهيت" - -لا يعرف Claude Code ولا Codex تلقائيًا ما تقصده بـ "انتهيت". تقول "أضف ميزة سلة تسوق"، وقد يكون تفسير النموذج "اكتب مكوّن Cart ودالة addToCart." لكنك كنت تقصد "يمكن للمستخدم تصفح المنتجات وإضافتها إلى السلة وإتمام الشراء من البداية إلى النهاية." تستمر هذه الفجوة في الفهم بدون قائمة ميزات. يستخدم agent معياره الضمني الخاص — عادةً "الكود ليس به أخطاء بناء جملة واضحة." ما تحتاجه هو تحقق سلوكي شامل. مثلما تطلب من صديق أن يشتري لك فاكهة — تقول "احضر بعض الفاكهة" فيعود بالليمون. فاكهته وفاكهتك ليستا نفس الفاكهة. - -انظر إلى ملاحظة التقدم الشائعة هذه: - -``` -Did user auth, shopping cart mostly done, still need payments -``` -هل يمكن لجلسة agent جديدة الإجابة على هذه الأسئلة من هذه الملاحظة؟ ماذا يعني "مكتملة إلى حد كبير"؟ أي اختبارات اجتازتها السلة؟ ما الذي يعيق المدفوعات؟ الإجابة على الكل هي "لا أحد يعرف." مثلما تخبر طبيبك "معدتي تؤلمني، كنت بخير مؤخرًا" — أي دواء يمكنه وصفه؟ - -النتيجة: تقضي الجلسة الجديدة 20 دقيقة في استنتاج حالة المشروع، وقد تعيد تنفيذ الميزات المكتملة. تُظهر بيانات هندسة Anthropic أن سجلات التقدم الجيدة تقلل وقت تشخيص بدء الجلسة بنسبة 60-80%. - -## آلة حالة الميزات - -```mermaid -flowchart LR - Feature["One feature row"] --> Behavior["Behavior
for example: POST /cart/items returns 201"] - Feature --> Check["Verification command
the exact check to run"] - Feature --> State["State
not_started / active / blocked / passing"] - - Behavior --> Complete["Only with all three fields
is the feature row usable"] - Check --> Complete - State --> Complete -``` - -```mermaid -flowchart LR - List["feature_list.json / features.md"] --> Scheduler["Pick the next not_started item"] - Scheduler --> Agent["Agent works on that one item"] - Agent --> Verifier["Run that item's verification command"] - Verifier -->|pass| Passing["Mark it passing
and write the evidence"] - Verifier -->|fail| Active["Keep it active"] - Verifier -->|dependency issue| Blocked["Mark it blocked"] - Passing --> Handoff["Update handoff note
and current progress"] - Active --> Agent -``` - -## المفاهيم الأساسية - -- **قوائم الميزات هي عناصر بدائية في harness**: ليست "أدوات تخطيط اختيارية"، بل هي هياكل بيانات أساسية تعتمد عليها جميع مكونات harness الأخرى. مثل هياكل جداول قاعدة البيانات — لا يمكنك أن تقول "لن نتخطى المفاتيح الأساسية." -- **البنية الثلاثية**: كل عنصر ميزة هو ثلاثية `(وصف السلوك، أمر التحقق، الحالة الحالية)`. غياب أي عنصر يجعل العنصر غير مكتمل. -- **نموذج آلة الحالة**: لكل عنصر ميزة أربع حالات — `not_started`، `active`، `blocked`، `passing`. تُ controlled انتقالات الحالة بواسطة harness، وليست قابلة للتغيير بحرية من agent. -- **بوابة حالة الاجتياز**: الطريقة الوحيدة لانتقال ميزة من `active` إلى `passing` هي تنفيذ أمر التحقق بنجاح. هذا لا رجعة فيه — بمجرد أن يصبح `passing`، لا يمكنه العودة. مثل اجتياز امتحان يعني أنك اجتزته، لا يمكنك تغيير الدرجة بأثر رجعي. -- **مصدر حقيقة واحد**: يجب أن تُستمد جميع المعلومات حول "ما يجب فعله" من قائمة ميزات واحدة. لا تناقضات بين قائمة الميزات وسجل المحادثة. -- **الضغط العكسي**: عدد الميزات التي لم تجتز بعد هو الضغط الذي يمارسه harness على agent. ضغط صفري = المشروع مكتمل. - -## لماذا يجب أن تكون قوائم الميزات "عناصر بدائية" - -المستندات للبشر ليقرأوها؛ العناصر البدائية للأنظمة لتنفذها. يمكن تجاهل المستندات؛ لا يمكن تجاوز العناصر البدائية. - -فكر في الأمر مثل قيود مشغلات قاعدة البيانات مقابل عمليات التحقق في طبقة التطبيق: الأولى تُفرض بواسطة محرك قاعدة البيانات، لا يمكن لأي SQL تخطيها؛ الثانية تعتمد على صحة كود التطبيق ويمكن تجاوزها عن طريق الخطأ. قوائم الميزات كعناصر بدائية في harness — تحديدًا، تخدم قائمة الميزات أربعة مكونات من harness: - -1. **المجدول**: يقرأ الحالات، يختار ميزة `not_started` التالية. مثل نظام تخطيط الإنتاج في المصنع. -2. **المدقق**: ينفذ أوامر التحقق، يقرر ما إذا كان سيسمح بانتقالات الحالة. مثل فحص الجودة. -3. **مُعدّ التسليم**: ينشئ تلقائيًا ملخصات تسليم الجلسة من قائمة الميزات. مثل تقرير تغيير الوردية التلقائي. -4. **متتبع التقدم**: يحصي توزيع الحالات، يوفر مقاييس صحة المشروع. مثل لوحة المعلومات. - -## كيف تفعل ذلك بشكل صحيح - -### 1. حدد تنسيق قائمة ميزات بسيط - -لا تحتاج إلى نظام معقد — ملف Markdown أو JSON منظم يكفي. المفتاح هو أن كل إدخال يجب أن يمتلك الثلاثية: - -```json -{ - "id": "F03", - "behavior": "POST /cart/items with {product_id, quantity} returns 201", - "verification": "curl -X POST http://localhost:3000/api/cart/items -H 'Content-Type: application/json' -d '{\"product_id\":1,\"quantity\":2}' | jq .status == 201", - "state": "passing", - "evidence": "commit abc123, test output log" -} -``` - -### 2. دع harness يتحكم في انتقالات الحالة - -لا يمكن لـ agent تغيير حالة ميزة مباشرة إلى `passing`. يمكنه فقط تقديم طلب تحقق؛ ينفذ harness أمر التحقق ويقرر ما إذا كان سيسمح بالانتقال. هذه هي "بوابة حالة الاجتياز." - -### 3. اكتب القواعد في CLAUDE.md - -``` -## Feature List Rules -- Feature list file: /docs/features.md -- Only one feature active at a time -- Verification command must pass before marking as passing -- Don't modify feature list states yourself — the verification script updates them automatically -``` - -### 4. معايرة الدقة - -يجب أن يكون نطاق كل عنصر ميزة "قابل للإنجاز في جلسة واحدة." واسع جدًا ولن ينتهي؛ ضيق جدًا وتزداد تكلفة الإدارة. "يمكن للمستخدم إضافة عناصر إلى السلة" دقة جيدة. "تنفيذ سلة التسوق" واسع جدًا. "إنشاء حقل الاسم في نموذج Cart" ضيق جدًا. مثل تقطيع شريحة لحم — ليس القطعة كاملة، وليست لحماً مفروماً. - -## حالة من العالم الحقيقي - -منصة تجارة إلكترونية مع 10 ميزات. مقارنة نهجي تتبع: - -**وضع المذكرة**: يستخدم agent ملاحظات غير منظمة. بعد 3 جلسات، تصبح الملاحظات "تمت مصادقة المستخدم وقائمة المنتجات، سلة التسوق مكتملة إلى حد كبير لكن بها أخطاء، المدفوعات لم تبدأ." تحتاج الجلسة الجديدة إلى 20 دقيقة لاستنتاج الحالة، وتعيد تنفيذ الميزات المكتملة في النهاية. مثل قائمة تسوقك التي تقول "حليب، خبز، وذلك الشيء" — في المتجر، ما زلت لا تعرف ماذا تشتري. - -**وضع العمود الفقري**: كل ميزة لها حالة واضحة وأمر تحقق. تقرأ الجلسة الجديدة قائمة الميزات وتعرف في 3 دقائق: F01-F05 `passing`، F06 `active`، F07-F10 `not_started`. تستأنف من F06 مباشرة، صيانة صفرية. - -النتيجة المُقاسة: تُظهر المشاريع التي تستخدم قوائم ميزات منظمة معدل إنجاز ميزات أعلى بنسبة 45% من التتبع الحر، مع صفر عمليات تنفيذ مكررة. - -## الخلاصات الأساسية - -- **قوائم الميزات هي العمود الفقري لـ harness**، وليست مذكرات للبشر. المجدول والمدقق ومُعدّ التسليم يعتمدون عليها جميعًا. -- **كل عنصر ميزة يجب أن يمتلك الثلاثية**: وصف السلوك + أمر التحقق + الحالة الحالية. غياب عنصر واحد يجعله غير مكتمل — مثل كرسي بثلاث أرجل ينقصه ساق. -- **انتقالات الحالة يتحكم فيها harness** — لا يمكن لـ agent تغيير الحالات بمفرده. اجتياز التحقق = مسار الترقية الوحيد. -- **قائمة الميزات هي مصدر الحقيقة الوحيد للمشروع** — جميع معلومات "ماذا نفعل" تُستمد من قائمة واحدة. -- **عاير الدقة إلى "قابل للإنجاز في جلسة واحدة."** - -## قراءات إضافية - -- [Building Effective Agents - Anthropic](https://www.anthropic.com/research/building-effective-agents) — يحدد صراحةً قائمة الميزات باعتبارها "هيكل البيانات الأساسي" للتحكم في نطاق agent -- [Harness Engineering - OpenAI](https://openai.com/index/harness-engineering/) — يؤكد على مبدأ "إخراج القطع الأثرية إلى الخارج" -- [Design by Contract - Bertrand Meyer](https://www.goodreads.com/book/show/130439.Object_Oriented_Software_Construction) — مبادئ تصميم العقود، الأساس النظري لقوائم الميزات -- [How Google Tests Software](https://www.goodreads.com/book/show/13563030-how-google-tests-software) — هرم الاختبار وممارسات هندسة المواصفات السلوكية - -## تمارين - -1. **تصميم قائمة الميزات**: حدد مخطط JSON بسيط لقائمة ميزات. يتضمن: المعرّف، وصف السلوك، أمر التحقق، الحالة الحالية، مرجع الدليل. استخدمه لوصف مشروع حقيقي مع 5 ميزات. - -2. **مقارنة صرامة التحقق**: اختر 3 ميزات وصمم تحققًا "مرنًا" (مثلاً، "الكود ليس به أخطاء بناء جملة") وتحققًا "صارمًا" (مثلاً، "اختبار شامل يجتاز"). قارن معدل الإيجابيات الكاذبة تحت كل نهج. - -3. **تدقيق مبدأ المصدر الوحيد**: راجع مشروع agent موجود وتحقق من معلومات النطاق التي تتعارض مع قائمة الميزات (متطلبات ضمنية في المحادثات، تعليقات TODO في الكود، إلخ). صمم خطة لتوحيد جميع المعلومات في قائمة الميزات. diff --git a/courses/learn-harness-engineering/docs/ar/lectures/lecture-09-why-agents-declare-victory-too-early/code/clean-state-checklist.md b/courses/learn-harness-engineering/docs/ar/lectures/lecture-09-why-agents-declare-victory-too-early/code/clean-state-checklist.md deleted file mode 100644 index 10b32f59..00000000 --- a/courses/learn-harness-engineering/docs/ar/lectures/lecture-09-why-agents-declare-victory-too-early/code/clean-state-checklist.md +++ /dev/null @@ -1,6 +0,0 @@ -# قائمة فحص الحالة النظيفة - -- التطبيق يبدأ بدون إصلاح يدوي -- التقدم الحالي مسجل -- لا توجد خطوة استيراد أو فهرسة ناقصة بدون توثيق -- الوكيل التالي يمكنه تشغيل مسار البدء والتحقق القياسي فورًا diff --git a/courses/learn-harness-engineering/docs/ar/lectures/lecture-09-why-agents-declare-victory-too-early/code/index.md b/courses/learn-harness-engineering/docs/ar/lectures/lecture-09-why-agents-declare-victory-too-early/code/index.md deleted file mode 100644 index 5a460c28..00000000 --- a/courses/learn-harness-engineering/docs/ar/lectures/lecture-09-why-agents-declare-victory-too-early/code/index.md +++ /dev/null @@ -1,8 +0,0 @@ -# كود المحاضرة 09 - -استخدم هذا المجلد لأمثلة عن: - -- السجلات كتغذية راجعة -- رؤية حالة التشغيل -- فحوصات الحالة النظيفة -- أمثلة الاسترداد diff --git a/courses/learn-harness-engineering/docs/ar/lectures/lecture-09-why-agents-declare-victory-too-early/code/victory-detector.ts b/courses/learn-harness-engineering/docs/ar/lectures/lecture-09-why-agents-declare-victory-too-early/code/victory-detector.ts deleted file mode 100644 index fad1a3ba..00000000 --- a/courses/learn-harness-engineering/docs/ar/lectures/lecture-09-why-agents-declare-victory-too-early/code/victory-detector.ts +++ /dev/null @@ -1,241 +0,0 @@ -/** - * victory-detector.ts - * - * Simulates an agent that claims task completion, then checks the claimed - * state against actual verification. Outputs: claimed vs actual, highlighting - * gaps between what the agent said and what's really true. - * - * Run: npx tsx docs/lectures/lecture-09-why-agents-declare-victory-too-early/code/victory-detector.ts - */ - -// --------------------------------------------------------------------------- -// Types -// --------------------------------------------------------------------------- - -interface Task { - name: string; - claimedComplete: boolean; - checks: VerificationCheck[]; -} - -interface VerificationCheck { - description: string; - claimed: boolean; // What the agent says - actual: boolean; // What is actually true - severity: "critical" | "warning"; -} - -// --------------------------------------------------------------------------- -// Simulated tasks with claimed vs actual states -// --------------------------------------------------------------------------- - -const tasks: Task[] = [ - { - name: "Add search endpoint", - claimedComplete: true, - checks: [ - { - description: "Route handler exists", - claimed: true, - actual: true, - severity: "critical", - }, - { - description: "Authentication middleware applied", - claimed: true, - actual: false, // Agent forgot auth - severity: "critical", - }, - { - description: "Input validation added", - claimed: true, - actual: true, - severity: "critical", - }, - { - description: "Unit tests written", - claimed: true, - actual: false, // Agent skipped tests - severity: "critical", - }, - { - description: "Integration tests pass", - claimed: true, - actual: false, // No tests to run - severity: "critical", - }, - { - description: "Documentation updated", - claimed: true, - actual: false, // Agent skipped docs - severity: "warning", - }, - ], - }, - { - name: "Fix pagination bug", - claimedComplete: true, - checks: [ - { - description: "Bug reproduction case confirmed", - claimed: true, - actual: true, - severity: "critical", - }, - { - description: "Root cause identified", - claimed: true, - actual: true, - severity: "critical", - }, - { - description: "Fix applied", - claimed: true, - actual: true, - severity: "critical", - }, - { - description: "Edge cases tested", - claimed: true, - actual: false, // Only happy path tested - severity: "warning", - }, - { - description: "Regression tests added", - claimed: true, - actual: false, - severity: "critical", - }, - ], - }, - { - name: "Refactor auth module", - claimedComplete: true, - checks: [ - { - description: "Old code removed", - claimed: true, - actual: false, // Old code still present - severity: "warning", - }, - { - description: "All existing tests still pass", - claimed: true, - actual: false, // Two tests broke - severity: "critical", - }, - { - description: "New auth flow works", - claimed: true, - actual: true, - severity: "critical", - }, - { - description: "Migration guide written", - claimed: true, - actual: false, - severity: "warning", - }, - ], - }, -]; - -// --------------------------------------------------------------------------- -// Verification -// --------------------------------------------------------------------------- - -interface TaskVerification { - taskName: string; - claimedComplete: boolean; - actuallyComplete: boolean; - totalChecks: number; - claimedPassing: number; - actualPassing: number; - gaps: { description: string; severity: string }[]; -} - -function verifyTask(task: Task): TaskVerification { - const claimedPassing = task.checks.filter((c) => c.claimed).length; - const actualPassing = task.checks.filter((c) => c.actual).length; - const gaps = task.checks.filter((c) => c.claimed && !c.actual); - - return { - taskName: task.name, - claimedComplete: task.claimedComplete, - actuallyComplete: gaps.length === 0, - totalChecks: task.checks.length, - claimedPassing, - actualPassing, - gaps: gaps.map((g) => ({ description: g.description, severity: g.severity })), - }; -} - -// --------------------------------------------------------------------------- -// Reporting -// --------------------------------------------------------------------------- - -function pad(s: string, len: number): string { - return s.length >= len ? s : s + " ".repeat(len - s.length); -} - -function run(): void { - console.log("\n" + "=".repeat(90)); - console.log(" VICTORY DETECTOR -- Claimed vs Actual Verification"); - console.log("=".repeat(90)); - - const verifications = tasks.map(verifyTask); - - for (const v of verifications) { - console.log("\n Task: " + v.taskName); - console.log(" " + "-".repeat(70)); - console.log(` Agent claimed: ${v.claimedComplete ? "COMPLETE" : "NOT COMPLETE"}`); - console.log(` Actually is: ${v.actuallyComplete ? "COMPLETE" : "INCOMPLETE"}`); - - if (!v.actuallyComplete) { - console.log(` MISMATCH: Agent declared victory but ${v.gaps.length} check(s) failed!`); - } - - // Detailed checks for this task - const task = tasks.find((t) => t.name === v.taskName)!; - console.log("\n Check detail:"); - const header = ` | ${pad("Check", 40)}| ${pad("Claimed", 9)}| ${pad("Actual", 9)}| ${pad("Gap?", 6)}|`; - const sep = ` |${"-".repeat(42)}|${"-".repeat(11)}|${"-".repeat(11)}|${"-".repeat(8)}|`; - console.log(header); - console.log(sep); - - for (const check of task.checks) { - const claimedLabel = check.claimed ? "PASS" : "FAIL"; - const actualLabel = check.actual ? "PASS" : "FAIL"; - const gapLabel = check.claimed && !check.actual ? "GAP" : ""; - const marker = gapLabel ? ">>" : " "; - console.log(`${marker}| ${pad(check.description, 40)}| ${pad(claimedLabel, 9)}| ${pad(actualLabel, 9)}| ${pad(gapLabel, 6)}|`); - } - } - - // Overall summary - console.log("\n" + "=".repeat(90)); - console.log(" OVERALL SUMMARY"); - console.log("=".repeat(90) + "\n"); - - const sHeader = `| ${pad("Task", 28)}| ${pad("Claimed", 10)}| ${pad("Actual", 10)}| ${pad("Gaps", 6)}| ${pad("Accuracy", 10)}|`; - const sSep = `|${"-".repeat(30)}|${"-".repeat(12)}|${"-".repeat(12)}|${"-".repeat(8)}|${"-".repeat(12)}|`; - console.log(sHeader); - console.log(sSep); - - for (const v of verifications) { - const accuracy = Math.round((v.actualPassing / v.totalChecks) * 100); - const cLabel = v.claimedComplete ? "Done" : "Pending"; - const aLabel = v.actuallyComplete ? "Done" : "Incomplete"; - console.log(`| ${pad(v.taskName, 28)}| ${pad(cLabel, 10)}| ${pad(aLabel, 10)}| ${pad(String(v.gaps.length), 6)}| ${pad(accuracy + "%", 10)}|`); - } - - const totalGaps = verifications.reduce((s, v) => s + v.gaps.length, 0); - const falseVictories = verifications.filter((v) => v.claimedComplete && !v.actuallyComplete).length; - - console.log("\n False victories: " + falseVictories + " of " + verifications.length + " tasks"); - console.log(" Total undetected gaps: " + totalGaps); - console.log("\n The agent declared 'done' on every task, but verification reveals " + totalGaps + " unmet criteria."); - console.log(" Without explicit verification, premature victory declarations go unchecked.\n"); -} - -run(); diff --git a/courses/learn-harness-engineering/docs/ar/lectures/lecture-09-why-agents-declare-victory-too-early/index.md b/courses/learn-harness-engineering/docs/ar/lectures/lecture-09-why-agents-declare-victory-too-early/index.md deleted file mode 100644 index 1920fccd..00000000 --- a/courses/learn-harness-engineering/docs/ar/lectures/lecture-09-why-agents-declare-victory-too-early/index.md +++ /dev/null @@ -1,145 +0,0 @@ -[中文版本 →](../../../zh/lectures/lecture-09-why-agents-declare-victory-too-early/) - -> Code examples for this lecture: [code/](https://github.com/walkinglabs/learn-harness-engineering/blob/main/docs/ar/lectures/lecture-09-why-agents-declare-victory-too-early/code/) -> Hands-on practice: [Project 05. Let the agent verify its own work](./../../projects/project-05-grounded-qa-verification/index.md) - -# المحاضرة 09. منع الوكلاء من إعلان النجاح مبكرًا - -تطلب من agent تنفيذ ميزة "إعادة تعيين كلمة المرور". يُعدّل مخطط قاعدة البيانات، ويكتب نقطة نهاية API، ويضيف قالب البريد الإلكتروني، ويشغّل اختبارات الوحدة (جميعها تجتاز)، ثم يخبرك بثقة "انتهيت." عندما تحاول تشغيله فعليًا — لا يمكن إرسال رابط إعادة تعيين كلمة المرور (نقص إعدادات خدمة البريد)، تفشل عملية ترحيل قاعدة البيانات في منتصف الطريق (عدم اتساق المخطط)، ولم يتم تنفيذ التدفق الشامل ولو لمرة واحدة. - -هذا الشعور لا ينبغي أن يكون غير مألوف — إنه مثل ملء ورقة الامتحان بالكامل، وأن تكون أول من يسلمها بثقة، فقط لترسب عند خروج الدرجات. مجرد أن الورقة ممتلئة لا يعني أن الإجابات صحيحة. - -هذه ليست حادثة معزولة. أثبتت الورقة البحثية الكلاسيكية من ICML 2017 لـ Guo وآخرين: **الشبكات العصبية الحديثة مفرطة الثقة بشكل منهجي** — الثقة التي تُبلغ عنها النماذج أعلى بكثير من دقتها الفعلية. ينطبق الشيء نفسه على وكلاء البرمجة بالذكاء الاصطناعي: هم "يشعرون" أنهم انتهوا، لكن في الواقع، هم بعيدون عن ذلك. يجب أن يحل harness خاصتك محل "مشاعر" agent بتحقق خارجي قائم على التنفيذ. - -## المنحدر الزلق - -إعلانات الاكتمال المبكر تتبع دائمًا تقريبًا نفس النمط: الكود يبدو جيدًا — البنية صحيحة، المنطق يبدو معقولًا، والتحليل الثابت لا يظهر أخطاء واضحة. لكن harness لا يفرض تحققًا شاملاً قائمًا على التنفيذ، لذا يتخطى agent التشغيل الفعلي أو يشغّل اختبارات جزئية فقط. يشغّل اختبارات الوحدة لكن يتخطى اختبارات التكامل؛ يشغّل الاختبارات لكن لا يتحقق من التغطية. في النهاية، "الكود يبدو جيدًا" يُتخذ كدليل على أن "الميزة مكتملة." وتُسلم ورقة الامتحان. - -تُفقد المعلومات في كل خطوة. من مواصفات المهام إلى تنفيذ الكود إلى السلوك وقت التشغيل، كل تحول يمكن أن يُدخل انحيازًا، وكل تحقق متخطع يزيد من تفاقم عدم تماثل المعلومات. - -## فحص الإنهاء ثلاثي الطبقات - -```mermaid -flowchart LR - Claim["Agent says: done"] --> L1["First run
lint / typecheck"] - L1 --> L2["Then run
tests and startup checks"] - L2 --> L3["Finally run
complete user flow"] - L3 --> Done["Pass all three to be done"] -``` - -```mermaid -flowchart LR - A["Code is written
unit tests are green"] --> B["But app didn't really start
full flow never ran"] - B --> C["Config, DB, external service issues
all remain hidden"] - C --> D["So agent declares victory too early"] -``` - -## المفاهيم الأساسية - -- **إعلان الاكتمال المبكر**: يؤكد agent أن المهمة مكتملة، لكن مواصفات الصحة غير المُستوفاة لا تزال قائمة. المشكلة الأساسية: يحكم agent بناءً على الثقة المحلية على مستوى الكود، بينما تتطلب الصحة على مستوى النظام تحققًا شاملاً. -- **انحياز معايرة الثقة**: الفجوة المنهجية بين الثقة المُبلغ عنها ذاتيًا من agent في الاكتمال وجودة الاكتمال الفعلية. للمهام المعقدة متعددة الملفات، هذا الانحياز إيجابي بشكل كبير — agent دائمًا أكثر ثقة مما يؤديه فعليًا. تمامًا مثل الطالب الذي يبالغ دائمًا في تقدير درجته بعد الامتحان. -- **معايير الإنهاء**: مجموعة واضحة وقابلة للتنفيذ من شروط الحكم مُعرّفة في harness. يجب أن يلبي agent جميع الشروط قبل إعلان الاكتمال. يتحول "انتهيت" من حكم ذاتي إلى تحديد موضوعي. -- **البوابة المزدوجة للتحقق والاعتماد**: طبقة التحقق الأولى تتحقق "هل نفّذ الكود السلوك المحدد بشكل صحيح"؛ طبقة الاعتماد الثانية تتحقق "هل يلبي السلوك على مستوى النظام المتطلبات الشاملة". كلتاهما يجب أن تجتاز ليُعتبر مكتملًا. -- **إشارات التغذية الراجعة وقت التشغيل**: السجلات وحالات العمليات وفحوصات الصحة من تنفيذ البرنامج. هذا هو الأساس الموضوعي لـ harness ليحكم على جودة الاكتمال. -- **قيد أولوية الاكتمال**: تحقق أولاً من الصحة الوظيفية، ثم تعامل مع الأداء، وأخيرًا عالج الأسلوب. يُمنع إعادة الهيكلة حتى يتم التحقق من الوظائف الأساسية. - -## اجتياز اختبارات الوحدة ≠ المهمة مكتملة - -هذا هو الفخ الأكثر شيوعًا، والأخطر. كتب agent الكود، شغّل اختبارات الوحدة، حصل على الأخضر كله، وقال "انتهيت." لكن فلسفة تصميم اختبارات الوحدة — عزل الوحدة قيد الاختبار ومحاكاة التبعيات — هي بالضبط ما يجعلها غير قادرة على اكتشاف مشاكل عبر المكونات: - -**عدم تطابق الواجهة**: مسار الملف الذي يمرره عرض (render process) إلى preload script هو مسار نسبي، لكن preload script يتوقع مسارًا مطلقًا. اختبارات الوحدة الخاصة بكل منهما استخدمت محاكيات (mocks) واجتازت. لا تُكتشف المشكلة إلا خلال الاختبار الشامل. مثل كل موسيقي في فرقة يتدرب بشكل مثالي بمفرده، ليدركوا فقط أنهم في مفاتيح مختلفة عند العزف معًا. - -**أخطاء انتشار الحالة**: ترحيل قاعدة بيانات يُغيّر مخطط الجدول، لكن طبقة تخزين ORM المؤقت لا تزال تحتفظ بمداخل مخزنة للمخطط القديم. توفر اختبارات الوحدة بيئة محاكاة جديدة في كل مرة، مما لن يكشف عدم اتساق الحالة هذا عبر الطبقات. - -**اعتماد البيئة**: يتصرف الكود بشكل صحيح في بيئة الاختبار (حيث كل شيء مُحاكى) لكنه يفشل في البيئة الحقيقية بسبب اختلافات الإعدادات أو زمن استجابة الشبكة أو عدم توفر الخدمة. مثل الغناء بشكل مثالي في غرفة البروفة، لكن مواجهة مشاكل في المعدات الصوتية على المسرح. - -### "إعادة الهيكلة أثناء ذلك" سُم ل حكم الاكتمال - -لدى Claude Code نمط سلوكي شائع: يبدأ في إعادة هيكلة الكود وتحسين الأداء وتحسين الأسلوب قبل أن تجتاز الوظائف الأساسية التحقق. اقتباس Knuth، "التحسين المبكر هو جذر كل الشرور،" يأخذ معنى جديدًا في سيناريو agent — إعادة الهيكلة تُغيّر الحدود بين الكود المُتحقق منه وغير المُتحقق منه، مما قد يكسر مسارات كود كانت صحيحة ضمنيًا سابقًا. الأمر يشبه إعادة نسخ إجاباتك متعددة الخيارات لتحسين التنسيق قبل أن تنهي أسئلة المقالة الرياضية — ليس فقط يضيع الوقت، بل قد تنسخها بشكل خاطئ. - -### الانحياز المنهجي في التقييم الذاتي - -اكتشفت Anthropic نمط فشل أعمق في أبحاثها لعام 2026: **عندما يُطلب من agent تقييم عمله الخاص، يقدم تقييمات إيجابية بشكل مفرط بشكل منهجي — حتى عندما يرى مراقب بشري أن الجودة دون المستوى بوضوح.** هذا مثل طلب من طالب تصحيح امتحانه بنفسه — سيكون دائمًا متساهلاً بشكل خاص مع إجاباته الخاصة. - -هذه المشكلة شديدة بشكل خاص في المهام الذاتية (مثل جماليات التصميم) — ما إذا كان "التصميم متقنًا" هو حكم تقديري، وagent يميل بشكل موثوق إلى الإيجابية. حتى في المهام ذات النتائج القابلة للتحقق، يمكن أن يتأثر أداء agent بسبب ضعف الحكم. - -الحل ليس جعل agent "أكثر موضوعية" — النموذج نفسه الذي يُنشئ ويُقيّم يميل بطبيعته إلى الكرم مع نفسه. **الحل هو فصل "العامل" عن "المدقق".** تمامًا كما لا ينبغي للطالب تصحيح امتحانه — تحتاج إلى مصحح مستقل. - -agent تقييم مستقل، مُضبوط خصيصًا ليكون "صارمًا"، أكثر فعالية بكثير من جعل agent المُنشئ يُقيّم نفسه. بيانات تجريبية من Anthropic: - -| Architecture | Runtime | Cost | Core Features Working? | -|--------------|---------|------|------------------------| -| Single Agent (bare run) | 20 mins | $9 | No (game entities unresponsive to input) | -| Three Agents (planner + generator + evaluator) | 6 hours | $200 | Yes (game is fully playable) | - -هذا هو نفس النموذج بالضبط (Opus 4.5) مع نفس الموجه بالضبط ("build a 2D retro game editor"). الفرق الوحيد هو harness — من "التشغيل المجرّد" إلى "المخطط يُوسّع المتطلبات ← المُنشئ ينفذ ميزة بميزة ← المُقيّم يقوم باختبار النقر الفعلي باستخدام Playwright". - -> المصدر: [Anthropic: Harness design for long-running application development](https://www.anthropic.com/engineering/harness-design-long-running-apps) - -## كيف تمنع التسليم المبكر - -### 1. خارجنة حكم الإنهاء - -لا ينبغي أن يُصدر حكم الاكتمال من agent نفسه. يجب أن ينفذ harness بشكل مستقل تحقق الإنهاء، مستخدمًا إشارات وقت التشغيل كمدخلات، وليس ثقة agent. اكتب هذا بوضوح في `CLAUDE.md`: - -``` -## Definition of Done -- Feature complete = end-to-end verification passed, not "code is written" -- Required verification levels: - 1. Unit tests pass - 2. Integration tests pass - 3. End-to-end flow verification passes -- Do not proceed to level 2 if level 1 fails -- Do not proceed to level 3 if level 2 fails -``` - -### 2. ابنِ تحقق إنهاء ثلاثي الطبقات - -- **الطبقة 1: التحليل البنيوي والثابت**. الأقل تكلفة، الأقل معلومات، لكن يجب أن تجتاز. هذا هو الحد الأدنى من الفحص — يجب أن تهجو الكلمات بشكل صحيح قبل أن ننظر إلى أي شيء آخر. -- **الطبقة 2: تحقق السلوك وقت التشغيل**. تنفيذ الاختبارات، فحوصات بدء التطبيق، التحقق من المسارات الحرجة. هذا هو الدليل الأساسي على الاكتمال. لا يكفي أن تكتبه فقط؛ يجب أن يعمل. -- **الطبقة 3: التأكيد على مستوى النظام**. الاختبار الشامل، تحقق التكامل، محاكاة سيناريوهات المستخدم. الخط الدفاعي الأخير ضد الإعلانات المبكرة. لا يكفي أن يعمل؛ يجب أن يعمل بشكل صحيح. - -### 3. صمم "تعليمات بالقلم الأحمر" جيدة للوكلاء - -قدمت OpenAI نمطًا فعالًا بشكل خاص خلال ممارسات Codex الخاصة بها: **يجب أن تتضمن رسائل الخطأ للوكلاء تعليمات الإصلاح**. لا ترسم فقط علامة حمراء كبيرة مثل مصحح كسول؛ كن مثل معلم جيد واكتب "هكذا يجب أن تُغيّر هذا" في الهوامش. لا تستخدم `"Test failed"`، بل استخدم `"Test failed: POST /api/reset-password returned 500. Check that the email service config exists in environment variables. The template file should be at templates/reset-email.html."` هذه التغذية الراجعة المحددة والقابلة للتنفيذ تسمح لـ agent بتصحيح نفسه بدون تدخل بشري. - -### 4. التقط إشارات وقت التشغيل - -تشمل إشارات وقت التشغيل الفعالة: -- هل بدأ التطبيق بنجاح ووصل إلى حالة الاستعداد؟ -- هل نُفذت مسارات الميزات الحرجة بنجاح وقت التشغيل؟ -- هل كانت عمليات كتابة قاعدة البيانات وعمليات الملفات والآثار الجانبية الأخرى صحيحة؟ -- هل تم تنظيف الموارد المؤقتة؟ - -## حالة من العالم الحقيقي - -**المهمة**: تنفيذ وظيفة إعادة تعيين كلمة مرور المستخدم. تتضمن عمليات قاعدة البيانات وإرسال البريد الإلكتروني وتعديلات نقطة نهاية API. - -**مسار التسليم المبكر**: يُعدّل agent مخطط قاعدة البيانات، يكتب نقطة نهاية API، يضيف قالب بريد إلكتروني، يشغّل اختبارات الوحدة (تجتاز)، ويُعلن الاكتمال. ورقة الامتحان ممتلئة بالكامل. - -**خصومات الدرجات الفعلية**: (1) التدفق الشامل غير مُختبَر — لم يتم تأكيد الإرسال والتحقق الفعلي لرابط إعادة التعيين أبدًا. (2) فشل ترحيل قاعدة البيانات بعد التنفيذ الجزئي، مما تسبب في عدم اتساق المخطط. (3) إعدادات خدمة البريد الإلكتروني كانت مفقودة في البيئة المستهدفة. - -**تدخل harness**: فُرض تحقق الإنهاء — (1) بدء التطبيق الكامل للتحقق من إمكانية الوصول لنقطة نهاية إعادة التعيين؛ (2) تنفيذ تدفق إعادة التعيين الكامل؛ (3) التحقق من اتساق حالة قاعدة البيانات. وُجدت جميع العيوب خلال الجلسة، مما وفر 5-10 أضعاف تكلفة الإصلاحات اللاحقة. المصحح المستقل وجد المشاكل الحقيقية. - -## الخلاصات الأساسية - -- **الوكلاء مفرطو الثقة بشكل منهجي** — انحياز معايرة الثقة هو حقيقة موضوعية. ملء ورقة الامتحان لا يعني أن إجاباتك صحيحة. -- **يجب خارجنة حكم الاكتمال** — harness يتحقق بشكل مستقل؛ لا تثق بـ "مشاعر" agent. لا يمكن للطلاب تصحيح امتحاناتهم بأنفسهم. -- **جميع طبقات التحقق الثلاث ضرورية** — اجتياز البنية، اجتياز السلوك، اجتياز النظام، التقدم طبقة تلو طبقة. -- **يجب أن تكون رسائل الخطأ مثل تعليمات القلم الأحمر لمعلم جيد** — تتضمن خطوات إصلاح محددة حتى يتمكن agent من تصحيح نفسه. -- **لا إعادة هيكلة حتى يتم التحقق من الوظائف الأساسية** — قيد أولوية الاكتمال هو المفتاح لمنع التحسين المبكر. - -## قراءات إضافية - -- [On Calibration of Modern Neural Networks - Guo et al.](https://arxiv.org/abs/1706.04599) — يثبت أن الشبكات العصبية العميقة الحديثة مفرطة الثقة بشكل منهجي -- [Building Effective Agents - Anthropic](https://www.anthropic.com/research/building-effective-agents) — الدور الحاسم لأدلة وقت التشغيل في حكم الاكتمال -- [Harness Engineering - OpenAI](https://openai.com/index/harness-engineering/) — إعلان الاكتمال المبكر هو أحد أنماط الفشل الرئيسية للوكلاء -- [The Art of Software Testing - Myers](https://www.goodreads.com/book/show/137543.The_Art_of_Software_Testing) — مرجع كلاسيكي حول تدرجات منهجية الاختبار وفعاليتها - -## تمارين - -1. **تصميم وظيفة تحقق الإنهاء**: صمم تحقق إنهاء كامل لمهمة تتضمن ترحيل قاعدة بيانات وتعديل API. اذكر إشارات وقت التشغيل المطلوبة ومعايير النجاح/الفشل لكل إشارة. شغّله على مهمة حقيقية وسجّل المشاكل المخفية التي يجدها. - -2. **قياس انحياز المعايرة**: اختر 10 مهام برمجية من أنواع مختلفة، وسجّل ثقة agent المُبلغ عنها ذاتيًا في الاكتمال مقابل جودة الاكتمال الفعلية. احسب قيمة الانحياز وحلل علاقتها بتعقيد المهمة. - -3. **تجربة الدفاع متعدد الطبقات**: شغّل ثلاثة تكوينات على نفس مجموعة المهام — (أ) تحليل ثابت فقط، (ب) إضافة اختبار الوحدات، (ج) تحقق ثلاثي الطبقات كامل. قارن نسبة إعلانات الاكتمال المبكر وعدد العيوب غير المُكتشفة. diff --git a/courses/learn-harness-engineering/docs/ar/lectures/lecture-10-why-end-to-end-testing-changes-results/code/architecture-rules.md b/courses/learn-harness-engineering/docs/ar/lectures/lecture-10-why-end-to-end-testing-changes-results/code/architecture-rules.md deleted file mode 100644 index fd33d73f..00000000 --- a/courses/learn-harness-engineering/docs/ar/lectures/lecture-10-why-end-to-end-testing-changes-results/code/architecture-rules.md +++ /dev/null @@ -1,6 +0,0 @@ -# قواعد بنية Electron - -- لا يجوز لرمز العارض الوصول مباشرة إلى نظام الملفات. -- الـ Preload هو الجسر الوحيد بين العارض و Electron main. -- منطق الاسترجاع والفهرسة يعيش في وحدات الخدمة، وليس مكونات واجهة المستخدم. -- يجب أن تكون السجلات منظمة ومنبعثة من حدود الخدمات. diff --git a/courses/learn-harness-engineering/docs/ar/lectures/lecture-10-why-end-to-end-testing-changes-results/code/e2e-runner.ts b/courses/learn-harness-engineering/docs/ar/lectures/lecture-10-why-end-to-end-testing-changes-results/code/e2e-runner.ts deleted file mode 100644 index 24284165..00000000 --- a/courses/learn-harness-engineering/docs/ar/lectures/lecture-10-why-end-to-end-testing-changes-results/code/e2e-runner.ts +++ /dev/null @@ -1,253 +0,0 @@ -/** - * e2e-runner.ts - * - * A minimal E2E test harness. Defines test cases as user action sequences - * (import doc -> index -> ask question -> verify citation). Simulates - * running them and shows the difference between "unit tests pass" and - * "full pipeline works". - * - * Run: npx tsx docs/lectures/lecture-10-why-end-to-end-testing-changes-results/code/e2e-runner.ts - */ - -// --------------------------------------------------------------------------- -// Types -// --------------------------------------------------------------------------- - -interface PipelineStep { - name: string; - unitTestPasses: boolean; - // Simulated actual behavior in the pipeline - actualBehavior: "works" | "fails" | "partial"; - failureReason?: string; -} - -interface TestCase { - name: string; - steps: PipelineStep[]; -} - -interface TestResult { - testCase: string; - unitTestsPassed: number; - unitTestsTotal: number; - unitTestResult: "PASS" | "FAIL"; - e2eResult: "PASS" | "FAIL"; - e2eFailureStep?: string; - e2eFailureReason?: string; -} - -// --------------------------------------------------------------------------- -// Test cases -- realistic scenarios where unit tests pass but E2E fails -// --------------------------------------------------------------------------- - -const testCases: TestCase[] = [ - { - name: "Import document and ask question", - steps: [ - { - name: "Parse uploaded document", - unitTestPasses: true, - actualBehavior: "works", - }, - { - name: "Store document chunks", - unitTestPasses: true, - actualBehavior: "works", - }, - { - name: "Index chunks for retrieval", - unitTestPasses: true, - actualBehavior: "partial", // Indexes but with wrong embedding dimensions - failureReason: "Embedding dimension mismatch between indexer and retriever", - }, - { - name: "Retrieve relevant chunks", - unitTestPasses: true, // Unit test uses mock data with correct dimensions - actualBehavior: "fails", - failureReason: "Empty results due to dimension mismatch from previous step", - }, - { - name: "Generate answer with citations", - unitTestPasses: true, // Unit test provides pre-retrieved chunks - actualBehavior: "fails", - failureReason: "No chunks retrieved, so answer has no citations", - }, - ], - }, - { - name: "Delete document and verify removal", - steps: [ - { - name: "Find document by ID", - unitTestPasses: true, - actualBehavior: "works", - }, - { - name: "Delete document record", - unitTestPasses: true, - actualBehavior: "works", - }, - { - name: "Remove indexed chunks", - unitTestPasses: true, - actualBehavior: "fails", // Orphaned chunks remain in the index - failureReason: "Index cleanup query timed out, chunks remain orphaned", - }, - { - name: "Verify document not in search results", - unitTestPasses: true, // Unit test mocks the search - actualBehavior: "fails", - failureReason: "Orphaned chunks from previous step still appear in results", - }, - ], - }, - { - name: "Multi-user concurrent access", - steps: [ - { - name: "User A imports document", - unitTestPasses: true, - actualBehavior: "works", - }, - { - name: "User B imports document", - unitTestPasses: true, - actualBehavior: "works", - }, - { - name: "User A queries their document", - unitTestPasses: true, - actualBehavior: "partial", // Cross-contamination of results - failureReason: "No user-scoping on retrieval, returns chunks from User B's doc", - }, - { - name: "Verify only User A's results returned", - unitTestPasses: true, - actualBehavior: "fails", - failureReason: "Results include documents from other users", - }, - ], - }, -]; - -// --------------------------------------------------------------------------- -// Run tests -// --------------------------------------------------------------------------- - -function runUnitTests(tc: TestCase): { passed: number; total: number } { - const passed = tc.steps.filter((s) => s.unitTestPasses).length; - return { passed, total: tc.steps.length }; -} - -function runE2ETest(tc: TestCase): { pass: boolean; failureStep?: string; failureReason?: string } { - // Pipeline: if any step actually fails, the whole E2E fails - for (const step of tc.steps) { - if (step.actualBehavior === "fails") { - return { - pass: false, - failureStep: step.name, - failureReason: step.failureReason ?? "Unknown failure", - }; - } - // "partial" means the step technically completes but creates problems downstream - // We let it continue but track it - } - // Check if any step was "partial" (which may cause downstream issues) - const partialSteps = tc.steps.filter((s) => s.actualBehavior === "partial"); - if (partialSteps.length > 0) { - // The partial steps may or may not cause overall failure - // In our simulation, partial steps always lead to failure downstream - // unless there's an explicit "fails" step that already caught it - // This case means all steps were either "works" or "partial" - return { - pass: false, - failureStep: partialSteps[partialSteps.length - 1].name, - failureReason: partialSteps[partialSteps.length - 1].failureReason ?? "Partial completion", - }; - } - - return { pass: true }; -} - -// --------------------------------------------------------------------------- -// Reporting -// --------------------------------------------------------------------------- - -function pad(s: string, len: number): string { - return s.length >= len ? s : s + " ".repeat(len - s.length); -} - -function run(): void { - console.log("\n" + "=".repeat(95)); - console.log(" E2E TEST RUNNER -- Unit Tests vs Full Pipeline"); - console.log("=".repeat(95)); - - const results: TestResult[] = testCases.map((tc) => { - const unit = runUnitTests(tc); - const e2e = runE2ETest(tc); - - return { - testCase: tc.name, - unitTestsPassed: unit.passed, - unitTestsTotal: unit.total, - unitTestResult: unit.passed === unit.total ? "PASS" : "FAIL", - e2eResult: e2e.pass ? "PASS" : "FAIL", - e2eFailureStep: e2e.failureStep, - e2eFailureReason: e2e.failureReason, - }; - }); - - // Per-test-case detail - for (let i = 0; i < testCases.length; i++) { - const tc = testCases[i]; - const r = results[i]; - - console.log("\n Test Case: " + tc.name); - console.log(" " + "-".repeat(70)); - - const stepHeader = ` | ${pad("Step", 40)}| ${pad("Unit Test", 11)}| ${pad("E2E Actual", 12)}|`; - const stepSep = ` |${"-".repeat(42)}|${"-".repeat(13)}|${"-".repeat(14)}|`; - console.log(stepHeader); - console.log(stepSep); - - for (const step of tc.steps) { - const utLabel = step.unitTestPasses ? "PASS" : "FAIL"; - let e2eLabel: string; - if (step.actualBehavior === "works") e2eLabel = "PASS"; - else if (step.actualBehavior === "partial") e2eLabel = "PARTIAL*"; - else e2eLabel = "FAIL"; - - const marker = step.actualBehavior !== "works" ? ">>" : " "; - console.log(`${marker}| ${pad(step.name, 40)}| ${pad(utLabel, 11)}| ${pad(e2eLabel, 12)}|`); - } - - if (r.e2eFailureStep) { - console.log(`\n E2E Failure at: ${r.e2eFailureStep}`); - console.log(` Reason: ${r.e2eFailureReason}`); - } - } - - // Summary comparison - console.log("\n" + "=".repeat(95)); - console.log(" COMPARISON: Unit Tests vs E2E Tests"); - console.log("=".repeat(95) + "\n"); - - const header = `| ${pad("Test Case", 35)}| ${pad("Unit Tests", 15)}| ${pad("E2E Result", 12)}| Discrepancy`; - const sep = `|${"-".repeat(37)}|${"-".repeat(17)}|${"-".repeat(14)}|${"-".repeat(30)}`; - console.log(header); - console.log(sep); - - for (const r of results) { - const utLabel = `${r.unitTestsPassed}/${r.unitTestsTotal} ${r.unitTestResult}`; - const discrepancy = r.unitTestResult === "PASS" && r.e2eResult === "FAIL"; - const discLabel = discrepancy ? "UNIT PASS BUT E2E FAIL" : "Consistent"; - - console.log(`| ${pad(r.testCase, 35)}| ${pad(utLabel, 15)}| ${pad(r.e2eResult, 12)}| ${discLabel}`); - } - - const falseConfidence = results.filter((r) => r.unitTestResult === "PASS" && r.e2eResult === "FAIL").length; - console.log("\n False confidence count: " + falseConfidence + " of " + results.length); - console.log(" Unit tests pass but E2E reveals integration failures that units cannot catch.\n"); -} - -run(); diff --git a/courses/learn-harness-engineering/docs/ar/lectures/lecture-10-why-end-to-end-testing-changes-results/code/index.md b/courses/learn-harness-engineering/docs/ar/lectures/lecture-10-why-end-to-end-testing-changes-results/code/index.md deleted file mode 100644 index 52921814..00000000 --- a/courses/learn-harness-engineering/docs/ar/lectures/lecture-10-why-end-to-end-testing-changes-results/code/index.md +++ /dev/null @@ -1,8 +0,0 @@ -# كود المحاضرة 10 - -استخدم هذا المجلد لأمثلة عن: - -- قيود البنية المعمارية -- اختبارات هيكلية -- ثوابت الذوق (taste invariants) -- رسائل lint موجهة للإصلاح diff --git a/courses/learn-harness-engineering/docs/ar/lectures/lecture-10-why-end-to-end-testing-changes-results/code/review-feedback-to-rule.md b/courses/learn-harness-engineering/docs/ar/lectures/lecture-10-why-end-to-end-testing-changes-results/code/review-feedback-to-rule.md deleted file mode 100644 index 6dae1ae8..00000000 --- a/courses/learn-harness-engineering/docs/ar/lectures/lecture-10-why-end-to-end-testing-changes-results/code/review-feedback-to-rule.md +++ /dev/null @@ -1,10 +0,0 @@ -# مثال: تحويل ملاحظات المراجعة إلى قاعدة - -تعليق مراجعة متكرر: - -> لا تستخدم أدوات نظام الملفات من العارض. استخدم جسر الـ preload. - -قاعدة harness مُرقّاة: - -- إضافة قاعدة lint أو استيراد تمنع استخدام `fs` في كود العارض -- إضافة نص إصلاح يشرح حدود الـ preload diff --git a/courses/learn-harness-engineering/docs/ar/lectures/lecture-10-why-end-to-end-testing-changes-results/index.md b/courses/learn-harness-engineering/docs/ar/lectures/lecture-10-why-end-to-end-testing-changes-results/index.md deleted file mode 100644 index 59d8a07c..00000000 --- a/courses/learn-harness-engineering/docs/ar/lectures/lecture-10-why-end-to-end-testing-changes-results/index.md +++ /dev/null @@ -1,158 +0,0 @@ -[中文版本 →](../../../zh/lectures/lecture-10-why-end-to-end-testing-changes-results/) - -> Code examples for this lecture: [code/](https://github.com/walkinglabs/learn-harness-engineering/blob/main/docs/ar/lectures/lecture-10-why-end-to-end-testing-changes-results/code/) -> Hands-on practice: [Project 05. Let the agent verify its own work](./../../projects/project-05-grounded-qa-verification/index.md) - -# المحاضرة 10. الاختبار end-to-end هو التحقق الحقيقي - -تطلب من agent إضافة ميزة تصدير ملفات إلى تطبيق Electron. يكتب مكوّن عرض (render process) وpreload script ومنطق طبقة الخدمة. اختبارات الوحدة لكل مكون تجتاز بشكل مثالي. يقول agent "انتهيت." عندما تنقر فعليًا على زر التصدير — تنسيق مسار الملف خاطئ، شريط التقدم لا يُحدَّث، وتصدير الملفات الكبيرة يسبب تسربًا في الذاكرة. خمسة عيوب في حدود المكونات، واختبارات الوحدة لم تكتشف أيًا منها. - -إنه مثل بروفة جوقة — كل جزء صوتي يبدو مثاليًا عند الغناء بشكل فردي، لكن عند الغناء معًا، السوبرانو أسرع بنصف نبضة من الباصات، والمرافقة بعيدة نصف نغمة عن اللحن الرئيسي. كل جزء "صحيح" بمفرده، لكن الكل غير متناغم. - -يخبرنا هرم اختبار Google: عدد كبير من اختبارات الوحدة هو الأساس، لكن إذا توقفت هناك، ستفوت بشكل منهجي مشاكل تفاعل المكونات. بالنسبة لوكلاء البرمجة بالذكاء الاصطناعي، هذه المشكلة أكثر حدة — agent يميل إلى تشغيل أسرع الاختبارات فقط ثم إعلان الاكتمال. **فقط الاختبار end-to-end يمكنه إثبات أن العيوب على مستوى النظام غير موجودة.** - -## النقاط العمياء لاختبار الوحدات - -فلسفة تصميم اختبار الوحدات هي العزل — محاكاة التبعيات والتركيز فقط على الوحدة قيد الاختبار. هذه الفلسفة تجعل اختبار الوحدات سريعًا ودقيقًا، لكنها تخلق أيضًا نقاطًا عمياء منهجية. الأمر كمثل جعل كل جزء صوتي يتدرب بسماعات أثناء بروفة الجوقة — يبدو جيدًا لهم، لكن المشاكل تظهر فقط عندما يجتمعون: - -**عدم تطابق الواجهة**: مسار الملف الذي يمرره عرض (render process) إلى preload script هو مسار نسبي، لكن preload script يتوقع مسارًا مطلقًا. اختبارات الوحدة الخاصة بكل منهما استخدمت محاكيات (mocks) واجتازت. لا تُكتشف المشكلة إلا عند تنفيذ التدفق الشامل — مثل جزأين صوتيين يتدربان بشكل مستقل ويشعران بالرضا، ليدركا فقط أثناء الأداء الجماعي أن أحدهما يغني بنظام 4/4 والآخر بنظام 3/4. - -**أخطاء انتشار الحالة**: ترحيل قاعدة بيانات يُغيّر مخطط الجدول، لكن طبقة تخزين ORM المؤقت لا تزال تحتفظ بمداخل مخزنة للمخطط القديم. توفر اختبارات الوحدة بيئة محاكاة جديدة بالكامل في كل مرة، مما لن يكشف عدم اتساق الحالة هذا عبر الطبقات. إنه مثل تغيير كلمات أغنية، لكن شخصًا ما لا يزال يغني النسخة القديمة. - -**مشاكل دورة حياة الموارد**: الحصول على مقابض الملفات وتحريرها واتصالات قاعدة البيانات ومنافذ الشبكة تمتد عبر مكونات متعددة. تنشئ اختبارات الوحدات وتدمر موارد مستقلة لكل اختبار، مما يفشل في كشف تنازع الموارد أو تسربها. إنه مثل كل جزء صوتي يتناوب استخدام الميكروفونات أثناء البروفة، لكن عندما يصعد الجميع إلى المسرح معًا، لا توجد ميكروفونات كافية. - -**اعتماد البيئة**: يتصرف الكود بشكل صحيح في بيئة الاختبار (حيث كل شيء مُحاكى) لكنه يفشل في البيئة الحقيقية بسبب اختلافات الإعدادات أو زمن استجابة الشبكة أو عدم توفر الخدمة. مثل الغناء بشكل مثالي في غرفة البروفة، لكن مواجهة ارتداد صوتي وتداخل الرياح في مهرجان خارجي. - -## الاختبار end-to-end لا يُغيّر النتائج فقط، بل يُغيّر السلوك أيضًا - -هذا شيء لا يدركه الكثيرون: عندما يعرف agent أن عمله سيخضع لاختبار end-to-end، يتغير سلوكه في البرمجة. - -1. **النظر في تفاعلات المكونات**: أثناء كتابة الكود، سيفكر في "كيف تتصل هذه الواجهة مع المنبع،" بدلاً من التركيز فقط على دالة واحدة. مثل معرفتك أنك ستغني معًا في النهاية، ستنتبه للأجزاء الصوتية الأخرى أثناء التدريب. -2. **احترام حدود البنية**: في الأنظمة ذات قيود البنية المعمارية، يُجبر الاختبار end-to-end agent على الالتزام بقواعد الحدود. مثل النوتات الموسيقية الموسومة بـ "تدرج صعودي هنا،" عليك أن تتبعها. -3. **معالجة مسارات الأخطاء**: تتضمن اختبارات end-to-end عادةً سيناريوهات الفشل، مما يُجبر agent على النظر في معالجة الاستثناءات. إنه مثل محاكاة "ماذا لو مات الميكروفون فجأة" أثناء البروفة، لتعرف ماذا تفعل. - -## هرم الاختبار وتعزيز تغذية المراجعة - -```mermaid -flowchart TB - subgraph Unit["Unit tests only check isolated parts"] - U1["Renderer tests"] - U2["Preload tests"] - U3["Service tests"] - end - - subgraph E2E["E2E runs through the real system"] - R["Click renderer button"] --> P["Preload bridge"] - P --> S["Service layer"] - S --> F["File System / OS"] - F --> Result["Actual exported file"] - end -``` - -```mermaid -flowchart LR - Review["Review feedback:
renderer cannot import fs directly"] --> Rule["Add a direct fs import check"] - Rule --> Message["Tell agent in error message
to move file access to preload"] - Message --> Harness["Add this check to harness"] - Harness --> Stronger["Will fail immediately next time"] -``` - -في ممارسات هندسة Codex، تشدد OpenAI: **يجب أن تتضمن رسائل الخطأ المكتوبة للوكلاء تعليمات إصلاح.** لا تكتب فقط `"Direct filesystem access in renderer"`؛ اكتب `"Direct filesystem access in renderer. All file operations must go through the preload bridge. Move this call to preload/file-ops.ts and invoke it via window.api."` هذا يحول القواعد المعمارية إلى حلقة تصحيح تلقائي. مثل قائد جوقة لا يقول فقط "لقد غنيت ذلك بشكل خاطئ،" بل يقول بدلاً من ذلك "كنت أسرع بنصف نبضة هنا، استمع إلى إيقاع الألتو، وادخل في المازورة 32." - -## المفاهيم الأساسية - -- **عيوب حدود المكونات**: المكون أ والمكون ب يجتازان اختبارات الوحدات الخاصة بهما، لكن تفاعلهما يُنتج سلوكًا غير صحيح. هذا هو نوع المشاكل التي ي best الاختبار end-to-end في اكتشافها — مثل أجزاء جوقة صحيحة بشكل فردي لكن غير متناغمة معًا. -- **تدرج كفاية الاختبار**: العيوب التي تكتشفها اختبارات الوحدات <= العيوب التي تكتشفها اختبارات التكامل <= العيوب التي تكتشفها اختبارات end-to-end. كل طبقة تصعد تزيد من قدرة الكشف. -- **قواعد فرض حدود البنية**: تحويل القواعد من مستندات البنية (مثل "لا يمكن لعرض (render process) الوصول إلى نظام الملفات مباشرة") إلى فحوصات قابلة للتنفيذ والآلية. من "مكتوبة على الورق" إلى "تعمل في CI." -- **تعزيز تغذية المراجعة**: تحويل تعليقات مراجعة الكود المتكررة إلى اختبارات آلية. في كل مرة يُعثر على مشكلة متكررة، أضف قاعدة، وينمو harness تلقائيًا أقوى. مثل قائد يحول أخطاء البروفة الشائعة إلى تمارين إحماء — في المرة القادمة التي يُرتكب فيها نفس الخطأ، التمرين نفسه يكشفها بدون الحاجة للقائد أن يقول كلمة. -- **رسائل الخطأ الموجهة للوكلاء**: يجب ألا تذكر رسائل الفشل فقط "ما الخطأ،" بل تخبر agent بالضبط كيف يصلحه. هذا يحول فشل الاختبار إلى حلقات تغذية راجعة ذاتية التصحيح. - -## كيفية التنفيذ - -### 0. حدد حدود البنية أولاً، ثم اكتب اختبارات E2E - -المتطلب الأساسي للاختبار end-to-end هو حدود نظام واضحة. إذا كانت البنية المعمارية طبقًا من السباغيتي، فسيثبت الاختبار end-to-end فقط "هذا الطبق من السباغيتي يعمل،" ولن يخبرك أين انتهكت نوايا التصميم. إنه مثل جوقة لم تقسم حتى إلى أجزاء صوتية — أي قدر من البروفة لن يجعلها تبدو جيدة. - -تجربة OpenAI: **لقواعد البيانات البرمجية التي ينشئها الوكلاء، يجب أن تكون القيود المعمارية متطلبات أساسية مبكرة تُؤسس في اليوم الأول، وليس شيئًا للنظر فيه عندما يكبر الفريق.** السبب بسيط — الوكلاء سينسخون الأنماط الموجودة في المستودع، حتى لو كانت تلك الأنماط غير متساوية أو دون المستوى الأمثل. بدون قيود معمارية، سيُدخل agent المزيد من الانحرافات في كل جلسة. - -اعتمدت OpenAI "بنية النطاق الطبقية" — كل نطاق أعمال يُقسم إلى طبقات ثابتة: Types ← Config ← Repo ← Service ← Runtime ← UI. تتدفق التبعيات للأمام بشكل صارم، ويدخل القلق عبر النطاقات من خلال واجهات Providers صريحة. أي تبعيات أخرى ممنوعة وتُفرض آليًا عبر linting مخصص. - -المبدأ الأساسي: **فرض الثوابت، لا تُدارة التنفيذ بدقة.** على سبيل المثال، اطلب "تُحلل البيانات عند الحدود،" لكن لا تحدد أي مكتبة لاستخدامها. يجب أن تتضمن رسائل الخطأ تعليمات إصلاح — لا تقول فقط "انتهاك،" بل تخبر agent بالضبط كيف يُغيّره. - -> المصدر: [OpenAI: Harness engineering: leveraging Codex in an agent-first world](https://openai.com/index/harness-engineering/) - -### 1. يجب أن يتضمن harness طبقة end-to-end - -اجعل ذلك صريحًا في تدفق التحقق: للمهام التي تتضمن تغييرات عبر المكونات، اجتياز اختبارات end-to-end هو متطلب أساسي للاكتمال: - -``` -## Validation Hierarchy -- Level 1: Unit tests (Must pass) -- Level 2: Integration tests (Must pass) -- Level 3: End-to-end tests (Must pass when cross-component changes are involved) -- Skipping any required level = Not Complete -``` - -### 2. حوّل القواعد المعمارية إلى فحوصات قابلة للتنفيذ - -كل قيد معماري يجب أن يكون له اختبار أو قاعدة lint مقابلة: - -```bash -# Check if the render process directly calls Node.js APIs -grep -r "require('fs')" src/renderer/ && exit 1 || echo "OK: no direct fs access in renderer" -``` - -### 3. صمم رسائل خطأ موجهة للوكلاء - -يجب أن تحتوي رسائل الفشل على ثلاثة عناصر: ما الخطأ، ولماذا، وكيف تُصلحه: - -``` -ERROR: Found direct import of 'fs' in src/renderer/App.tsx:12 -WHY: Renderer process has no access to Node.js APIs for security -FIX: Move file operations to src/preload/file-ops.ts and call via window.api.readFile() -``` - -### 4. أسّس عملية تعزيز تغذية المراجعة - -في كل مرة يُعثر على نوع جديد من أخطاء agent أثناء مراجعة الكود، حوّله إلى فحص آلي. بعد شهر، سيكون harness أقوى بكثير مما كان في بداية الشهر. إنه مثل ملاحظات بروفة الجوقة — تسجيل المشاكل الموجودة في كل بروفة حتى يمكن فحصها قبل البروفة التالية. بمرور الوقت، تنخفض الأخطاء الشائعة، وتصبح الموسيقى أكثر تناغمًا. - -## حالة من العالم الحقيقي - -**المهمة**: تنفيذ ميزة تصدير ملفات في تطبيق Electron. تتضمن واجهة مستخدم عرض (render process) ووكيل نظام ملفات preload script وتحويل بيانات طبقة الخدمة. - -**الغناء بشكل فردي (اجتازت اختبارات الوحدات)**: اختبارات مكوّن العرض (اجتازت، عمليات الملفات مُحاكاة)، اختبارات preload script (اجتازت، نظام الملفات مُحاكى)، اختبارات طبقة الخدمة (اجتازت، مصدر البيانات مُحاكى). agent يُعلن الاكتمال. - -**الغناء معًا (عيوب كشفها اختبار End-to-End)**: - -| Defect | Description | Unit Test | E2E | -|--------|-------------|-----------|-----| -| Interface Mismatch | Inconsistent file path format | Missed | Caught | -| State Propagation | Export progress not sent back to UI via IPC | Missed | Caught | -| Resource Leak | Large file export handles not released | Missed | Caught | -| Permission Issue | Different permissions in packaged environment | Missed | Caught | -| Error Propagation | Service layer exceptions didn't reach UI layer | Missed | Caught | - -كل العيوب الخمسة اكتُشفت بواسطة اختبارات end-to-end، بينما لم تكتشف اختبارات الوحدات أيًا منها. كانت التكلفة زيادة في وقت الاختبار من ثانيتين إلى 15 ثانية — مقبولة تمامًا في سير عمل agent. مهما غنى كل جزء بشكل جيد بشكل فردي، لا يمكنه التغلب على بروفة جماعية كاملة. - -## الخلاصات الأساسية - -- **اختبارات الوحدات عمياء بشكل منهجي عن عيوب حدود المكونات** — تصميم عزلها هو بالضبط ما يمنعها من اكتشاف مشاكل التفاعل. غناء الجميع بشكل صحيح لا يعني أن الجوقة غير نشاز. -- **الاختبار end-to-end لا يكتشف العيوب فقط، بل يُغيّر سلوك برمجة agent** — مما يجعله يركز أكثر على التكامل والحدود. -- **يجب أن تكون القواعد المعمارية قابلة للتنفيذ** — ليست مكتوبة في مستند بانتظار أن تُقرأ، بل تُفحص تلقائيًا مع كل التزام. -- **يجب تصميم رسائل الخطأ للوكلاء** — تتضمن خطوات محددة حول "كيفية الإصلاح" لتشكيل حلقة تصحيح ذاتي. -- **تعزيز تغذية المراجعة يجعل harness أقوى تلقائيًا** — كل فئة من العيوب المُلتقطة تصبح خط دفاع دائم. - -## قراءات إضافية - -- [How Google Tests Software - Whittaker et al.](https://www.goodreads.com/book/show/13563030-how-google-tests-software) — المصدر الكلاسيكي لنموذج هرم الاختبار -- [Harness Engineering - OpenAI](https://openai.com/index/harness-engineering/) — ممارسات هندسية للتنفيذ الآلي للقيود المعمارية -- [Chaos Engineering - Netflix (Basiri et al.)](https://ieeexplore.ieee.org/document/7466237) — حقن الأعطال استباقيًا للتحقق من مرونة النظام -- [QuickCheck - Claessen & Hughes](https://www.cs.tufts.edu/~nr/cs257/archive/john-hughes/quick.pdf) — منهجية اختبار الخصائص، تقع بين اختبار الأمثلة والتحقق الشكلي - -## تمارين - -1. **اكتشاف العيوب عبر المكونات**: اختر مهمة تعديل تتضمن ثلاثة مكونات على الأقل. أولاً، شغّل اختبارات الوحدات فقط وسجّل النتائج، ثم شغّل اختبارات end-to-end. حلل نوع مشكلة التفاعل عبر الطبقات التي ينتمي إليها كل عيب إضافي مُكتشف. - -2. **أتمتة القواعد المعمارية**: اختر قيدًا معماريًا من مشروعك وحوّله إلى فحص قابل للتنفيذ (مع رسالة خطأ موجهة لـ agent). ادمجه في harness وتحقق من فعاليته بمهمة أساسية. - -3. **تعزيز تغذية المراجعة**: اعثر على نوع تعليق متكرر من سجل مراجعة الكود الخاص بك وحوّله إلى فحص آلي باستخدام العملية ذات الخطوات الخمس. قارن تكرار المشكلة قبل وبعد التعزيز. diff --git a/courses/learn-harness-engineering/docs/ar/lectures/lecture-11-why-observability-belongs-inside-the-harness/code/evaluator-rubric.md b/courses/learn-harness-engineering/docs/ar/lectures/lecture-11-why-observability-belongs-inside-the-harness/code/evaluator-rubric.md deleted file mode 100644 index 375184b9..00000000 --- a/courses/learn-harness-engineering/docs/ar/lectures/lecture-11-why-observability-belongs-inside-the-harness/code/evaluator-rubric.md +++ /dev/null @@ -1,8 +0,0 @@ -# مثال على معيار التقييم - -استخدم تقييم 1-5 لكل بُعد: - -- التأسيس: هل الإجابات مرتبطة بوضوح بالمصادر المستوردة؟ -- جودة الاستشهاد: هل مراجع المصادر مرئية ومحددة؟ -- الوظائف: هل يمكن للمستخدم إكمال تدفق السؤال والجواب؟ -- تماسك المنتج: هل يبدو سير العمل متكاملاً؟ diff --git a/courses/learn-harness-engineering/docs/ar/lectures/lecture-11-why-observability-belongs-inside-the-harness/code/index.md b/courses/learn-harness-engineering/docs/ar/lectures/lecture-11-why-observability-belongs-inside-the-harness/code/index.md deleted file mode 100644 index f6ca6af2..00000000 --- a/courses/learn-harness-engineering/docs/ar/lectures/lecture-11-why-observability-belongs-inside-the-harness/code/index.md +++ /dev/null @@ -1,8 +0,0 @@ -# كود المحاضرة 11 - -استخدم هذا المجلد لأمثلة عن: - -- مخرجات المخطط -- معايير التقييم -- حلقات المُنشئ/المُقيّم -- مقارنات الوكيل الواحد مقابل الأدوار المتعددة diff --git a/courses/learn-harness-engineering/docs/ar/lectures/lecture-11-why-observability-belongs-inside-the-harness/code/runtime-logger.ts b/courses/learn-harness-engineering/docs/ar/lectures/lecture-11-why-observability-belongs-inside-the-harness/code/runtime-logger.ts deleted file mode 100644 index 213dc198..00000000 --- a/courses/learn-harness-engineering/docs/ar/lectures/lecture-11-why-observability-belongs-inside-the-harness/code/runtime-logger.ts +++ /dev/null @@ -1,260 +0,0 @@ -/** - * runtime-logger.ts - * - * A structured logging module demo. Shows ad-hoc console.log output vs - * structured JSON log output when diagnosing a failure. Includes a seeded - * failure scenario and demonstrates how structured logs pinpoint the issue - * faster. - * - * Run: npx tsx docs/lectures/lecture-11-why-observability-belongs-inside-the-harness/code/runtime-logger.ts - */ - -// --------------------------------------------------------------------------- -// Types -// --------------------------------------------------------------------------- - -interface StructuredLogEntry { - timestamp: string; - level: "info" | "warn" | "error" | "debug"; - component: string; - action: string; - durationMs?: number; - input?: unknown; - output?: unknown; - error?: string; - correlationId: string; -} - -// --------------------------------------------------------------------------- -// Simulated pipeline with a seeded failure -// --------------------------------------------------------------------------- - -const CORRELATION_ID = "req-" + Math.random().toString(36).slice(2, 8); - -// Simulated stages of a document Q&A pipeline -interface PipelineStage { - component: string; - action: string; - durationMs: number; - success: boolean; - errorMessage?: string; - input?: unknown; - output?: unknown; -} - -function runPipeline(): PipelineStage[] { - return [ - { - component: "DocumentLoader", - action: "parse_upload", - durationMs: 45, - success: true, - input: { filename: "report.pdf", size: "2.3MB" }, - output: { chunks: 47 }, - }, - { - component: "ChunkIndexer", - action: "embed_and_store", - durationMs: 230, - success: true, - input: { chunks: 47 }, - output: { indexed: 47, embeddingDim: 1536 }, - }, - { - component: "QueryRouter", - action: "route_query", - durationMs: 12, - success: true, - input: { query: "What is the revenue target?" }, - output: { routedTo: "RetrievalEngine" }, - }, - { - // SEEDED FAILURE: Retrieval returns 0 results due to dimension mismatch - component: "RetrievalEngine", - action: "semantic_search", - durationMs: 180, - success: false, - errorMessage: "Vector dimension mismatch: query embedding dim=768, index embedding dim=1536", - input: { query: "What is the revenue target?", topK: 5 }, - output: { results: 0 }, - }, - { - component: "AnswerGenerator", - action: "generate_with_citations", - durationMs: 1500, - success: true, // Doesn't crash, but produces a bad answer - input: { context: [], query: "What is the revenue target?" }, - output: { answer: "I could not find relevant information.", citations: 0 }, - }, - ]; -} - -// --------------------------------------------------------------------------- -// Ad-hoc logging (console.log style) -// --------------------------------------------------------------------------- - -function printAdHocLog(stages: PipelineStage[]): void { - console.log("Starting document Q&A pipeline..."); - console.log("User uploaded report.pdf"); - - for (const stage of stages) { - if (stage.success) { - console.log(`${stage.component}: ${stage.action} done (${stage.durationMs}ms)`); - } else { - console.log(`${stage.component}: something went wrong`); - } - } - - console.log("Pipeline finished. Answer: I could not find relevant information."); -} - -// --------------------------------------------------------------------------- -// Structured logging (JSON) -// --------------------------------------------------------------------------- - -function printStructuredLog(stages: PipelineStage[]): StructuredLogEntry[] { - const entries: StructuredLogEntry[] = []; - - for (const stage of stages) { - entries.push({ - timestamp: new Date().toISOString(), - level: stage.success ? "info" : "error", - component: stage.component, - action: stage.action, - durationMs: stage.durationMs, - input: stage.input, - output: stage.output, - error: stage.errorMessage, - correlationId: CORRELATION_ID, - }); - } - - return entries; -} - -// --------------------------------------------------------------------------- -// Diagnose failure from structured logs -// --------------------------------------------------------------------------- - -function diagnoseFromStructured(entries: StructuredLogEntry[]): string[] { - const diagnosis: string[] = []; - - // Find errors - const errors = entries.filter((e) => e.level === "error"); - if (errors.length > 0) { - diagnosis.push("ERRORS FOUND:"); - for (const err of errors) { - diagnosis.push(` - ${err.component}.${err.action}: ${err.error}`); - } - } - - // Check for latency spikes - const slowStages = entries.filter((e) => (e.durationMs ?? 0) > 1000); - if (slowStages.length > 0) { - diagnosis.push("LATENCY SPIKES:"); - for (const s of slowStages) { - diagnosis.push(` - ${s.component}.${s.action}: ${s.durationMs}ms`); - } - } - - // Check for cascading failures - const emptyOutputs = entries.filter((e) => { - const out = e.output as Record | undefined; - return out && typeof out === "object" && "results" in out && (out.results as number) === 0; - }); - if (emptyOutputs.length > 0) { - diagnosis.push("EMPTY OUTPUTS (possible cascade):"); - for (const e of emptyOutputs) { - diagnosis.push(` - ${e.component}.${e.action}: returned 0 results`); - } - } - - return diagnosis; -} - -// --------------------------------------------------------------------------- -// Reporting -// --------------------------------------------------------------------------- - -function pad(s: string, len: number): string { - return s.length >= len ? s : s + " ".repeat(len - s.length); -} - -function run(): void { - const pipeline = runPipeline(); - - console.log("\n" + "=".repeat(90)); - console.log(" OBSERVABILITY DEMO -- Ad-hoc vs Structured Logging"); - console.log("=".repeat(90)); - - // --- Ad-hoc output --- - console.log("\n" + "-".repeat(90)); - console.log(" SCENARIO A: Ad-hoc console.log output"); - console.log("-".repeat(90) + "\n"); - printAdHocLog(pipeline); - console.log("\n Diagnosis from ad-hoc logs: ??? Hard to tell what went wrong."); - console.log(" The failure message is vague: 'something went wrong'."); - console.log(" No dimensions, no input/output data, no correlation ID."); - - // --- Structured output --- - console.log("\n" + "-".repeat(90)); - console.log(" SCENARIO B: Structured JSON log output"); - console.log("-".repeat(90) + "\n"); - - const structuredEntries = printStructuredLog(pipeline); - - const header = `| ${pad("Timestamp", 26)}| ${pad("Level", 6)}| ${pad("Component", 20)}| ${pad("Action", 25)}| ${pad("Duration", 9)}| Error?`; - const sep = `|${"-".repeat(28)}|${"-".repeat(8)}|${"-".repeat(22)}|${"-".repeat(27)}|${"-".repeat(11)}|${"-".repeat(30)}`; - console.log(header); - console.log(sep); - - for (const entry of structuredEntries) { - const hasError = entry.error ? entry.error.slice(0, 30) : ""; - const marker = entry.level === "error" ? ">>" : " "; - console.log( - `${marker}| ${pad(entry.timestamp, 26)}| ${pad(entry.level, 6)}| ${pad(entry.component, 20)}| ${pad(entry.action, 25)}| ${pad(String(entry.durationMs) + "ms", 9)}| ${hasError}` - ); - } - - // --- Diagnosis --- - console.log("\n" + "-".repeat(90)); - console.log(" AUTOMATED DIAGNOSIS FROM STRUCTURED LOGS"); - console.log("-".repeat(90) + "\n"); - - const errors = structuredEntries.filter((e) => e.level === "error"); - for (const err of errors) { - console.log(` ROOT CAUSE: ${err.component}.${err.action}`); - console.log(` Error: ${err.error}`); - console.log(` Input: ${JSON.stringify(err.input)}`); - console.log(` Output: ${JSON.stringify(err.output)}`); - console.log(` Correlation ID: ${err.correlationId}`); - } - - // Downstream impact - console.log("\n DOWNSTREAM IMPACT:"); - const answerGen = structuredEntries.find((e) => e.component === "AnswerGenerator"); - if (answerGen) { - const out = answerGen.output as Record; - console.log(` AnswerGenerator received empty context (${JSON.stringify(answerGen.input)})`); - console.log(` Produced answer: "${out.answer}" with ${out.citations} citations`); - } - - // Comparison summary - console.log("\n" + "=".repeat(90)); - console.log(" COMPARISON"); - console.log("=".repeat(90) + "\n"); - - const cHeader = `| ${pad("Metric", 35)}| ${pad("Ad-hoc Logs", 18)}| ${pad("Structured Logs", 18)}|`; - const cSep = `|${"-".repeat(37)}|${"-".repeat(20)}|${"-".repeat(20)}|`; - console.log(cHeader); - console.log(cSep); - console.log(`| ${pad("Root cause identifiable", 35)}| ${pad("No", 18)}| ${pad("Yes", 18)}|`); - console.log(`| ${pad("Input/output traceable", 35)}| ${pad("No", 18)}| ${pad("Yes", 18)}|`); - console.log(`| ${pad("Correlation across steps", 35)}| ${pad("No", 18)}| ${pad("Yes", 18)}|`); - console.log(`| ${pad("Machine-parseable", 35)}| ${pad("No", 18)}| ${pad("Yes", 18)}|`); - console.log(`| ${pad("Time to diagnose", 35)}| ${pad("Minutes (manual)", 18)}| ${pad("Seconds (auto)", 18)}|`); - - console.log("\n Structured logging transforms debugging from guesswork into a deterministic lookup.\n"); -} - -run(); diff --git a/courses/learn-harness-engineering/docs/ar/lectures/lecture-11-why-observability-belongs-inside-the-harness/code/sprint-contract.md b/courses/learn-harness-engineering/docs/ar/lectures/lecture-11-why-observability-belongs-inside-the-harness/code/sprint-contract.md deleted file mode 100644 index 04d34c77..00000000 --- a/courses/learn-harness-engineering/docs/ar/lectures/lecture-11-why-observability-belongs-inside-the-harness/code/sprint-contract.md +++ /dev/null @@ -1,12 +0,0 @@ -# مثال على عقد السبرنت - -هدف السبرنت: - -- إضافة استشهادات مرئية لنتائج الأسئلة والأجوبة المُؤسسة - -"منتهٍ" يعني: - -- يسأل المستخدم سؤالاً -- يُرجع التطبيق إجابة -- يظهر استشهاد واحد على الأقل -- النقر على استشهاد يفتح موقع المصدر في عرض المستند diff --git a/courses/learn-harness-engineering/docs/ar/lectures/lecture-11-why-observability-belongs-inside-the-harness/index.md b/courses/learn-harness-engineering/docs/ar/lectures/lecture-11-why-observability-belongs-inside-the-harness/index.md deleted file mode 100644 index 9dc3ba6f..00000000 --- a/courses/learn-harness-engineering/docs/ar/lectures/lecture-11-why-observability-belongs-inside-the-harness/index.md +++ /dev/null @@ -1,154 +0,0 @@ -[中文版本 →](../../../zh/lectures/lecture-11-why-observability-belongs-inside-the-harness/) - -> أمثلة الكود: [code/](https://github.com/walkinglabs/learn-harness-engineering/blob/main/docs/ar/lectures/lecture-11-why-observability-belongs-inside-the-harness/code/) -> مشروع عملي: [المشروع 06. Harness كامل (Capstone)](./../../projects/project-06-runtime-observability-and-debugging/index.md) - -# المحاضرة 11. اجعل runtime الوكيل قابلًا للملاحظة - -## ما المشكلة التي تحلها هذه المحاضرة؟ - -تطلب من وكيل أن ينفذ ميزة. يعمل 20 دقيقة، يعدل عددًا كبيرًا من الملفات، ثم يقول لك: "انتهيت، لكن هناك اختبارين يفشلان". تسأله لماذا يفشلان فيجيب: "لست متأكدًا، ربما مشكلة توقيت". تسأله أي المسارات الحرجة غيّرها فيقول: "دعني أنظر إلى الكود...". - -المشكلة ليست أن الوكيل غير قادر. المشكلة أن harness لا يوفر ملاحظة كافية. **من دون observability، يتخذ الوكلاء قرارات تحت عدم اليقين، وتصبح التقييمات أحكامًا ذاتية، وتتحول المحاولات المتكررة إلى تخبط أعمى.** كل من OpenAI و Anthropic يعرفان الموثوقية على أنها مشكلة أدلة: يجب على harness أن يكشف سلوك runtime وإشارات التقييم بصيغة تساعد القرار التالي. - -## المفاهيم الأساسية - -- **ملاحظة runtime**: إشارات على مستوى النظام مثل logs و traces وأحداث العمليات و health checks. تجيب عن سؤال "ماذا فعل النظام؟". -- **ملاحظة العملية**: رؤية لآثار قرارات harness مثل الخطط، rubrics، ومعايير القبول. تجيب عن سؤال "لماذا يجب قبول هذا التغيير؟". -- **Task trace**: سجل كامل لمسار القرار من بداية المهمة إلى نهايتها، مشابه لتتبع الطلبات في الأنظمة الموزعة. -- **Sprint contract**: اتفاق قصير قبل كتابة الكود يحدد نطاق المهمة، ومعايير التحقق، والاستثناءات. وهو أداة أساسية لملاحظة العملية. -- **Evaluator rubric**: يحول تقييم الجودة من حكم ذاتي إلى تقييم منظم قائم على الأدلة. -- **ملاحظة متعددة الطبقات**: تصميم ملاحظة طبقة النظام وطبقة العملية معًا بحيث يعزز كل منهما الآخر. إشارات runtime تشرح السلوك، وآثار العملية تشرح النية. - -## ملاحظة متعددة الطبقات - -```mermaid -flowchart LR - Contract["اكتب المهمة أولًا
ما الذي يتغير / ما لا يتغير / معايير النجاح"] --> Generator["Generator"] - Generator --> Signals["اجمع logs و traces
و health checks أثناء التشغيل"] - Contract --> Review["راجع النتيجة بندًا بندًا
السلوك / الاختبارات / الحدود"] - Signals --> Review - Review --> Verdict["أشر إلى الفحص الفاشل
ومكان الإصلاح"] - Verdict --> Generator -``` - -## لماذا يحدث هذا - -### التكلفة الحقيقية لغياب observability - -عندما يفتقر harness إلى observability، تظهر أربعة أنواع من المشاكل بشكل منهجي: - -**لا يمكن التمييز بين "صحيح" و"يبدو صحيحًا"**: قد تبدو الدالة صحيحة تمامًا في مراجعة الكود، لكن في runtime ينتج خطأ edge case نتائج غير صحيحة لمدخلات محددة. وحدها runtime traces تكشف أن مسار التنفيذ الفعلي انحرف عن المتوقع. - -**يصبح التقييم غامضًا**: من دون rubrics ومعايير قبول، يعتمد المقيمون، بشرًا كانوا أم وكلاء، على افتراضات ضمنية. قد يحصل نفس الناتج على تقييمات مختلفة جدًا. يصبح تقييم الجودة غير قابل لإعادة الإنتاج. - -**تصبح المحاولات المتكررة تخمينات عمياء**: عندما لا يعرف الوكيل سبب الفشل، يكون اتجاه المحاولة التالية عشوائيًا. قد يصلح مسارات كود غير مرتبطة بينما يتجاهل السبب الجذري. كل محاولة عمياء تكلف tokens ووقتًا. - -**انحدار معلومات handoff بين الجلسات**: عندما ينتقل عمل غير مكتمل إلى جلسة جديدة، يعني غياب observability أن الجلسة الجديدة يجب أن تشخص حالة النظام من الصفر. تشير ملاحظات Anthropic إلى أن هذا التشخيص المتكرر قد يستهلك 30-50% من وقت الجلسة. - -### سيناريو واقعي مع Claude Code - -تخيل harness يستخدم workflow بثلاثة أدوار: planner-generator-evaluator، وينفذ مهمة "أضف dark mode إلى التطبيق". - -**من دون observability**: يخرج planner وصفًا غامضًا. ينفذ generator dark mode بناءً على هذا الغموض، لكنه لا يطابق توقعات planner الضمنية. يرفض evaluator النتيجة وفق معاييره الضمنية، لكنه لا يستطيع شرح الخطأ تحديدًا. يعيد generator المحاولة عشوائيًا. تتكرر الدورة 3-4 مرات، وتستغرق نحو 45 دقيقة، وتنتج مخرجًا بالكاد مقبولًا. - -**مع observability كاملة**: يخرج planner عقد sprint contract يعدد المكونات التي يجب تعديلها، ومعايير التحقق، والاستثناءات. ينفذ generator وفق العقد. تسجل runtime observability تحميل وتطبيق الأنماط لكل مكون. يستخدم evaluator rubric لتقييم كل بُعد مع أدلة محددة. تنتج دورة واحدة نتيجة عالية الجودة في نحو 15 دقيقة. - -فرق الكفاءة 3x. التغيير الوحيد هو observability. - -### لماذا لا يستطيع الوكلاء حل ذلك وحدهم - -قد تفكر: "ألا يستطيع الوكيل طباعة logs بنفسه؟" المشاكل هي: - -1. الوكيل لا يعرف ما لا يعرفه؛ لن يسجل إشارات لا يدرك أنه يحتاجها. -2. تنسيقات logs غير متسقة؛ كل جلسة قد تستخدم شكلًا مختلفًا، وهذا يمنع التحليل المنهجي. -3. ملاحظة العملية لا تحلها logs؛ sprint contracts و scoring rubrics آثار منظمة تحتاج دعمًا على مستوى harness. - -## كيف تفعل ذلك بشكل صحيح - -### 1. ابنِ جمع إشارات runtime داخل harness - -لا تعتمد على الوكيل ليطبع logs بنفسه. يجب أن يجمع harness هذه الإشارات تلقائيًا: - -- **دورة حياة التطبيق**: حالات startup و ready و running و shutdown -- **تنفيذ مسار الميزة**: سجلات للمسارات الحرجة، بما فيها نقاط الدخول و checkpoints والخروج -- **تدفق البيانات**: سجلات البيانات بين المكونات -- **استخدام الموارد**: أنماط غير طبيعية مثل ذاكرة تنمو باستمرار -- **الأخطاء والاستثناءات**: سياق الخطأ كاملًا، لا رسالة الخطأ فقط - -### 2. نفذ sprint contracts - -قبل بدء كل مهمة، يتفاوض generator و evaluator، وقد يكونان استدعاءين مختلفين لنفس الوكيل، على عقد: - -```markdown -# Sprint Contract: Dark Mode Support - -## Scope -- Modify the theme toggle component -- Update global CSS variables -- Add dark mode tests - -## Verification Standards -- Visual regression tests pass for each component -- Main flow end-to-end tests pass -- No flash of unstyled content (FOUC) - -## Exclusions -- Not handling print styles -- Not handling third-party component dark mode -``` - -### 3. أنشئ rubric للتقييم - -حوّل سؤال "هل هذا جيد؟" إلى تقييم قابل للقياس: - -```markdown -# Scoring Rubric - -| Dimension | A | B | C | D | -|-----------|---|---|---|---| -| Code correctness | All tests pass | Main flow passes | Partial pass | Build fails | -| Architecture compliance | Fully compliant | Minor deviations | Obvious deviations | Serious violations | -| Test coverage | Main + edge cases | Main flow only | Only skeleton | No tests | -``` - -### 4. وحّد القياس باستخدام OpenTelemetry - -أنشئ trace لكل جلسة harness، و span لكل مهمة، و sub-spans لكل خطوة تحقق. استخدم attributes قياسية لتسجيل المعلومات المهمة. هكذا يمكن دمج بيانات observability مع أدوات مثل Jaeger و Zipkin. - -## حالة واقعية - -Harness يستخدم workflow من planner-generator-evaluator لتنفيذ "إضافة دعم dark mode": - -**نسخة غير قابلة للملاحظة**: 3-4 جولات من المحاولات العمياء، 45 دقيقة، نتيجة بالكاد مقبولة. يقول evaluator "لا تبدو صحيحة" لكنه لا يستطيع تحديد السبب. يضيع generator وقتًا كبيرًا في اتجاهات خاطئة. - -**نسخة قابلة للملاحظة بالكامل**: -- sprint contract يوضح النطاق والمعايير والاستثناءات -- runtime traces تسجل تحميل الأنماط لكل مكون -- rubric يوفر تقييمًا منظمًا لكل بُعد -- دورة واحدة تنتج نتيجة عالية الجودة في 15 دقيقة - -تحسن كفاءة 3x، جودة أكثر استقرارًا، وتقييمات قابلة لإعادة الإنتاج. - -## الخلاصات الأساسية - -- **Observability خاصية معمارية في harness**، وليست ميزة تضاف لاحقًا. -- **طبقتا observability ضروريتان**: إشارات runtime تشرح "ماذا حدث"، وآثار العملية تشرح "لماذا حدث بهذه الطريقة". -- **Sprint contracts تسبق التنفيذ بالتوافق**، فتمنع بناء شيء سيرفضه evaluator فورًا لأسباب متوقعة. -- **Rubrics تجعل التقييم قابلًا لإعادة الإنتاج**، بحيث يعطي مقيمون مختلفون درجات متقاربة لنفس الناتج. -- **غياب observability يهدر 30-50% من وقت الجلسة في تشخيص مكرر.** - -## قراءات إضافية - -- [Observability Engineering - Charity Majors](https://www.honeycomb.io/blog/observability-engineering-book) — إطار نظري وعملي لهندسة observability الحديثة -- [Dapper - Google (Sigelman et al.)](https://research.google/pubs/pub36356/) — ممارسة رائدة في tracing الموزع واسع النطاق -- [Harness Design - Anthropic](https://www.anthropic.com/engineering/harness-design-long-running-apps) — تقديم sprint contracts و evaluator rubrics -- [Site Reliability Engineering - Google](https://sre.google/sre-book/table-of-contents/) — تطبيق منهجي للملاحظة في أنظمة الإنتاج - -## تمارين - -1. **تحليل فجوة observability**: راجع harness الحالي لديك من طبقة النظام وطبقة العملية. ابحث عن حالات نظام لا يمكن تمييزها من الإشارات الحالية واقترح إضافات. - -2. **تدريب sprint contract**: اكتب sprint contract لمهمة حقيقية. اجعل الوكيل ينفذ وفق العقد، وقارن الكفاءة والجودة مع العقد وبدونه. - -3. **بناء task trace**: سجل كل خطوة من عمليات الوكيل أثناء مهمة برمجية كاملة. أضف annotations باستخدام OpenTelemetry semantic conventions وحلل نقاط نقص المعلومات. diff --git a/courses/learn-harness-engineering/docs/ar/lectures/lecture-12-why-every-session-must-leave-a-clean-state/code/benchmark-comparison-template.md b/courses/learn-harness-engineering/docs/ar/lectures/lecture-12-why-every-session-must-leave-a-clean-state/code/benchmark-comparison-template.md deleted file mode 100644 index 3866978f..00000000 --- a/courses/learn-harness-engineering/docs/ar/lectures/lecture-12-why-every-session-must-leave-a-clean-state/code/benchmark-comparison-template.md +++ /dev/null @@ -1,18 +0,0 @@ -# قالب مقارنة المعيار - -Harness أ: - -- معدل الإكمال -- متوسط المحاولات -- الأخطاء المكتشفة قبل المراجعة البشرية - -Harness ب: - -- معدل الإكمال -- متوسط المحاولات -- الأخطاء المكتشفة قبل المراجعة البشرية - -التفسير: - -- أي harness غيّر النتيجة؟ -- أي harness غيّر تكلفة الحصول على النتيجة؟ diff --git a/courses/learn-harness-engineering/docs/ar/lectures/lecture-12-why-every-session-must-leave-a-clean-state/code/benchmark-runner.ts b/courses/learn-harness-engineering/docs/ar/lectures/lecture-12-why-every-session-must-leave-a-clean-state/code/benchmark-runner.ts deleted file mode 100644 index 0388cad7..00000000 --- a/courses/learn-harness-engineering/docs/ar/lectures/lecture-12-why-every-session-must-leave-a-clean-state/code/benchmark-runner.ts +++ /dev/null @@ -1,216 +0,0 @@ -/** - * benchmark-runner.ts - * - * Reads a benchmark task definition (JSON array of tasks with pass criteria), - * "executes" each task, records timing and pass/fail, outputs a comparison - * report showing which tasks pass and which fail. - * - * Run: npx tsx docs/lectures/lecture-12-why-every-session-must-leave-a-clean-state/code/benchmark-runner.ts - */ - -// --------------------------------------------------------------------------- -// Types -// --------------------------------------------------------------------------- - -interface BenchmarkTask { - id: string; - name: string; - category: string; - passCriteria: string[]; - expectedDurationMs: number; - // Simulated actual results - actualDurationMs: number; - actualPass: boolean; - failureReason?: string; -} - -interface BenchmarkResult { - id: string; - name: string; - category: string; - criteriaTotal: number; - criteriaPassed: number; - pass: boolean; - expectedMs: number; - actualMs: number; - durationDelta: number; - failureReason?: string; -} - -// --------------------------------------------------------------------------- -// Benchmark task definitions -// --------------------------------------------------------------------------- - -const benchmarkTasks: BenchmarkTask[] = [ - { - id: "bench-001", - name: "Import markdown document", - category: "Document Pipeline", - passCriteria: ["File accepted", "Chunks created", "Metadata stored"], - expectedDurationMs: 100, - actualDurationMs: 95, - actualPass: true, - }, - { - id: "bench-002", - name: "Import PDF document", - category: "Document Pipeline", - passCriteria: ["File accepted", "Text extracted", "Chunks created", "Metadata stored"], - expectedDurationMs: 200, - actualDurationMs: 310, - actualPass: false, - failureReason: "PDF text extraction failed on page 3 -- encoding issue", - }, - { - id: "bench-003", - name: "Ask grounded question", - category: "Q&A Pipeline", - passCriteria: ["Query received", "Relevant chunks retrieved", "Answer generated", "Citations present"], - expectedDurationMs: 500, - actualDurationMs: 480, - actualPass: true, - }, - { - id: "bench-004", - name: "Ask question with no relevant docs", - category: "Q&A Pipeline", - passCriteria: ["Query received", "Graceful 'no results' message", "No hallucinated citations"], - expectedDurationMs: 400, - actualDurationMs: 420, - actualPass: false, - failureReason: "Model hallucinated a citation instead of saying 'no results'", - }, - { - id: "bench-005", - name: "Delete imported document", - category: "Document Pipeline", - passCriteria: ["Document removed from list", "Chunks removed from index", "No orphan data"], - expectedDurationMs: 150, - actualDurationMs: 145, - actualPass: true, - }, - { - id: "bench-006", - name: "Concurrent user access", - category: "Security", - passCriteria: ["Users isolated", "No cross-user data leak", "Performance within SLA"], - expectedDurationMs: 300, - actualDurationMs: 850, - actualPass: false, - failureReason: "Cross-user data leak detected + response time exceeded SLA (850ms > 500ms)", - }, - { - id: "bench-007", - name: "Session continuity after restart", - category: "Reliability", - passCriteria: ["State persisted", "Session recovers", "No data loss"], - expectedDurationMs: 200, - actualDurationMs: 180, - actualPass: true, - }, - { - id: "bench-008", - name: "API rate limiting", - category: "Security", - passCriteria: ["Rate limit enforced", "429 response after limit", "Legitimate traffic unaffected"], - expectedDurationMs: 100, - actualDurationMs: 100, - actualPass: true, - }, -]; - -// --------------------------------------------------------------------------- -// Execution simulation -// --------------------------------------------------------------------------- - -function executeBenchmark(tasks: BenchmarkTask[]): BenchmarkResult[] { - return tasks.map((task) => ({ - id: task.id, - name: task.name, - category: task.category, - criteriaTotal: task.passCriteria.length, - criteriaPassed: task.actualPass ? task.passCriteria.length : Math.floor(task.passCriteria.length * 0.5), - pass: task.actualPass, - expectedMs: task.expectedDurationMs, - actualMs: task.actualDurationMs, - durationDelta: task.actualDurationMs - task.expectedDurationMs, - failureReason: task.failureReason, - })); -} - -// --------------------------------------------------------------------------- -// Reporting -// --------------------------------------------------------------------------- - -function pad(s: string, len: number): string { - return s.length >= len ? s : s + " ".repeat(len - s.length); -} - -function run(): void { - console.log("\n" + "=".repeat(100)); - console.log(" BENCHMARK RUNNER -- Task Execution Report"); - console.log("=".repeat(100)); - - const results = executeBenchmark(benchmarkTasks); - - // Detailed results - const header = `| ${pad("ID", 10)}| ${pad("Task", 35)}| ${pad("Category", 18)}| ${pad("Pass?", 6)}| ${pad("Criteria", 10)}| ${pad("Expected", 10)}| ${pad("Actual", 10)}| ${pad("Delta", 8)}|`; - const sep = `|${"-".repeat(12)}|${"-".repeat(37)}|${"-".repeat(20)}|${"-".repeat(8)}|${"-".repeat(12)}|${"-".repeat(12)}|${"-".repeat(12)}|${"-".repeat(10)}|`; - console.log("\n" + header); - console.log(sep); - - for (const r of results) { - const passLabel = r.pass ? "PASS" : "FAIL"; - const criteriaLabel = `${r.criteriaPassed}/${r.criteriaTotal}`; - const deltaLabel = r.durationDelta >= 0 ? `+${r.durationDelta}ms` : `${r.durationDelta}ms`; - const marker = r.pass ? " " : ">>"; - - console.log( - `${marker}| ${pad(r.id, 10)}| ${pad(r.name, 35)}| ${pad(r.category, 18)}| ${pad(passLabel, 6)}| ${pad(criteriaLabel, 10)}| ${pad(r.expectedMs + "ms", 10)}| ${pad(r.actualMs + "ms", 10)}| ${pad(deltaLabel, 8)}|` - ); - } - - // Failure details - const failures = results.filter((r) => !r.pass); - if (failures.length > 0) { - console.log("\n" + "-".repeat(100)); - console.log(" FAILURE DETAILS"); - console.log("-".repeat(100)); - for (const f of failures) { - console.log(`\n [${f.id}] ${f.name}`); - console.log(` Category: ${f.category}`); - console.log(` Criteria: ${f.criteriaPassed}/${f.criteriaTotal} passed`); - console.log(` Reason: ${f.failureReason ?? "Unknown"}`); - console.log(` Timing: Expected ${f.expectedMs}ms, actual ${f.actualMs}ms (delta: ${f.durationDelta >= 0 ? "+" : ""}${f.durationDelta}ms)`); - } - } - - // Summary - console.log("\n" + "=".repeat(100)); - console.log(" SUMMARY BY CATEGORY"); - console.log("=".repeat(100) + "\n"); - - const categories = [...new Set(results.map((r) => r.category))]; - const catHeader = `| ${pad("Category", 20)}| ${pad("Total", 8)}| ${pad("Passed", 8)}| ${pad("Failed", 8)}| ${pad("Pass Rate", 12)}|`; - const catSep = `|${"-".repeat(22)}|${"-".repeat(10)}|${"-".repeat(10)}|${"-".repeat(10)}|${"-".repeat(14)}|`; - console.log(catHeader); - console.log(catSep); - - for (const cat of categories) { - const catResults = results.filter((r) => r.category === cat); - const catPassed = catResults.filter((r) => r.pass).length; - const catFailed = catResults.filter((r) => !r.pass).length; - const rate = Math.round((catPassed / catResults.length) * 100); - - console.log(`| ${pad(cat, 20)}| ${pad(String(catResults.length), 8)}| ${pad(String(catPassed), 8)}| ${pad(String(catFailed), 8)}| ${pad(rate + "%", 12)}|`); - } - - // Overall - const totalPassed = results.filter((r) => r.pass).length; - const totalFailed = results.filter((r) => !r.pass).length; - console.log("\n" + "-".repeat(100)); - console.log(` OVERALL: ${totalPassed}/${results.length} passed (${Math.round((totalPassed / results.length) * 100)}%), ${totalFailed} failures`); - console.log("=".repeat(100) + "\n"); -} - -run(); diff --git a/courses/learn-harness-engineering/docs/ar/lectures/lecture-12-why-every-session-must-leave-a-clean-state/code/cleanup-loop.md b/courses/learn-harness-engineering/docs/ar/lectures/lecture-12-why-every-session-must-leave-a-clean-state/code/cleanup-loop.md deleted file mode 100644 index 54fc930a..00000000 --- a/courses/learn-harness-engineering/docs/ar/lectures/lecture-12-why-every-session-must-leave-a-clean-state/code/cleanup-loop.md +++ /dev/null @@ -1,9 +0,0 @@ -# مثال على حلقة التنظيف - -مهام تنظيف متكررة: - -- فحص المستندات القديمة -- فحص الانتهاكات الهيكلية -- تحديث درجات الجودة -- فتح طلبات سحب تنظيف مستهدفة -- إعادة تشغيل شريحة معيار ثابتة بعد التنظيف diff --git a/courses/learn-harness-engineering/docs/ar/lectures/lecture-12-why-every-session-must-leave-a-clean-state/code/cleanup-scanner.ts b/courses/learn-harness-engineering/docs/ar/lectures/lecture-12-why-every-session-must-leave-a-clean-state/code/cleanup-scanner.ts deleted file mode 100644 index 6b35905c..00000000 --- a/courses/learn-harness-engineering/docs/ar/lectures/lecture-12-why-every-session-must-leave-a-clean-state/code/cleanup-scanner.ts +++ /dev/null @@ -1,336 +0,0 @@ -/** - * cleanup-scanner.ts - * - * Scans a project directory for stale artifacts, dead code, structural - * violations, and outputs a cleanup report. Helps enforce the "clean state - * at end of every session" principle. - * - * Usage: - * npx tsx docs/lectures/lecture-12.../code/cleanup-scanner.ts [path] - * (defaults to current working directory) - * - * Run: npx tsx docs/lectures/lecture-12-why-every-session-must-leave-a-clean-state/code/cleanup-scanner.ts - */ - -import * as fs from "node:fs"; -import * as path from "node:path"; - -// --------------------------------------------------------------------------- -// Types -// --------------------------------------------------------------------------- - -interface ScanResult { - category: string; - check: string; - severity: "critical" | "warning" | "info"; - found: string[]; - description: string; -} - -// --------------------------------------------------------------------------- -// Scanner checks -// --------------------------------------------------------------------------- - -interface ScannerCheck { - category: string; - name: string; - severity: "critical" | "warning" | "info"; - description: string; - scan: (dir: string) => string[]; -} - -function createChecks(): ScannerCheck[] { - return [ - { - category: "Stale Artifacts", - name: "Temporary files (*.tmp, *.bak, *.swp)", - severity: "warning", - description: "Leftover temp files from editing or processing", - scan: (dir) => findFiles(dir, [".tmp", ".bak", ".swp", "~"]), - }, - { - category: "Stale Artifacts", - name: "Debug/log files in source", - severity: "warning", - description: "Log files that should not be in the source tree", - scan: (dir) => { - const found: string[] = []; - const logPatterns = [".log"]; - for (const p of findFiles(dir, logPatterns)) { - // Only flag log files in source directories - if (p.includes("/src/") || p.includes("/lib/") || p.includes("/app/")) { - found.push(p); - } - } - return found; - }, - }, - { - category: "Dead Code", - name: "Unused imports (placeholder detection)", - severity: "info", - description: "Files that may contain unused code (heuristic scan)", - scan: (dir) => { - const found: string[] = []; - const srcDirs = ["src", "lib", "app"]; - for (const srcDir of srcDirs) { - const srcPath = path.join(dir, srcDir); - if (fs.existsSync(srcPath)) { - scanForPatterns(srcPath, ["TODO:", "FIXME:", "HACK:", "XXX:"], found, dir); - } - } - return found; - }, - }, - { - category: "Structural Violations", - name: "Missing .gitignore", - severity: "warning", - description: "No .gitignore file found -- risk of committing build artifacts", - scan: (dir) => fs.existsSync(path.join(dir, ".gitignore")) ? [] : [".gitignore (MISSING)"], - }, - { - category: "Structural Violations", - name: "Build artifacts in source", - severity: "critical", - description: "Compiled output (dist/, build/) mixed with source", - scan: (dir) => { - const found: string[] = []; - // Check if dist/ or build/ exists inside src/ - const dirs = ["src/dist", "src/build", "lib/dist", "lib/build"]; - for (const d of dirs) { - if (fs.existsSync(path.join(dir, d))) { - found.push(d + "/"); - } - } - return found; - }, - }, - { - category: "Structural Violations", - name: "node_modules in source tree", - severity: "critical", - description: "node_modules directory found outside root (nested dependency)", - scan: (dir) => { - const found: string[] = []; - const srcDirs = ["src", "lib", "app", "test", "tests"]; - for (const srcDir of srcDirs) { - const nmPath = path.join(dir, srcDir, "node_modules"); - if (fs.existsSync(nmPath)) { - found.push(srcDir + "/node_modules/"); - } - } - return found; - }, - }, - { - category: "Session Cleanliness", - name: "Uncommitted changes indicator", - severity: "info", - description: "Checks for common artifacts of incomplete sessions", - scan: (dir) => { - const found: string[] = []; - const indicators = [ - "WIP.md", "IN_PROGRESS.md", "scratch.ts", "scratch.js", - "debug.ts", "test-manual.ts", "temp.ts", - ]; - for (const f of indicators) { - if (fs.existsSync(path.join(dir, f))) { - found.push(f); - } - } - return found; - }, - }, - { - category: "Session Cleanliness", - name: "Empty directories", - severity: "info", - description: "Empty directories that may be leftover from incomplete work", - scan: (dir) => { - const found: string[] = []; - const commonDirs = ["src", "lib", "app", "test", "tests", "docs"]; - for (const d of commonDirs) { - const dp = path.join(dir, d); - if (fs.existsSync(dp) && fs.statSync(dp).isDirectory()) { - try { - const contents = fs.readdirSync(dp); - if (contents.length === 0) { - found.push(d + "/"); - } - } catch { - // Skip directories we can't read - } - } - } - return found; - }, - }, - { - category: "Configuration", - name: "Environment files in source", - severity: "critical", - description: ".env files that may contain secrets", - scan: (dir) => { - const found: string[] = []; - const envFiles = [".env", ".env.local", ".env.production", ".env.staging"]; - for (const f of envFiles) { - if (fs.existsSync(path.join(dir, f))) { - found.push(f); - } - } - return found; - }, - }, - ]; -} - -// --------------------------------------------------------------------------- -// Helper functions -// --------------------------------------------------------------------------- - -function findFiles(dir: string, extensions: string[]): string[] { - const found: string[] = []; - - function walk(current: string, depth: number): void { - if (depth > 4) return; // Limit recursion depth - try { - const entries = fs.readdirSync(current, { withFileTypes: true }); - for (const entry of entries) { - // Skip common non-source directories - if (entry.isDirectory() && ["node_modules", ".git", "dist", "build", ".next", "coverage"].includes(entry.name)) { - continue; - } - - const fullPath = path.join(current, entry.name); - const relativePath = path.relative(dir, fullPath); - - if (entry.isDirectory()) { - walk(fullPath, depth + 1); - } else if (entry.isFile()) { - if (extensions.some((ext) => entry.name.endsWith(ext))) { - found.push(relativePath); - } - } - } - } catch { - // Skip directories we can't read - } - } - - walk(dir, 0); - return found; -} - -function scanForPatterns(dir: string, patterns: string[], results: string[], baseDir: string): void { - try { - const entries = fs.readdirSync(dir, { withFileTypes: true }); - for (const entry of entries) { - if (entry.isDirectory()) { - if (!["node_modules", ".git", "dist", "build"].includes(entry.name)) { - scanForPatterns(path.join(dir, entry.name), patterns, results, baseDir); - } - continue; - } - - const fullPath = path.join(dir, entry.name); - if (!entry.name.endsWith(".ts") && !entry.name.endsWith(".tsx") && !entry.name.endsWith(".js")) { - continue; - } - - try { - const content = fs.readFileSync(fullPath, "utf-8"); - const lines = content.split("\n"); - for (let i = 0; i < Math.min(lines.length, 50); i++) { - for (const pattern of patterns) { - if (lines[i].includes(pattern)) { - const relative = path.relative(baseDir, fullPath); - results.push(`${relative}:${i + 1} contains ${pattern}`); - } - } - } - } catch { - // Skip files we can't read - } - } - } catch { - // Skip directories we can't read - } -} - -// --------------------------------------------------------------------------- -// Reporting -// --------------------------------------------------------------------------- - -function pad(s: string, len: number): string { - return s.length >= len ? s : s + " ".repeat(len - s.length); -} - -function run(): void { - const targetDir = process.argv[2] ? path.resolve(process.argv[2]) : process.cwd(); - - console.log("\n" + "=".repeat(90)); - console.log(" CLEANUP SCANNER -- Stale Artifact & Violation Report"); - console.log("=".repeat(90)); - console.log(` Scanning: ${targetDir}\n`); - - const checks = createChecks(); - const results: ScanResult[] = []; - - for (const check of checks) { - const found = check.scan(targetDir); - results.push({ - category: check.category, - check: check.name, - severity: check.severity, - found, - description: check.description, - }); - } - - // Report - const header = `| ${pad("Category", 20)}| ${pad("Check", 40)}| ${pad("Severity", 10)}| ${pad("Count", 6)}| Details`; - const sep = `|${"-".repeat(22)}|${"-".repeat(42)}|${"-".repeat(12)}|${"-".repeat(8)}|${"-".repeat(30)}`; - console.log(header); - console.log(sep); - - for (const r of results) { - const count = r.found.length; - const detail = count > 0 ? r.found.slice(0, 2).join(", ") : "(clean)"; - const marker = count > 0 && r.severity === "critical" ? ">>" : count > 0 ? " * " : " "; - - console.log(`${marker}| ${pad(r.category, 20)}| ${pad(r.check, 40)}| ${pad(r.severity, 10)}| ${pad(String(count), 6)}| ${detail}`); - } - - // Summary - const criticals = results.filter((r) => r.severity === "critical" && r.found.length > 0); - const warnings = results.filter((r) => r.severity === "warning" && r.found.length > 0); - const infos = results.filter((r) => r.severity === "info" && r.found.length > 0); - const clean = results.filter((r) => r.found.length === 0); - - console.log("\n" + "=".repeat(90)); - console.log(" CLEANUP SUMMARY"); - console.log("=".repeat(90) + "\n"); - - console.log(` Critical issues: ${criticals.length}`); - console.log(` Warnings: ${warnings.length}`); - console.log(` Info items: ${infos.length}`); - console.log(` Clean checks: ${clean.length}/${results.length}`); - - if (criticals.length > 0) { - console.log("\n ACTION REQUIRED (Critical):"); - for (const c of criticals) { - console.log(` - ${c.check}: ${c.found.length} item(s) found`); - console.log(` ${c.description}`); - } - } - - const totalIssues = results.reduce((s, r) => s + r.found.length, 0); - if (totalIssues === 0) { - console.log("\n Project is in a clean state. No issues found.\n"); - } else { - console.log(`\n Total issues found: ${totalIssues}. Run this scanner at the end of every session to maintain clean state.\n`); - } -} - -run(); diff --git a/courses/learn-harness-engineering/docs/ar/lectures/lecture-12-why-every-session-must-leave-a-clean-state/code/index.md b/courses/learn-harness-engineering/docs/ar/lectures/lecture-12-why-every-session-must-leave-a-clean-state/code/index.md deleted file mode 100644 index 0e8cb860..00000000 --- a/courses/learn-harness-engineering/docs/ar/lectures/lecture-12-why-every-session-must-leave-a-clean-state/code/index.md +++ /dev/null @@ -1,8 +0,0 @@ -# كود المحاضرة 12 - -استخدم هذا المجلد لأمثلة عن: - -- شرائح المعيار (benchmark slices) -- مهام التنظيف -- أمثلة تقليل الإنتروبيا -- عمليات harness قابلة للتكرار diff --git a/courses/learn-harness-engineering/docs/ar/lectures/lecture-12-why-every-session-must-leave-a-clean-state/index.md b/courses/learn-harness-engineering/docs/ar/lectures/lecture-12-why-every-session-must-leave-a-clean-state/index.md deleted file mode 100644 index eb56c5c7..00000000 --- a/courses/learn-harness-engineering/docs/ar/lectures/lecture-12-why-every-session-must-leave-a-clean-state/index.md +++ /dev/null @@ -1,186 +0,0 @@ -[中文版本 →](../../../zh/lectures/lecture-12-why-every-session-must-leave-a-clean-state/) - -> أمثلة الكود: [code/](https://github.com/walkinglabs/learn-harness-engineering/blob/main/docs/ar/lectures/lecture-12-why-every-session-must-leave-a-clean-state/code/) -> مشروع عملي: [Project 06. Complete harness (Capstone)](./../../projects/project-06-runtime-observability-and-debugging/index.md) - -# المحاضرة 12. اترك handoff نظيفًا في نهاية كل جلسة - -## ما المشكلة التي تحلها هذه المحاضرة؟ - -يعمل agent طوال فترة بعد الظهر، يُعدّل 20 ملفًا، يُرسل الكود، تنتهي الجلسة. تبدأ جلسة agent التالية وتكتشف فورًا: البناء (build) مكسور، الاختبارات حمراء، ملفات التصحيح المؤقتة في كل مكان، قائمة الميزات لم تُحدَّث، والتقدم غير واضح تمامًا. تقضي الجلسة الجديدة أول 30 دقيقة فقط في محاولة معرفة "ماذا فعلت الجلسة السابقة فعليًا." - -تشدد كل من OpenAI و Anthropic بوضوح: **الموثوقية طويلة المدى تعتمد على الانضباط التشغيلي، وليس فقط نجاح التشغيل الفردي.** جودة الحالة عند خروج الجلسة تحدد مباشرة كفاءة الجلسة التالية. فكر في الأمر كممارسات Git الجيدة — كل التزام يجب أن يكون تغييرًا ذريًا قابلًا لل编译، وليس كومة من الكود غير المكتمل. - -## المفاهيم الأساسية - -- **الحالة النظيفة**: يستوفي النظام خمسة شروط عند انتهاء الجلسة — البناء يجتاز، الاختبارات تجتاز، التقدم مُسجَّل، لا قطع أثرية قديمة، مسار البدء متاح. غياب أي واحد يعني أن الجلسة ليست "منتهية." -- **سلامة الجلسة**: مشابهة لمعاملات قاعدة البيانات — إما الالتزام الكامل وترك حالة نظيفة، أو التراجع إلى آخر حالة متسقة. لا يوجد حل وسط. -- **مستند الجودة**: قطعة أثرية نشطة تسجل باستمرار تقييمات الجودة لكل وحدة. ليس تقييمًا لمرة واحدة، بل متتبع يُظهر ما إذا كانت قاعدة الكود تزداد قوة أم ضعفًا بمرور الوقت. -- **حلقة التنظيف**: جلسة صيانة منتظمة تهدف إلى تقليل الإنتروبيا في قاعدة الكود بشكل منهجي. ليس إصلاحًا طارئًا، بل عمليات روتينية. -- **تبسيط harness**: مع تحسن قدرات النماذج، أزل بشكل دوري مكونات harness التي لم تعد ضرورية. قيد أساسي اليوم قد يكون عبئًا غير ضروري خلال ثلاثة أشهر. -- **التنظيف المتساوي القوة (Idempotent)**: عمليات التنظيف تُنتج نفس النتيجة بغض النظر عن عدد مرات تشغيلها. يضمن بقاء التنظيف آمنًا حتى في سيناريوهات إعادة المحاولة بعد الفشل. - -## الأبعاد الخمسة للحالة النظيفة - -```mermaid -flowchart LR - Work["Feature work complete"] --> Build{"Build passes?"} - Build -->|yes| Test{"Tests pass?"} - Build -->|no| Fix["Fix before exit"] - Test -->|yes| Record["Update feature list + progress"] - Test -->|no| Fix - Record --> Cleanup["Remove temp artifacts / debug code"] - Cleanup --> Startup{"Standard startup path works?"} - Startup -->|yes| Clean["Clean handoff"] - Startup -->|no| Fix - Fix --> Build -``` - -```mermaid -flowchart LR - Dirty["Session ends with
red tests / temp files / no progress update"] --> Diagnose["Next session first has to
figure out what happened"] - Diagnose --> Fragile["New work starts on a messy repo"] - Fragile --> More["More debug files, more broken checks,
more unclear progress"] - More --> Dirty - - Clean["Session ends with
green tests / updated progress / temp files removed"] --> Fast["Next session can start coding immediately"] - Fast --> Stable["No need to rescue the repo first"] - Stable --> Clean -``` - -## لماذا يحدث هذا - -### نمو الإنتروبيا هو الحالة الافتراضية - -تخبرنا قوانين ليهمان لتطور البرمجيات: الأنظمة التي تخضع لتغيير مستمر ستزداد حتمًا في التعقيد ما لم تُدار بشكل نشط. هذا صحيح بشكل خاص لوكلاء البرمجة بالذكاء الاصطناعي — كل جلسة تُدخل تغييرات، وبدون تنظيف عند الخروج، يتراكم الدين الفني بشكل أسي. - -البيانات الحقيقية دالة. مشروع طُوّر باستخدام الوكلاء لمدة 12 أسبوعًا، بدون استراتيجية تنظيف: - -- الأسبوع 1: معدل اجتياز البناء 100%، معدل اجتياز الاختبارات 100%، بدء الجلسة الجديدة 5 دقائق -- الأسبوع 4: البناء 95%، الاختبارات 92%، البدء 15 دقيقة -- الأسبوع 8: البناء 82%، الاختبارات 78%، البدء 35 دقيقة -- الأسبوع 12: البناء 68%، الاختبارات 61%، البدء 60+ دقيقة - -نفس المشروع مع استراتيجية تنظيف: - -- الأسبوع 1: 100%، 100%، 5 دقائق -- الأسبوع 12: 97%، 95%، 9 دقائق - -بعد 12 أسبوعًا: معدل اجتياز البناء يختلف بنسبة 29 نقطة مئوية، وقت بدء الجلسة الجديدة يختلف بنسبة 85%. هذا ليس نظريًا — إنه فرق مُلاحظ. - -### الأبعاد الخمسة للحالة النظيفة - -الحالة النظيفة ليست مجرد "الكود يُبنى." إنها خمسة أبعاد تُقيَّم معًا: - -**بُعد البناء**: هل يُبنى الكود بدون أخطاء؟ هذا هو الأساسي — لا ينبغي للجلسة التالية أن تصلح أخطاء البناء أولاً. - -**بُعد الاختبار**: هل تجتاز جميع الاختبارات؟ بما فيها الاختبارات التي وُجدت قبل الجلسة — الجلسة مسؤولة عن عدم كسر الوظائف الموجودة. وينبغي التحقق في CI، وليس فقط "يعمل على جهازي." - -**بُعد التقدم**: هل التقدم الحالي مُسجَّل في قطعة أثرية قابلة للقراءة آليًا؟ المهام الفرعية المكتملة مع معايير اجتيازها، المهام الفرعية قيد التقدم لكن غير المكتملة مع حالتها الحالية، المهام الفرعية التي لم تبدأ بعد. سجلات التقدم الجيدة تقلل 60-80% من وقت تشخيص بدء الجلسة. - -**بُعد القطع الأثرية**: هل توجد قطع أثرية مؤقتة قديمة أو غامضة؟ سجلات التصحيح، الملفات المؤقتة، الكود المُعلَّق، علامات TODO — كل هذه تزيد العبء المعرفي للجلسة التالية. - -**بُعد البدء**: هل مسار البدء القياسي متاح؟ هل يمكن للجلسة التالية بدء العمل بدون تدخل يدوي؟ تهيئة البيئة، تحميل قاعدة الكود، اكتساب السياق، اختيار المهام — هذه المسارات يجب ألا تكون مكسورة. - -### "التنظيف لاحقًا" يعني عدم التنظيف أبدًا - -الفخ الذهني الأكثر شيوعًا هو "لا وقت للتنظيف في هذه الجلسة، سأفعل ذلك في المرة القادمة." لكن جلسة agent التالية لا تعرف ما تركته وراءك — ترى فوضى من الكود وحالة غير مؤكدة. ستقضي وقتًا كبيرًا في استنتاج "أي أجزاء من هذا الكود مقصودة وأيها مؤقتة." - -ما هو أسوأ، كل جلسة لها أهداف مهامها الخاصة. الجلسة الجديدة موجودة للقيام بعمل جديد، وليس لتنظيف فوضى الجلسة السابقة. ستتجاهل الفوضى وتبدأ عملاً جديدًا فوقها، مُدخلة فوضى أكثر فوق الفوضى. هذه حلقة التغذية الراجعة الإيجابية للإنتروبيا. - -## كيف تفعل ذلك بشكل صحيح - -### 1. الحالة النظيفة كمتطلب للإكمال - -حدد صراحةً في harness: **اكتمال الجلسة = المهمة تجتاز التحقق وفحص الحالة النظيفة يجتاز.** غياب أي منهما يعني أن الجلسة ليست مكتملة. اكتب في CLAUDE.md: - -``` -## Session Exit Checklist -- [ ] Build passes (npm run build) -- [ ] All tests pass (npm test) -- [ ] Feature list updated -- [ ] No debug code remaining (console.log, debugger, TODO) -- [ ] Standard startup path available (npm run dev) -``` - -### 2. استراتيجية التنظيف ذات الوضع المزدوج - -اجمع بين وضعي تنظيف: - -**التنظيف الفوري (في نهاية كل جلسة)**: تنظيف القطع الأثرية المؤقتة المُنشأة خلال الجلسة، تحديث حالة قائمة الميزات، التأكد من اجتياز البناء والاختبارات. هذا تنظيف "عد المرجع." - -**التنظيف الدوري (أسبوعي)**: فحص كامل للنظام — معالجة المشاكل الهيكلية المتراكمة، تحديث مستندات الجودة، تشغيل اختبارات قياسية لاكتشاف الانحراف. هذا تنظيف "التتبع." - -### 3. حافظ على مستند جودة - -مستند الجودة هو قطعة أثرية نشطة تسجل باستمرار درجات لكل وحدة: - -```markdown -# Quality Document - -## User Authentication Module (Quality: A) -- Verification passing: Yes -- Agent understandable: Yes -- Test stability: Stable -- Architecture boundaries: Compliant -- Code conventions: Followed - -## Payment Module (Quality: C) -- Verification passing: Partial (payment callback untested) -- Agent understandable: Difficult (logic spread across 3 files) -- Test stability: Unstable (2 flaky tests) -- Architecture boundaries: Violations present -- Code conventions: Partially followed -``` - -الجلسات الجديدة تقرأ هذا المستند وتعرف فورًا أين تعطي الأولوية. أصلح الوحدة ذات الدرجة الأدنى أولاً. - -### 4. بسّط harness بشكل دوري - -رؤية مهمة من Anthropic: **كل مكون من harness موجود لأن النموذج لا يستطيع فعل شيء بشكل موثوق بمفرده. لكن مع تحسن النماذج، تصبح هذه الافتراضات قديمة.** قيد كان أساسيًا قبل ثلاثة أشهر قد يكون عبئًا غير ضروري اليوم. - -الممارسة الموصى بها: كل شهر، اختر مكون harness واحد، عطّله مؤقتًا، وشغّل مهام قياسية. إذا لم تتراجع النتائج، أزله بشكل دائم. إذا تراجعت، استعده أو استبدله ببديل أخف. - -### 5. عمليات التنظيف يجب أن تكون متساوية القوة (Idempotent) - -يجب أن تكون نصوص التنظيف آمنة للتشغيل المتكرر: - -```bash -# Idempotent cleanup operations -rm -f /tmp/debug-*.log # -f ensures no error when files don't exist -git checkout -- .env.local # Restore to known state -npm run test # Verify cleanup didn't break anything -``` - -## حالة من العالم الحقيقي - -تطبيق Electron طُوّر باستخدام الوكلاء على مدار 12 أسبوعًا، مقارنة نهجين: - -**بدون استراتيجية تنظيف** (المجموعة الضابطة): الأسبوع 12، معدل اجتياز البناء 68%، معدل اجتياز الاختبارات 61%، بدء الجلسة الجديدة 60+ دقيقة، قطع أثرية قديمة 103. - -**مع استراتيجية تنظيف** (المجموعة التجريبية): فحص حالة نظيفة كامل في نهاية كل جلسة + حلقة تنظيف أسبوعية. الأسبوع 12، معدل اجتياز البناء 97%، معدل اجتياز الاختبارات 95%، بدء الجلسة الجديدة 9 دقائق، قطع أثرية قديمة 11. - -بنهاية الأسبوع 12، معدل اجتياز البناء للمجموعة التجريبية أعلى بـ 29 نقطة مئوية، معدل اجتياز الاختبارات أعلى بـ 34 نقطة مئوية، ووقت بدء الجلسة الجديدة أقل بنسبة 85%. - -## الخلاصات الأساسية - -- **الحالة النظيفة شرط ضروري لاكتمال الجلسة** — ليست تنظيفًا اختياريًا، بل جزء من "تعريف الانتهاء." -- **جميع الأبعاد الخمسة مطلوبة** — البناء، الاختبارات، التقدم، القطع الأثرية، البدء — كل منها يجب فحصه صراحةً. -- **مستندات الجودة تجعل صحة قاعدة الكود قابلة للتتبع** — لا يمكنك إصلاح إلا ما تعرف أنه يتراجع. -- **بسّط harness بشكل دوري** — مع تحسن قدرات النماذج، أزل القيود التي لم تعد ضرورية. -- **"التنظيف لاحقًا" يساوي عدم التنظيف أبدًا** — نمو الإنتروبيا هو الافتراضي؛ فقط التنظيف النشط يُبطئه. - -## قراءات إضافية - -- [Clean Code - Robert C. Martin](https://www.goodreads.com/book/show/3735293-clean-code) — مبادئ منهجية لنظافة الكود -- [Harness Engineering - OpenAI](https://openai.com/index/harness-engineering/) — القابلية للتكرار كمتطلب تصميم أساسي لـ harness -- [Effective Harnesses - Anthropic](https://www.anthropic.com/engineering/effective-harnesses-for-long-running-agents) — الدور الحاسم لعمليات خروج الجلسة النظيفة للموثوقية طويلة المدى -- [Programs, Life Cycles, and Laws of Software Evolution - Lehman](https://ieeexplore.ieee.org/document/1702314) — قوانين تطور البرمجيات تثبت أن تعقيد النظام ينمو حتمًا بدون صيانة نشطة - -## تمارين - -1. **قائمة فحص الحالة النظيفة**: صمم قائمة فحص خروج الجلسة لقاعدة الكود الخاصة بك تغطي جميع الأبعاد الخمسة. طبّقها عبر 5 جلسات متتالية وسجّل الانتهاكات لكل بُعد. - -2. **مقارنة قياسية**: استخدم مجموعة مهام ثابتة مع نوعين من harness (مع/بدون متطلبات الحالة النظيفة). قارن معدل الإكمال وعدد إعادة المحاولات ومعدل تسرب العيوب. - -3. **ممارسة تبسيط harness**: اختر مكون harness واحد، عطّله مؤقتًا، وشغّل مهام قياسية. قارن النتائج مع وبدونه. قرر ما إذا كنت ستبقيه أو تزيله أو تستبدله. diff --git a/courses/learn-harness-engineering/docs/ar/projects/index.md b/courses/learn-harness-engineering/docs/ar/projects/index.md deleted file mode 100644 index 72f7ae09..00000000 --- a/courses/learn-harness-engineering/docs/ar/projects/index.md +++ /dev/null @@ -1,23 +0,0 @@ -# مرحبًا بك في المشاريع - -هذا هو القسم العملي من Learn Harness Engineering. قراءة المحاضرات وحدها لا تكفي؛ عليك بناء البيئات بنفسك ومراقبة كيف يتصرف Codex أو Claude Code أو غيرهما من وكلاء الذكاء الاصطناعي تحت قواعد مختلفة. - -## نظرة عامة على المشاريع - -يضم هذا المساق 6 مشاريع عملية متدرجة تعلمك كيف تبني بيئة عمل وكيلية موثوقة من الصفر: - -1. **Prompt فقط مقابل القواعد أولًا**: قارن أداء الوكيل عند استخدام prompt فقط مقابل harness أساسي. -2. **Workspace قابل للقراءة من الوكيل**: تعلم كيف تنظم المستودع ليكون مناسبًا للذكاء الاصطناعي، وكيف تنشئ آليات handoff. -3. **استمرارية متعددة الجلسات**: صمم ملفات حالة وسكربتات تهيئة حتى يستطيع الوكيل استئناف العمل بسلاسة بين الجلسات. -4. **Feedback في runtime والتحكم في النطاق**: أدخل أدوات تسمح للوكيل باختبار كوده وتصحيح الأخطاء أثناء التنفيذ. -5. **التحقق الذاتي وفصل الأدوار**: ابن آلية مراجعة مستقلة لمنع الهلوسة وإعلان النجاح المبكر. -6. **Harness كامل (Capstone)**: اجمع بيئة عمل وكيلية نهائية قابلة للملاحظة وتعمل end-to-end. - -## كيفية المتابعة - -عادة يحتوي كل مجلد مشروع على: -- `starter/`: مساحة العمل التي تبدأ منها. -- `solution/`: تنفيذ مرجعي إذا علقت. -- تعليمات مهمة توضح الخلفية والأهداف المحددة. - -استخدم AI Coding Agent المفضل لديك، مثل Claude Code أو Cursor أو Trae، لإكمال المهام داخل مجلد `starter/`. diff --git a/courses/learn-harness-engineering/docs/ar/projects/project-01-baseline-vs-minimal-harness/index.md b/courses/learn-harness-engineering/docs/ar/projects/project-01-baseline-vs-minimal-harness/index.md deleted file mode 100644 index e1aac95e..00000000 --- a/courses/learn-harness-engineering/docs/ar/projects/project-01-baseline-vs-minimal-harness/index.md +++ /dev/null @@ -1,25 +0,0 @@ -[中文版本 →](../../../zh/projects/project-01-baseline-vs-minimal-harness/) - -> محاضرات مرتبطة: [المحاضرة 01. النماذج القوية لا تعني تنفيذًا موثوقًا](./../../lectures/lecture-01-why-capable-agents-still-fail/index.md) · [المحاضرة 02. ما معنى harness فعليًا](./../../lectures/lecture-02-what-a-harness-actually-is/index.md) -> ملفات القوالب: [templates/](https://github.com/walkinglabs/learn-harness-engineering/blob/main/docs/ar/resources/templates/) - -# المشروع 01. Prompt فقط مقابل القواعد أولًا: ما حجم الفرق؟ - -## ما الذي ستفعله - -ابنِ هيكلًا بسيطًا لتطبيق معرفة باستخدام Electron: نافذة فيها قائمة مستندات على اليسار، ولوحة Q&A على اليمين، ومجلد بيانات محلي. المهمة نفسها ليست معقدة. المعقد هو كيف تجعل الوكيل يكملها. - -ستنفذها مرتين. المرة الأولى: prompt فقط، دون تحضير. المرة الثانية: مع وضع `AGENTS.md` و `init.sh` و `feature_list.json` مسبقًا في المستودع. ثم قارن. - -جوهر هذا المشروع ليس كتابة الكود، بل قياس الفرق بين "قضاء 15 دقيقة في تحضير القواعد أولًا" و"ترك الوكيل يعمل مباشرة". - -## الأدوات - -- Claude Code أو Codex (اختر واحدًا واستخدمه في التشغيلين) -- Git (إدارة الفروع والمقارنة) -- Node.js + Electron (حزمة المشروع) -- مؤقت (لتسجيل مدة كل تشغيل) - -## آلية harness - -Harness بسيط: `AGENTS.md` + `init.sh` + `feature_list.json` diff --git a/courses/learn-harness-engineering/docs/ar/projects/project-02-agent-readable-workspace/index.md b/courses/learn-harness-engineering/docs/ar/projects/project-02-agent-readable-workspace/index.md deleted file mode 100644 index 08afcdb0..00000000 --- a/courses/learn-harness-engineering/docs/ar/projects/project-02-agent-readable-workspace/index.md +++ /dev/null @@ -1,22 +0,0 @@ -[中文版本 →](../../../zh/projects/project-02-agent-readable-workspace/) - -> محاضرات مرتبطة: [المحاضرة 03. اجعل المستودع مصدر الحقيقة الوحيد](./../../lectures/lecture-03-why-the-repository-must-become-the-system-of-record/index.md) · [المحاضرة 04. قسّم التعليمات عبر ملفات](./../../lectures/lecture-04-why-one-giant-instruction-file-fails/index.md) -> ملفات القوالب: [templates/](https://github.com/walkinglabs/learn-harness-engineering/blob/main/docs/ar/resources/templates/) - -# المشروع 02. اجعل المشروع قابلًا للقراءة واستأنف من حيث توقفت - -## ما الذي ستفعله - -أضف "قابلية القراءة" إلى المستودع حتى يستطيع وكيل جديد فهم بنية المشروع بسرعة، ومعرفة التقدم الحالي، واستئناف العمل. تحديدًا: نفذ استيراد المستندات، وعرض تفاصيل المستند، والحفظ المحلي، عبر جلستين. - -ستنفذه مرتين: الأولى دون مساعدة، والثانية مع وضع `ARCHITECTURE.md` و `PRODUCT.md` و `session-handoff.md` مسبقًا في المستودع. - -## الأدوات - -- Claude Code أو Codex -- Git -- Node.js + Electron - -## آلية harness - -Workspace قابل للقراءة من الوكيل + ملفات حالة دائمة diff --git a/courses/learn-harness-engineering/docs/ar/projects/project-03-multi-session-continuity/index.md b/courses/learn-harness-engineering/docs/ar/projects/project-03-multi-session-continuity/index.md deleted file mode 100644 index 6257bc78..00000000 --- a/courses/learn-harness-engineering/docs/ar/projects/project-03-multi-session-continuity/index.md +++ /dev/null @@ -1,22 +0,0 @@ -[中文版本 →](../../../zh/projects/project-03-multi-session-continuity/) - -> محاضرات مرتبطة: [المحاضرة 05. حافظ على السياق حيًا عبر الجلسات](./../../lectures/lecture-05-why-long-running-tasks-lose-continuity/index.md) · [المحاضرة 06. هيئ قبل كل جلسة وكيل](./../../lectures/lecture-06-why-initialization-needs-its-own-phase/index.md) -> ملفات القوالب: [templates/](https://github.com/walkinglabs/learn-harness-engineering/blob/main/docs/ar/resources/templates/) - -# المشروع 03. أبقِ الوكيل يعمل بعد إعادة تشغيل الجلسات - -## ما الذي ستفعله - -أضف تحكمًا في النطاق وبوابات تحقق إلى الوكيل. نفذ تقسيم المستندات، واستخراج metadata، وعرض تقدم الفهرسة، وتدفق Q&A قائم على الاستشهادات. استخدم `feature_list.json` لتتبع حالة الميزات: ميزة واحدة كل مرة، ولا تضع `pass` دون دليل تحقق. - -ستنفذه مرتين: الأولى دون قيود، والثانية مع تطبيق صارم. - -## الأدوات - -- Claude Code أو Codex -- Git -- Node.js + Electron - -## آلية harness - -سجل تقدم + handoff الجلسة + استمرارية متعددة الجلسات diff --git a/courses/learn-harness-engineering/docs/ar/projects/project-04-incremental-indexing/index.md b/courses/learn-harness-engineering/docs/ar/projects/project-04-incremental-indexing/index.md deleted file mode 100644 index 5682447c..00000000 --- a/courses/learn-harness-engineering/docs/ar/projects/project-04-incremental-indexing/index.md +++ /dev/null @@ -1,22 +0,0 @@ -[中文版本 →](../../../zh/projects/project-04-incremental-indexing/) - -> محاضرات مرتبطة: [المحاضرة 07. ارسم حدود مهمة واضحة للوكلاء](./../../lectures/lecture-07-why-agents-overreach-and-under-finish/index.md) · [المحاضرة 08. استخدم قوائم الميزات لتقييد ما يفعله الوكيل](./../../lectures/lecture-08-why-feature-lists-are-harness-primitives/index.md) -> ملفات القوالب: [templates/](https://github.com/walkinglabs/learn-harness-engineering/blob/main/docs/ar/resources/templates/) - -# المشروع 04. استخدم feedback في runtime لتصحيح سلوك الوكيل - -## ما الذي ستفعله - -أضف قابلية ملاحظة runtime، مثل سجلات التشغيل، وسجلات الاستيراد/الفهرسة، وحالات الأخطاء، بالإضافة إلى قيود معمارية لمنع اختراق الحدود بين الطبقات. ازرع خطأ runtime ليصلحه الوكيل. - -ستنفذه مرتين: الأولى دون سجلات أو قيود، والثانية بالأدوات والقواعد المناسبة. - -## الأدوات - -- Claude Code أو Codex -- Git -- Node.js + Electron - -## آلية harness - -Feedback في runtime + تحكم في النطاق + فهرسة تدريجية diff --git a/courses/learn-harness-engineering/docs/ar/projects/project-05-grounded-qa-verification/index.md b/courses/learn-harness-engineering/docs/ar/projects/project-05-grounded-qa-verification/index.md deleted file mode 100644 index 74d618a4..00000000 --- a/courses/learn-harness-engineering/docs/ar/projects/project-05-grounded-qa-verification/index.md +++ /dev/null @@ -1,22 +0,0 @@ -[中文版本 →](../../../zh/projects/project-05-grounded-qa-verification/) - -> محاضرات مرتبطة: [المحاضرة 09. امنع الوكلاء من إعلان النجاح مبكرًا](./../../lectures/lecture-09-why-agents-declare-victory-too-early/index.md) · [المحاضرة 10. التشغيل الكامل للـ pipeline فقط يُعد تحققًا حقيقيًا](./../../lectures/lecture-10-why-end-to-end-testing-changes-results/index.md) -> ملفات القوالب: [templates/](https://github.com/walkinglabs/learn-harness-engineering/blob/main/docs/ar/resources/templates/) - -# المشروع 05. اجعل الوكيل يتحقق من عمله - -## ما الذي ستفعله - -نفذ فصل الأدوار: generator ينفذ، و evaluator يراجع، وربما planner. شغّل ثلاث مرات لقياس أثر كل دور مضاف. - -اختر ترقية جوهرية لميزة، مثل محادثة متعددة الأدوار، أو إعادة تصميم لوحة الاستشهادات، أو فلترة المستندات، وحافظ عليها ثابتة في كل التشغيلات. - -## الأدوات - -- Claude Code أو Codex -- Git -- Node.js + Electron - -## آلية harness - -تحقق ذاتي + Q&A مستند إلى أدلة + إكمال مبني على الدليل diff --git a/courses/learn-harness-engineering/docs/ar/projects/project-06-runtime-observability-and-debugging/index.md b/courses/learn-harness-engineering/docs/ar/projects/project-06-runtime-observability-and-debugging/index.md deleted file mode 100644 index 3df3aacc..00000000 --- a/courses/learn-harness-engineering/docs/ar/projects/project-06-runtime-observability-and-debugging/index.md +++ /dev/null @@ -1,25 +0,0 @@ -[中文版本 →](../../../zh/projects/project-06-runtime-observability-and-debugging/) - -> محاضرات مرتبطة: [المحاضرة 11. اجعل runtime الخاص بالوكيل قابلًا للملاحظة](./../../lectures/lecture-11-why-observability-belongs-inside-the-harness/index.md) · [المحاضرة 12. Handoff نظيف في نهاية كل جلسة](./../../lectures/lecture-12-why-every-session-must-leave-a-clean-state/index.md) -> ملفات القوالب: [templates/](https://github.com/walkinglabs/learn-harness-engineering/blob/main/docs/ar/resources/templates/) - -# المشروع 06. ابنِ harness كاملًا للوكيل (Capstone) - -## ما الذي ستفعله - -هذا هو مشروع التخرج. اجمع كل ما تعلمته في المشاريع الخمسة الأولى، وشغّل benchmark كاملًا، ثم نفذ جولة تنظيف للتحقق من أن الجودة قابلة للصيانة. - -استخدم مجموعة مهام ثابتة متعددة الميزات تغطي شريحة منتج كاملة: استيراد المستندات، الفهرسة، Q&A مع الاستشهادات، قابلية ملاحظة runtime، وحالة مستودع قابلة للقراءة والاستئناف. ابدأ بتشغيل baseline مع harness ضعيف، ثم شغّل أقوى harness لديك، ثم نظف وأعد التشغيل. أخيرًا، نفذ تجربة ablation للـ harness: أزل مكونًا واحدًا كل مرة وانظر أيها مهم فعليًا. - -## الأدوات - -- Claude Code أو Codex -- Git -- Node.js + Electron -- قالب وثيقة جودة -- rubric للمقيّم -- كل مكونات harness المتراكمة من المشاريع الخمسة الأولى - -## آلية harness - -Harness كامل: كل الآليات + قابلية الملاحظة + دراسة ablation diff --git a/courses/learn-harness-engineering/docs/ar/resources/index.md b/courses/learn-harness-engineering/docs/ar/resources/index.md deleted file mode 100644 index 2764cd74..00000000 --- a/courses/learn-harness-engineering/docs/ar/resources/index.md +++ /dev/null @@ -1,50 +0,0 @@ -# مكتبة الموارد العربية - -يحول هذا المجلد طرق المساق إلى قوالب جاهزة للنسخ ومراجع مختصرة يمكنك استخدامها في مستودع حقيقي. - -## متى تستخدمها - -ابدأ هنا عندما تريد من Codex أو Claude Code أو وكيل برمجة آخر أن يعمل عبر عدة جلسات دون إعادة استنتاج الإعداد، والحالة، والنطاق في كل مرة. - -تكون مفيدة خصوصًا عندما: - -- يمتد العمل عبر عدة جلسات -- تكون الميزات كثيرة ويسهل تركها نصف مكتملة -- يميل الوكلاء إلى إعلان النجاح مبكرًا -- يعاد اكتشاف خطوات التشغيل في كل مرة - -## ابدأ من هنا - -لإعداد بسيط، ابدأ بـ: - -- تعليمات الجذر: [`templates/AGENTS.md`](./templates/AGENTS.md) أو [`templates/CLAUDE.md`](./templates/CLAUDE.md) -- حالة الميزات: [`templates/feature_list.json`](./templates/feature_list.json) -- سجل التقدم: [`templates/claude-progress.md`](./templates/claude-progress.md) -- مرجع سكربت bootstrap: `docs/ar/resources/templates/init.sh` - -ثم أضف: - -- handoff الجلسة: [`templates/session-handoff.md`](./templates/session-handoff.md) -- قائمة تحقق للخروج النظيف: [`templates/clean-state-checklist.md`](./templates/clean-state-checklist.md) -- rubric للمقيّم: [`templates/evaluator-rubric.md`](./templates/evaluator-rubric.md) - -إذا أردت بنية مستودع أكثر اكتمالًا على نمط OpenAI من مقالة "Harness engineering"، استخدم الحزمة المتقدمة: - -- [`openai-advanced/index.md`](./openai-advanced/index.md) - -## بنية المكتبة - -- [`templates/`](./templates/index.md): قوالب لنسخها إلى مستودع حقيقي -- [`reference/`](./reference/index.md): ملاحظات منهجية، تدفق بدء التشغيل، وخرائط أنماط الفشل -- [`openai-advanced/`](./openai-advanced/index.md): هيكل مستودع متقدم، وثائق system-of-record، وقوالب حوكمة agent-first - -## الحزمة البسيطة الموصى بها - -- `AGENTS.md` أو `CLAUDE.md` -- `feature_list.json` -- `claude-progress.md` -- `init.sh` - -هذه الملفات الأربعة كافية لجعل معظم workflows الخاصة بالوكلاء أكثر استقرارًا بشكل واضح. - -عندما ينمو المستودع إلى نظام طويل الأمد بعدة نطاقات، وخطط نشطة، وتقييم جودة، وسياسات موثوقية، انتقل إلى حزمة [`openai-advanced/`](./openai-advanced/index.md) بدلًا من مد الحزمة البسيطة أكثر من اللازم. diff --git a/courses/learn-harness-engineering/docs/ar/resources/openai-advanced/index.md b/courses/learn-harness-engineering/docs/ar/resources/openai-advanced/index.md deleted file mode 100644 index 95de7d01..00000000 --- a/courses/learn-harness-engineering/docs/ar/resources/openai-advanced/index.md +++ /dev/null @@ -1,35 +0,0 @@ -# حزمة OpenAI المتقدمة - -تحتوي هذه المجلد على شكل المستودع الأكثر توجيهاً الموصوف في -مقال OpenAI "Harness engineering: leveraging Codex in an agent-first world" -كملفات بداية جاهزة للنسخ. - -استخدم هذه الحزمة عندما لا يكفي harness البسيط بعد ومستودعك -يحتاج الآن إلى: - -- ملف `AGENTS.md` قصير بنمط التوجيه -- وثائق نظام السجل الدائم داخل المستودع -- خطط تنفيذ نشطة ومكتملة -- ملفات سياسة صريحة للمنتج والموثوقية والأمان والواجهة الأمامية -- تسجيل الجودة حسب مجال المنتج والطبقة المعمارية -- مجلدات مراجع صديقة للنماذج -- إجراءات تشغيل قياسية للهندسة المعمارية والتقاط المعرفة والتحقق في وقت التشغيل - -## كيفية التبني - -1. ابدأ بالحزمة البسيطة إذا كان مستودعك لا يزال صغيراً. -2. انسخ الملفات في [`repo-template/`](./repo-template/index.md) إلى - مستودعك الخاص عندما تحتاج هيكلاً أقوى. -3. أبقِ `AGENTS.md` قصيراً. تعامل معه كموّجه إلى الوثائق الأعمق، لا كموسوعة. -4. حدّث وثائق الجودة والموثوقية والخطط كجزء من العمل العادي، لا كيوم تنظيف منفصل. -5. أبقِ المنتجات المُنشأة والمراجع الخارجية صريحة حتى يتمكن الوكلاء من إيجادها بدون الاعتماد على سجل المحادثة. - -## مبادئ التصميم - -- نقطة دخول قصيرة، وثائق أعمق مرتبطة -- المستودع كسجل للنظام -- الفحوصات الميكانيكية أفضل من القواعد المحفوظة -- الخطط وتاريخ الجودة بجانب الكود -- التنظيف والتبسيط مسؤوليات من الدرجة الأولى - -هذه الحزمة متعمدة التوجيه، لكن يجب تكييفها لمشروعك بدلاً من نسخها بشكل أعمى. diff --git a/courses/learn-harness-engineering/docs/ar/resources/openai-advanced/repo-template/AGENTS.md b/courses/learn-harness-engineering/docs/ar/resources/openai-advanced/repo-template/AGENTS.md deleted file mode 100644 index ceda43ea..00000000 --- a/courses/learn-harness-engineering/docs/ar/resources/openai-advanced/repo-template/AGENTS.md +++ /dev/null @@ -1,57 +0,0 @@ -# AGENTS.md - -هذا المستودع مُحسَّن للعمل طويل الأمد لوكلاء البرمجة. أبقِ هذا الملف -قصيرًا. استخدمه كطبقة توجيه نحو وثائق السجل المرجعي للنظام، وليس كملف -تعليمات ضخم. - -## سير عمل بدء التشغيل - -قبل تغيير الكود: - -1. تأكد من جذر المستودع باستخدام `pwd`. -2. اقرأ `ARCHITECTURE.md` للاطلاع على خريطة النظام الحالية وقواعد التبعيات الثابتة. -3. اقرأ `docs/QUALITY_SCORE.md` لمعرفة المجالات أو الطبقات الأضعف. -4. اقرأ `docs/PLANS.md`، ثم افتح الخطة النشطة التي تعمل وفقًا لها. -5. اقرأ مواصفات المنتج ذات الصلة في `docs/product-specs/`. -6. شغّل مسار الإعداد والتحقق القياسي لهذا المستودع. -7. إذا كان التحقق الأساسي يفشل، أصلح الأساس قبل إضافة نطاق جديد. - -## خريطة التوجيه - -- `ARCHITECTURE.md`: خريطة المجالات، نموذج الطبقات، قواعد التبعيات -- `docs/design-docs/index.md`: قرارات التصميم والمعتقدات الأساسية -- `docs/product-specs/index.md`: سلوكيات المنتج الحالية وأهداف القبول -- `docs/PLANS.md`: دورة حياة الخطط وسياسة خطط التنفيذ -- `docs/QUALITY_SCORE.md`: صحة مجالات المنتج والطبقات -- `docs/RELIABILITY.md`: إشارات التشغيل، معايير الأداء، وتوقعات إعادة التشغيل -- `docs/SECURITY.md`: الأسرار، الحماية الرملية، البيانات، وقواعد الإجراءات الخارجية -- `docs/FRONTEND.md`: قيود واجهة المستخدم، قواعد نظام التصميم، وفحوصات إمكانية الوصول - -## عقد العمل - -- اعمل وفق خطة محددة أو شريحة ميزة واحدة في كل مرة. -- لا تعتبر العمل منتهيًا من فحص الكود فقط؛ الأدلة القابلة للتنفيذ مطلوبة. -- إذا غيّرت سلوكًا، حدِّث وثائق المنتج أو الخطة أو الموثوقية المطابقة في نفس الجلسة. -- إذا لاحظت ملاحظات مراجعة متكررة،حوّلها إلى قاعدة آلية أو فحص أو أداة تنسيق بدلاً من إعادة شرحها في المحادثة. -- أبقِ المواد المُولَّدة في `docs/generated/` والمراجع المصدرية في `docs/references/`. -- فضِّل إضافة وثائق صغيرة وحالية بدلاً من توسيع هذا الملف. - -## تعريف الإنجاز - -التغيير يُعتبر منتهيًا فقط عندما يكون جميع ما يلي صحيحًا: - -- السلوك المستهدف مُنفَّذ -- التحقق المطلوب تم تشغيله فعلًا -- الأدلة مرتبطة من الخطة أو وثيقة الجودة ذات الصلة -- الوثائق المتأثرة لا تزال محدثة -- المستودع يمكن إعادة تشغيله بنظافة من مسار بدء التشغيل القياسي - -## نهاية الجلسة - -قبل إنهاء جلسة: - -1. حدِّث خطة التنفيذ النشطة. -2. حدِّث `docs/QUALITY_SCORE.md` إذا تغيّر أي مجال أو طبقة بشكل ملحوظ. -3. سجّل الديون الجديدة في `docs/exec-plans/tech-debt-tracker.md` إذا أجلتها. -4. انقل الخطط المنتهية إلى `docs/exec-plans/completed/` عند الاقتضاء. -5. اترك المستودع في حالة قابلة لإعادة التشغيل مع إجراء تالٍ واضح. diff --git a/courses/learn-harness-engineering/docs/ar/resources/openai-advanced/repo-template/ARCHITECTURE.md b/courses/learn-harness-engineering/docs/ar/resources/openai-advanced/repo-template/ARCHITECTURE.md deleted file mode 100644 index 302375b2..00000000 --- a/courses/learn-harness-engineering/docs/ar/resources/openai-advanced/repo-template/ARCHITECTURE.md +++ /dev/null @@ -1,57 +0,0 @@ -# ARCHITECTURE.md - -هذا الملف هو الخريطة العلوية للنظام. يجب أن يظل موجزًا ويُشير إلى وثائق -أعمق عند الحاجة. - -## شكل النظام - -- المنتج: `[استبدل باسم المنتج]` -- سير عمل المستخدم الرئيسي: `[استبدل بسير العمل الرئيسي]` -- أسطح التشغيل: `[desktop / web / cli / services / workers]` -- مصدر الحقيقة لسلوك المنتج: `docs/product-specs/` - -## خريطة المجالات - -| المجال | الغرض | نقاط الدخول الرئيسية | المواصفة ذات الصلة | -|--------|---------|----------------------|--------------| -| `[domain-a]` | `[ما يملكه]` | `[modules / routes / commands]` | `[مسار المواصفة]` | -| `[domain-b]` | `[ما يملكه]` | `[modules / routes / commands]` | `[مسار المواصفة]` | - -## نموذج الطبقات - -استخدم نموذجًا اتجاهيًا ثابتًا حتى لا يخترع الوكلاء بنية عشوائية: - -`Types -> Config -> Repo -> Service -> Runtime -> UI` - -المخاوف المشتركة يجب أن تدخل من خلال حدود موفّر أو محوّل صريحة بدلاً -من الوصول المباشر عبر الطبقات. - -## قواعد التبعيات الثابتة - -- الطبقات السفلية يجب ألا تعتمد على الطبقات العليا. -- واجهة المستخدم يجب ألا تتجاوز عقود التشغيل أو الخدمات. -- الوصول إلى البيانات يجب أن يتم من خلال مستودعات أو محوّلات معادلة. -- الأدوات المساعدة المشتركة يجب أن تظل عامة وألا تتراكم فيها منطق المجال. -- التبعيات الجديدة يجب تبريرها في الخطة أو وثيقة التصميم المطابقة. - -## الواجهات المشتركة - -| المخاوف | الحد المعتمد | ملاحظات | -|--------|-------------------|-------| -| التسجيل والتتبع | `[مسار الموفر / الأداة المساعدة]` | `[منظم فقط، بدون استخدام console عشوائي]` | -| المصادقة | `[مسار الموفر]` | `[قواعد token/session]` | -| واجهات برمجة التطبيقات الخارجية | `[مسار العميل أو الموفر]` | `[إرشادات حد السرعة / إعادة المحاولة]` | -| علامات الميزات | `[حد العلم]` | `[الملكية]` | - -## النقاط الساخنة الحالية - -- `[المنطقة الأصعب على الوكلاء تغييرها بأمان]` -- `[منطقة بحدود ضعيفة أو اختبارات هشة]` - -## قائمة فحص التغيير - -عندما تلمس كودًا يتعلق بالبنية: - -1. حدِّث هذا الملف إذا تغيرت خريطة المجالات أو الحدود المسموح بها. -2. حدِّث وثيقة التصميم ذات الصلة في `docs/design-docs/` إذا تغير المنطق. -3. أضف أو حدِّث فحصًا قابلًا للتنفيذ إذا كانت القاعدة يجب تُفرض آليًا. diff --git a/courses/learn-harness-engineering/docs/ar/resources/openai-advanced/repo-template/docs/DESIGN.md b/courses/learn-harness-engineering/docs/ar/resources/openai-advanced/repo-template/docs/DESIGN.md deleted file mode 100644 index c1d72e9f..00000000 --- a/courses/learn-harness-engineering/docs/ar/resources/openai-advanced/repo-template/docs/DESIGN.md +++ /dev/null @@ -1,27 +0,0 @@ -# DESIGN.md - -هذا الملف هو نقطة دخول التصميم. أبقِه موجزًا واستخدمه للتوجيه نحو -الملفات الأكثر تفصيلًا تحت `docs/design-docs/`. - -## الغرض - -تسجيل قرارات تصميم المنتج والنظام الدائمة التي يجب أن تبقى بعد -محادثة واحدة أو سباق تطوير واحد أو ذاكرة مراجع واحدة. - -## اقرأ هذا عندما - -- تحتاج إلى فلسفة التصميم الحالية -- أنت على وشك تقديم نمط جديد -- تحتاج إلى معرفة قرارات التصميم المُستقرة مقابل تلك التي لا تزال مفتوحة - -## وثائق التصميم المعتمدة - -- `docs/design-docs/index.md`: فهرس الوثائق المقبولة والمقترحة والمُهمَلة -- `docs/design-docs/core-beliefs.md`: معتقدات المشروع الموجهة للوكلاء أولًا - -## قواعد التصميم - -- أبقِ وثائق التصميم صغيرة وحالية. -- فضِّل وثيقة واحدة لكل مجال قرار. -- اربط وثائق التصميم من الخطط والمواصفات عندما يعتمد التغيير عليها. -- إذا أصبحت قاعدة تصميم حرجة تشغيليًا،رتقها إلى فحص آلي أو حدِّث `ARCHITECTURE.md`. diff --git a/courses/learn-harness-engineering/docs/ar/resources/openai-advanced/repo-template/docs/FRONTEND.md b/courses/learn-harness-engineering/docs/ar/resources/openai-advanced/repo-template/docs/FRONTEND.md deleted file mode 100644 index b0ded408..00000000 --- a/courses/learn-harness-engineering/docs/ar/resources/openai-advanced/repo-template/docs/FRONTEND.md +++ /dev/null @@ -1,24 +0,0 @@ -# FRONTEND.md - -يحدد هذا الملف توقعات واجهة الأمامية المستقرة حتى لا يخترع الوكلاء أنماط -واجهة مستخدم بشكل غير متوقع. - -## مبادئ واجهة المستخدم - -- حسِّن الوضوح قبل الإبداع. -- أبقِ تدفقات التفاعل قابلة للاكتشاف وقابلة لإعادة التشغيل. -- فضِّل عددًا صغيرًا من المكونات القابلة لإعادة الاستخدام على المتغيرات الفريدة. -- فحوصات إمكانية الوصول هي جزء من التحقق العادي، وليست أعمال تشطيب. - -## الحواجز الوقائية - -- وثِّق نظام التصميم أو مكتبة المكونات في `docs/references/`. -- سجِّل حالات واجهة المستخدم الرئيسية: فارغ، تحميل، نجاح، خطأ، إعادة محاولة. -- أبقِ النصوص وسلوك لوحة المفاتيح والتسلسل الهرمي البصري متسقة عبر التدفقات. -- عند إصلاح خطأ في واجهة المستخدم، أضف أو حدِّث خطوة التحقق المطابقة. - -## توقعات التحقق - -- التقط أدلة لرحلات المستخدم الحرجة. -- سجِّل خطوات التحقق في المتصفح أو وقت التشغيل في الخطة ذات الصلة. -- إذا كانت الانحدارات البصرية شائعة، قم بتوحيد فحوصات لقطات الشاشة أو DOM. diff --git a/courses/learn-harness-engineering/docs/ar/resources/openai-advanced/repo-template/docs/PLANS.md b/courses/learn-harness-engineering/docs/ar/resources/openai-advanced/repo-template/docs/PLANS.md deleted file mode 100644 index 533f98c6..00000000 --- a/courses/learn-harness-engineering/docs/ar/resources/openai-advanced/repo-template/docs/PLANS.md +++ /dev/null @@ -1,34 +0,0 @@ -# PLANS.md - -يحدد هذا الملف كيفية إنشاء خطط التنفيذ وتحديثها وإكمالها وأرشفتها. - -## متى تكون الخطة مطلوبة - -أنشئ خطة تنفيذ عندما يكون العمل: - -- يمتد عبر أكثر من جلسة واحدة -- يغيّر أكثر من نظام فرعي واحد -- يحمل مخاطر تحقق أو طرح غير بسيطة -- يعتمد على قرارات مفتوحة يجب تسجيلها - -## مواقع الخطط - -- `docs/exec-plans/active/`: الخطط التي تقود العمل حاليًا -- `docs/exec-plans/completed/`: الخطط المنتهية المحفوظة لسياق الوكلاء المستقبلي -- `docs/exec-plans/tech-debt-tracker.md`: العمل المؤجل والمتابعات - -## أقسام الخطة كحد أدنى - -- الهدف -- النطاق وخارج النطاق -- مسار التحقق -- المخاطر والعوائق -- سجل التقدم -- القرارات المفتوحة - -## قواعد التشغيل - -- يجب أن تحتوي الخطة النشطة الواحدة على خطوة حالية مملوكة بوضوح. -- حدِّث الخطة مع تقدم العمل؛ لا تعاملها كنص ثابت. -- إذا غيّر قرار مسار التنفيذ، سجّله في الخطة. -- انقل الخطط المنتهية إلى `completed/` حتى يتمكن الوكلاء من اكتشاف السياق السابق. diff --git a/courses/learn-harness-engineering/docs/ar/resources/openai-advanced/repo-template/docs/PRODUCT_SENSE.md b/courses/learn-harness-engineering/docs/ar/resources/openai-advanced/repo-template/docs/PRODUCT_SENSE.md deleted file mode 100644 index 97818a49..00000000 --- a/courses/learn-harness-engineering/docs/ar/resources/openai-advanced/repo-template/docs/PRODUCT_SENSE.md +++ /dev/null @@ -1,26 +0,0 @@ -# PRODUCT_SENSE.md - -يلتقط هذا الملف أحكام المنتج الدائمة التي لا يمكن للوكلاء استنتاجها -بشكل موثوق من الكود وحده. - -## جوهر المنتج - -- المستخدم الأساسي: `[استبدل]` -- المهمة المطلوب إنجازها: `[استبدل]` -- الإحباط الرئيسي المراد إزالته: `[استبدل]` -- مستوى الجودة للقبول: `[استبدل]` - -## قواعد المنتج - -- فضِّل الموثوقية المرئية للمستخدم على عدد الميزات. -- تعامل مع السلوك الغامض كفجوة في المواصفات، وليس كإذن للتخمين. -- إذا غيّر التنفيذ ما يراه المستخدمون أو يثقون به، حدِّث المواصفة المطابقة. -- استخدم مواصفات المنتج للتدفقات المحددة، واستخدم هذا الملف لأولويات - المنتج المشتركة. - -## أنماط الرفض - -- إجراءات مدمرة مخفية -- فشل صامت بدون تغذية راجعة للمستخدم -- مصدر حقيقة غير واضح للحالة المرئية -- ميزات لا يمكن شرحها في جملة واحدة diff --git a/courses/learn-harness-engineering/docs/ar/resources/openai-advanced/repo-template/docs/QUALITY_SCORE.md b/courses/learn-harness-engineering/docs/ar/resources/openai-advanced/repo-template/docs/QUALITY_SCORE.md deleted file mode 100644 index 7b0a6a2c..00000000 --- a/courses/learn-harness-engineering/docs/ar/resources/openai-advanced/repo-template/docs/QUALITY_SCORE.md +++ /dev/null @@ -1,39 +0,0 @@ -# QUALITY_SCORE.md - -يتتبع هذا المستند ما إذا كان المستودع يزداد قوة أو ضعفًا عبر الوقت. - -## مقياس الدرجات - -- `A`: مُتحقَّق، مقروء، مستقر، الحدود مُطبَّقة -- `B`: يعمل مع فجوات طفيفة -- `C`: يعمل جزئيًا، ارتباك أو عدم استقرار ملحوظ -- `D`: معطل، غير آمن، أو غير واضح هيكليًا - -## مجالات المنتج - -| المجال | الدرجة | التحقق | قابلية قراءة الوكيل | استقرار الاختبار | الفجوات الرئيسية | آخر تحديث | -|--------|-------|-------------|-----------------|---------------|----------|-------------| -| `[domain-a]` | - | - | - | - | - | - | -| `[domain-b]` | - | - | - | - | - | - | -| `[domain-c]` | - | - | - | - | - | - | - -## الطبقات المعمارية - -| الطبقة | الدرجة | تطبيق الحدود | قابلية قراءة الوكيل | الفجوات الرئيسية | آخر تحديث | -|-------|-------|---------------------|-----------------|----------|-------------| -| Types | - | - | - | - | - | -| Services | - | - | - | - | - | -| Runtime | - | - | - | - | - | -| UI | - | - | - | - | - | - -## لقطات المعايير - -| التاريخ | متغير الهارنيس | معدل الإنجاز | إعادة المحاولات | العيوب قبل المراجعة | ملاحظات | -|------|-----------------|----------------|--------|-----------------------|------| -| YYYY-MM-DD | `[baseline / improved / simplified]` | - | - | - | - | - -## سجل التبسيط - -| التاريخ | المكون المُزال | النتيجة | القرار | -|------|-------------------|---------|----------| -| YYYY-MM-DD | `[component]` | `[degraded / unchanged]` | `[restore / keep removed]` | diff --git a/courses/learn-harness-engineering/docs/ar/resources/openai-advanced/repo-template/docs/RELIABILITY.md b/courses/learn-harness-engineering/docs/ar/resources/openai-advanced/repo-template/docs/RELIABILITY.md deleted file mode 100644 index 7822717f..00000000 --- a/courses/learn-harness-engineering/docs/ar/resources/openai-advanced/repo-template/docs/RELIABILITY.md +++ /dev/null @@ -1,32 +0,0 @@ -# RELIABILITY.md - -يحدد هذا الملف كيف يثبت النظام أنه سليم وقابل لإعادة التشغيل. - -## المسارات القياسية - -- الإعداد: `[command]` -- التحقق: `[command]` -- بدء التطبيق أو الخدمة: `[command]` -- التصحيح أو فحص وقت التشغيل: `[command]` - -## إشارات وقت التشغيل المطلوبة - -- سجلات منظمة لبدء التشغيل والتدفقات الحرجة -- فحوصات صحة الخدمات الرئيسية -- بيانات التتبع أو التوقيت للمسارات البطيئة عند التوفر -- حالات الخطأ المرئية للمستخدم للأعطال القابلة للاسترداد - -## الرحلات الذهبية - -- `[journey 1]` -- `[journey 2]` -- `[journey 3]` - -يجب أن يكون لكل رحلة ذهبية مسار تحقق قابل للتكرار وإشارات فشل واضحة. - -## قواعد الموثوقية - -- لا تكتمل أي ميزة إذا لم يتمكن النظام من إعادة التشغيل بنظافة بعدها. -- يجب أن تكون أعطال وقت التشغيل قابلة للتشخيص من إشارات محلية في المستودع. -- إذا ظهر نمط فشل متكرر، أضف معيارًا أو حاجزًا وقائيًا له. -- التنظيف جزء من الموثوقية، وليس اهتمامًا منفصلًا. diff --git a/courses/learn-harness-engineering/docs/ar/resources/openai-advanced/repo-template/docs/SECURITY.md b/courses/learn-harness-engineering/docs/ar/resources/openai-advanced/repo-template/docs/SECURITY.md deleted file mode 100644 index 1b5e980d..00000000 --- a/courses/learn-harness-engineering/docs/ar/resources/openai-advanced/repo-template/docs/SECURITY.md +++ /dev/null @@ -1,27 +0,0 @@ -# SECURITY.md - -يحدد هذا الملف قواعد الأمان والسلامة التي يجب ألا يخمنها الوكلاء. - -## الأسرار وبيانات الاعتماد - -- لا تُشفِّر الأسرار بشكل ثابت في المصدر أو الوثائق. -- وثِّق مسارات تحميل الأسرار المعتمدة هنا. -- قم بتنقيح الرموز ومفاتيح واجهة برمجة التطبيقات والبيانات الشخصية من السجلات ولقطات الشاشة. - -## المدخلات غير الموثوقة - -- تعامل مع المحتوى الخارجي على أنه غير موثوق حتى يتم التحقق منه. -- سجِّل حدود الجلب أو التنفيذ المسموح بها هنا. -- إذا كان هناك خطر حقن أوامر أو حقن أوامر، وثِّق الحاجز الوقائي. - -## الإجراءات الخارجية - -- اذكر الإجراءات التي تتطلب موافقة صريحة. -- سجِّل أي أوامر إنتاجية أو مدمرة التي يجب ألا يشغلها الوكلاء افتراضيًا. -- فضِّل سير العمل الآمن في الحماية الرملية للتصحيح والتحقق. - -## قواعد التبعيات والمراجعة - -- التبعيات الجديدة تحتاج تبريرًا في الخطة النشطة. -- التغييرات الحساسة أمنيًا تتطلب خطوات تحقق صريحة. -- تعليقات مراجعة الأمان المتكررة يجب أن تصبح فحوصات، وليس معرفة قبلية. diff --git a/courses/learn-harness-engineering/docs/ar/resources/openai-advanced/repo-template/docs/design-docs/core-beliefs.md b/courses/learn-harness-engineering/docs/ar/resources/openai-advanced/repo-template/docs/design-docs/core-beliefs.md deleted file mode 100644 index 17796a1b..00000000 --- a/courses/learn-harness-engineering/docs/ar/resources/openai-advanced/repo-template/docs/design-docs/core-beliefs.md +++ /dev/null @@ -1,9 +0,0 @@ -# المعتقدات الأساسية - -- المستودع هو سجل النظام المرجعي للوكلاء. -- `AGENTS.md` هو موجّه، وليس موسوعة. -- أدلة التحقق أهم من الثقة. -- مهمة واحدة محددة أفضل من العديد من المهام نصف المنتهية. -- التغذية الراجعة البشرية المتكررة يجب أن تصبح قواعد هارنيس قابلة لإعادة الاستخدام. -- التنظيف والتبسيط جزء من الشحن، وليس أفكارًا لاحقة. -- إذا لم يستطع وكيل اكتشاف حقيقة في المستودع، تعامل مع تلك الحقيقة على أنها غير متاحة تشغيليًا. diff --git a/courses/learn-harness-engineering/docs/ar/resources/openai-advanced/repo-template/docs/design-docs/index.md b/courses/learn-harness-engineering/docs/ar/resources/openai-advanced/repo-template/docs/design-docs/index.md deleted file mode 100644 index ea412657..00000000 --- a/courses/learn-harness-engineering/docs/ar/resources/openai-advanced/repo-template/docs/design-docs/index.md +++ /dev/null @@ -1,21 +0,0 @@ -# فهرس وثائق التصميم - -استخدم هذا الفهرس كخريطة قابلة للاكتشاف لتاريخ التصميم. - -## مقبولة - -- `core-beliefs.md`: معتقدات التشغيل الموجهة للوكلاء والمعايير الدائمة للمشروع - -## مقترحة - -- `[أضف مسارات وثائق التصميم الجديدة هنا]` - -## مُهمَلة - -- `[انقل وثائق التصميم القديمة أو المتجاوزة هنا مع روابط البدائل]` - -## قواعد الصيانة - -- يجب أن يكون لكل وثيقة تصميم مالك أو محفز تحديث. -- أزل الوثائق القديمة أو ضع علامة مهملة عليها بدلاً من تركها تنجرف. -- اربط خطط التنفيذ النشطة بوثائق التصميم التي تعتمد عليها. diff --git a/courses/learn-harness-engineering/docs/ar/resources/openai-advanced/repo-template/docs/exec-plans/active/index.md b/courses/learn-harness-engineering/docs/ar/resources/openai-advanced/repo-template/docs/exec-plans/active/index.md deleted file mode 100644 index cd00a2db..00000000 --- a/courses/learn-harness-engineering/docs/ar/resources/openai-advanced/repo-template/docs/exec-plans/active/index.md +++ /dev/null @@ -1,10 +0,0 @@ -# الخطط النشطة - -احتفظ بملف Markdown واحد لكل خطة تنفيذ نشطة في هذا المجلد. - -نمط اسم الملف المقترح: - -- `YYYY-MM-DD-short-topic.md` - -يجب أن تكون كل خطة نشطة حديثة بما يكفي لكي يتمكن وكيل جديد من استئناف -العمل من المستودع وحده. diff --git a/courses/learn-harness-engineering/docs/ar/resources/openai-advanced/repo-template/docs/exec-plans/completed/index.md b/courses/learn-harness-engineering/docs/ar/resources/openai-advanced/repo-template/docs/exec-plans/completed/index.md deleted file mode 100644 index 0d0aee78..00000000 --- a/courses/learn-harness-engineering/docs/ar/resources/openai-advanced/repo-template/docs/exec-plans/completed/index.md +++ /dev/null @@ -1,5 +0,0 @@ -# الخطط المكتملة - -انقل الخطط المنتهية هنا بدلاً من حذفها. الخطط المكتملة جزء من -سطح ذاكرة المستودع وتساعد عمليات الوكيل اللاحقة على فهم لماذا يبدو -الكود بالشكل الحالي. diff --git a/courses/learn-harness-engineering/docs/ar/resources/openai-advanced/repo-template/docs/exec-plans/tech-debt-tracker.md b/courses/learn-harness-engineering/docs/ar/resources/openai-advanced/repo-template/docs/exec-plans/tech-debt-tracker.md deleted file mode 100644 index 0c3a92c0..00000000 --- a/courses/learn-harness-engineering/docs/ar/resources/openai-advanced/repo-template/docs/exec-plans/tech-debt-tracker.md +++ /dev/null @@ -1,7 +0,0 @@ -# متتبع الديون التقنية - -استخدم هذا الملف للديون الحقيقية المعترف بها والمؤجلة عمدًا. - -| التاريخ | المجال | الدين | سبب التأجيل | الخطر | المحفز التالي | -|------|------|------|--------------|------|--------------| -| YYYY-MM-DD | `[area]` | `[debt]` | `[reason]` | `[risk]` | `[when to revisit]` | diff --git a/courses/learn-harness-engineering/docs/ar/resources/openai-advanced/repo-template/docs/generated/db-schema.md b/courses/learn-harness-engineering/docs/ar/resources/openai-advanced/repo-template/docs/generated/db-schema.md deleted file mode 100644 index 7d3f81ea..00000000 --- a/courses/learn-harness-engineering/docs/ar/resources/openai-advanced/repo-template/docs/generated/db-schema.md +++ /dev/null @@ -1,14 +0,0 @@ -# مخطط قاعدة البيانات - -استخدم هذا المجلد للمنتجات المُولَّدة أو المشتقة التي يجب أن يتمكن الوكلاء -من فحصها دون هندسة عكسية من الكود. - -## المصدر - -- مُولَّد من: `[command or source path]` -- آخر تحديث: `YYYY-MM-DD` - -## ملاحظات - -- لا تقم بتعديل الأقسام المُولَّدة يدويًا. -- أعد توليد هذا الملف عندما يتغير المخطط الأساسي. diff --git a/courses/learn-harness-engineering/docs/ar/resources/openai-advanced/repo-template/docs/product-specs/index.md b/courses/learn-harness-engineering/docs/ar/resources/openai-advanced/repo-template/docs/product-specs/index.md deleted file mode 100644 index 86413769..00000000 --- a/courses/learn-harness-engineering/docs/ar/resources/openai-advanced/repo-template/docs/product-specs/index.md +++ /dev/null @@ -1,13 +0,0 @@ -# فهرس مواصفات المنتج - -استخدم هذا المجلد لمواصفات سلوك واجهة المستخدم الحالية. - -## المواصفات النشطة - -- `new-user-onboarding.md` - -## القواعد - -- يجب أن تصف المواصفات سلوك المستخدم المرئي ومعايير القبول. -- إذا انحرف التنفيذ عن المواصفة، حدِّث أحدهما في نفس الجلسة. -- أبقِ هذا الفهرس محدثًا حتى يتمكن وكيل جديد من اكتشاف نطاق المنتج بسرعة. diff --git a/courses/learn-harness-engineering/docs/ar/resources/openai-advanced/repo-template/docs/product-specs/new-user-onboarding.md b/courses/learn-harness-engineering/docs/ar/resources/openai-advanced/repo-template/docs/product-specs/new-user-onboarding.md deleted file mode 100644 index 59c95585..00000000 --- a/courses/learn-harness-engineering/docs/ar/resources/openai-advanced/repo-template/docs/product-specs/new-user-onboarding.md +++ /dev/null @@ -1,26 +0,0 @@ -# تهيئة المستخدم الجديد - -## الهدف - -وصف تجربة التشغيل الأولى التي يجب أن يحصل عليها المستخدم الجديد. - -## شروط الدخول - -- `[الحالة قبل بدء التدفق]` - -## تدفق المستخدم - -1. `[الخطوة الأولى]` -2. `[الخطوة الثانية]` -3. `[الخطوة الثالثة]` - -## معايير القبول - -- `[النتيجة القابلة للملاحظة]` -- `[النتيجة القابلة للملاحظة]` -- `[النتيجة القابلة للملاحظة]` - -## حالات الفشل - -- `[خطأ قابل للاسترداد وتغذية راجعة للمستخدم]` -- `[حالة محظورة والخيار البديل]` diff --git a/courses/learn-harness-engineering/docs/ar/resources/openai-advanced/repo-template/docs/references/design-system-reference-llms.txt b/courses/learn-harness-engineering/docs/ar/resources/openai-advanced/repo-template/docs/references/design-system-reference-llms.txt deleted file mode 100644 index 6f1c612c..00000000 --- a/courses/learn-harness-engineering/docs/ar/resources/openai-advanced/repo-template/docs/references/design-system-reference-llms.txt +++ /dev/null @@ -1,9 +0,0 @@ -Purpose: store model-friendly reference material for the design system. - -Suggested contents: -- component naming rules -- spacing and typography tokens -- state variants -- accessibility expectations - -Keep this file concise and refresh it when the upstream design system changes. diff --git a/courses/learn-harness-engineering/docs/ar/resources/openai-advanced/repo-template/docs/references/nixpacks-llms.txt b/courses/learn-harness-engineering/docs/ar/resources/openai-advanced/repo-template/docs/references/nixpacks-llms.txt deleted file mode 100644 index c30cc9c2..00000000 --- a/courses/learn-harness-engineering/docs/ar/resources/openai-advanced/repo-template/docs/references/nixpacks-llms.txt +++ /dev/null @@ -1,8 +0,0 @@ -Purpose: store a clean agent-readable extract of the deployment or packaging -rules your repository depends on. - -Suggested contents: -- build entrypoints -- runtime assumptions -- environment variable expectations -- common failure signatures diff --git a/courses/learn-harness-engineering/docs/ar/resources/openai-advanced/repo-template/docs/references/uv-llms.txt b/courses/learn-harness-engineering/docs/ar/resources/openai-advanced/repo-template/docs/references/uv-llms.txt deleted file mode 100644 index f2ceba9a..00000000 --- a/courses/learn-harness-engineering/docs/ar/resources/openai-advanced/repo-template/docs/references/uv-llms.txt +++ /dev/null @@ -1,8 +0,0 @@ -Purpose: store a compact reference for your Python package and environment -workflow when `uv` or similar tooling matters to the repo. - -Suggested contents: -- install and sync commands -- lockfile policy -- virtualenv expectations -- verification commands diff --git a/courses/learn-harness-engineering/docs/ar/resources/openai-advanced/repo-template/index.md b/courses/learn-harness-engineering/docs/ar/resources/openai-advanced/repo-template/index.md deleted file mode 100644 index 578ab5fd..00000000 --- a/courses/learn-harness-engineering/docs/ar/resources/openai-advanced/repo-template/index.md +++ /dev/null @@ -1,24 +0,0 @@ -# قالب المستودع المتقدم - -انسخ هذه البداية إلى مستودع حقيقي عندما تريد سطح توثيق -بنمط OpenAI موجه للوكلاء بدلاً من هارنيس بسيط فقط. - -## ترتيب النسخ - -1. انسخ `AGENTS.md` و `ARCHITECTURE.md` إلى جذر المستودع. -2. انسخ شجرة `docs/` بالكامل. -3. املأ `docs/PRODUCT_SENSE.md` و `docs/QUALITY_SCORE.md` و - `docs/RELIABILITY.md` أولًا. -4. أضف أول خطة نشطة تحت `docs/exec-plans/active/`. -5. أبقِ ملفات الدخول قصيرة وجوّه التفاصيل إلى الوثائق المرتبطة. - -## ما يحسّنه هذا القالب - -- سياق محلي دائم داخل المستودع -- الكشف التدريجي بدلاً من ملف تعليمات واحد ضخم -- دورة حياة خطط صريحة -- تتبع الجودة عبر الوقت -- حدود مقروءة للوكلاء والبشر - -تعامل مع كل ملف هنا كبداية. استبدل العناصر النائبة والأمثلة وأوامر -العينات بتفاصيل مشروعك الحقيقي قبل الاعتماد عليها. diff --git a/courses/learn-harness-engineering/docs/ar/resources/openai-advanced/sops/chrome-devtools-validation-loop.md b/courses/learn-harness-engineering/docs/ar/resources/openai-advanced/sops/chrome-devtools-validation-loop.md deleted file mode 100644 index 3482c41c..00000000 --- a/courses/learn-harness-engineering/docs/ar/resources/openai-advanced/sops/chrome-devtools-validation-loop.md +++ /dev/null @@ -1,51 +0,0 @@ -# إجراء التشغيل القياسي: حلقة التحقق من Chrome DevTools - -استخدم هذا الإجراء عندما يعتمد عمل واجهة المستخدم على تفاعل وقت التشغيل الفعلي -ولقطات الشاشة وحالة DOM ومخرجات وحدة التحكم وهي أهم من فحص الكود وحده. - -## الهدف - -تحويل التحقق من واجهة المستخدم إلى حلقة تفاعل قابلة للتكرار يمكن للوكيل تشغيلها -حتى تصبح الرحلة نظيفة. - -## الحلقة الأساسية - -1. حدد الصفحة أو مثيل التطبيق المستهدف. -2. امسح ضوضاء وحدة التحكم القديمة. -3. التقط حالة "قبل". -4. شغّل مسار واجهة المستخدم. -5. راقب أحداث وقت التشغيل أثناء التفاعل. -6. التقط حالة "بعد". -7. طبِّق الإصلاح وأعد تشغيل التطبيق إذا لزم الأمر. -8. أعد تشغيل التحقق حتى تصبح الرحلة نظيفة. - -## المدخلات المطلوبة - -- أمر بدء تشغيل مستقر -- رحلة واجهة مستخدم قابلة لإعادة الإنتاج -- طريقة لالتقاط DOM أو وحدة التحكم أو لقطات الشاشة -- قاعدة لما يُحتسب "نظيفًا" - -## إجراء التشغيل القياسي للتنفيذ - -1. اكتب الرحلة المستهدفة في الخطة النشطة. -2. عرِّف النجاح بمصطلحات قابلة للملاحظة: نص موجود، زر مُفعَّل، خطأ اختفى، - وحدة تحكم نظيفة، طلب ناجح. -3. التقط الحالة الأولية قبل التفاعل. -4. شغّل مسار واحد فقط في كل مرة. -5. سجِّل أحداث وقت التشغيل وتغييرات DOM والمخرجات المرئية. -6. إذا فشلت الرحلة، أصلح الطبقة المسؤولة الأصغر وأعد التشغيل. -7. أعد تشغيل نفس المسار وقارن أدلة قبل/بعد. - -## معايير النظافة - -- الحالة المرئية المقصودة موجودة -- الأخطاء غير المتوقعة غائبة -- ضوضاء وحدة التحكم مفهومة أو مُزالة -- إعادة تشغيل نفس المسار تعطي نفس النتيجة - -## منتجات المستودع للتحديث - -- خطة التنفيذ النشطة -- `docs/RELIABILITY.md` إذا أصبحت الرحلة مسارًا ذهبيًا -- مواصفة المنتج إذا تغير السلوك المرئي diff --git a/courses/learn-harness-engineering/docs/ar/resources/openai-advanced/sops/encode-knowledge-into-repo.md b/courses/learn-harness-engineering/docs/ar/resources/openai-advanced/sops/encode-knowledge-into-repo.md deleted file mode 100644 index 9b77beef..00000000 --- a/courses/learn-harness-engineering/docs/ar/resources/openai-advanced/sops/encode-knowledge-into-repo.md +++ /dev/null @@ -1,45 +0,0 @@ -# إجراء التشغيل القياسي: ترميز المعرفة غير المرئية في المستودع - -استخدم هذا الإجراء عندما لا تزال هناك معلومات سياقية مهمة في مستندات Google -أو سلاسل المحادثات أو التذاكر أو عقول الأشخاص. - -## الهدف - -جعل المعرفة غير المرئية للوكلاء قابلة للاكتشاف في قاعدة الكود حتى تتمكن الجلسة -الجديدة من التصرف بناءً عليها دون الاعتماد على محادثة سابقة. - -## إشارات المحفز - -- يستمر الوكيل في السؤال عن كيفية عمل النظام. -- يقول البشر "قررنا هذا في Slack" أو "اتبع ما قاله فلان الأسبوع الماضي." -- تشير المراجعات إلى قواعد منتج أو أمان غير مكتوبة في المستودع. -- الجلسات الجديدة تكرر عمل الاكتشاف الذي يجب أن يكون مستقرًا بالفعل. - -## إجراء التشغيل القياسي للتنفيذ - -1. اذكر مصادر المعرفة غير المرئية: المستندات، المحادثات، قواعد الفريق الضمنية، القرارات الشفهية. -2. لكل مصدر، اسأل: هل هذه بنية أم سلوك منتج أم سياسة أمان أم - توقع موثوقية أم سياق خطة أم مادة مرجعية؟ -3. رمِّزها في منتج المستودع المطابق: - - البنية -> `ARCHITECTURE.md` - - سلوك المنتج -> `docs/product-specs/` - - منطق التصميم -> `docs/design-docs/` - - حالة التنفيذ -> `docs/exec-plans/` - - المراجع الخارجية المتكررة -> `docs/references/` - - توقعات الجودة أو الموثوقية -> `docs/QUALITY_SCORE.md` أو `docs/RELIABILITY.md` -4. استبدل العبارات الغامضة بصياغة مفيدة تشغيليًا. -5. أزل أو أهمل النسخ القديمة حتى يحتفظ المستودع بحقيقة واحدة قابلة للاكتشاف. - -## قواعد الترميز الجيد - -- اكتب من أجل قابلية الاكتشاف، وليس من أجل الاكتمال الأدبي. -- فضِّل المستندات القصيرة بأسماء ملفات واضحة. -- اربط المنتجات ذات الصلة ببعضها. -- خزِّن القواعد الدائمة، وليس نصوص الاجتماعات. -- حدِّث المستودع في نفس الجلسة التي يُتخذ فيها القرار. - -## تعريف الإنجاز - -- يمكن لوكيل جديد اكتشاف القاعدة ذات الصلة دون سؤال بشري. -- نفس الحقيقة غير مبعثرة عبر ملفات متعددة متناقضة. -- المنتج الجديد يعيش بالقرب من الكود أو سير العمل الذي يحكمه. diff --git a/courses/learn-harness-engineering/docs/ar/resources/openai-advanced/sops/index.md b/courses/learn-harness-engineering/docs/ar/resources/openai-advanced/sops/index.md deleted file mode 100644 index 770336ef..00000000 --- a/courses/learn-harness-engineering/docs/ar/resources/openai-advanced/sops/index.md +++ /dev/null @@ -1,25 +0,0 @@ -# إجراءات التشغيل القياسية المتقدمة من OpenAI - -تترجم إجراءات التشغيل القياسية هذه أنماط التشغيل من المقالة إلى كتيبات تنفيذ -محددة يمكنك اتباعها أو تكييفها. - -## إجراءات التشغيل القياسية المضمنة - -- [`layered-domain-architecture.md`](./layered-domain-architecture.md): - إنشاء طبقات صريحة وحدود مشتركة -- [`encode-knowledge-into-repo.md`](./encode-knowledge-into-repo.md): - نقل المعرفة غير المرئية من المحادثات والمستندات والذاكرة إلى ملفات محلية في المستودع -- [`observability-feedback-loop.md`](./observability-feedback-loop.md): - إعطاء الوكلاء سجلات ومقاييس وتتبعات وحلقة تصحيح قابلة للتكرار -- [`chrome-devtools-validation-loop.md`](./chrome-devtools-validation-loop.md): - استخدام أتمتة المتصفح واللقطات للتحقق من سلوك واجهة المستخدم حتى يصبح نظيفًا - -## كيفية استخدامها - -1. اختر إجراء التشغيل القياسي الذي يتطابق مع عنق الزجاجة الحالي لديك. -2. استخدم قائمة الفحص لإعداد المنتجات أو الأدوات المفقودة. -3. رمِّز القواعد الناتجة في وثائق `repo-template/` المنسوخة. -4. حوِّل تعليقات المراجعة المتكررة إلى فحوصات أو نصوص برمجية أو حواجز وقائية. - -هذه ليست مخصصة لاتباعها بشكل أعمى. بل مصممة لجعل الهارنيس أكثر -وضوحًا وقابلية للتطبيق وقابلية للتكرار. diff --git a/courses/learn-harness-engineering/docs/ar/resources/openai-advanced/sops/layered-domain-architecture.md b/courses/learn-harness-engineering/docs/ar/resources/openai-advanced/sops/layered-domain-architecture.md deleted file mode 100644 index 0e90618f..00000000 --- a/courses/learn-harness-engineering/docs/ar/resources/openai-advanced/sops/layered-domain-architecture.md +++ /dev/null @@ -1,50 +0,0 @@ -# إجراء التشغيل القياسي: بنية المجالات المتطبقة - -استخدم هذا الإجراء عندما يستمر الوكيل في انتهاك الحدود أو تكرار المنطق عبر -الطبقات أو إنتاج كود يصعب مراجعته بعد بضع جلسات. - -## الهدف - -جعل حدود المجالات صريحة بما يكفي حتى يتمكن الوكلاء من التحرك بسرعة دون -تدهور هيكلي صامت. - -## النموذج المستهدف - -ضمن مجال أعمال، فضِّل هذا التدفق الاتجاهي: - -`Types -> Config -> Repo -> Service -> Runtime -> UI` - -المخاوف المشتركة يجب أن تدخل من خلال موفّرين أو محوّلات صريحة. -الأدوات المساعدة المشتركة تبقى خارج المجال وألا تتراكم فيها منطق المجال. - -## قائمة فحص الإعداد - -- عرِّف المجالات الحالية في `ARCHITECTURE.md`. -- اكتب اتجاهات التبعية المسموح بها في `ARCHITECTURE.md`. -- سجِّل الواجهات المشتركة مثل المصادقة وقياس الأداء وواجهات برمجة التطبيقات الخارجية. -- أضف ملاحظة قصيرة واحدة لأكثر انتهاك حدود صعوبة حاليًا. -- قرر ما يجب تفعيله آليًا بواسطة lint أو الاختبارات أو النصوص البرمجية. - -## إجراء التشغيل القياسي للتنفيذ - -1. ارسم خريطة قاعدة الكود إلى مجالات قبل لمس نمط التنفيذ. -2. لكل مجال، حدد تسلسل الطبقات المسموح به. -3. حدد جميع المخاوف المشتركة ووجهها عبر موفّرين أو محوّلات. -4. انقل المنطق المشترك الغامض إما إلى المجال المالك أو إلى أدوات مساعدة عامة حقًا. -5. وثِّق القواعد في `ARCHITECTURE.md`. -6. أضف حاجزًا وقائيًا قابلًا للتنفيذ للانتهاك الأعلى تكلفة. -7. حدِّث تسجيل الجودة بعد التغيير. - -## تعريف الإنجاز - -- يمكن لوكيل جديد معرفة أي طبقة تملك تغييرًا. -- كود واجهة المستخدم لم يعد يصل إلى المستودع أو الآثار الجانبية الخارجية مباشرة. -- المخاوف المشتركة لها نقاط دخول مسماة. -- حد واحد مهم على الأقل مُفعَّل آليًا. - -## منتجات المستودع للتحديث - -- `ARCHITECTURE.md` -- `docs/QUALITY_SCORE.md` -- `docs/design-docs/` عندما يتغير المنطق -- `docs/PLANS.md` أو خطة التنفيذ النشطة diff --git a/courses/learn-harness-engineering/docs/ar/resources/openai-advanced/sops/observability-feedback-loop.md b/courses/learn-harness-engineering/docs/ar/resources/openai-advanced/sops/observability-feedback-loop.md deleted file mode 100644 index 55ae033f..00000000 --- a/courses/learn-harness-engineering/docs/ar/resources/openai-advanced/sops/observability-feedback-loop.md +++ /dev/null @@ -1,44 +0,0 @@ -# إجراء التشغيل القياسي: حلقة التغذية الراجعة للمراقبة - -استخدم هذا الإجراء عندما يكون التصحيح بطيئًا، أو يستمر الوكلاء في الادعاء بالنجاح -بدون أدلة، أو عندما يكون سلوك وقت التشغيل أصعب في الفحص من الكود نفسه. - -## الهدف - -إعطاء الوكيل حلقة تغذية راجعة محلية عبر السجلات والمقاييس والتتبعات وأحمال -العمل القابلة للتنفيذ حتى يتمكن من الاستنتاج من التنفيذ، وليس فقط من فحص الكود. - -## الحد الأدنى من الحزمة - -- التطبيق يُصدر سجلات منظمة -- التطبيق يُصدر مقاييس وتتبعات عندما يكون ذلك ممكنًا -- طبقة توزيع أو تجميع محلية -- واجهات استعلام للسجلات والمقاييس والتتبعات -- حمل عمل أو رحلة مستخدم قابلة للتكرار لإعادة التشغيل بعد كل تغيير - -## إجراء التشغيل القياسي للتنفيذ - -1. حدد رحلات وقت التشغيل الذهبية الأهم. -2. أضف سجلات منظمة لبدء التشغيل والمسار الحرج. -3. أضف مقاييس لزمن الاستجابة أو عدد الإخفاقات أو عمق الطابور حيث يكون مفيدًا. -4. أضف تتبعات أو علامات توقيت للتدفقات البطيئة أو متعددة الخطوات. -5. اجعل الإشارات قابلة للاستعلام من بيئة التطوير المحلية. -6. أعطِ الوكيل حمل عمل أو سيناريو واحد قابل للتكرار لإعادة تشغيله. -7. اطلب الحلقة: استعلام -> ربط -> استنتاج -> تنفيذ -> إعادة تشغيل -> - إعادة تشغيل -> تحقق. - -## قائمة فحص جلسة التصحيح - -- ما الذي فشل؟ -- أي إشارة تثبت الفشل؟ -- أي طبقة تملك الفشل؟ -- ما الذي تغير بعد الإصلاح؟ -- هل أُعيد تشغيل التطبيق بنظافة؟ -- هل نجح نفس حمل العمل بعد إعادة التشغيل؟ - -## تعريف الإنجاز - -- يمكن للوكيل شرح نمط فشل من أدلة وقت التشغيل. -- يمكن إعادة تشغيل نفس حمل العمل بعد كل تغيير. -- إعادة التشغيل وإعادة تشغيل حمل العمل جزء من حلقة المهام العادية. -- إشارات الموثوقية موثقة في `docs/RELIABILITY.md`. diff --git a/courses/learn-harness-engineering/docs/ar/resources/reference/coding-agent-startup-flow.md b/courses/learn-harness-engineering/docs/ar/resources/reference/coding-agent-startup-flow.md deleted file mode 100644 index ad1b8f28..00000000 --- a/courses/learn-harness-engineering/docs/ar/resources/reference/coding-agent-startup-flow.md +++ /dev/null @@ -1,33 +0,0 @@ -# تدفق بدء تشغيل وكيل البرمجة - -استخدم هذا في بداية كل جلسة بعد اكتمال التهيئة. - -## قالب بدء التشغيل الثابت - -1. شغّل `pwd` وتأكد من جذر المستودع. -2. اقرأ `claude-progress.md`. -3. اقرأ `feature_list.json`. -4. راجع الالتزامات الأخيرة باستخدام `git log --oneline -5`. -5. شغّل `./init.sh`. -6. شغّل مسار الفحص الأساسي أو اختبار شامل. -7. إذا كان الأساس معطلًا، أصلح ذلك أولًا. -8. حدد الميزة غير المنتهية ذات الأولوية القصوى. -9. اعمل فقط على تلك الميزة حتى يتم التحقق منها أو حظرها صراحةً. - -## لماذا يهم هذا الترتيب - -- `pwd` يمنع العمل العرضي في الدليل الخاطئ. -- ملفات التقدم والميزات تستعيد الحالة الدائمة قبل بدء التعديلات الجديدة. -- الالتزامات الأخيرة تشرح ما تغير مؤخرًا. -- `init.sh` يوحد بدء التشغيل بدلاً من الاعتماد على الذاكرة. -- التحقق الأساسي يلتقط حالات البداية المعطلة قبل أن يخفيها العمل الجديد. - -## مرآة نهاية الجلسة - -يجب أن تنتهي نفس الجلسة بـ: - -1. تسجيل التقدم -2. تحديث حالة الميزة -3. كتابة تسليم إذا لزم الأمر -4. الالتزام بالعمل الآمن -5. ترك مسار إعادة تشغيل نظيف diff --git a/courses/learn-harness-engineering/docs/ar/resources/reference/index.md b/courses/learn-harness-engineering/docs/ar/resources/reference/index.md deleted file mode 100644 index 1a8cccfe..00000000 --- a/courses/learn-harness-engineering/docs/ar/resources/reference/index.md +++ /dev/null @@ -1,29 +0,0 @@ -# مرجع عربي - -تشرح هذه الملاحظات كيفية استخدام القوالب كـ harness عامل بدلاً من -مجموعة ملفات فضفاضة. - -## ملاحظات مرجعية داخلية - -- [`method-map.md`](./method-map.md): يربط أنماط الفشل الشائعة بالمنتج أو السياسة الذي يعالجها أولاً -- [`initializer-agent-playbook.md`](./initializer-agent-playbook.md): ما يجب أن يتركه المُهيئ قبل بدء عمل الميزات -- [`coding-agent-startup-flow.md`](./coding-agent-startup-flow.md): سير بدء جلسة ثابت لعمليات البرمجة اللاحقة -- [`prompt-calibration.md`](./prompt-calibration.md): كيف تحافظ على التعليمات الجذرية حادة بدون جعلها منتفخة وهشة - -## المقالات الأساسية - -هذه القائمة ضيقة عمداً. الـ harness يعني نظام التنفيذ حول النموذج: حلقة الوكيل، تنفيذ الأدوات، الحماية، الحالة، السياق، التحقق، الإنهاء، التنسيق، والمراقبة. - -المقالات الثلاثة الأصلية تبقى العمود الفقري للدورة: - -- [OpenAI: Harness engineering](https://openai.com/index/harness-engineering/) (2026-02-11) -- [Anthropic: Effective harnesses for long-running agents](https://www.anthropic.com/engineering/effective-harnesses-for-long-running-agents) (2025-11-26) -- [Anthropic: Harness design for long-running application development](https://www.anthropic.com/engineering/harness-design-long-running-apps) (2026-03-24) - -## ترتيب القراءة المقترح - -1. `method-map.md` -2. `initializer-agent-playbook.md` -3. `coding-agent-startup-flow.md` -4. `prompt-calibration.md` -5. مقالات OpenAI و Anthropic الأساسية diff --git a/courses/learn-harness-engineering/docs/ar/resources/reference/initializer-agent-playbook.md b/courses/learn-harness-engineering/docs/ar/resources/reference/initializer-agent-playbook.md deleted file mode 100644 index f5a9da81..00000000 --- a/courses/learn-harness-engineering/docs/ar/resources/reference/initializer-agent-playbook.md +++ /dev/null @@ -1,36 +0,0 @@ -# دليل وكيل التهيئة - -استخدم هذا الدليل لأول جلسة جادة في مستودع، قبل بدء عمل الميزات التدريجي. - -## الهدف - -إنشاء سطح تشغيل مستقر حتى تتمكن الجلسات اللاحقة من تنفيذ السلوكيات -دون إعادة اشتقاق أوامر بدء التشغيل أو الحالة الحالية أو حدود المهام. - -## المخرجات المطلوبة - -يجب أن يترك وكيل التهيئة وراءه هذه المنتجات على الأقل: - -- ملف تعليمات جذري مثل `AGENTS.md` أو `CLAUDE.md` -- سطح ميزات قابل للقراءة آليًا مثل `feature_list.json` -- منتج تقدم دائم مثل `claude-progress.md` -- مساعد بدء تشغيل قياسي مثل `init.sh` -- التزام أولي آمن يلتقط الهيكل الأساسي - -## قائمة الفحص - -1. حدد مسار بدء التشغيل القياسي. -2. حدد مسار التحقق القياسي. -3. أنشئ سجل التقدم وسجّل الحالة الأولية. -4. قسّم العمل إلى ميزات صريحة مع حالات. -5. أنشئ أول التزام أساسي نظيف. - -## اختبار النجاح - -يجب أن تكون جلسة جديدة بدون سياق محادثة سابق قادرة على الإجابة على: - -- ماذا يفعل هذا المستودع -- كيفية بدء تشغيله -- كيفية التحقق منه -- ما الذي لم يكتمل -- ما هي أفضل خطوة تالية diff --git a/courses/learn-harness-engineering/docs/ar/resources/reference/method-map.md b/courses/learn-harness-engineering/docs/ar/resources/reference/method-map.md deleted file mode 100644 index 6f0c3184..00000000 --- a/courses/learn-harness-engineering/docs/ar/resources/reference/method-map.md +++ /dev/null @@ -1,18 +0,0 @@ -# خريطة الأساليب - -يربط هذا الجدول أوضاع فشل وكيل البرمجة طويل الأمد الأكثر شيوعًا بالمنتج -أو قاعدة التشغيل التي تصلحها عادةً أولًا. - -| وضع الفشل | كيف يبدو في الممارسة | الإصلاح الأساسي | المنتج الداعم | -| --- | --- | --- | --- | -| ارتباك البدء البارد | تقضي الجلسة الجديدة معظم وقتها في إعادة اكتشاف الإعداد والحالة | اجعل المستودع سجل النظام المرجعي | `claude-progress.md` | -| اتساع النطاق | يبدأ الوكيل عدة ميزات ولا ينتهي من أي منها بنظافة | قيّد النطاق النشط | `feature_list.json` | -| الإنجاز المبكر | يدعي الوكيل الانتهاء بعد تعديلات الكود لكن قبل الدليل القابل للتنفيذ | اربط الإنجاز بالأدلة | `clean-state-checklist.md` | -| بدء التشغيل الهش | كل جلسة تعيد تعلم كيفية تشغيل المشروع | وحِّد الإعداد والتحقق | `init.sh` | -| التسليم الضعيف | الجلسة التالية لا تستطيع التمييز بين ما تم التحقق منه وما هو معطل أو تالٍ | انتهِ بتسليم صريح | `session-handoff.md` | -| المراجعة الذاتية | جودة المراجعة تعتمد على الذوق أو الذاكرة | قيِّم المخرجات بفئات ثابتة | `evaluator-rubric.md` | - -## مبدأ التشغيل - -أضف أصغر منتج يعالج وضع الفشل الملاحظ مباشرة. -تجنب حل كل مشكلة موثوقية بإلقاء المزيد من النص في ملف تعليمات عالمي واحد. diff --git a/courses/learn-harness-engineering/docs/ar/resources/reference/prompt-calibration.md b/courses/learn-harness-engineering/docs/ar/resources/reference/prompt-calibration.md deleted file mode 100644 index 14b246d7..00000000 --- a/courses/learn-harness-engineering/docs/ar/resources/reference/prompt-calibration.md +++ /dev/null @@ -1,24 +0,0 @@ -# معايرة التعليمات - -يجب أن تحدد التعليمات الجذرية إطار التشغيل، وليس كل حركة ممكنة. - -## أبقِ في الملف الجذري - -- غرض المستودع ونطاقه -- مسار بدء التشغيل -- مسار التحقق -- القيود غير القابلة للتفاوض -- منتجات الحالة المطلوبة -- قواعد نهاية الجلسة - -## أخرج من الملف الجذري - -- الحالات الحدية التاريخية الطويلة -- تفاصيل التنفيذ الخاصة بموضوع معين -- ملاحظات البنية المحلية التي يجب أن تكون بالقرب من الكود -- أمثلة تنطبق على نظام فرعي واحد فقط - -## قاعدة العمل - -يجب أن يساعد الملف الجذري جلسة جديدة على توجيه نفسها بسرعة. إذا أصبح الملف -مكانًا لرمي كل فشل سابق، قسّم التفاصيل إلى مستندات أصغر واربط بها بدلاً من ذلك. diff --git a/courses/learn-harness-engineering/docs/ar/resources/templates/AGENTS.md b/courses/learn-harness-engineering/docs/ar/resources/templates/AGENTS.md deleted file mode 100644 index 8544a030..00000000 --- a/courses/learn-harness-engineering/docs/ar/resources/templates/AGENTS.md +++ /dev/null @@ -1,53 +0,0 @@ -# AGENTS.md - -هذا المستودع مصمم للعمل طويل الأمد لوكلاء البرمجة. الهدف ليس تعظيم -إنتاج الكود الخام. الهدف هو ترك المستودع في حالة يمكن للجلسة التالية -أن تستمر فيها بدون تخمين. - -## سير عمل بدء التشغيل - -قبل كتابة الكود: - -1. تأكد من دليل العمل باستخدام `pwd`. -2. اقرأ `claude-progress.md` لمعرفة آخر حالة مُتحقَّقة والخطوة التالية. -3. اقرأ `feature_list.json` واختر الميزة غير المنتهية ذات الأولوية القصوى. -4. راجع الالتزامات الأخيرة باستخدام `git log --oneline -5`. -5. شغّل `./init.sh`. -6. شغّل الفحص أو التحقق الشامل المطلوب قبل بدء عمل جديد. - -إذا كان التحقق الأساسي يفشل بالفعل، أصلح ذلك أولًا. لا تضع عمل ميزات جديد -فوق حالة بداية معطلة. - -## قواعد العمل - -- اعمل على ميزة واحدة في كل مرة. -- لا تعتبر الميزة مكتملة لمجرد أنه تم إضافة كود. -- أبقِ التغييرات ضمن نطاق الميزة المحددة ما لم يُجبرك عائق على إصلاح داعم ضيق. -- لا تغيّر قواعد التحقق بصمت أثناء التنفيذ. -- فضِّل منتجات المستودع الدائمة على ملخصات المحادثة. - -## المنتجات المطلوبة - -- `feature_list.json`: مصدر الحقيقة لحالة الميزات -- `claude-progress.md`: سجل الجلسة والحالة المُتحقَّقة الحالية -- `init.sh`: مسار بدء التشغيل والتحقق القياسي -- `session-handoff.md`: تسليم مضغوط اختياري للجلسات الأكبر - -## تعريف الإنجاز - -الميزة تُعتبر منتهية فقط عندما يكون جميع ما يلي صحيحًا: - -- السلوك المستهدف مُنفَّذ -- التحقق المطلوب تم تشغيله فعلًا -- الأدلة مسجلة في `feature_list.json` أو `claude-progress.md` -- المستودع يبقى قابلًا لإعادة التشغيل من مسار بدء التشغيل القياسي - -## نهاية الجلسة - -قبل إنهاء جلسة: - -1. حدِّث `claude-progress.md`. -2. حدِّث `feature_list.json`. -3. سجِّل أي خطر أو عائق غير محلول. -4. التزم برسالة وصفية بمجرد أن يكون العمل في حالة آمنة. -5. اترك المستودع نظيفًا بما يكفي لتشغيل الجلسة التالية `./init.sh` فورًا. diff --git a/courses/learn-harness-engineering/docs/ar/resources/templates/CLAUDE.md b/courses/learn-harness-engineering/docs/ar/resources/templates/CLAUDE.md deleted file mode 100644 index be1851d1..00000000 --- a/courses/learn-harness-engineering/docs/ar/resources/templates/CLAUDE.md +++ /dev/null @@ -1,46 +0,0 @@ -# CLAUDE.md - -أنت تعمل في مستودع مصمم لعمل تنفيذ طويل الأمد. أعطِ الأولوية للإنجاز -الموثوق والاستمرارية عبر الجلسات والتحقق الصريح على السرعة. - -## حلقة التشغيل - -في بداية كل جلسة: - -1. شغّل `pwd` وتأكد أنك في جذر المستودع المتوقع. -2. اقرأ `claude-progress.md`. -3. اقرأ `feature_list.json`. -4. راجع الالتزامات الأخيرة باستخدام `git log --oneline -5`. -5. شغّل `./init.sh`. -6. تحقق مما إذا كان مسار الفحص الأساسي أو الاختبار الشامل معطلًا بالفعل. - -ثم حدد ميزة واحدة غير منتهية بالضبط واعمل فقط على تلك الميزة حتى -تتحقق منها أو توثق سبب حظرها. - -## القواعد - -- ميزة نشطة واحدة في كل مرة. -- لا تدّعي الإنجاز بدون أدلة قابلة للتنفيذ. -- لا تعيد كتابة قائمة الميزات لإخفاء العمل غير المنتهي. -- لا تزل أو تضعف الاختبارات فقط لجعل المهمة تبدو مكتملة. -- استخدم منتجات المستودع كسجل النظام المرجعي. - -## الملفات المطلوبة - -- `feature_list.json` -- `claude-progress.md` -- `init.sh` -- `session-handoff.md` عندما يكون التسليم المضغوط مفيدًا - -## بوابة الإنجاز - -يمكن للميزة الانتقال إلى `passing` فقط بعد نجاح التحقق المطلوب -وتسجيل النتيجة. - -## قبل أن تتوقف - -1. حدِّث سجل التقدم. -2. حدِّث حالة الميزة. -3. سجِّل ما لا يزال معطلًا أو غير مُتحقَّق. -4. التزم بمجرد أن يكون المستودع آمنًا للاستئناف. -5. اترك مسار إعادة تشغيل نظيف للجلسة التالية. diff --git a/courses/learn-harness-engineering/docs/ar/resources/templates/claude-progress.md b/courses/learn-harness-engineering/docs/ar/resources/templates/claude-progress.md deleted file mode 100644 index c1385f9d..00000000 --- a/courses/learn-harness-engineering/docs/ar/resources/templates/claude-progress.md +++ /dev/null @@ -1,35 +0,0 @@ -# سجل التقدم - -## الحالة المُتحقَّقة الحالية - -- جذر المستودع: -- مسار بدء التشغيل القياسي: -- مسار التحقق القياسي: -- الميزة غير المنتهية ذات الأولوية القصوى الحالية: -- العائق الحالي: - -## سجل الجلسات - -### الجلسة 001 - -- التاريخ: -- الهدف: -- المُنجَز: -- التحقق المُشغَّل: -- الأدلة الملتقطة: -- الالتزامات: -- الملفات أو المنتجات المحدَّثة: -- الخطر المعروف أو المشكلة غير المحلولة: -- أفضل خطوة تالية: - -### الجلسة 002 - -- التاريخ: -- الهدف: -- المُنجَز: -- التحقق المُشغَّل: -- الأدلة الملتقطة: -- الالتزامات: -- الملفات أو المنتجات المحدَّثة: -- الخطر المعروف أو المشكلة غير المحلولة: -- أفضل خطوة تالية: diff --git a/courses/learn-harness-engineering/docs/ar/resources/templates/clean-state-checklist.md b/courses/learn-harness-engineering/docs/ar/resources/templates/clean-state-checklist.md deleted file mode 100644 index 27b562b2..00000000 --- a/courses/learn-harness-engineering/docs/ar/resources/templates/clean-state-checklist.md +++ /dev/null @@ -1,8 +0,0 @@ -# قائمة فحص الحالة النظيفة - -- [ ] مسار بدء التشغيل القياسي لا يزال يعمل. -- [ ] مسار التحقق القياسي لا يزال يعمل. -- [ ] التقدم الحالي مسجل في سجل التقدم. -- [ ] حالة الميزات تعكس ما هو ناجح فعلًا مقابل غير مُتحقَّق. -- [ ] لا توجد خطوة نصف منتهية بدون توثيق. -- [ ] الجلسة التالية يمكنها الاستمرار بدون إصلاح يدوي. diff --git a/courses/learn-harness-engineering/docs/ar/resources/templates/evaluator-rubric.md b/courses/learn-harness-engineering/docs/ar/resources/templates/evaluator-rubric.md deleted file mode 100644 index 59bf0eb2..00000000 --- a/courses/learn-harness-engineering/docs/ar/resources/templates/evaluator-rubric.md +++ /dev/null @@ -1,24 +0,0 @@ -# معيار المُقيِّم - -استخدم هذا المعيار بعد التنفيذ وقبل القبول النهائي. - -| الفئة | السؤال | النتيجة (0-2) | ملاحظات | -| --- | --- | --- | --- | -| الصحة | هل السلوك المُنفَّذ يتطابق مع الميزة المطلوبة؟ | | | -| التحقق | هل تم تشغيل الفحوصات المطلوبة فعلًا، مع أدلة؟ | | | -| انضباط النطاق | هل بقيت الجلسة داخل نطاق الميزة المختارة؟ | | | -| الموثوقية | هل تتحمل النتيجة إعادة التشغيل أو إعادة التشغيل بدون إصلاح؟ | | | -| قابلية الصيانة | هل الكود والتوثيق واضحان بما يكفي للجلسة التالية؟ | | | -| جاهزية التسليم | هل يمكن لجلسة جديدة الاستمرار من منتجات المستودع فقط؟ | | | - -## الحكم - -- قبول -- مراجعة -- حظر - -## المتابعة المطلوبة - -- أدلة مفقودة: -- إصلاحات مطلوبة: -- محفز المراجعة التالية: diff --git a/courses/learn-harness-engineering/docs/ar/resources/templates/feature_list.json b/courses/learn-harness-engineering/docs/ar/resources/templates/feature_list.json deleted file mode 100644 index 138adc6a..00000000 --- a/courses/learn-harness-engineering/docs/ar/resources/templates/feature_list.json +++ /dev/null @@ -1,64 +0,0 @@ -{ - "project": "replace-with-project-name", - "last_updated": "YYYY-MM-DD", - "rules": { - "single_active_feature": true, - "passing_requires_evidence": true, - "do_not_skip_verification": true - }, - "status_legend": { - "not_started": "Work has not begun.", - "in_progress": "The feature is the current active task.", - "blocked": "Work cannot continue until a documented blocker is resolved.", - "passing": "Required verification has passed and evidence is recorded." - }, - "features": [ - { - "id": "chat-001", - "priority": 1, - "area": "chat", - "title": "Create a new conversation", - "user_visible_behavior": "A user can click New Chat and see a fresh empty conversation.", - "status": "not_started", - "verification": [ - "Open the app.", - "Click New Chat.", - "Verify a new conversation appears in the sidebar.", - "Verify the main panel shows an empty conversation state." - ], - "evidence": [], - "notes": "" - }, - { - "id": "chat-002", - "priority": 2, - "area": "chat", - "title": "Send a message in the current conversation", - "user_visible_behavior": "A user can submit a message and see it appear in the active thread.", - "status": "not_started", - "verification": [ - "Open an existing conversation.", - "Type a message into the input.", - "Submit the message.", - "Verify the new message appears in the thread." - ], - "evidence": [], - "notes": "" - }, - { - "id": "chat-003", - "priority": 3, - "area": "chat", - "title": "Persist the active conversation list", - "user_visible_behavior": "A user sees previously created conversations after restarting the app.", - "status": "not_started", - "verification": [ - "Create two conversations.", - "Restart the app.", - "Verify both conversations still appear in the sidebar." - ], - "evidence": [], - "notes": "" - } - ] -} diff --git a/courses/learn-harness-engineering/docs/ar/resources/templates/index.md b/courses/learn-harness-engineering/docs/ar/resources/templates/index.md deleted file mode 100644 index 7d66bd83..00000000 --- a/courses/learn-harness-engineering/docs/ar/resources/templates/index.md +++ /dev/null @@ -1,41 +0,0 @@ -# دليل القوالب - -هذه القوالب جاهزة للنسخ إلى مشروعك الخاص. كل واحدة تخدم غرضاً محدداً في سير عمل الوكيل. عدّل المحتوى ليتوافق مع أوامر ومسارات وأسماء ميزات وخطوات تحقق مشروعك. - -## كيف تبدأ - -انسخ هذه الملفات الأربعة إلى جذر مشروعك أولاً: - -1. `AGENTS.md` أو `CLAUDE.md` -2. `init.sh` -3. `claude-progress.md` -4. `feature_list.json` - -أضف الملفات المتبقية مع نمو مشروعك. - ---- - -## AGENTS.md - -ملف التعليمات الجذري. هذا أول ما يقرأه الوكيل عند بدء جلسة. يحدد قواعد التشغيل: ماذا يفعل قبل كتابة الكود، كيف يعمل، وكيف ينهي العمل. - -استخدم `AGENTS.md` لـ Codex أو وكلاء آخرين. استخدم `CLAUDE.md` إذا كنت تعمل مع Claude Code — البنية نفسها، بتنسيق نمط تعليمات Claude. - -## init.sh - -سكربت التشغيل. يُشغّل تثبيت التبعيات والتحقق ويطبع أمر البدء — كل ذلك في خطوة واحدة. - -## claude-progress.md - -سجل التقدم. كل جلسة تكتب في هذا الملف، وكل جلسة جديدة تقرأه أولاً. - -## feature_list.json - -متتبع الميزات. قائمة مقروءة آلياً لكل ميزة يحتاجها الوكيل مع حالتها وخطوات التحقق والأدلة. - -## الملفات الأخرى - -- `session-handoff.md` — ملاحظة تسليم مدمجة بين الجلسات -- `clean-state-checklist.md` — قائمة فحص قبل إنهاء كل جلسة -- `evaluator-rubric.md` — بطاقة تسجيل لمراجعة جودة مخرجات الوكيل -- `quality-document.md` — لقطة جودة تُقيّم كل مجال منتج وطبقة معمارية diff --git a/courses/learn-harness-engineering/docs/ar/resources/templates/init.sh b/courses/learn-harness-engineering/docs/ar/resources/templates/init.sh deleted file mode 100755 index d7522d66..00000000 --- a/courses/learn-harness-engineering/docs/ar/resources/templates/init.sh +++ /dev/null @@ -1,29 +0,0 @@ -#!/usr/bin/env bash - -set -euo pipefail - -ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" -cd "$ROOT_DIR" - -# Replace these commands with the correct commands for your repository. -INSTALL_CMD=(npm install) -VERIFY_CMD=(npm test) -START_CMD=(npm run dev) - -echo "==> Working directory: $PWD" -echo "==> Syncing dependencies" -"${INSTALL_CMD[@]}" - -echo "==> Running baseline verification" -"${VERIFY_CMD[@]}" - -echo "==> Startup command" -printf ' %q' "${START_CMD[@]}" -printf '\n' - -if [ "${RUN_START_COMMAND:-0}" = "1" ]; then - echo "==> Starting the app" - exec "${START_CMD[@]}" -fi - -echo "Set RUN_START_COMMAND=1 if you want init.sh to launch the app directly." diff --git a/courses/learn-harness-engineering/docs/ar/resources/templates/quality-document.md b/courses/learn-harness-engineering/docs/ar/resources/templates/quality-document.md deleted file mode 100644 index cc26c8fa..00000000 --- a/courses/learn-harness-engineering/docs/ar/resources/templates/quality-document.md +++ /dev/null @@ -1,43 +0,0 @@ -# وثيقة الجودة - -لقطة جودة لكل مجال منتج وطبقة معمارية. يمكن للوكلاء والبشر على حد سواء استخدام هذه الوثيقة لفهم أين تكون قاعدة الكود قوية وأين تحتاج إلى عمل بسرعة. - -**وتيرة التحديث:** بعد كل جلسة مهمة، أو قبل بدء مرحلة عمل جديدة. - -**مقياس الدرجات:** - -- **A**: جميع عمليات التحقق ناجحة، بنية نظيفة، قابلية قراءة الوكيل، اختبارات مستقرة -- **B**: التحقق ناجح، نظيف في الغالب، فجوات طفيفة في قابلية القراءة أو تغطية الاختبار -- **C**: يعمل جزئيًا، فجوات معروفة، بعض مناطق الكود صعبة على الوكلاء لفهمها -- **D**: لا يعمل، أو مشاكل هيكلية كبيرة - ---- - -## مجالات المنتج - -| المجال | الدرجة | التحقق | قابلية قراءة الوكيل | استقرار الاختبار | الفجوات الرئيسية | آخر تحديث | -|--------|-------|-------------|-----------------|---------------|----------|-------------| -| استيراد المستندات | - | - | - | - | - | - | -| إدارة المستندات | - | - | - | - | - | - | -| فهرسة المستندات | - | - | - | - | - | - | -| تدفق الأسئلة والأجوبة | - | - | - | - | - | - | -| الإجابات المسبقة | - | - | - | - | - | - | - -## الطبقات المعمارية - -| الطبقة | الدرجة | تطبيق الحدود | قابلية قراءة الوكيل | الفجوات الرئيسية | آخر تحديث | -|-------|-------|---------------------|-----------------|----------|-------------| -| العملية الرئيسية | - | - | - | - | - | -| التحميل المسبق | - | - | - | - | - | -| العارض | - | - | - | - | - | -| الخدمات | - | - | - | - | - | - -## سجل التغييرات - -### YYYY-MM-DD - -- التغييرات: -- المجالات المُرقَّاة: -- المجالات المُخفَّضة: -- الفجوات الجديدة المكتشفة: -- الفجوات المغلقة: diff --git a/courses/learn-harness-engineering/docs/ar/resources/templates/session-handoff.md b/courses/learn-harness-engineering/docs/ar/resources/templates/session-handoff.md deleted file mode 100644 index da6dc433..00000000 --- a/courses/learn-harness-engineering/docs/ar/resources/templates/session-handoff.md +++ /dev/null @@ -1,30 +0,0 @@ -# تسليم الجلسة - -## مُتحقَّق الآن - -- ما هو يعمل حاليًا: -- ما التحقق الذي تم تشغيله فعلًا: - -## تغيّر في هذه الجلسة - -- كود أو سلوك مضاف: -- تغييرات البنية التحتية أو الهارنيس: - -## معطل أو غير مُتحقَّق - -- عيب معروف: -- مسار غير مُتحقَّق: -- خطر على الجلسة التالية: - -## أفضل خطوة تالية - -- الميزة غير المنتهية ذات الأولوية القصوى: -- لماذا هي التالية: -- ما الذي يُحتسب كناجح: -- ما الذي يجب ألا يتغير خلال تلك الخطوة: - -## الأوامر - -- بدء التشغيل: -- التحقق: -- أمر التصحيح المركز: diff --git a/courses/learn-harness-engineering/docs/ar/skills/index.md b/courses/learn-harness-engineering/docs/ar/skills/index.md deleted file mode 100644 index 2a7c5764..00000000 --- a/courses/learn-harness-engineering/docs/ar/skills/index.md +++ /dev/null @@ -1,49 +0,0 @@ -# المهارات - -يحتوي هذا المجلد على مهارات وكلاء الذكاء الاصطناعي المرفقة مع هذا المساق. المهارات هي قوالب prompt مستقلة يمكن لوكلاء البرمجة مثل Claude Code و Codex و Cursor و Windsurf تحميلها لتنفيذ مهام متخصصة. - -## harness-creator - -مهارة harness engineering بمستوى إنتاجي لوكلاء البرمجة بالذكاء الاصطناعي. تساعدك على إنشاء وتقييم وتحسين الأنظمة الفرعية الخمسة الأساسية لأي harness: التعليمات، الحالة، التحقق، النطاق، ودورة حياة الجلسة. - -### ماذا تفعل - -- **إنشاء harnesses من الصفر** — AGENTS.md، قوائم ميزات، workflows للتحقق -- **تحسين harnesses موجودة** — تقييم للأنظمة الفرعية الخمسة مع تحسينات ذات أولوية -- **تصميم استمرارية الجلسات** — ذاكرة دائمة، تتبع التقدم، وإجراءات handoff -- **تطبيق أنماط إنتاجية** — الذاكرة، context engineering، أمان الأدوات، والتنسيق متعدد الوكلاء - -### بداية سريعة - -توجد ملفات المهارة في المستودع ضمن [`skills/harness-creator/`](https://github.com/walkinglabs/learn-harness-engineering/tree/main/skills/harness-creator). - -لاستخدامها مع Claude Code، انسخ مجلد `harness-creator/` إلى مسار المهارات في مشروعك، أو وجه الوكيل إلى ملف SKILL.md. - -### أنماط مرجعية - -تتضمن المهارة 6 وثائق مرجعية مفصلة: - -| النمط | متى تستخدمه | -|---------|-------------| -| Memory Persistence | عندما ينسى الوكيل بين الجلسات | -| Context Engineering | إدارة ميزانية السياق والتحميل عند الحاجة | -| Tool Registry | أمان الأدوات والتحكم في التوازي | -| Multi-Agent Coordination | التوازي و workflows التخصص | -| Lifecycle & Bootstrap | hooks، مهام الخلفية، والتهيئة | -| Gotchas | 15 نمط فشل غير بديهي مع حلول | - -### القوالب - -تضم المهارة قوالب جاهزة للاستخدام: - -- `agents.md` — هيكل AGENTS.md مع قواعد عمل -- `feature-list.json` — JSON Schema ومثال قائمة ميزات -- `init.sh` — سكربت تهيئة قياسي -- `progress.md` — قالب سجل تقدم الجلسة - -### كيف بُنيت هذه المهارة - -طُورت `harness-creator` باستخدام منهجية **skill-creator**، وهي meta-skill الرسمية من Anthropic لإنشاء مهارات الوكلاء واختبارها وتحسينها تكراريًا. توفر skill-creator workflow منظمًا: مسودة → اختبار → تقييم → تكرار، مع مشغلات eval ومصححات وواجهة benchmark. - -- **مصدر skill-creator**: [anthropics/skills — skill-creator](https://github.com/anthropics/skills/tree/main/skills/skill-creator) -- **وثائق مهارات Claude Code**: [anthropics/claude-code — plugin-dev/skills](https://github.com/anthropics/claude-code/tree/main/plugins/plugin-dev/skills) diff --git a/courses/learn-harness-engineering/docs/de/index.md b/courses/learn-harness-engineering/docs/de/index.md deleted file mode 100644 index a58a7b6f..00000000 --- a/courses/learn-harness-engineering/docs/de/index.md +++ /dev/null @@ -1,73 +0,0 @@ -# Willkommen bei Learn Harness Engineering - -Learn Harness Engineering ist ein Kurs über die technische Gestaltung von KI-Coding-Agenten. Wir haben fortgeschrittene Theorien und Praktiken des Harness Engineering aus der Industrie untersucht und in eine praktisch nutzbare Form gebracht. Unsere wichtigsten Referenzen sind: -- [OpenAI: Harness engineering: leveraging Codex in an agent-first world](https://openai.com/index/harness-engineering/) -- [Anthropic: Effective harnesses for long-running agents](https://www.anthropic.com/engineering/effective-harnesses-for-long-running-agents) -- [Anthropic: Harness design for long-running application development](https://www.anthropic.com/engineering/harness-design-long-running-apps) -- [Awesome Harness Engineering](https://github.com/walkinglabs/awesome-harness-engineering) - -Durch systematisches Umgebungsdesign, Zustandsverwaltung, Verifikation und Kontrollmechanismen zeigt dieser Kurs, wie agentische Coding-Tools wie Codex und Claude Code wirklich zuverlässig werden. Du lernst, Features zu bauen, Bugs zu beheben und Entwicklungsaufgaben zu automatisieren, indem du deinen KI-Coding-Assistenten mit expliziten Regeln und Grenzen führst. - -## Erste Schritte - -Wähle deinen Lernpfad. Der Kurs besteht aus theoretischen Lektionen, praktischen Projekten und einer sofort nutzbaren Ressourcenbibliothek. - - - -## Der Kernmechanismus eines Harness - -Ein harness "macht das Modell nicht klüger"; er schafft ein geschlossenes **Arbeitssystem** für das Modell. Der Kernablauf lässt sich mit diesem Diagramm verstehen: - -```mermaid -graph TD - A["Clear Objective
AGENTS.md"] --> B("Initialization
init.sh") - B --> C{"Run Tasks
AI Agent"} - C -->|Encounter Issues| D["Runtime Feedback
CLI / Logs"] - D -->|Auto-fix| C - C -->|Code Completed| E{"Verify & QA
Test suite"} - E -->|Failed| D - E -->|Passed| F["Cleanup & Handoff
claude-progress.md"] - - classDef primary fill:#D95C41,stroke:#C14E36,color:#fff,font-weight:bold; - classDef process fill:#F4F3EE,stroke:#D1D1D1,color:#1A1A1A; - classDef check fill:#EAE8E1,stroke:#B3B3B3,color:#1A1A1A; - - class A,F primary; - class B,D process; - class C,E check; -``` - -## Was du lernen wirst - -Zu den zentralen Konzepten gehören: - -
    -
  • Agentenverhalten begrenzen durch explizite Regeln und Grenzen.
  • -
  • Kontext erhalten über lange Aufgaben und mehrere Sessions hinweg.
  • -
  • Agenten davon abhalten, zu früh Erfolg zu melden.
  • -
  • Arbeit verifizieren mit vollständigen Pipeline-Tests und Selbstprüfung.
  • -
  • Runtime beobachtbar machen und leichter debuggen.
  • -
- -## Nächste Schritte - -Wenn du die Grundkonzepte verstanden hast, helfen diese Leitfäden beim Vertiefen: - - diff --git a/courses/learn-harness-engineering/docs/de/lectures/lecture-01-why-capable-agents-still-fail/code/failure-pattern-demo.ts b/courses/learn-harness-engineering/docs/de/lectures/lecture-01-why-capable-agents-still-fail/code/failure-pattern-demo.ts deleted file mode 100644 index 311e665b..00000000 --- a/courses/learn-harness-engineering/docs/de/lectures/lecture-01-why-capable-agents-still-fail/code/failure-pattern-demo.ts +++ /dev/null @@ -1,170 +0,0 @@ -/** - * failure-pattern-demo.ts - * - * Simulates the 4-step failure pattern that capable agents fall into: - * 1. Incomplete context - * 2. Locally reasonable changes - * 3. No global verification - * 4. Premature completion - * - * Run: npx tsx docs/lectures/lecture-01-why-capable-agents-still-fail/code/failure-pattern-demo.ts - */ - -// --------------------------------------------------------------------------- -// Types -// --------------------------------------------------------------------------- - -interface StepState { - step: number; - name: string; - contextAvailable: string[]; - contextMissing: string[]; - actionTaken: string; - localOutcome: string; - globalImpact: string; - completed: boolean; -} - -// --------------------------------------------------------------------------- -// Simulated "model" -- a simple decision function that bases its output -// solely on whatever context it has been given. -// --------------------------------------------------------------------------- - -function modelDecide(context: string[], task: string): string { - const has = (s: string) => context.some((c) => c.includes(s)); - - // The task is to "add a search endpoint to the API". - // Correct answer requires knowing about auth middleware and rate limiting. - - if (!has("auth")) { - return "Created new route handler /search without authentication checks"; - } - if (!has("rate-limit")) { - return "Added search route with auth but forgot rate limiting"; - } - if (!has("test-standards")) { - return "Implemented search with auth and rate-limit, but no tests"; - } - return "Fully implemented search endpoint with auth, rate-limit, and tests"; -} - -// --------------------------------------------------------------------------- -// Failure simulation -// --------------------------------------------------------------------------- - -function simulateFailurePattern(): StepState[] { - const steps: StepState[] = []; - - // ---- Step 1: Incomplete Context ---- - const step1Context = ["project structure", "route definitions"]; - const step1Missing = ["auth middleware", "rate-limiting policy", "test standards"]; - const step1Decision = modelDecide(step1Context, "add search endpoint"); - - steps.push({ - step: 1, - name: "Incomplete Context", - contextAvailable: step1Context, - contextMissing: step1Missing, - actionTaken: step1Decision, - localOutcome: "Looks good -- route compiles, returns data", - globalImpact: "Missing auth means unauthenticated access to search", - completed: false, - }); - - // ---- Step 2: Locally Reasonable Changes ---- - // The agent adds auth after a hint, but still lacks other context. - const step2Context = [...step1Context, "auth middleware"]; - const step2Missing = ["rate-limiting policy", "test standards"]; - const step2Decision = modelDecide(step2Context, "add search endpoint"); - - steps.push({ - step: 2, - name: "Locally Reasonable Changes", - contextAvailable: step2Context, - contextMissing: step2Missing, - actionTaken: step2Decision, - localOutcome: "Route has auth -- looks complete locally", - globalImpact: "No rate limiting means the endpoint can be abused", - completed: false, - }); - - // ---- Step 3: No Global Verification ---- - const step3Context = [...step2Context, "rate-limiting policy"]; - const step3Missing = ["test standards"]; - const step3Decision = modelDecide(step3Context, "add search endpoint"); - - steps.push({ - step: 3, - name: "No Global Verification", - contextAvailable: step3Context, - contextMissing: step3Missing, - actionTaken: step3Decision, - localOutcome: "Feature appears fully implemented", - globalImpact: "No tests -- regression risk, violates project standards", - completed: false, - }); - - // ---- Step 4: Premature Completion ---- - steps.push({ - step: 4, - name: "Premature Completion", - contextAvailable: step3Context, - contextMissing: step3Missing, - actionTaken: 'Agent outputs: "Done. Added search endpoint."', - localOutcome: "Agent is satisfied, task marked complete", - globalImpact: "Task is incomplete -- missing tests, no E2E verification", - completed: true, - }); - - return steps; -} - -// --------------------------------------------------------------------------- -// Comparison table -// --------------------------------------------------------------------------- - -function printComparisonTable(steps: StepState[]): void { - console.log("\n" + "=".repeat(90)); - console.log(" FAILURE PATTERN DEMO -- 4 Steps to a Broken Deliverable"); - console.log("=".repeat(90)); - - for (const s of steps) { - console.log(`\n Step ${s.step}: ${s.name}`); - console.log(" " + "-".repeat(60)); - console.log(` Context available : ${s.contextAvailable.join(", ")}`); - console.log(` Context missing : ${s.contextMissing.join(", ") || "(none)"}`); - console.log(` Action taken : ${s.actionTaken}`); - console.log(` Local outcome : ${s.localOutcome}`); - console.log(` Global impact : ${s.globalImpact}`); - console.log(` Marked complete? : ${s.completed ? "YES (premature)" : "No"}`); - } - - // Summary comparison - console.log("\n" + "=".repeat(90)); - console.log(" COMPARISON: What the agent saw vs. what was actually needed"); - console.log("=".repeat(90)); - - const totalRequired = ["project structure", "route definitions", "auth middleware", "rate-limiting policy", "test standards"]; - const finalAvailable = steps[steps.length - 1].contextAvailable; - - const header = "| Criterion | Available | Missing | Status |"; - const sep = "|------------------------|-----------|---------|----------|"; - console.log("\n" + header); - console.log(sep); - - for (const item of totalRequired) { - const avail = finalAvailable.includes(item); - const row = `| ${item.padEnd(23)}| ${(avail ? "Yes" : "No").padEnd(10)}| ${(!avail ? "Yes" : "").padEnd(8)}| ${(avail ? "OK" : "GAP").padEnd(9)}|`; - console.log(row); - } - - const gapCount = totalRequired.filter((i) => !finalAvailable.includes(i)).length; - console.log("\n Result: Agent completed with " + gapCount + " of " + totalRequired.length + " context items missing."); - console.log(" This is the core failure pattern: each step looked reasonable in isolation.\n"); -} - -// --------------------------------------------------------------------------- -// Run -// --------------------------------------------------------------------------- - -printComparisonTable(simulateFailurePattern()); diff --git a/courses/learn-harness-engineering/docs/de/lectures/lecture-01-why-capable-agents-still-fail/code/failure-signals-checklist.md b/courses/learn-harness-engineering/docs/de/lectures/lecture-01-why-capable-agents-still-fail/code/failure-signals-checklist.md deleted file mode 100644 index c9eab0c4..00000000 --- a/courses/learn-harness-engineering/docs/de/lectures/lecture-01-why-capable-agents-still-fail/code/failure-signals-checklist.md +++ /dev/null @@ -1,9 +0,0 @@ -# Fehlersignal-Checkliste - -Verwenden Sie diese Checkliste bei der Überprüfung eines schwachen Harness-Laufs. - -- Hat der Agent gefragt oder falsch abgeleitet, wie die App gestartet wird? -- Hat er Verzeichnisse oder Abstraktionen erstellt, die nicht zum intended Produkt passen? -- Hat er nach Erstellung einer sichtbaren UI-Hülle ohne vollständigen Workflow aufgehört? -- Hat er Notizen oder Artefakte hinterlassen, die einem zukünftigen Lauf helfen? -- Könnte eine frische Session in unter fünf Minuten verstehen, was passiert ist? diff --git a/courses/learn-harness-engineering/docs/de/lectures/lecture-01-why-capable-agents-still-fail/code/index.md b/courses/learn-harness-engineering/docs/de/lectures/lecture-01-why-capable-agents-still-fail/code/index.md deleted file mode 100644 index 6774c8a8..00000000 --- a/courses/learn-harness-engineering/docs/de/lectures/lecture-01-why-capable-agents-still-fail/code/index.md +++ /dev/null @@ -1,7 +0,0 @@ -# Code für Vorlesung 01 - -Verwenden Sie diesen Ordner für kleine Beispiele, die zeigen: - -- ein starkes Modell, das in einer schwachen Umgebung scheitert -- unzureichend spezifizierte Repo-Einrichtung -- fehlende Feedback-Schleifen diff --git a/courses/learn-harness-engineering/docs/de/lectures/lecture-01-why-capable-agents-still-fail/code/underspecified-task.md b/courses/learn-harness-engineering/docs/de/lectures/lecture-01-why-capable-agents-still-fail/code/underspecified-task.md deleted file mode 100644 index dc502cd0..00000000 --- a/courses/learn-harness-engineering/docs/de/lectures/lecture-01-why-capable-agents-still-fail/code/underspecified-task.md +++ /dev/null @@ -1,18 +0,0 @@ -# Beispiel für eine unzureichend spezifizierte Aufgabe - -Erstelle eine Desktop-Wissensdatenbank-App mit KI-Frage-Antwort-Funktion. - -Einschränkungen: - -- Keine angegeben -- Kein Startbefehl gegeben -- Keine Ordnerstruktur-Anleitung -- Kein Datenmodell definiert -- Keine expliziten Abschlusskriterien - -Typische Ergebnisse bei dieser Art von Prompt: - -- der Agent erfindet ad hoc eine Struktur -- die App kompiliert möglicherweise, startet aber nicht konsistent -- die UI erscheint möglicherweise, bevor ein nutzbarer Ingest/Query-Pfad existiert -- der Agent stoppt oft nach kosmetischem Erfolg diff --git a/courses/learn-harness-engineering/docs/de/lectures/lecture-01-why-capable-agents-still-fail/index.md b/courses/learn-harness-engineering/docs/de/lectures/lecture-01-why-capable-agents-still-fail/index.md deleted file mode 100644 index 574863f4..00000000 --- a/courses/learn-harness-engineering/docs/de/lectures/lecture-01-why-capable-agents-still-fail/index.md +++ /dev/null @@ -1,117 +0,0 @@ -[中文版本 →](../../../zh/lectures/lecture-01-why-capable-agents-still-fail/) - -> Codebeispiele: [code/](https://github.com/walkinglabs/learn-harness-engineering/blob/main/docs/de/lectures/lecture-01-why-capable-agents-still-fail/code/) -> Praxisprojekt: [Projekt 01. Nur Prompt vs. Regeln zuerst](./../../projects/project-01-baseline-vs-minimal-harness/index.md) - -# Lektion 01. Starke Modelle bedeuten keine zuverlässige Ausführung - -Du kennst dich in der KI-Welt gut aus: Claude-Pro-Abo, GPT-4o-API-Key, SWE-bench-Ranglisten im Kopf. Eines Tages gibst du einem KI-Agenten endlich ein echtes Projekt und bist voller Zuversicht. Das Ergebnis? Er fügt ein Feature hinzu, bricht aber die Tests, behebt einen Bug und erzeugt zwei neue, läuft 20 Minuten und erklärt stolz "fertig" - und wenn du dir den Code ansiehst, ist es gar nicht das, was du verlangt hast. - -Dein erster Impuls? "Dieses Modell ist nicht gut genug. Zeit für ein Upgrade." Warte kurz. Bevor du zur Kreditkarte greifst, solltest du in Betracht ziehen, dass das Problem vielleicht gar nicht das Modell ist. - -Schauen wir auf ein paar Zahlen. Ende 2025 erreichen die stärksten Coding-Agenten auf SWE-bench Verified ungefähr 50-60%. Und das bei sorgfältig ausgewählten Aufgaben mit klaren Issue-Beschreibungen und vorhandenen Tests. Übertrage das auf deine tägliche Entwicklungsumgebung - vage Anforderungen, keine vorhandenen Tests, überall verstreute implizite Geschäftsregeln - und diese Zahl sinkt weiter. - -Hinter diesen Zahlen steckt aber eine kontraintuitive Wahrheit. - -## Gleiches Pferd, anderes Ergebnis - -Anthropic führte ein kontrolliertes Experiment durch. Gleicher Prompt ("baue einen 2D-Retro-Game-Maker"), gleiches Modell (Opus 4.5). Erster Lauf: nackt, ohne Unterstützung - 20 Minuten, 9 Dollar, die Kernfunktionen des Spiels funktionierten überhaupt nicht. Zweiter Lauf: vollständiger Harness (Planner + Generator + Evaluator als Drei-Agenten-Architektur) - 6 Stunden, 200 Dollar, das Spiel war spielbar. - -Sie änderten nicht das Modell. Opus 4.5 blieb Opus 4.5. Was sich änderte, war das Zaumzeug. - -OpenAIs Artikel zu Harness Engineering aus dem Jahr 2025 formuliert es direkt: Codex wechselt in einem gut geharnessten Repository von "unzuverlässig" zu "zuverlässig". Achte auf die Wortwahl - nicht "ein bisschen besser", sondern ein qualitativer Sprung. Wie bei einem Vollblutpferd: Du kannst ohne passendes Zaumzeug reiten, aber du kommst nicht weit, nicht schnell, und ein Sturz ist keine Überraschung. Der Harness ist dieses gesamte Zaumzeug - **alles in der Engineering-Infrastruktur außerhalb der Modellgewichte.** - -## Wo Agenten tatsächlich stecken bleiben - -Was geht also konkret schief? - -Am häufigsten: Du hast die Aufgabe nie eindeutig definiert. Du sagst "füge eine Suchfunktion hinzu", und das Verständnis des Agenten unterscheidet sich komplett von deinem - wonach suchen? Volltext oder strukturiert? Pagination? Hervorhebungen? Du hast es nicht angegeben, also rät der Agent. Ein richtiger Tipp ist Glück; ein falscher Tipp kostet mehr Nacharbeit, als klare Spezifikation am Anfang gekostet hätte. Es ist, als würdest du in ein Restaurant gehen und dem Koch sagen: "Ich nehme Fisch" - ob er geschmort, gedämpft oder im Hot Pot kommt, bleibt dem Zufall überlassen. - -Selbst wenn du spezifizierst, hat das Projekt implizite Architekturkonventionen, die der Agent nicht kennt. Dein Team hat SQLAlchemy-2.0-Syntax standardisiert, aber der Agent schreibt standardmäßig 1.x-Code. Alle API-Endpunkte müssen OAuth-2.0-Authentifizierung nutzen, aber diese Regel existiert nur in deinem Kopf und in einer Slack-Nachricht von vor drei Monaten. Der Agent sieht das nicht - nicht weil er sich nicht daran halten will, sondern weil er buchstäblich nicht weiß, dass diese Regeln existieren. - -Auch die Umgebung ist eine Falle. Unvollständige Entwicklungsumgebung, fehlende Abhängigkeiten, falsche Tool-Versionen. Der Agent verbrennt wertvolles Kontextfenster mit `pip install`-Fehlern und Node-Versionenkonflikten, statt deine eigentliche Aufgabe zu lösen. Das ist, als würdest du einen fähigen Tischler einstellen, aber Hammer, Nägel und eine ebene Werkbank vergessen - egal wie talentiert er ist, so kann er die Arbeit nicht erledigen. - -Noch häufiger: Es gibt schlicht keine Möglichkeit zur Verifikation. Keine Tests, kein Linting, oder die Verifikationsbefehle wurden dem Agenten nie mitgeteilt. Der Agent schreibt Code, sieht ihn sich an, entscheidet, dass er gut aussieht, und sagt "fertig". Das ist wie Hausaufgaben ohne Lösungsschlüssel: Der Schüler glaubt, alles richtig zu haben, aber beim Korrigieren liegen überall Fehler. Anthropic beobachtete außerdem ein interessantes Phänomen: Wenn Agenten spüren, dass der Kontext knapp wird, beeilen sie sich, überspringen Verifikation und wählen eine einfache statt der besten Lösung. Sie nennen das "context anxiety" - dasselbe Gefühl, wenn bei einer Prüfung die Zeit fast abgelaufen ist und man die letzten Multiple-Choice-Fragen zufällig ankreuzt. - -Lange Aufgaben über mehrere Sitzungen sind noch schlimmer: Alle Erkenntnisse aus der vorherigen Sitzung gehen verloren, und jede neue Sitzung muss Projektstruktur und Codeorganisation erneut erkunden und verstehen. Bei Agenten ohne persistenten Zustand steigen die Fehlerraten bei Aufgaben über 30 Minuten stark an. - -## Schlüsselbegriffe - -Mit diesen Szenarien im Hinterkopf sind die folgenden Begriffe kein bloßer Jargon mehr: - -- **Capability Gap**: Die große Lücke zwischen Modellleistung auf Benchmarks und Leistung bei realen Aufgaben. Eine Erfolgsrate von 50-60% auf SWE-bench Verified bedeutet, dass fast die Hälfte realer Issues nicht gelöst wird. -- **Harness**: Alles außerhalb des Modells - Anweisungen, Tools, Umgebung, Zustandsverwaltung, Verifikationsfeedback. Wenn es keine Modellgewichte sind, gehört es zum Harness. Das ist das "Zaumzeug", von dem wir gesprochen haben. -- **Harness-Induced Failure**: Das Modell hat grundsätzlich genug Fähigkeit, aber die Ausführungsumgebung hat strukturelle Defekte. Anthropics kontrolliertes Experiment hat genau das gezeigt. -- **Verification Gap**: Die Lücke zwischen dem Vertrauen des Agenten in sein Ergebnis und der tatsächlichen Korrektheit. Der Agent sagt "ich bin fertig", obwohl er nicht fertig ist - der häufigste Fehlermodus. -- **Diagnostic Loop**: Ausführen, Fehler beobachten, einer bestimmten Harness-Schicht zuordnen, diese Schicht reparieren, erneut ausführen. Das ist die Kernmethodik von Harness Engineering. -- **Definition of Done**: Eine Menge maschinell überprüfbarer Bedingungen - Tests grün, Lint sauber, Type Checks bestanden. Ohne explizite Definition of Done erfindet der Agent seine eigene. - -## Wenn etwas scheitert, repariere zuerst den Harness - -Kernprinzip: **Wenn etwas scheitert, tausche nicht zuerst das Modell aus - prüfe den Harness.** Wenn dasselbe Modell bei ähnlichen, gut strukturierten Aufgaben erfolgreich ist, gehe von einem Harness-Problem aus. Das ist wie bei einem Auto, das liegen bleibt: Du verdächtigst nicht sofort den Motor. Du prüfst zuerst, ob der Tank leer ist. - -Konkrete Schritte: - -**Ordne jeden Fehler einer bestimmten Schicht zu.** Sag nicht nur "das Modell taugt nichts". Frage: War die Aufgabe unklar? War der Kontext unzureichend? Gab es keine Verifikationsmethode? Ordne jeden Fehler einer der fünf Fehlerschichten zu: Aufgabenspezifikation, Kontextbereitstellung, Ausführungsumgebung, Verifikationsfeedback, Zustandsverwaltung. Das sind praktische Diagnoseschichten, nicht die sechs Glossarbegriffe oben. Wenn du diese Gewohnheit aufbaust, wirst du "das Modell ist nicht gut genug" immer seltener in deinen Logs sehen. - -**Schreibe für jede Aufgabe eine explizite Definition of Done.** Sag nicht "füge eine Suchfunktion hinzu". Sag: -``` -Completion criteria: -- New endpoint GET /api/search?q=xxx -- Supports pagination, default 20 items -- Results include highlighted snippets -- All new code passes pytest -- Type checking passes (mypy --strict) -``` - -**Erstelle eine AGENTS.md-Datei.** Lege sie im Repository-Root ab, damit der Agent Tech Stack, Architekturkonventionen und Verifikationsbefehle des Projekts kennt. Das ist der erste Schritt im Harness Engineering und der Schritt mit dem höchsten ROI. Eine einzige `AGENTS.md`-Datei kann wirksamer sein als ein Upgrade auf ein teureres Modell - das ist kein Witz. - -**Baue eine Diagnoseschleife.** Behandle Fehler nicht als "das Modell ist wieder dumm". Behandle sie als Signale, dass dein Harness einen Defekt hat. Bei jedem Fehler: Schicht identifizieren, reparieren, dafür sorgen, dass derselbe Fehler nicht wieder auftritt. Nach ein paar Runden wird dein Harness stärker und die Agentenleistung stabiler. Wie Straßenreparatur: Jedes gefüllte Schlagloch macht den nächsten Abschnitt glatter. - -**Quantifiziere Verbesserungen.** Führe ein einfaches Log: War jede Aufgabe erfolgreich oder nicht, und welche Schicht hat den Fehler verursacht? Nach ein paar Runden siehst du, welche Schicht der Engpass ist - konzentriere deine Energie dort. - -## Das Millionen-Zeilen-Experiment - -OpenAI führte 2025 ein aggressives Experiment durch: Codex sollte aus einem leeren Git-Repository ein vollständiges internes Produkt bauen. Fünf Monate später hatte das Repository ungefähr eine Million Zeilen Code - Anwendungslogik, Infrastruktur, Tooling, Dokumentation, interne Entwicklerwerkzeuge - alles von Agenten erzeugt. Drei Engineers steuerten Codex und öffneten sowie mergten etwa 1.500 PRs. Im Schnitt 3,5 PRs pro Person und Tag. - -Die zentrale Einschränkung: **Menschen schreiben nie direkt Code.** Das war kein Trick, sondern sollte das Team zwingen herauszufinden, was sich ändert, wenn die Hauptaufgabe des Engineers nicht mehr Code schreiben ist, sondern Umgebungen zu entwerfen, Absichten präzise auszudrücken und Feedbackschleifen zu bauen. - -Der frühe Fortschritt war langsamer als erwartet. Nicht weil Codex unfähig war, sondern weil die Umgebung nicht vollständig genug war - dem Agenten fehlten notwendige Werkzeuge, Abstraktionen und interne Strukturen, um übergeordnete Ziele voranzubringen. Die Arbeit der Engineers wurde: große Ziele in kleine Bausteine zerlegen (Design, Code, Review, Test), den Agenten diese zusammensetzen lassen und diese Bausteine dann nutzen, um komplexere Aufgaben freizuschalten. Wenn etwas scheiterte, lautete die Lösung fast nie "streng dich mehr an", sondern "welche Fähigkeit fehlt dem Agenten, und wie machen wir sie verständlich und ausführbar?" - -Dieses Experiment belegt direkt die Kernaussage dieser Lektion: **Dasselbe Modell erzeugt in einer nackten Umgebung grundlegend andere Ergebnisse als in einer Umgebung mit vollständigem Harness.** Das Modell änderte sich nicht. Die Umgebung änderte sich. - -> Quelle: [OpenAI: Harness engineering: leveraging Codex in an agent-first world](https://openai.com/index/harness-engineering/) - -## Ein bodenständigeres Beispiel - -Ein Team nutzte Claude Sonnet, um einer mittelgroßen Python-Web-App (FastAPI + PostgreSQL + Redis, ca. 15.000 Codezeilen) einen neuen API-Endpunkt hinzuzufügen. - -Zunächst gaben sie nur einen Satz: "add user preferences endpoints under `/api/v2/users`." Das Ergebnis? Der Agent verbrauchte 40% seines Kontextfensters damit, die Repository-Struktur zu erkunden, erzeugte Code, der plausibel aussah, aber nicht den Error-Handling-Mustern des Projekts folgte, verwendete alte SQLAlchemy-Syntax und erklärte die Aufgabe für abgeschlossen, obwohl der Endpunkt Laufzeitfehler hatte. Die nächste Sitzung musste die gesamte Entdeckungsarbeit erneut leisten. - -Später ergänzten sie `AGENTS.md` (mit Projektarchitektur und Tech-Stack-Versionen), explizite Verifikationsbefehle (`pytest tests/api/v2/ && python -m mypy src/`) und Architecture Decision Records. Dasselbe Modell war in allen drei unabhängigen Läufen erfolgreich, mit etwa 60% besserer Kontext-Effizienz. - -Sie änderten nicht das Modell. Sie änderten den Harness. - -## Wichtigste Erkenntnisse - -- Modellfähigkeit und Ausführungszuverlässigkeit sind verschiedene Dinge. Auch ein Vollblut braucht gutes Zaumzeug. -- Wenn etwas scheitert, prüfe zuerst den Harness und dann das Modell. Modelle zu wechseln ist die teuerste Option - und oft ist es nicht einmal ein Modellproblem. -- Jeder Fehler ist ein Signal: Dein Harness hat einen strukturellen Defekt. Finde ihn und behebe ihn. -- Fünf Verteidigungsschichten: Aufgabenspezifikation, Kontextbereitstellung, Ausführungsumgebung, Verifikationsfeedback, Zustandsverwaltung. Prüfe sie systematisch, wie ein Arzt die häufigsten Ursachen zuerst ausschließt. -- Eine einzige `AGENTS.md`-Datei kann wirksamer sein als ein Upgrade auf ein teureres Modell. Ernsthaft. - -## Weiterführende Literatur - -- [OpenAI: Harness Engineering — Leveraging Codex in an Agent-First World](https://openai.com/index/harness-engineering/) -- [Anthropic: Effective Harnesses for Long-Running Agents](https://www.anthropic.com/engineering/effective-harnesses-for-long-running-agents) -- [HumanLayer: Skill Issue — Harness Engineering for Coding Agents](https://humanlayer.dev/articles/harness-engineering-for-coding-agents/) -- [SWE-bench Leaderboard](https://www.swebench.com/) -- [Thoughtworks Technology Radar: Harness Engineering](https://www.thoughtworks.com/radar) - -## Übungen - -1. **Vergleichsexperiment**: Wähle eine Codebase, die du gut kennst, und eine nicht triviale Änderungsaufgabe. Führe den Agenten zuerst ohne Harness-Unterstützung aus und protokolliere die Fehler. Füge dann eine `AGENTS.md` mit expliziten Verifikationsbefehlen hinzu und führe denselben Agenten erneut aus. Vergleiche die Ergebnisse und ordne jeden Fehler einer der fünf Verteidigungsschichten zu. - -2. **Messung der Verification Gap**: Wähle 5 Coding-Aufgaben. Notiere nach jeder Aufgabe, ob der Agent Abschluss behauptet, und prüfe anschließend die tatsächliche Korrektheit mit unabhängigen Tests. Berechne den Anteil der Fälle, in denen der Agent "fertig" sagt, obwohl er es tatsächlich nicht ist - das ist deine Verification Gap. Überlege dann: Welche Verifikationsbefehle würden diesen Anteil senken? - -3. **Übung zur Diagnoseschleife**: Finde eine Aufgabe, bei der der Agent in deinem Projekt wiederholt scheitert. Führe sie einmal aus und protokolliere den Fehler. Ordne ihn einer der fünf Schichten zu. Repariere diese Schicht. Führe erneut aus. Wiederhole das drei bis fünf Runden und dokumentiere die Verbesserungen nach jeder Runde. diff --git a/courses/learn-harness-engineering/docs/de/lectures/lecture-02-what-a-harness-actually-is/code/harness-components.md b/courses/learn-harness-engineering/docs/de/lectures/lecture-02-what-a-harness-actually-is/code/harness-components.md deleted file mode 100644 index 5e6c10dd..00000000 --- a/courses/learn-harness-engineering/docs/de/lectures/lecture-02-what-a-harness-actually-is/code/harness-components.md +++ /dev/null @@ -1,21 +0,0 @@ -# Harness-Komponenten Beispiel - -Für einen Coding-Agenten, der in einem lokalen Repository arbeitet: - -- Modell: - das LLM selbst - -- Harness: - - System-Prompt - - AGENTS.md - - Bash-Tool - - Datei-Lese-/Schreib-Tools - - Git-Zugriff - - Lokales Dateisystem - - Startup-Skripte - - Test-Befehle - - Stop-Hooks - - Lint-Prüfungen - - Evaluator-Schleife - -Wenn Sie eines der obigen Harness-Elemente ändern, ändern Sie den effektiven Agenten. diff --git a/courses/learn-harness-engineering/docs/de/lectures/lecture-02-what-a-harness-actually-is/code/harness-vs-no-harness.ts b/courses/learn-harness-engineering/docs/de/lectures/lecture-02-what-a-harness-actually-is/code/harness-vs-no-harness.ts deleted file mode 100644 index d856ba36..00000000 --- a/courses/learn-harness-engineering/docs/de/lectures/lecture-02-what-a-harness-actually-is/code/harness-vs-no-harness.ts +++ /dev/null @@ -1,180 +0,0 @@ -/** - * harness-vs-no-harness.ts - * - * Side-by-side comparison of the same task executor running with and without - * a harness. The harness version adds explicit rules, verification steps, - * and stop conditions. - * - * Run: npx tsx docs/lectures/lecture-02-what-a-harness-actually-is/code/harness-vs-no-harness.ts - */ - -// --------------------------------------------------------------------------- -// Types & helpers -// --------------------------------------------------------------------------- - -interface TaskResult { - name: string; - passed: boolean; - durationMs: number; - issues: string[]; -} - -function pad(s: string, len: number): string { - return s.length >= len ? s : s + " ".repeat(len - s.length); -} - -// --------------------------------------------------------------------------- -// Simulated task executor -// --------------------------------------------------------------------------- - -/** A simple task that can succeed or fail depending on rules. */ -type Task = { - name: string; - requiresAuth: boolean; - hasTests: boolean; - withinScope: boolean; -}; - -const tasks: Task[] = [ - { name: "Add search endpoint", requiresAuth: true, hasTests: false, withinScope: true }, - { name: "Add delete endpoint", requiresAuth: true, hasTests: true, withinScope: true }, - { name: "Refactor auth middleware", requiresAuth: true, hasTests: true, withinScope: false }, - { name: "Add health check", requiresAuth: false, hasTests: true, withinScope: true }, - { name: "Add rate limiter", requiresAuth: true, hasTests: false, withinScope: true }, -]; - -// --------------------------------------------------------------------------- -// Run WITHOUT harness -- just execute every task, no checks -// --------------------------------------------------------------------------- - -function runWithoutHarness(taskList: Task[]): TaskResult[] { - const results: TaskResult[] = []; - - for (const t of taskList) { - const start = performance.now(); - const issues: string[] = []; - - // The "agent" does the work and calls it done. - let passed = true; - - // Problems that go undetected without a harness - if (t.requiresAuth && !t.hasTests) { - issues.push("No tests for auth-protected endpoint"); - // Agent doesn't notice -- marks as pass anyway - } - if (!t.withinScope) { - issues.push("Task is outside current scope"); - // Agent doesn't notice - } - - const duration = performance.now() - start; - results.push({ name: t.name, passed, durationMs: duration, issues }); - } - - return results; -} - -// --------------------------------------------------------------------------- -// Run WITH harness -- rules, verification, stop conditions -// --------------------------------------------------------------------------- - -function runWithHarness(taskList: Task[]): TaskResult[] { - const rules = { - requireTestsForAuth: true, - enforceScope: true, - }; - - const results: TaskResult[] = []; - - for (const t of taskList) { - const start = performance.now(); - const issues: string[] = []; - let passed = true; - - // Rule: auth endpoints must have tests - if (rules.requireTestsForAuth && t.requiresAuth && !t.hasTests) { - issues.push("BLOCKED: Auth-protected endpoint missing tests"); - passed = false; - } - - // Rule: stay in scope - if (rules.enforceScope && !t.withinScope) { - issues.push("BLOCKED: Task outside active scope -- skipped"); - passed = false; - } - - // Verification: re-check after execution - if (passed && !t.hasTests) { - issues.push("WARNING: No tests exist for this change"); - } - - const duration = performance.now() - start; - results.push({ name: t.name, passed, durationMs: duration, issues }); - } - - return results; -} - -// --------------------------------------------------------------------------- -// Reporting -// --------------------------------------------------------------------------- - -function printComparison( - noHarness: TaskResult[], - withHarness: TaskResult[] -): void { - console.log("\n" + "=".repeat(80)); - console.log(" HARNESS vs NO-HARNESS COMPARISON"); - console.log("=".repeat(80)); - - const header = `| ${pad("Task", 28)}| ${pad("No-Harness", 12)}| ${pad("Issues(NH)", 35)}| ${pad("Harness", 12)}| ${pad("Issues(H)", 35)}|`; - const sep = `|${"-".repeat(30)}|${"-".repeat(14)}|${"-".repeat(37)}|${"-".repeat(14)}|${"-".repeat(37)}|`; - - console.log("\n" + header); - console.log(sep); - - for (let i = 0; i < noHarness.length; i++) { - const nh = noHarness[i]; - const wh = withHarness[i]; - const nhPass = nh.passed ? "PASS" : "FAIL"; - const whPass = wh.passed ? "PASS" : "FAIL"; - const nhIssue = nh.issues[0] ?? "(none)"; - const whIssue = wh.issues[0] ?? "(none)"; - - console.log( - `| ${pad(nh.name, 28)}| ${pad(nhPass, 12)}| ${pad(nhIssue, 35)}| ${pad(whPass, 12)}| ${pad(whIssue, 35)}|` - ); - } - - // Metrics - const nhPassed = noHarness.filter((r) => r.passed).length; - const whPassed = withHarness.filter((r) => r.passed).length; - const nhIssues = noHarness.reduce((sum, r) => sum + r.issues.length, 0); - const whIssues = withHarness.reduce((sum, r) => sum + r.issues.length, 0); - - console.log("\n" + "=".repeat(80)); - console.log(" SUMMARY METRICS"); - console.log("=".repeat(80)); - - const metricsHeader = `| ${pad("Metric", 30)}| ${pad("No Harness", 15)}| ${pad("With Harness", 15)}|`; - const metricsSep = `|${"-".repeat(32)}|${"-".repeat(17)}|${"-".repeat(17)}|`; - console.log("\n" + metricsHeader); - console.log(metricsSep); - console.log(`| ${pad("Tasks passed", 30)}| ${pad(String(nhPassed) + "/" + noHarness.length, 15)}| ${pad(String(whPassed) + "/" + withHarness.length, 15)}|`); - console.log(`| ${pad("Issues detected", 30)}| ${pad(String(nhIssues), 15)}| ${pad(String(whIssues), 15)}|`); - console.log(`| ${pad("False positives (passed but flawed)", 30)}| ${pad(String(nhPassed - truePassCount(noHarness)), 15)}| ${pad(String(whPassed - truePassCount(withHarness)), 15)}|`); - - console.log("\n The harness catches problems the no-harness run silently ignores."); - console.log(" Without a harness, every task 'passes' even when it shouldn't.\n"); -} - -/** Count tasks that actually pass (have tests and are in scope). */ -function truePassCount(results: TaskResult[]): number { - return results.filter((r) => r.passed && r.issues.length === 0).length; -} - -// --------------------------------------------------------------------------- -// Run -// --------------------------------------------------------------------------- - -printComparison(runWithoutHarness(tasks), runWithHarness(tasks)); diff --git a/courses/learn-harness-engineering/docs/de/lectures/lecture-02-what-a-harness-actually-is/code/index.md b/courses/learn-harness-engineering/docs/de/lectures/lecture-02-what-a-harness-actually-is/code/index.md deleted file mode 100644 index 25af2eb5..00000000 --- a/courses/learn-harness-engineering/docs/de/lectures/lecture-02-what-a-harness-actually-is/code/index.md +++ /dev/null @@ -1,8 +0,0 @@ -# Code für Vorlesung 02 - -Verwenden Sie diesen Ordner für kleine Beispiele, die Folgendes unterscheiden: - -- Modellverhalten -- Harness-Verhalten -- Nur-Prompt-Setups -- Umgebungsgestützte Agent-Setups diff --git a/courses/learn-harness-engineering/docs/de/lectures/lecture-02-what-a-harness-actually-is/code/minimal-harness-loop.ts b/courses/learn-harness-engineering/docs/de/lectures/lecture-02-what-a-harness-actually-is/code/minimal-harness-loop.ts deleted file mode 100644 index e56cddd8..00000000 --- a/courses/learn-harness-engineering/docs/de/lectures/lecture-02-what-a-harness-actually-is/code/minimal-harness-loop.ts +++ /dev/null @@ -1,36 +0,0 @@ -type Message = { - role: "user" | "assistant"; - content: string; -}; - -type ToolResult = { - ok: boolean; - output: string; -}; - -function runTool(name: string, input: string): ToolResult { - if (name === "read_file") { - return { ok: true, output: `contents of ${input}` }; - } - - return { ok: false, output: `unknown tool: ${name}` }; -} - -export function minimalHarness(messages: Message[]) { - const nextAction = { - tool: "read_file", - input: "README.md" - }; - - const result = runTool(nextAction.tool, nextAction.input); - - return { - messages: [ - ...messages, - { - role: "assistant", - content: `Tool ${nextAction.tool} returned: ${result.output}` - } - ] - }; -} diff --git a/courses/learn-harness-engineering/docs/de/lectures/lecture-02-what-a-harness-actually-is/index.md b/courses/learn-harness-engineering/docs/de/lectures/lecture-02-what-a-harness-actually-is/index.md deleted file mode 100644 index 8f089f6d..00000000 --- a/courses/learn-harness-engineering/docs/de/lectures/lecture-02-what-a-harness-actually-is/index.md +++ /dev/null @@ -1,109 +0,0 @@ -[中文版本 →](../../../zh/lectures/lecture-02-what-a-harness-actually-is/) - -> Codebeispiele: [code/](https://github.com/walkinglabs/learn-harness-engineering/blob/main/docs/de/lectures/lecture-02-what-a-harness-actually-is/code/) -> Praxisprojekt: [Project 01. Prompt-only vs. rules-first](./../../projects/project-01-baseline-vs-minimal-harness/index.md) - -# Lektion 02. Was harness wirklich bedeutet - -Das Wort „harness" wird in Kreisen von KI-Coding-Agenten viel herumgereicht, aber ganz ehrlich: Die meisten Menschen meinen einfach „eine Prompt-Datei", wenn sie harness sagen. Das ist kein harness. Das ist, als würde man ein Restaurant mit nichts als Zutaten eröffnen — kein Herd, keine Messer, keine Rezepte, kein Anrichte-Workflow. Das ist kein Restaurant. Das ist ein Kühlschrank. - -Diese Lektion gibt Ihnen eine präzise, umsetzbare harness-Definition. Keine akademische Abstraktion, sondern ein Framework, das Sie heute nutzen können: Ein harness besteht aus fünf Subsystemen, jedes mit klaren Zuständigkeiten und Bewertungskriterien. - -## Beginnen wir mit einer Analogie - -Stellen Sie sich vor, Sie sind ein neu eingestellter Ingenieur, der ohne jegliche Dokumentation in ein Projekt geworfen wird. Kein README, keine Kommentare im Code, niemand sagt Ihnen, wie man Tests ausführt, die CI-Konfiguration ist irgendwo vergraben. Können Sie guten Code schreiben? Vielleicht — wenn Sie klug und geduldig genug sind. Aber Sie werden enorme Zeit damit verbringen, herauszufinden, „worum es in diesem Projekt geht", anstatt „das Problem zu lösen". - -Ein KI-Agent steht vor genau derselben Situation. Und es ist noch schlimmer — Sie können wenigstens einen Kollegen fragen. Der Agent kann nur die Dateien sehen, die Sie ihm vorlegen, und die Befehle ausführen, die ihm zur Verfügung stehen. Er kann niemanden auf die Schulter tippen und fragen: „Hey, welche Version des ORMs verwendet dieses Projekt?" - -OpenAI formuliert das Kernprinzip als „the repo IS the spec" — der gesamte notwendige Kontext sollte im Repository enthalten sein, geliefert durch strukturierte Instruktionsdateien, explizite Verifizierungsbefehle und eine klare Verzeichnisorganisation. Anthropics Dokumentation zu langlebigen Agenten betont Zustandspersistenz, explizite Wiederherstellungspfade und strukturiertes Fortschritts-Tracking. Die beiden Unternehmen konzentrieren sich auf unterschiedliche Aspekte, aber sie sagen dasselbe: **alles in der Engineering-Infrastruktur außerhalb des Modells bestimmt, wie viel der Modelfähigkeiten tatsächlich realisiert werden.** - -Betrachten Sie einige Tools, die Sie bereits kennen: - -**Claude Code** verkörpert das harness-Denken. Es liest `CLAUDE.md` aus Ihrem Repo (Rezeptregal), kann Shell-Befehle ausführen (Messerblock), arbeitet in Ihrer lokalen Umgebung (Herd), verwaltet Sitzungsverläufe (Vorbereitungsstation) und kann Tests ausführen und Ergebnisse sehen (Qualitätskontrollfenster). Aber wenn Sie ihm nicht sagen, wie man Tests ausführt, ist das Qualitätskontrollfenster kaputt — niemand weiß, ob das Gericht gar ist. - -**Cursor** folgt einer ähnlichen Logik. Seine `.cursorrules`-Datei ist das Rezeptregal, das Terminal ist der Messerblock, es liest Ihre Projektstruktur und Lint-Konfiguration für den Herd. Aber Cursors Zustandsverwaltung ist relativ schwach — IDE schließen und wieder öffnen, und der vorherige Kontext ist verschwunden. - -**Codex** (OpenAIs Coding-Agent) verwendet git worktrees, um die Laufzeitumgebung jeder Aufgabe zu isolieren, gepaart mit einem lokalen Observability-Stack (Logs, Metriken, Traces), sodass jede Änderung in einer unabhängigen Umgebung verifiziert wird. In Repos mit `AGENTS.md` und klaren Verifizierungsbefehlen funktioniert er deutlich besser als in „nackten" Repos. - -**AutoGPT** ist das abschreckende Beispiel — fehlendes strukturiertes Zustandsmanagement führt bei langen Aufgaben zu Kontextakkumulation, und fehlende präzise Feedback-Mechanismen verursachen Endlosschleifen beim Agenten. Viele sagen, AutoGPT „funktioniert nicht", aber in Wirklichkeit funktioniert AutoGPTs harness nicht — geben Sie einem Koch einen kaputten Herd, und selbst die besten Zutaten werden kein Essen produzieren. - -## Zentrale Konzepte - -- **Was ist ein harness**: Alles in der Engineering-Infrastruktur außerhalb der Modellgewichte. OpenAI destilliert die Kernaufgabe des Ingenieurs in drei Dinge: Umgebungen gestalten, Intention ausdrücken und Feedback-Schleifen aufbauen. Anthropic nennt sein Claude Agent SDK ein „general-purpose agent harness." -- **Das Repo ist die einzige Quelle der Wahrheit**: Alles, was der Agent nicht sehen kann, existiert für alle praktischen Zwecke nicht. OpenAI behandelt das Repo als „system of record" — der gesamte notwendige Kontext muss dort leben, durch strukturierte Dateien und klare Verzeichnisorganisation. -- **Eine Karte geben, kein Handbuch**: OpenAIs Erfahrung — `AGENTS.md` sollte eine Übersichtsseite sein, keine Enzyklopädie. Etwa 100 Zeilen reichen aus. Wenn es nicht passt, teilen Sie es in das `docs/`-Verzeichnis auf und lassen Sie den Agenten bei Bedarf lesen. -- **Einschränken, nicht mikroverwalten**: Ein gutes harness verwendet ausführbare Regeln, um den Agenten einzuschränken, anstatt Anweisungen einzeln aufzuzählen. OpenAI sagt „enforce invariants, don't micromanage implementation"; Anthropic hat festgestellt, dass Agenten ihre eigene Arbeit zuversichtlich loben, und die Lösung darin besteht, „die Person, die die Arbeit erledigt" von „der Person, die die Arbeit prüft" zu trennen. -- **Komponenten einzeln entfernen**: Um den Grenzbeitrag jeder harness-Komponente zu quantifizieren, entfernen Sie sie einzeln und prüfen Sie, welche Entfernung den größten Leistungsabfall verursacht. Anthropic hat diese Methode angewandt und festgestellt, dass mit stärker werdenden Modellen einige Komponenten aufhören, kritisch zu sein — aber neue tauchen immer auf. - -## Das Fünf-Subsystem-harness-Modell - -Zurück zur Küchenanalogie. Eine vollständige Küche hat fünf Funktionsbereiche, und ein harness hat fünf Subsysteme: - -```mermaid -flowchart LR - Rules["Project rules
AGENTS.md / CLAUDE.md"] --> Agent["AI Agent"] - State["Progress and git
PROGRESS.md / commits"] --> Agent - Agent --> Tools["Tools
shell / files / tests"] - Tools --> Env["Runtime
deps / services / versions"] - Env --> Checks["Check results
test / lint / build"] - Checks --> Agent -``` - -**Instruktions-Subsystem (Rezeptregal)**: Erstellen Sie `AGENTS.md` (oder `CLAUDE.md`) mit einer Projektübersicht und einem Zweck (ein Satz), Tech-Stack und Versionen (Python 3.11, FastAPI 0.100+, PostgreSQL 15), Erststart-Befehlen (`make setup`, `make test`), nicht verhandelbaren harten Einschränkungen („Alle APIs müssen OAuth 2.0 verwenden") und Links zu detaillierterer Dokumentation. - -**Tool-Subsystem (Messerblock)**: Stellen Sie sicher, dass der Agent ausreichenden Tool-Zugriff hat. Deaktivieren Sie die Shell nicht aus „Sicherheitsgründen" — wenn der Agent nicht einmal `pip install` ausführen kann, wie soll er dann arbeiten? Aber öffnen Sie auch nicht alles — folgen Sie dem Prinzip der geringsten Rechte. - -**Umgebungs-Subsystem (Herd)**: Machen Sie den Umgebungszustand selbstbeschreibend. Verwenden Sie `pyproject.toml` oder `package.json` zum Sperren von Abhängigkeiten, `.nvmrc` oder `.python-version` für Laufzeitversionen, Docker oder Devcontainer für Reproduzierbarkeit. - -**Zustands-Subsystem (Vorbereitungsstation)**: Lange Aufgaben benötigen Fortschritts-Tracking. Verwenden Sie eine einfache `PROGRESS.md`-Datei, die Folgendes dokumentiert: was erledigt ist, was in Arbeit ist, was blockiert ist. Aktualisieren Sie vor jedem Sitzungsende, lesen Sie beim Start der nächsten Sitzung. - -**Feedback-Subsystem (Qualitätskontrollfenster)**: Dies ist das Subsystem mit dem höchsten ROI. Listen Sie Verifizierungsbefehle explizit in `AGENTS.md` auf: -``` -Verification commands: -- Tests: pytest tests/ -x -- Type check: mypy src/ --strict -- Lint: ruff check src/ -- Full verification: make check (includes all above) -``` - -Wenn ein Subsystem fehlt, ist das wie ein fehlender Funktionsbereich in der Küche — Sie können immer noch kochen, aber es ist immer umständlich. - -**Wert von harness-Komponenten quantifizieren**: Verwenden Sie „isometrische Modellkontrolle". Halten Sie das Modell fest, entfernen Sie Subsysteme einzeln und messen Sie, welche Entfernung den größten Leistungsabfall verursacht. Der größte Abfall zeigt die Komponente mit dem höchsten Grenzbeitrag in dieser Aufgabe, nicht automatisch den Engpass. Um den Engpass zu finden, kombinieren Sie das Experiment mit Fehlerprotokollen und Attribution: unklare Aufgabe, zu wenig Kontext, nicht reproduzierbare Umgebung, fehlendes Verifikationsfeedback oder gebrochene Zustandsverwaltung. - -## Die wahre Geschichte eines Teams - -Ein Team verwendete GPT-4o für eine TypeScript + React Frontend-App (~20.000 Zeilen Code). Sie durchliefen vier Stufen — im Grunde rüsteten sie die Küche Stück für Stück aus: - -**Stufe 1 — Leere Küche**: Nur eine einfache Projektbeschreibung im README. 1 von 5 Durchläufen erfolgreich (20%). Hauptsächliche Fehler: falschen Package-Manager gewählt (npm vs. yarn), Komponenten-Benennungskonventionen nicht befolgt, konnte Tests nicht ausführen. - -**Stufe 2 — Rezeptregal installiert**: `AGENTS.md` mit Tech-Stack-Versionen, Namenskonventionen und wichtigen Architekturentscheidungen hinzugefügt. Erfolgsquote stieg auf 60%. Verbleibende Fehler waren hauptsächlich Umgebungsprobleme und fehlende Verifizierung. - -**Stufe 3 — Qualitätskontrollfenster geöffnet**: Verifizierungsbefehle in `AGENTS.md` aufgelistet: `yarn test && yarn lint && yarn build`. Erfolgsquote stieg auf 80%. - -**Stufe 4 — Vorbereitungsstation bereit**: Fortschrittsdatei-Vorlagen eingeführt, in denen Agenten bei jedem Durchlauf abgeschlossene und nicht abgeschlossene Arbeit dokumentierten. Erfolgsquote stabilisierte sich bei 80–100%. - -Vier Iterationen, das Modell hat sich gar nicht geändert, die Erfolgsquote stieg von 20% auf nahezu 100%. Das ist die Macht von harness Engineering. Sie haben keine teureren Zutaten gekauft — Sie haben die Küche einfach richtig organisiert. - -## Wichtigste Erkenntnisse - -- Harness = Instructions + Tools + Environment + State + Feedback. Fünf Subsysteme, wie die fünf Funktionsbereiche einer Küche — alle unverzichtbar. -- Wenn es keine Modellgewichte sind, ist es harness. Ihr harness bestimmt, wie viel der Modelfähigkeiten realisiert wird. -- Unter den fünf Subsystemen hat das Feedback-Subsystem normalerweise die geringste Investition und die höchste Rendite. Bringen Sie zuerst Ihre Verifizierungsbefehle in Ordnung — das Qualitätskontrollfenster ist das lohnendste Upgrade. -- Verwenden Sie „isometrische Modellkontrolle", um den Grenzbeitrag jedes Subsystems zu quantifizieren; nutzen Sie Fehlerprotokolle und Attribution, um den echten Engpass zu finden. -- Harness verrottet wie Code. Überprüfen Sie regelmäßig und tilgen Sie harness-Schulden wie Sie technische Schulden tilgen. - -## Weiterführende Literatur - -- [OpenAI: Harness Engineering](https://openai.com/index/harness-engineering/) -- [Anthropic: Effective Harnesses for Long-Running Agents](https://www.anthropic.com/engineering/effective-harnesses-for-long-running-agents) -- [HumanLayer: Harness Engineering for Coding Agents](https://humanlayer.dev/articles/harness-engineering-for-coding-agents/) -- [SWE-agent: Agent-Computer Interfaces](https://github.com/princeton-nlp/SWE-agent) -- [Thoughtworks: Harness Engineering on Technology Radar](https://www.thoughtworks.com/radar) - -## Übungen - -1. **Fünftupel-harness-Audit**: Nehmen Sie ein Projekt, in dem Sie einen KI-Agenten verwenden, und führen Sie ein vollständiges Audit mit dem Fünftupel-Framework durch. Bewerten Sie jedes Subsystem mit 1–5. Finden Sie das am niedrigsten bewertete Subsystem, investieren Sie 30 Minuten in die Verbesserung und beobachten Sie die Veränderung der Agentenleistung. - -2. **Isometrisches Modellkontrollexperiment**: Wählen Sie ein Modell und eine anspruchsvolle Aufgabe. Entfernen Sie nacheinander Instruktionen (AGENTS.md löschen), Feedback entfernen (keine Verifizierungsbefehle angeben), Zustand entfernen (keine Fortschrittsdateien) — entfernen Sie jeweils nur eines und messen Sie den Leistungsabfall. Ordnen Sie basierend auf den Ergebnissen die Subsystem-Wichtigkeit für Ihr Projekt. - -3. **Affordanz-Analyse**: Finden Sie ein Szenario, in dem der Agent in Ihrem Projekt „etwas tun möchte, aber nicht kann" (z. B. weiß, dass er parametrisierte Abfragen verwenden sollte, aber die ORM-Muster Ihres Projekts nicht kennt). Analysieren Sie, ob dies eine Gulf of Execution (weiß nicht wie) oder Gulf of Evaluation (weiß nicht, ob es richtig ist) ist, und entwerfen Sie dann eine harness-Verbesserung, um die Lücke zu schließen. diff --git a/courses/learn-harness-engineering/docs/de/lectures/lecture-03-why-the-repository-must-become-the-system-of-record/code/index.md b/courses/learn-harness-engineering/docs/de/lectures/lecture-03-why-the-repository-must-become-the-system-of-record/code/index.md deleted file mode 100644 index 182027ad..00000000 --- a/courses/learn-harness-engineering/docs/de/lectures/lecture-03-why-the-repository-must-become-the-system-of-record/code/index.md +++ /dev/null @@ -1,7 +0,0 @@ -# Code für Vorlesung 03 - -Verwenden Sie diesen Ordner für Beispiele zu: - -- Agenten-lesbaren Repository-Strukturen -- Dokumentation als System of Record -- Schlechte vs. gute Platzierung von Wissen diff --git a/courses/learn-harness-engineering/docs/de/lectures/lecture-03-why-the-repository-must-become-the-system-of-record/code/repo-knowledge-layout.txt b/courses/learn-harness-engineering/docs/de/lectures/lecture-03-why-the-repository-must-become-the-system-of-record/code/repo-knowledge-layout.txt deleted file mode 100644 index 45315dac..00000000 --- a/courses/learn-harness-engineering/docs/de/lectures/lecture-03-why-the-repository-must-become-the-system-of-record/code/repo-knowledge-layout.txt +++ /dev/null @@ -1,13 +0,0 @@ -AGENTS.md -docs/ - ARCHITECTURE.md - PRODUCT.md - RELIABILITY.md - references/ - electron.md - sqlite.md - plans/ - active/ - completed/ -src/ -scripts/ diff --git a/courses/learn-harness-engineering/docs/de/lectures/lecture-03-why-the-repository-must-become-the-system-of-record/code/repo-reader.ts b/courses/learn-harness-engineering/docs/de/lectures/lecture-03-why-the-repository-must-become-the-system-of-record/code/repo-reader.ts deleted file mode 100644 index 46f6a62a..00000000 --- a/courses/learn-harness-engineering/docs/de/lectures/lecture-03-why-the-repository-must-become-the-system-of-record/code/repo-reader.ts +++ /dev/null @@ -1,189 +0,0 @@ -/** - * repo-reader.ts - * - * Reads a directory structure and scores it on discoverability. - * Checks for: AGENTS.md, docs/, architecture docs, feature tracking, - * handoff files, and other signals of a repository that serves as the - * system of record. - * - * Usage: - * npx tsx docs/lectures/lecture-03.../code/repo-reader.ts [path] - * (defaults to current working directory if no path given) - * - * Run: npx tsx docs/lectures/lecture-03-why-the-repository-must-become-the-system-of-record/code/repo-reader.ts - */ - -import * as fs from "node:fs"; -import * as path from "node:path"; - -// --------------------------------------------------------------------------- -// Scoring criteria -// --------------------------------------------------------------------------- - -interface Check { - name: string; - description: string; - maxPoints: number; - check: (dir: string) => { points: number; found: string[]; missing: string[] }; -} - -const checks: Check[] = [ - { - name: "AGENTS.md / CLAUDE.md", - description: "Agent-readable instruction file at repo root", - maxPoints: 15, - check: (dir) => { - const candidates = ["AGENTS.md", "CLAUDE.md", ".claude/CLAUDE.md"]; - const found = candidates.filter((f) => fs.existsSync(path.join(dir, f))); - return { points: found.length > 0 ? 15 : 0, found, missing: found.length > 0 ? [] : candidates }; - }, - }, - { - name: "Documentation directory", - description: "Dedicated docs/ or documentation/ directory", - maxPoints: 10, - check: (dir) => { - const candidates = ["docs", "documentation", "doc"]; - const found = candidates.filter((f) => { - const p = path.join(dir, f); - return fs.existsSync(p) && fs.statSync(p).isDirectory(); - }); - return { points: found.length > 0 ? 10 : 0, found, missing: found.length > 0 ? [] : ["docs/"] }; - }, - }, - { - name: "Architecture documentation", - description: "Files describing system architecture", - maxPoints: 15, - check: (dir) => { - const patterns = [ - "architecture.md", "ARCHITECTURE.md", "docs/architecture.md", - "docs/architecture/", "design.md", "DESIGN.md", - ]; - const found = patterns.filter((f) => fs.existsSync(path.join(dir, f))); - return { points: found.length > 0 ? 15 : 0, found, missing: found.length > 0 ? [] : ["architecture.md or docs/architecture/"] }; - }, - }, - { - name: "Feature tracking", - description: "Feature list or task tracking file", - maxPoints: 15, - check: (dir) => { - const patterns = [ - "feature_list.json", "features.md", "FEATURES.md", - "docs/features.md", "tasks.json", "TODO.md", - ]; - const found = patterns.filter((f) => fs.existsSync(path.join(dir, f))); - return { points: found.length > 0 ? 15 : 0, found, missing: found.length > 0 ? [] : ["feature_list.json or features.md"] }; - }, - }, - { - name: "Handoff / session continuity", - description: "Files for multi-session continuity", - maxPoints: 15, - check: (dir) => { - const patterns = [ - "HANDOFF.md", "handoff.md", "SESSION_NOTES.md", - "docs/handoff.md", ".handoff", "PROGRESS.md", - ]; - const found = patterns.filter((f) => fs.existsSync(path.join(dir, f))); - return { points: found.length > 0 ? 15 : 0, found, missing: found.length > 0 ? [] : ["HANDOFF.md or PROGRESS.md"] }; - }, - }, - { - name: "Testing structure", - description: "Test directory or test configuration", - maxPoints: 10, - check: (dir) => { - const patterns = ["test", "tests", "__tests__", "spec"]; - const found = patterns.filter((f) => { - const p = path.join(dir, f); - return fs.existsSync(p) && fs.statSync(p).isDirectory(); - }); - return { points: found.length > 0 ? 10 : 0, found, missing: found.length > 0 ? [] : ["test/ or tests/"] }; - }, - }, - { - name: "Configuration files", - description: "Project config (package.json, tsconfig, etc.)", - maxPoints: 10, - check: (dir) => { - const patterns = ["package.json", "tsconfig.json", "pyproject.toml", "Cargo.toml", "go.mod"]; - const found = patterns.filter((f) => fs.existsSync(path.join(dir, f))); - return { points: found.length > 0 ? 10 : 0, found, missing: found.length > 0 ? [] : ["package.json or equivalent"] }; - }, - }, - { - name: "README", - description: "Root README file", - maxPoints: 10, - check: (dir) => { - const patterns = ["README.md", "README.rst", "README.txt", "README"]; - const found = patterns.filter((f) => fs.existsSync(path.join(dir, f))); - return { points: found.length > 0 ? 10 : 0, found, missing: found.length > 0 ? [] : ["README.md"] }; - }, - }, -]; - -// --------------------------------------------------------------------------- -// Report generation -// --------------------------------------------------------------------------- - -function pad(s: string, len: number): string { - return s.length >= len ? s : s + " ".repeat(len - s.length); -} - -function scoreRepo(targetDir: string): void { - const resolved = path.resolve(targetDir); - - if (!fs.existsSync(resolved)) { - console.error(` Error: Directory not found: ${resolved}`); - process.exit(1); - } - - console.log("\n" + "=".repeat(80)); - console.log(" REPOSITORY DISCOVERABILITY SCORE"); - console.log("=".repeat(80)); - console.log(` Target: ${resolved}\n`); - - const header = `| ${pad("Criterion", 30)}| ${pad("Points", 8)}| ${pad("Status", 10)}| Details`; - const sep = `|${"-".repeat(32)}|${"-".repeat(10)}|${"-".repeat(12)}|${"-".repeat(30)}`; - console.log(header); - console.log(sep); - - let totalScore = 0; - let maxScore = 0; - - for (const check of checks) { - const result = check.check(resolved); - totalScore += result.points; - maxScore += check.maxPoints; - const status = result.points > 0 ? "PASS" : "FAIL"; - const detail = result.found.length > 0 ? result.found.join(", ") : result.missing[0]; - - console.log(`| ${pad(check.name, 30)}| ${pad(result.points + "/" + check.maxPoints, 8)}| ${pad(status, 10)}| ${detail}`); - } - - // Final score - console.log("\n" + "-".repeat(80)); - console.log(` TOTAL SCORE: ${totalScore} / ${maxScore} (${Math.round((totalScore / maxScore) * 100)}%)`); - - // Grade - const pct = totalScore / maxScore; - let grade: string; - if (pct >= 0.9) grade = "A -- Repository is a strong system of record"; - else if (pct >= 0.7) grade = "B -- Good foundation, minor gaps"; - else if (pct >= 0.5) grade = "C -- Partial structure, significant gaps"; - else if (pct >= 0.3) grade = "D -- Minimal structure, hard for agents to navigate"; - else grade = "F -- Repository lacks discoverability signals"; - - console.log(` GRADE: ${grade}`); - console.log("=".repeat(80) + "\n"); -} - -// --------------------------------------------------------------------------- -// Main -// --------------------------------------------------------------------------- - -const target = process.argv[2] || process.cwd(); -scoreRepo(target); diff --git a/courses/learn-harness-engineering/docs/de/lectures/lecture-03-why-the-repository-must-become-the-system-of-record/code/system-of-record-checklist.md b/courses/learn-harness-engineering/docs/de/lectures/lecture-03-why-the-repository-must-become-the-system-of-record/code/system-of-record-checklist.md deleted file mode 100644 index ecfc3fe0..00000000 --- a/courses/learn-harness-engineering/docs/de/lectures/lecture-03-why-the-repository-must-become-the-system-of-record/code/system-of-record-checklist.md +++ /dev/null @@ -1,11 +0,0 @@ -# System-of-Record Checkliste - -Kann ein neuer Agent Folgendes nur aus dem Repository herausfinden? - -- Welches Produkt entwickelt wird? -- Was die App für Benutzer tun soll? -- Wie die Codebase organisiert ist? -- Wie die App gestartet wird? -- Wie der Zustand geprüft wird? -- Welche Arbeit aktuell in Bearbeitung ist? -- Welche Qualitätsstandards wichtig sind? diff --git a/courses/learn-harness-engineering/docs/de/lectures/lecture-03-why-the-repository-must-become-the-system-of-record/index.md b/courses/learn-harness-engineering/docs/de/lectures/lecture-03-why-the-repository-must-become-the-system-of-record/index.md deleted file mode 100644 index 18d51419..00000000 --- a/courses/learn-harness-engineering/docs/de/lectures/lecture-03-why-the-repository-must-become-the-system-of-record/index.md +++ /dev/null @@ -1,137 +0,0 @@ -[中文版本 →](../../../zh/lectures/lecture-03-why-the-repository-must-become-the-system-of-record/) - -> Codebeispiele: [code/](https://github.com/walkinglabs/learn-harness-engineering/blob/main/docs/de/lectures/lecture-03-why-the-repository-must-become-the-system-of-record/code/) -> Praxisprojekt: [Projekt 02. Agentenlesbarer Workspace](./../../projects/project-02-agent-readable-workspace/index.md) - -# Lektion 03. Das Repository zur einzigen Quelle der Wahrheit machen - -Die Architekturentscheidungen deines Teams sind über Confluence, Slack, Jira und die Köpfe einiger Senior Engineers verstreut. Für Menschen funktioniert das gerade so: Man kann Kolleginnen fragen, Chatverläufe durchsuchen, Dokumente ausgraben. Wenn alles scheitert, kann man jemanden im Pausenraum abfangen. Für einen KI-Agenten aber existiert Information, die nicht im Repository steht, schlicht nicht. - -Das ist keine Übertreibung. Denk darüber nach, was die Eingaben eines Agenten tatsächlich sind: Systemprompts und Aufgabenbeschreibungen, Dateiinhalte aus dem Repository und Tool-Ausgaben. Das war's. Deine Slack-Historie, Jira-Tickets, Confluence-Seiten und die Architekturentscheidung, die du am Freitagnachmittag bei Kaffee mit einem Kollegen besprochen hast - der Agent sieht nichts davon. Er kann nicht "jemanden fragen" oder "den Chatverlauf durchsuchen". Er ist ein Engineer, der im Repository eingeschlossen ist. Alles außerhalb kennt er nicht. - -Die Frage lautet also: Gibst du diesem Engineer eine gute Karte? - -## Was auf die Karte gehört - -OpenAI formuliert es sehr direkt: **Information, die nicht im Repo existiert, existiert für den Agenten nicht.** Sie nennen das das Prinzip "repo as spec" - das Repository selbst ist das Spezifikationsdokument mit der höchsten Autorität. - -Anthropics Dokumentation zu lang laufenden Agenten sagt dasselbe: Persistenter Zustand ist eine notwendige Bedingung für Kontinuität bei langen Aufgaben. Die Wiederherstellbarkeit von Wissen über Sitzungen hinweg bestimmt direkt die Erfolgsraten. Und dieser Zustand muss im Repository existieren, denn das ist der einzige stabile, zugängliche Speicher, den der Agent hat. - -Vielleicht denkst du: "Unser Team ist klein, das Wissen steckt in allen Köpfen, und das funktioniert." Für Menschen, ja. Aber wenn du einen Agenten nutzt, akzeptiere diese Tatsache: Der Agent kann keine Menschen fragen. Alles, was er wissen muss, muss aufgeschrieben und dort abgelegt sein, wo er es finden kann. - -Es geht nicht darum, "mehr Dokumentation zu schreiben". Es geht darum, "Entscheidungsinformationen an den richtigen Ort zu bringen". Eine 50-zeilige `ARCHITECTURE.md` im Verzeichnis `src/api/` ist zehntausendmal nützlicher als ein 500-seitiges Design-Dokument in Confluence, das niemand pflegt. Es ist wie eine handgezeichnete Bürokarte, die an deinem Schreibtisch klebt, gegenüber einem wunderschönen Architekturplan, der in einem Aktenschrank eingeschlossen ist: Erstere ist da, wenn du sie brauchst; Letzterer ist technisch überlegen, aber im Moment nutzlos. - -## Wissenssichtbarkeit - -```mermaid -flowchart LR - Slack["Regeln in Slack"] --> Write["In Repo-Dateien schreiben
AGENTS.md / ARCHITECTURE.md / PROGRESS.md"] - Confluence["Regeln in Confluence"] --> Write - Heads["Regeln in Köpfen von Menschen"] --> Write - Jira["Regeln in Jira-Tickets"] --> Write - Write --> Repo["Repository-Dateien"] - Repo --> Agent["Neue Agentensitzung
liest das Repo direkt"] - Warning["Wenn eine Regel nicht im Repo steht,
kann der Agent sie nicht sehen"] --> Agent -``` - -Wie testest du, ob deine Karte gut genug ist? Führe einen "Cold-Start-Test" aus: Öffne eine komplett neue Agentensitzung, die nur Repository-Inhalte nutzt, und prüfe, ob sie fünf grundlegende Fragen beantworten kann: - -```mermaid -flowchart TB - Q1["Was ist dieses System?"] --> A1["AGENTS.md / README"] - Q2["Wie ist es organisiert?"] --> A2["ARCHITECTURE.md / Modul-Dokumente"] - Q3["Wie führe ich es aus?"] --> A3["Makefile / init.sh / package scripts"] - Q4["Wie verifiziere ich es?"] --> A4["Test-, Lint- und Check-Befehle"] - Q5["Wo stehen wir gerade?"] --> A5["PROGRESS.md / feature list / git history"] - - A1 --> Ready["Eine neue Sitzung kann starten
ohne einen Menschen zu fragen"] - A2 --> Ready - A3 --> Ready - A4 --> Ready - A5 --> Ready -``` - -Wenn sie das nicht beantworten kann, hat die Karte weiße Flecken. Wo die Karte leer ist, rät der Agent. Falsche Vermutungen werden zu Bugs, zu viel Raten verschwendet Kontext. Und jede neue Sitzung rät erneut. Die Kosten des Ratens sind immer höher als die Kosten, die Karte gleich richtig zu zeichnen. - -## Zentrale Konzepte - -- **Knowledge Visibility Gap**: Der Anteil des gesamten Projektwissens, der NICHT im Repository steht. Je größer die Lücke, desto höher die Fehlerrate des Agenten. Wie viel implizites Wissen über dieses Projekt lebt in deinem Kopf? Zähle alles und schau dann, wie viel davon im Repo angekommen ist. Die Differenz ist deine Sichtbarkeitslücke. -- **System of Record**: Das Code-Repository als autoritative Quelle für Projektentscheidungen, Architekturconstraints, Ausführungszustand und Verifikationsstandards. Das Repo hat das letzte Wort; nichts anderes zählt. Wie eine Karte mit "Straße gesperrt": Du fährst diese Straße nicht. Wenn diese Information aber nur im Kopf einer Person existiert, musst du jedes Mal fragen. -- **Cold-Start Test**: Die fünf Fragen oben. Wie viele beantwortet werden können, zeigt, wie vollständig deine Karte ist. -- **Discovery Cost**: Wie viel Kontextbudget der Agent verbrennt, um eine zentrale Information im Repo zu finden. Je versteckter die Information, desto höher die Discovery Cost und desto weniger Budget bleibt für die eigentliche Aufgabe. Kritische Information in einer README zehn Verzeichnisebenen tief zu verstecken ist wie den Feuerlöscher in einem Tresor im Keller einzuschließen: Er existiert, aber du findest ihn nicht, wenn du ihn brauchst. -- **Knowledge Decay Rate**: Der Anteil der Wissenseinträge, der pro Zeiteinheit veraltet. Dokumentation, die nicht mehr mit dem Code synchron ist, ist der größte Feind - schlimmer als gar keine Dokumentation. -- **ACID Analogy**: Datenbank-Transaktionsprinzipien (Atomicity, Consistency, Isolation, Durability) auf Agentenzustandsverwaltung anwenden. Unten wird das ausgeführt. - -## Wie man eine gute Karte zeichnet - -**Prinzip 1: Wissen lebt neben dem Code.** Eine Regel zur Authentifizierung von API-Endpunkten gehört neben den API-Code, nicht in ein riesiges globales Dokument. Lege in jedem Modulverzeichnis ein kurzes Dokument ab, das Verantwortlichkeiten, Schnittstellen und besondere Constraints des Moduls erklärt. Wie Regalbeschriftungen in einer Bibliothek: Wenn du Geschichtsbücher willst, gehst du direkt zum Regal "History". Du musst nicht die ganze Bibliothek durchsuchen. - -**Prinzip 2: Nutze eine standardisierte Einstiegsdatei.** `AGENTS.md` (oder `CLAUDE.md`) ist die Landing Page des Agenten. Sie muss nicht alle Informationen enthalten, aber sie muss dem Agenten erlauben, drei Fragen schnell zu beantworten: "Was ist dieses Projekt?", "Wie führe ich es aus?" und "Wie verifiziere ich es?" 50-100 Zeilen reichen. - -**Prinzip 3: Minimal, aber vollständig.** Jedes Wissensstück sollte einen klaren Use Case haben. Wenn das Entfernen einer Regel die Entscheidungsqualität des Agenten nicht beeinträchtigt, sollte diese Regel nicht existieren. Aber jede Frage aus dem Cold-Start-Test muss eine Antwort haben. Das ist ein feines Gleichgewicht: nicht zu viel, nicht zu wenig, genau genug. - -**Prinzip 4: Mit Code aktualisieren.** Kopple Wissensupdates an Codeänderungen. Der einfachste Ansatz: Architekturdocs im entsprechenden Modulverzeichnis ablegen. Wenn du Code änderst, siehst du das Dokument natürlich. Nach Codeänderungen kann CI daran erinnern zu prüfen, ob die Docs aktualisiert werden müssen. - -**Konkrete Repo-Struktur**: - -``` -project/ -├── AGENTS.md # Entry: project overview, run commands, hard constraints -├── src/ -│ ├── api/ -│ │ ├── ARCHITECTURE.md # API layer architecture decisions -│ │ └── ... -│ ├── db/ -│ │ ├── CONSTRAINTS.md # Database operation hard constraints -│ │ └── ... -│ └── ... -├── PROGRESS.md # Current progress: done, in-progress, blocked -└── Makefile # Standardized commands: setup, test, lint, check -``` - -## Agentenzustand mit ACID-Prinzipien verwalten - -Diese Analogie stammt aus dem Datenbank-Transaktionsmanagement. Du könntest denken, dass das überkompliziert ist, aber tatsächlich gibt es dir einen sehr praktischen Rahmen: - -- **Atomicity**: Jede "logische Operation" (z. B. "neuen Endpoint hinzufügen und Tests aktualisieren") bekommt einen git commit. Wenn sie auf halbem Weg scheitert, mit `git stash` zurückrollen. Alles oder nichts - kein "halb fertig". -- **Consistency**: Definiere Verifikationsprädikate für einen "konsistenten Zustand": alle Tests grün, Lint meldet null Fehler. Der Agent führt nach jeder Operation Verifikation aus; inkonsistente Zwischenzustände werden nicht committed. Wie bei einer Banküberweisung: Du kannst nicht abbuchen, ohne gutzuschreiben. -- **Isolation**: Wenn mehrere Agenten gleichzeitig arbeiten, entwirf Zustandsdateien so, dass Race Conditions vermieden werden. Einfacher Ansatz: Jeder Agent nutzt seine eigene Progress-Datei, oder git branches sorgen für Isolation. Zwei Köche können nicht gleichzeitig denselben Topf würzen - wer ist verantwortlich, wenn er versalzen ist? -- **Durability**: Kritisches Projektwissen lebt in git-getrackten Dateien. Temporärer Zustand kann im Sitzungspeicher bleiben, aber sitzungsübergreifendes Wissen muss in Dateien persistiert werden. Was in deinem Kopf ist, zählt nicht - nur was auf Papier steht, zählt. - -## Eine echte Transformationsgeschichte - -Ein Team betreute eine E-Commerce-Plattform mit etwa 30 Microservices. Architekturentscheidungen (Inter-Service-Kommunikationsprotokolle, Datenkonsistenzstrategien, API-Versionierungsregeln) waren verstreut über Confluence (teilweise veraltet), Slack (schwer zu durchsuchen), die Köpfe einiger Senior Engineers (nicht skalierbar) und sporadische Codekommentare (nicht systematisch). - -Nach der Einführung von KI-Agenten erforderten 70% der Aufgaben menschliche Intervention. Fast jeder Fehler hing damit zusammen, dass der Agent gegen eine implizite Constraint verstieß, die "alle kennen, aber niemand aufgeschrieben hat". Wie bei einem neuen Mitarbeitenden, dem niemand gesagt hat: "Du musst deine Essensbestellung in den Gruppenchat posten." Er rät falsch, wird zurechtgewiesen, aber danach schreibt immer noch niemand die Regel auf. - -Das Team führte eine Transformation durch: -1. `AGENTS.md` im Repo-Root erstellt, mit Projektüberblick, Tech-Stack-Versionen und globalen harten Constraints -2. `ARCHITECTURE.md` in jedem Microservice-Verzeichnis hinzugefügt, mit Verantwortlichkeiten, Schnittstellen und Abhängigkeiten -3. Zentrales `CONSTRAINTS.md` mit harten Constraints in expliziter "MUST/MUST NOT"-Sprache erstellt -4. `PROGRESS.md` in jedem Service-Verzeichnis hinzugefügt, um aktuellen Arbeitsstatus zu verfolgen - -Nach der Transformation konnte derselbe Agent beim Cold Start alle zentralen Projektfragen beantworten, und die Qualität der Aufgabenabschlüsse verbesserte sich deutlich. - -## Wichtigste Erkenntnisse - -- Wissen, das nicht im Repo steht, existiert für den Agenten nicht. Kritische Entscheidungen ins Repo zu legen ist die grundlegendste Harness-Investition: Zeichne eine gute Karte, damit du dich nicht verläufst. -- Nutze den "Cold-Start-Test", um Repo-Qualität zu bewerten: Kann eine frische Sitzung fünf grundlegende Fragen nur mit Repo-Inhalten beantworten? -- Wissen sollte nahe am Code liegen, minimal aber vollständig sein und mit dem Code aktualisiert werden. Es geht nicht darum, mehr Docs zu schreiben, sondern Information an den richtigen Ort zu bringen. -- Nutze ACID-Prinzipien für Agentenzustand: atomare Commits, Konsistenzverifikation, Parallelitätsisolation, dauerhaftes kritisches Wissen. -- Wissensverfall ist der größte Feind. Dokumentation, die nicht mit dem Code synchron ist, ist gefährlicher als keine Dokumentation: Sie schickt den Agenten in die falsche Richtung, während er glaubt, richtig zu liegen. - -## Weiterführende Literatur - -- [OpenAI: Harness Engineering](https://openai.com/index/harness-engineering/) -- [Anthropic: Effective Harnesses for Long-Running Agents](https://www.anthropic.com/engineering/effective-harnesses-for-long-running-agents) -- [Infrastructure as Code — Martin Fowler](https://martinfowler.com/bliki/InfrastructureAsCode.html) -- [ADR: Architecture Decision Records](https://adr.github.io/) -- [The Twelve-Factor App](https://12factor.net/) - -## Übungen - -1. **Cold-Start-Test**: Öffne in deinem Projekt eine komplett frische Agentensitzung (kein verbaler Kontext, nur Repo-Inhalte). Stelle fünf Fragen: Was ist dieses System? Wie ist es organisiert? Wie führe ich es aus? Wie verifiziere ich es? Was ist der aktuelle Fortschritt? Notiere, was nicht beantwortet werden kann, und verbessere das Repo, bis es geht. - -2. **Quantifizierung der Wissensexternalisierung**: Liste alle Entscheidungen und Constraints auf, die für Entwicklungsarbeit in deinem Projekt wichtig sind. Markiere jedes Element als innerhalb oder außerhalb des Repos. Berechne deine Knowledge Visibility Gap (Anteil außerhalb des Repos). Erstelle einen Plan, um sie unter 10% zu bringen. - -3. **ACID-Bewertung**: Bewerte die Zustandsverwaltung deines Projekts anhand der ACID-Analogie dieser Lektion. Atomicity - Können Agentenoperationen sauber zurückgerollt werden? Consistency - Gibt es Verifikation eines "konsistenten Zustands"? Isolation - Treten sich parallele Agenten gegenseitig auf die Füße? Durability - Ist alles sitzungsübergreifende Wissen persistiert? diff --git a/courses/learn-harness-engineering/docs/de/lectures/lecture-04-why-one-giant-instruction-file-fails/code/AGENTS-short.md b/courses/learn-harness-engineering/docs/de/lectures/lecture-04-why-one-giant-instruction-file-fails/code/AGENTS-short.md deleted file mode 100644 index 4801b51b..00000000 --- a/courses/learn-harness-engineering/docs/de/lectures/lecture-04-why-one-giant-instruction-file-fails/code/AGENTS-short.md +++ /dev/null @@ -1,15 +0,0 @@ -# AGENTS.md - -## Start hier - -- Lies `docs/ARCHITECTURE.md` -- Lies `docs/PRODUCT.md` -- Verwende `npm run dev` zum Starten der App -- Verwende `npm run check` bevor du Arbeit als abgeschlossen markierst - -## Strenge Regeln - -- Ändere nicht die Electron main/preload/renderer-Grenzen ohne - `docs/ARCHITECTURE.md` gelesen zu haben -- Markiere kein Feature als abgeschlossen ohne Verifikation -- Hinterlasse einen sauberen Zustand (clean state) für die nächste Session diff --git a/courses/learn-harness-engineering/docs/de/lectures/lecture-04-why-one-giant-instruction-file-fails/code/anti-patterns.md b/courses/learn-harness-engineering/docs/de/lectures/lecture-04-why-one-giant-instruction-file-fails/code/anti-patterns.md deleted file mode 100644 index 8987347a..00000000 --- a/courses/learn-harness-engineering/docs/de/lectures/lecture-04-why-one-giant-instruction-file-fails/code/anti-patterns.md +++ /dev/null @@ -1,7 +0,0 @@ -# Anti-Patterns für Instruktionsdateien - -- Alles Repository-Wissen in eine Datei packen -- Dieselbe Regel an mehreren Stellen wiederholen -- Veraltete Regeln kodieren, die niemand prüft -- So spezifische bedingte Anweisungen schreiben, dass sie selten zutreffen -- Lange Tool-Handbücher in den Startup-Kontext einbetten diff --git a/courses/learn-harness-engineering/docs/de/lectures/lecture-04-why-one-giant-instruction-file-fails/code/index.md b/courses/learn-harness-engineering/docs/de/lectures/lecture-04-why-one-giant-instruction-file-fails/code/index.md deleted file mode 100644 index f77d1026..00000000 --- a/courses/learn-harness-engineering/docs/de/lectures/lecture-04-why-one-giant-instruction-file-fails/code/index.md +++ /dev/null @@ -1,7 +0,0 @@ -# Code für Vorlesung 04 - -Verwenden Sie diesen Ordner für Beispiele zu: - -- Monolithischen Instruktionsdateien -- Kurzen Einstiegspunkten -- Progressive-Disclosure-Mustern diff --git a/courses/learn-harness-engineering/docs/de/lectures/lecture-04-why-one-giant-instruction-file-fails/code/split-vs-monolithic.ts b/courses/learn-harness-engineering/docs/de/lectures/lecture-04-why-one-giant-instruction-file-fails/code/split-vs-monolithic.ts deleted file mode 100644 index 59a96328..00000000 --- a/courses/learn-harness-engineering/docs/de/lectures/lecture-04-why-one-giant-instruction-file-fails/code/split-vs-monolithic.ts +++ /dev/null @@ -1,209 +0,0 @@ -/** - * split-vs-monolithic.ts - * - * Creates a monolithic instruction file (~200 lines) and then shows how - * splitting into 4 focused files dramatically reduces the context needed - * for any single query. Simulates an "agent" searching for a specific rule - * and measures how many lines it must read in each approach. - * - * Run: npx tsx docs/lectures/lecture-04-why-one-giant-instruction-file-fails/code/split-vs-monolithic.ts - */ - -// --------------------------------------------------------------------------- -// Simulated monolithic instruction file (200 lines of rules) -// --------------------------------------------------------------------------- - -const monolithicInstructions: { lineNumber: number; section: string; content: string }[] = []; - -// Section 1: Project Overview (lines 1-50) -for (let i = 1; i <= 50; i++) { - monolithicInstructions.push({ - lineNumber: i, - section: "Project Overview", - content: i === 25 ? "This project uses React 18 with TypeScript strict mode." : `Overview detail line ${i}`, - }); -} - -// Section 2: Code Style Rules (lines 51-100) -for (let i = 51; i <= 100; i++) { - monolithicInstructions.push({ - lineNumber: i, - section: "Code Style", - content: - i === 72 - ? "RULE: Always use explicit return types on exported functions." - : i === 78 - ? "RULE: Use const assertions for immutable arrays." - : `Style rule detail line ${i}`, - }); -} - -// Section 3: Testing Standards (lines 101-150) -for (let i = 101; i <= 150; i++) { - monolithicInstructions.push({ - lineNumber: i, - section: "Testing", - content: - i === 120 - ? "RULE: Every new endpoint must have integration tests." - : i === 135 - ? "RULE: Test files must mirror the source file structure." - : `Testing detail line ${i}`, - }); -} - -// Section 4: Deployment Rules (lines 151-200) -for (let i = 151; i <= 200; i++) { - monolithicInstructions.push({ - lineNumber: i, - section: "Deployment", - content: - i === 175 - ? "RULE: Never deploy on Fridays. Deploy window is Tue-Thu 10am-3pm." - : `Deployment detail line ${i}`, - }); -} - -// --------------------------------------------------------------------------- -// Split instruction files (4 focused files) -// --------------------------------------------------------------------------- - -const splitInstructions: Record = { - "01-project-overview.md": monolithicInstructions.filter((l) => l.section === "Project Overview"), - "02-code-style.md": monolithicInstructions.filter((l) => l.section === "Code Style"), - "03-testing.md": monolithicInstructions.filter((l) => l.section === "Testing"), - "04-deployment.md": monolithicInstructions.filter((l) => l.section === "Deployment"), -}; - -// --------------------------------------------------------------------------- -// Simulated queries -- the agent needs to find specific rules -// --------------------------------------------------------------------------- - -interface Query { - description: string; - targetRule: string; - relevantSection: string; -} - -const queries: Query[] = [ - { - description: "Find the rule about return types", - targetRule: "explicit return types", - relevantSection: "Code Style", - }, - { - description: "Find the deployment window rule", - targetRule: "deploy on Fridays", - relevantSection: "Deployment", - }, - { - description: "Find the integration test rule", - targetRule: "integration tests", - relevantSection: "Testing", - }, - { - description: "Find the test file structure rule", - targetRule: "mirror the source file", - relevantSection: "Testing", - }, -]; - -// --------------------------------------------------------------------------- -// Search simulation -// --------------------------------------------------------------------------- - -function searchMonolithic(query: Query): { linesRead: number; found: boolean } { - // Agent must scan from the top, reading each line until it finds the rule. - // In the worst case it reads all lines. - let linesRead = 0; - let found = false; - - for (const line of monolithicInstructions) { - linesRead++; - if (line.content.toLowerCase().includes(query.targetRule.toLowerCase())) { - found = true; - break; - } - } - - return { linesRead, found }; -} - -function searchSplit(query: Query): { linesRead: number; found: boolean; fileAccessed: string } { - // Agent knows which file to look in based on the section. - // It only reads lines from that one file. - const fileMap: Record = { - "Project Overview": "01-project-overview.md", - "Code Style": "02-code-style.md", - Testing: "03-testing.md", - Deployment: "04-deployment.md", - }; - - const targetFile = fileMap[query.relevantSection]; - const lines = splitInstructions[targetFile]; - let linesRead = 0; - let found = false; - - for (const line of lines) { - linesRead++; - if (line.content.toLowerCase().includes(query.targetRule.toLowerCase())) { - found = true; - break; - } - } - - return { linesRead, found, fileAccessed: targetFile }; -} - -// --------------------------------------------------------------------------- -// Reporting -// --------------------------------------------------------------------------- - -function pad(s: string, len: number): string { - return s.length >= len ? s : s + " ".repeat(len - s.length); -} - -function run(): void { - console.log("\n" + "=".repeat(90)); - console.log(" MONOLITHIC vs SPLIT INSTRUCTION FILES"); - console.log("=".repeat(90)); - - console.log("\n Monolithic file: 1 file, " + monolithicInstructions.length + " lines total"); - console.log(" Split files: 4 files, ~" + Math.round(monolithicInstructions.length / 4) + " lines each\n"); - - const header = `| ${pad("Query", 42)}| ${pad("Monolithic (lines)", 20)}| ${pad("Split (lines)", 15)}| ${pad("File Accessed", 22)}| Savings`; - const sep = `|${"-".repeat(44)}|${"-".repeat(22)}|${"-".repeat(17)}|${"-".repeat(24)}|${"-".repeat(10)}`; - console.log(header); - console.log(sep); - - let totalMono = 0; - let totalSplit = 0; - - for (const q of queries) { - const mono = searchMonolithic(q); - const split = searchSplit(q); - totalMono += mono.linesRead; - totalSplit += split.linesRead; - - const savings = Math.round(((mono.linesRead - split.linesRead) / mono.linesRead) * 100); - console.log( - `| ${pad(q.description, 42)}| ${pad(String(mono.linesRead), 20)}| ${pad(String(split.linesRead), 15)}| ${pad(split.fileAccessed, 22)}| ${savings}%` - ); - } - - console.log(sep); - const avgMono = Math.round(totalMono / queries.length); - const avgSplit = Math.round(totalSplit / queries.length); - console.log( - `| ${pad("AVERAGE", 42)}| ${pad(String(avgMono), 20)}| ${pad(String(avgSplit), 15)}| ${pad("(targeted file)", 22)}| ${Math.round(((avgMono - avgSplit) / avgMono) * 100)}%` - ); - - console.log("\n" + "=".repeat(90)); - console.log(" KEY INSIGHT"); - console.log("=".repeat(90)); - console.log(" With a monolithic file, the agent must scan up to " + monolithicInstructions.length + " lines for every query."); - console.log(" With split files, it reads only the relevant " + Math.round(monolithicInstructions.length / 4) + "-line file."); - console.log(" This means less context window usage, fewer hallucinations, and faster execution.\n"); -} - -run(); diff --git a/courses/learn-harness-engineering/docs/de/lectures/lecture-04-why-one-giant-instruction-file-fails/index.md b/courses/learn-harness-engineering/docs/de/lectures/lecture-04-why-one-giant-instruction-file-fails/index.md deleted file mode 100644 index b775e16e..00000000 --- a/courses/learn-harness-engineering/docs/de/lectures/lecture-04-why-one-giant-instruction-file-fails/index.md +++ /dev/null @@ -1,132 +0,0 @@ -[中文版本 →](../../../zh/lectures/lecture-04-why-one-giant-instruction-file-fails/) - -> Codebeispiele: [code/](https://github.com/walkinglabs/learn-harness-engineering/blob/main/docs/de/lectures/lecture-04-why-one-giant-instruction-file-fails/code/) -> Praxisprojekt: [Projekt 02. Agentenlesbarer Workspace](./../../projects/project-02-agent-readable-workspace/index.md) - -# Lektion 04. Anweisungen auf Dateien verteilen - -Du hast Harness Engineering ernst genommen. Du hast eine `AGENTS.md` erstellt und jede Regel, jede Einschränkung und jede gelernte Lektion hineingepackt, die dir eingefallen ist. Einen Monat später war die Datei auf 300 Zeilen angewachsen, nach zwei Monaten auf 450, nach drei Monaten auf 600. Dann merkst du, dass die Leistung des Agenten tatsächlich schlechter wird: Bei einem einfachen Bugfix verbrennt er massenhaft Kontext für irrelevante Deployment-Anweisungen; eine kritische Sicherheitsregel in Zeile 300 wird komplett ignoriert; drei widersprüchliche Code-Style-Regeln führen dazu, dass der Agent jedes Mal zufällig eine auswählt. - -Das ist die Falle der "riesigen Anweisungsdatei". Es ist wie ein überfüllter Koffer: Alles scheint nützlich, also stopfst du es hinein, bis der Reißverschluss fast platzt. Um frische Unterwäsche zu finden, musst du den ganzen Koffer ausleeren. Du trägst einen vollen Koffer, nutzt aber vielleicht nur ein Drittel des Inhalts. - -## Der Teufelskreis an der Wurzel - -Der häufigste Teufelskreis läuft so: Der Agent macht einen Fehler, du sagst "füge eine Regel hinzu, damit das nicht wieder passiert", du packst sie in `AGENTS.md`, sie hilft vorübergehend, der Agent macht einen anderen Fehler, du fügst noch eine Regel hinzu, und so weiter. Die Datei bläht sich unkontrolliert auf. - -Das ist nicht deine Schuld. Es ist eine sehr natürliche Reaktion: Jedes Mal, wenn etwas schiefgeht, eine Regel hinzuzufügen, fühlt sich vernünftig an, wie wenn man beim Verlassen des Hauses "nur für den Fall" noch etwas in die Tasche wirft. Aber der kumulative Effekt ist katastrophal. Schauen wir uns konkret an, was schiefgeht. - -**Das Kontextbudget wird aufgefressen.** Das Kontextfenster des Agenten ist endlich. Angenommen, dein Agent hat ein 200K-Token-Fenster (ein typischer Claude-Wert). Eine aufgeblähte Anweisungsdatei kann 10-20K Tokens verbrauchen. Klingt, als wäre noch viel Platz? Aber eine komplexe Aufgabe muss vielleicht Dutzende Quelldateien lesen, Tool-Ausgaben benötigen ebenfalls Kontext, und Gesprächshistorie sammelt sich an. Wenn der Agent den Code wirklich verstehen muss, ist das Budget bereits knapp - wie ein Koffer voller "nur für den Fall"-Dinge, in dem kein Platz mehr für den Laptop bleibt. - -**In der Mitte verloren.** Das Paper "Lost in the Middle" (Liu et al., 2023) zeigte klar, dass LLMs Informationen in der Mitte langer Texte deutlich schlechter nutzen als Informationen am Anfang oder Ende. Deine `AGENTS.md` hat 600 Zeilen, und Zeile 300 sagt "alle Datenbankabfragen müssen parametrisierte Queries verwenden" - eine harte Sicherheitsregel. Aber sie ist in der Mitte vergraben, und der Agent wird sie mit hoher Wahrscheinlichkeit ignorieren. Wie die Sonnencreme am Boden eines überfüllten Koffers: Du weißt, sie ist da, du suchst dreimal, findest sie nicht und kaufst am Ende eine neue. - -**Prioritätskonflikte.** Die Datei mischt nicht verhandelbare harte Constraints ("never use eval()"), wichtige Designrichtlinien ("prefer functional style") und eine konkrete historische Lektion ("letzte Woche WebSocket-Memory-Leak behoben, auf ähnliche Muster achten"). Diese drei Regeln haben völlig unterschiedliche Wichtigkeit, sehen in der Datei aber identisch aus. Der Agent hat kein zuverlässiges Signal zur Unterscheidung - wie Reisepass und Ladekabel durcheinander im Koffer, ohne Hinweis, was dringender ist. - -**Wartungsverfall.** Große Dateien sind inhärent schwer zu warten. Veraltete Anweisungen werden selten gelöscht, weil die Folgen unklar sind ("vielleicht hängt etwas anderes von dieser Regel ab"), während neue Anweisungen hinzuzufügen kostenlos wirkt. Ergebnis: Die Datei wächst nur, schrumpft nie, und das Signal-Rausch-Verhältnis sinkt kontinuierlich. Das ist genau die Akkumulation technischer Schulden, nur bei Anweisungen. - -**Widersprüche sammeln sich an.** Anweisungen, die zu unterschiedlichen Zeiten hinzugefügt wurden, beginnen einander zu widersprechen: Eine sagt "TypeScript strict mode verwenden", eine andere sagt "einige Legacy-Dateien dürfen any-Typen nutzen". Der Agent wählt jedes Mal zufällig eine aus. Wie wenn deine Mutter sagt "zieh dich warm an" und dein Vater sagt "zieh nicht zu viel an", und du an der Tür stehst und nicht weißt, auf wen du hören sollst. - -## Zentrale Konzepte - -- **Instruction Bloat**: Wenn eine Anweisungsdatei mehr als 10-15% des Kontextfensters belegt, verdrängt sie Budget für Code-Lesen und Aufgabenlogik. Eine 600-Zeilen-`AGENTS.md` kann 10.000-20.000 Tokens verbrauchen - 8-15% eines 128K-Fensters, bevor der Agent überhaupt beginnt. -- **Lost in the Middle Effect**: Liu et al. zeigten 2023, dass LLMs Informationen in der Mitte langer Texte deutlich schlechter nutzen als Informationen am Anfang oder Ende. Eine kritische Regel in Zeile 300 einer 600-Zeilen-Datei wird mit hoher Wahrscheinlichkeit praktisch ignoriert. -- **Instruction Signal-to-Noise Ratio (SNR)**: Der Anteil der Anweisungen in einer Datei, der für die aktuelle Aufgabe relevant ist. Bei einem Bugfix 50 Zeilen Deployment-Anweisungen lesen zu müssen, ist niedriger SNR. -- **Routing File**: Eine kurze Einstiegsdatei, deren Kernfunktion darin besteht, den Agenten auf detailliertere Dokumente zu verweisen, statt alles selbst zu enthalten. 50-200 Zeilen reichen. -- **Progressive Disclosure**: Erst Überblick geben, Details bei Bedarf. Gutes Harness-Design ist wie gutes UI-Design: nicht alle Optionen auf einmal über den Nutzer kippen. -- **Priority Ambiguity**: Wenn alle Anweisungen im gleichen Format und am gleichen Ort erscheinen, kann der Agent nicht zwischen nicht verhandelbaren harten Constraints und weichen Empfehlungen unterscheiden. - -## Anweisungsarchitektur - -```mermaid -flowchart LR - Mono["Eine 600-Zeilen-AGENTS.md"] --> MonoLoad["Selbst ein kleiner Bugfix
muss Deploy-Regeln und alte Notizen lesen"] - MonoLoad --> MonoRisk["Wichtige Regeln in der Mitte
werden leicht übersehen"] - - Router["Kurze AGENTS.md"] --> Topics["API- / DB- / Testing-Dokumente nur laden,
wenn die Aufgabe sie braucht"] - Topics --> RoutedResult["Mehr Kontext bleibt fürs Code-Lesen
und die Verifikation"] -``` - -```mermaid -flowchart TB - File["600-Zeilen-Anweisungsdatei"] --> Top["Oberer Abschnitt
Quick Start + harte Constraints"] - File --> Mid["Mittlerer Abschnitt
kritische Sicherheitsregel in Zeile 300"] - File --> Bot["Unterer Abschnitt
explizite Abschluss-Checkliste"] - Top --> Seen["Hohe Chance, erinnert zu werden"] - Bot --> Seen - Mid --> Missed["Hohe Chance, verdünnt oder übersehen zu werden"] -``` - -## Wie man aufteilt - -Kernprinzip: Häufig benötigte Informationen griffbereit halten, gelegentlich benötigte Informationen auslagern und Dinge, die du nie nutzt, entfernen. - -Die Einstiegsdatei `AGENTS.md` bleibt bei 50-200 Zeilen und enthält nur die am häufigsten benötigten Elemente: Projektüberblick (ein bis zwei Sätze), Erststart-Befehle (`make setup && make test`), globale harte Constraints (höchstens 15 nicht verhandelbare Regeln) und Links zu Themendokumenten (Ein-Zeilen-Beschreibung + Anwendungsbedingung). - -```markdown -# AGENTS.md - -## Projektüberblick -Python 3.11 FastAPI backend, PostgreSQL 15 database. - -## Quick Start -- Install: `make setup` -- Test: `make test` -- Full verification: `make check` - -## Hard Constraints -- All APIs must use OAuth 2.0 authentication -- All database queries must use SQLAlchemy 2.0 syntax -- All PRs must pass pytest + mypy --strict + ruff check - -## Topic Docs -- [API Design Patterns](docs/api-patterns.md) — Required reading when adding endpoints -- [Database Rules](docs/database-rules.md) — Required when modifying database operations -- [Testing Standards](docs/testing-standards.md) — Reference when writing tests -``` - -Jedes Themendokument umfasst 50-150 Zeilen, nach Thema im `docs/`-Verzeichnis oder neben dem entsprechenden Modul organisiert. Der Agent liest es nur bei Bedarf. Wie Packwürfel im Koffer: Unterwäsche in einem, Toilettenartikel in einem anderen, Ladegeräte in einem dritten. Um etwas zu finden, musst du nicht den ganzen Koffer ausleeren. - -Manche Informationen gehören besser direkt in den Code: Typdefinitionen, Interface-Kommentare, Erklärungen in Konfigurationsdateien. Der Agent sieht sie natürlich beim Lesen des Codes, ohne dass sie in den Anweisungen dupliziert werden müssen. - -Jede Anweisung sollte eine Quelle haben ("warum wurde diese Regel hinzugefügt?"), eine Anwendungsbedingung ("wann wird sie gebraucht?") und eine Ablaufbedingung ("unter welchen Umständen kann sie entfernt werden?"). Prüfe regelmäßig und entferne veraltete, redundante und widersprüchliche Einträge. Verwalte Anweisungen wie Code-Abhängigkeiten: Unbenutzte Abhängigkeiten sollten gelöscht werden, sonst bremsen sie nur das System. - -Wenn eine Anweisung unbedingt in die Einstiegsdatei muss, platziere sie oben oder unten - niemals in der Mitte. Der "lost in the middle"-Effekt sagt uns, dass LLMs Informationen an den Rändern deutlich besser nutzen als im Zentrum. Besser ist jedoch, Anweisungen in Themendokumente zu verschieben, die bei Bedarf geladen werden. - -OpenAI und Anthropic unterstützen implizit den Aufteilungsansatz. OpenAI sagt, Einstiegsdateien sollten "kurz und routing-orientiert" sein; Anthropic sagt, Kontrollinformationen für lang laufende Agenten sollten "prägnant und hoch priorisiert" sein. Beide sagen dasselbe: Stopf nicht alles in eine Datei. Ein Koffer braucht Organisation, nicht brutales Hineinpressen. - -## Beispiel aus der Praxis - -Die `AGENTS.md` eines SaaS-Teams wuchs von 50 auf 600 Zeilen. Der Inhalt mischte Tech-Stack-Versionen, Coding-Standards, historische Bugfix-Notizen, API-Nutzungsleitfäden, Deployment-Prozeduren und persönliche Präferenzen von Teammitgliedern - der ganze Koffer stand kurz vorm Platzen. - -Die Agentenleistung sank spürbar: Bei einfachen Bugfixes verbrachte der Agent viel Kontext mit irrelevanten Deployment-Anweisungen; die Sicherheitsregel "alle Datenbankabfragen müssen parametrisierte Queries verwenden" war in Zeile 300 vergraben und wurde häufig ignoriert; drei widersprüchliche Code-Style-Regeln führten zu zufälligem Verhalten. - -Das Team führte eine "Koffer-Neuorganisation" durch: -1. `AGENTS.md` auf 80 Zeilen gekürzt: nur Projektüberblick, Ausführungsbefehle und 15 globale harte Constraints -2. Themendokumente erstellt: `docs/api-patterns.md` (120 Zeilen), `docs/database-rules.md` (60 Zeilen), `docs/testing-standards.md` (80 Zeilen) -3. Links zu Themendokumenten in der Routing-Datei ergänzt -4. Historische Notizen entweder in Testfälle umgewandelt oder gelöscht - -Nach dem Refactoring stieg die Erfolgsrate desselben Aufgabensatzes von 45% auf 72%. Die Einhaltung der Sicherheitsregel stieg von 60% auf 95%, weil sie aus der Dateimitte an den Anfang der Routing-Datei verschoben wurde und nicht mehr "in der Mitte verloren" ging. - -## Wichtigste Erkenntnisse - -- "Eine Regel hinzufügen" ist kurzfristige Schmerzlinderung und langfristig Gift. Bevor du eine Regel hinzufügst, frage: Wäre das besser in einem Themendokument? Stopf nicht einfach weiter Dinge in den Koffer. -- Die Einstiegsdatei ist ein Router, keine Enzyklopädie. 50-200 Zeilen mit Überblick, harten Constraints und Links reichen. -- Nutze den "lost in the middle"-Effekt: Wichtige Informationen kommen nach oben oder unten; weniger wichtige Informationen wandern in Themendokumente. -- Behandle Instruction Bloat wie technische Schulden. Regelmäßige Audits; jede Anweisung braucht Quelle, Anwendungsbedingung und Ablaufbedingung. -- Nach dem Aufteilen steigt der SNR, und der Agent verwendet mehr Kontextbudget auf die echte Aufgabe statt auf irrelevante Anweisungen. - -## Weiterführende Literatur - -- [OpenAI: Harness Engineering](https://openai.com/index/harness-engineering/) -- [Anthropic: Effective Harnesses for Long-Running Agents](https://www.anthropic.com/engineering/effective-harnesses-for-long-running-agents) -- [Lost in the Middle: How Language Models Use Long Contexts](https://arxiv.org/abs/2307.03172) -- [HumanLayer: Harness Engineering for Coding Agents](https://humanlayer.dev/articles/harness-engineering-for-coding-agents/) -- [Nielsen Norman Group: Progressive Disclosure](https://www.nngroup.com/articles/progressive-disclosure/) - -## Übungen - -1. **SNR-Audit**: Nimm deine aktuelle Einstiegs-Anweisungsdatei und liste alle Anweisungseinträge auf. Wähle 5 häufige Aufgabentypen und markiere, ob jede Anweisung für diese Aufgabe relevant ist. Berechne den SNR pro Aufgabentyp. Anweisungen, die für die meisten Aufgaben Rauschen sind, sollten in Themendokumente verschoben werden. - -2. **Progressive-Disclosure-Refactoring**: Wenn du eine Anweisungsdatei mit mehr als 300 Zeilen hast, teile sie auf in: (a) eine Routing-Datei unter 100 Zeilen, (b) 3-5 Themendokumente. Führe denselben Aufgabensatz (mindestens 5 Aufgaben) vor und nach der Aufteilung aus und vergleiche die Erfolgsraten. - -3. **Lost-in-the-Middle-Verifikation**: Platziere in einer langen Anweisungsdatei eine kritische Regel jeweils oben, in der Mitte und unten und führe jedes Mal denselben Aufgabensatz aus (mindestens 5 Läufe pro Position). Prüfe, ob sich die Einhaltungsrate unterscheidet. Der Positionseffekt kann überraschend stark sein. diff --git a/courses/learn-harness-engineering/docs/de/lectures/lecture-05-why-long-running-tasks-lose-continuity/code/continuity-checklist.md b/courses/learn-harness-engineering/docs/de/lectures/lecture-05-why-long-running-tasks-lose-continuity/code/continuity-checklist.md deleted file mode 100644 index cd4b9349..00000000 --- a/courses/learn-harness-engineering/docs/de/lectures/lecture-05-why-long-running-tasks-lose-continuity/code/continuity-checklist.md +++ /dev/null @@ -1,6 +0,0 @@ -# Kontinuitäts-Checkliste - -- Kann ein neuer Agent aktuelle Arbeit in unter fünf Minuten identifizieren? -- Ist der aktuelle stabile Startpfad dokumentiert? -- Ist unvollständige Arbeit klar gekennzeichnet? -- Ist die nächste beste Aufgabe ohne Lesen alter Chat-Protokolle sichtbar? diff --git a/courses/learn-harness-engineering/docs/de/lectures/lecture-05-why-long-running-tasks-lose-continuity/code/index.md b/courses/learn-harness-engineering/docs/de/lectures/lecture-05-why-long-running-tasks-lose-continuity/code/index.md deleted file mode 100644 index d7fa3138..00000000 --- a/courses/learn-harness-engineering/docs/de/lectures/lecture-05-why-long-running-tasks-lose-continuity/code/index.md +++ /dev/null @@ -1,7 +0,0 @@ -# Code für Vorlesung 05 - -Verwenden Sie diesen Ordner für Beispiele zu: - -- unterbrochenen Multi-Session-Aufgaben -- fehlenden Kontinuitäts-Artefakten -- Kontinuitäts-Wiederherstellungsmustern diff --git a/courses/learn-harness-engineering/docs/de/lectures/lecture-05-why-long-running-tasks-lose-continuity/code/session-handoff.md b/courses/learn-harness-engineering/docs/de/lectures/lecture-05-why-long-running-tasks-lose-continuity/code/session-handoff.md deleted file mode 100644 index a2f4ce1b..00000000 --- a/courses/learn-harness-engineering/docs/de/lectures/lecture-05-why-long-running-tasks-lose-continuity/code/session-handoff.md +++ /dev/null @@ -1,17 +0,0 @@ -# Session-Übergabe Beispiel - -## Abgeschlossen - -- Markdown-Import-Unterstützung hinzugefügt -- Eine grundlegende Dokumentenliste im Renderer hinzugefügt - -## Defekt oder Unverifiziert - -- Import erfolgreich für `.md`, scheitert aber bei großen `.txt`-Dateien -- Die App startet, aber die Detailansicht ist noch nicht verbunden - -## Nächster bester Schritt - -- `.txt`-Import-Pfad reparieren -- Import End-to-End verifizieren -- Dann das Dokument-Detailpanel hinzufügen diff --git a/courses/learn-harness-engineering/docs/de/lectures/lecture-05-why-long-running-tasks-lose-continuity/code/session-simulator.ts b/courses/learn-harness-engineering/docs/de/lectures/lecture-05-why-long-running-tasks-lose-continuity/code/session-simulator.ts deleted file mode 100644 index 69f9c3e2..00000000 --- a/courses/learn-harness-engineering/docs/de/lectures/lecture-05-why-long-running-tasks-lose-continuity/code/session-simulator.ts +++ /dev/null @@ -1,162 +0,0 @@ -/** - * session-simulator.ts - * - * Simulates two sessions working on a multi-step task. - * Run 1: No handoff file -- Session B starts from scratch and duplicates work. - * Run 2: With handoff file -- Session B picks up where Session A left off. - * - * Run: npx tsx docs/lectures/lecture-05-why-long-running-tasks-lose-continuity/code/session-simulator.ts - */ - -// --------------------------------------------------------------------------- -// Types -// --------------------------------------------------------------------------- - -interface TaskStep { - id: number; - name: string; - durationMs: number; -} - -interface SessionResult { - session: string; - stepsCompleted: number; - totalDurationMs: number; - duplicatedSteps: number; - output: string[]; -} - -// --------------------------------------------------------------------------- -// Task definition -// --------------------------------------------------------------------------- - -const taskSteps: TaskStep[] = [ - { id: 1, name: "Read project structure", durationMs: 50 }, - { id: 2, name: "Understand existing auth module", durationMs: 80 }, - { id: 3, name: "Design new search endpoint", durationMs: 60 }, - { id: 4, name: "Implement search endpoint", durationMs: 100 }, - { id: 5, name: "Write integration tests", durationMs: 70 }, - { id: 6, name: "Update documentation", durationMs: 40 }, -]; - -// --------------------------------------------------------------------------- -// Simulated session executor -// --------------------------------------------------------------------------- - -/** Simulate a session doing work. */ -function runSession( - sessionName: string, - startStep: number, - endStep: number -): SessionResult { - const output: string[] = []; - let totalDuration = 0; - - for (let i = startStep; i <= endStep; i++) { - const step = taskSteps.find((s) => s.id === i)!; - totalDuration += step.durationMs; - output.push(` [${sessionName}] Step ${step.id}: ${step.name} (${step.durationMs}ms)`); - } - - return { - session: sessionName, - stepsCompleted: endStep - startStep + 1, - totalDurationMs: totalDuration, - duplicatedSteps: 0, - output, - }; -} - -// --------------------------------------------------------------------------- -// Run 1: No handoff file -// --------------------------------------------------------------------------- - -function simulateNoHandoff(): { sessionA: SessionResult; sessionB: SessionResult } { - // Session A does steps 1-3 before timing out - const sessionA = runSession("Session A", 1, 3); - - // Session B has no context, starts from scratch - const sessionB = runSession("Session B", 1, 6); - sessionB.duplicatedSteps = 3; // Redoes steps 1-3 that A already did - - return { sessionA, sessionB }; -} - -// --------------------------------------------------------------------------- -// Run 2: With handoff file -// --------------------------------------------------------------------------- - -function simulateWithHandoff(): { sessionA: SessionResult; sessionB: SessionResult } { - // Session A does steps 1-3 and writes a handoff file - const sessionA = runSession("Session A", 1, 3); - - // Session B reads handoff, starts from step 4 - const sessionB = runSession("Session B", 4, 6); - sessionB.duplicatedSteps = 0; - - return { sessionA, sessionB }; -} - -// --------------------------------------------------------------------------- -// Reporting -// --------------------------------------------------------------------------- - -function pad(s: string, len: number): string { - return s.length >= len ? s : s + " ".repeat(len - s.length); -} - -function printRun(title: string, result: { sessionA: SessionResult; sessionB: SessionResult }): void { - console.log("\n" + "=".repeat(80)); - console.log(` ${title}`); - console.log("=".repeat(80)); - - console.log("\n Session A output:"); - for (const line of result.sessionA.output) { - console.log(line); - } - - console.log("\n Session B output:"); - for (const line of result.sessionB.output) { - console.log(line); - } - - console.log("\n Session B notes:"); - if (result.sessionB.duplicatedSteps > 0) { - console.log(` - Redid ${result.sessionB.duplicatedSteps} steps that Session A already completed`); - console.log(` - Wasted ${result.sessionB.totalDurationMs - (taskSteps.slice(3).reduce((s, t) => s + t.durationMs, 0))}ms on duplicate work`); - } else { - console.log(" - Read handoff file, continued from step 4"); - console.log(" - No duplicate work performed"); - } -} - -function printComparison(): void { - const noHandoff = simulateNoHandoff(); - const withHandoff = simulateWithHandoff(); - - printRun("RUN 1: NO HANDOFF FILE", noHandoff); - printRun("RUN 2: WITH HANDOFF FILE", withHandoff); - - // Comparison table - console.log("\n" + "=".repeat(80)); - console.log(" COMPARISON TABLE"); - console.log("=".repeat(80) + "\n"); - - const header = `| ${pad("Metric", 35)}| ${pad("No Handoff", 15)}| ${pad("With Handoff", 15)}|`; - const sep = `|${"-".repeat(37)}|${"-".repeat(17)}|${"-".repeat(17)}|`; - console.log(header); - console.log(sep); - - const noTotal = noHandoff.sessionA.totalDurationMs + noHandoff.sessionB.totalDurationMs; - const withTotal = withHandoff.sessionA.totalDurationMs + withHandoff.sessionB.totalDurationMs; - - console.log(`| ${pad("Session A steps completed", 35)}| ${pad(String(noHandoff.sessionA.stepsCompleted), 15)}| ${pad(String(withHandoff.sessionA.stepsCompleted), 15)}|`); - console.log(`| ${pad("Session B steps completed", 35)}| ${pad(String(noHandoff.sessionB.stepsCompleted), 15)}| ${pad(String(withHandoff.sessionB.stepsCompleted), 15)}|`); - console.log(`| ${pad("Duplicated steps", 35)}| ${pad(String(noHandoff.sessionB.duplicatedSteps), 15)}| ${pad(String(withHandoff.sessionB.duplicatedSteps), 15)}|`); - console.log(`| ${pad("Total work (ms)", 35)}| ${pad(String(noTotal), 15)}| ${pad(String(withTotal), 15)}|`); - console.log(`| ${pad("Time saved by handoff", 35)}| ${pad("-", 15)}| ${pad(String(noTotal - withTotal) + "ms", 15)}|`); - - console.log("\n A handoff file eliminates duplicate work and ensures continuity across sessions.\n"); -} - -printComparison(); diff --git a/courses/learn-harness-engineering/docs/de/lectures/lecture-05-why-long-running-tasks-lose-continuity/index.md b/courses/learn-harness-engineering/docs/de/lectures/lecture-05-why-long-running-tasks-lose-continuity/index.md deleted file mode 100644 index ecc40fc0..00000000 --- a/courses/learn-harness-engineering/docs/de/lectures/lecture-05-why-long-running-tasks-lose-continuity/index.md +++ /dev/null @@ -1,180 +0,0 @@ -[中文版本 →](../../../zh/lectures/lecture-05-why-long-running-tasks-lose-continuity/) - -> Codebeispiele: [code/](https://github.com/walkinglabs/learn-harness-engineering/blob/main/docs/de/lectures/lecture-05-why-long-running-tasks-lose-continuity/code/) -> Praxisprojekt: [Project 03. Multi-session continuity](./../../projects/project-03-multi-session-continuity/index.md) - -# Lektion 05. Kontext über Sessions hinweg erhalten - -Sie bitten Claude Code, ein vollständiges Feature zu implementieren. Es läuft 30 Minuten, erledigt den Großteil der Arbeit, aber der Kontext wird knapp. Sie starten eine neue Session, um fortzufahren — und stellen fest, dass es sich nicht an die Entscheidungen der letzten Session erinnert, warum Option A gegenüber Option B gewählt wurde, welche Dateien bereits geändert wurden oder in welchem Zustand sich die Tests befinden. Es verbringt 15 Minuten damit, das Projekt neu zu erkunden, und könnte inkonsistent zum vorherigen Ansatz sein. - -Stellen Sie sich vor, Sie wären ein Handwerker, der jeden Morgen beim Aufwachen alles vergisst. Sie müssten sich die gesamte Baustelle neu vertraut machen — welche Mauer halb fertig ist, warum rote statt blaue Ziegel gewählt wurden, wo die Rohrleitungen verlaufen. Schlimmer noch, Sie könnten ein Fenster herausreißen, das gestern bereits eingebaut wurde, einfach weil Sie sich nicht erinnern, dass es erledigt war. - -Genau in dieser Situation befinden sich KI-Coding-Agenten bei übergreifenden Aufgaben. Diese Lektion erklärt, warum Agenten bei langen Aufgaben „aussteigen" und wie strukturierte Zustandspersistenz sie wie einen Handwerker machen kann, der ein zuverlässiges Tagebuch führt — immer noch amnesisch, aber das Tagebuch erinnert sich an alles. - -## Kontextfenster: Nicht unendlich - -Kontextfenster sind endlich. Das lässt sich nicht durch Modell-Upgrades lösen — selbst wenn Fenstergrößen auf 1M Token anwachsen, werden komplexe Aufgaben sie dennoch erschöpfen. Denn Agenten generieren nicht nur Code; sie verstehen Codebases, verfolgen ihre eigene Entscheidungsgeschichte, verarbeiten Tool-Ausgaben und pflegen den Gesprächskontext. All diese Informationen wachsen schneller als die Fenstervergrößerung. - -Ein tieferes Problem: Die Informationen, die der Agent produziert, sind nicht gleichmäßig wichtig. Zwischen den Reasoning-Schritten steckt das „warum" der Entscheidungen — warum Option B gegenüber A gewählt wurde, warum diese Bibliothek statt jener, warum eine bestimmte Optimierung übersprungen wurde. Die endgültige Ausgabe enthält nur das „was" — den Code selbst. Kompressionsstrategien bewahren normalerweise letzteres, verlieren aber ersteres. Die nächste Session sieht den Code, weiß aber nicht, warum er so geschrieben wurde, und „optimiert" möglicherweise eine bewusste Designentscheidung weg. - -Anthropic hat in seiner Forschung zu langlebigen Agenten etwas Faszinierendes entdeckt: Wenn Agenten spüren, dass der Kontext knapp wird, zeigen sie ein „voreilige Konvergenz"-Verhalten — sie hetzen, die aktuelle Arbeit abzuschließen, überspringen Verifizierungsschritte oder wählen eine einfache Lösung statt der optimalen. Es ist, als würde man merken, dass die Zeit im Exam abläuft, und schnell die restlichen Multiple-Choice-Fragen raten. Anthropic nennt dies „context anxiety". - -## Session-Kontinuitätsfluss - -Ohne Kontinuitäts-Artefakte ist jede neue Session eine Katastrophe: - -```mermaid -flowchart LR - S1["Session 1
feature is half done"] --> End1["Context is nearly full
session ends"] - End1 --> S2["Session 2 starts fresh"] - S2 --> Guess["Re-read folders, rerun tests,
guess why the code was written this way"] - Guess --> Drift["Work gets repeated
and recovery is slow"] -``` - -Mit Kontinuitäts-Artefakten können neue Sessions schnell aufsetzen: - -```mermaid -flowchart LR - Work["Session 1 work"] --> Progress["PROGRESS.md
done / in progress / next step"] - Work --> Decisions["DECISIONS.md
why this approach was chosen"] - Work --> Verify["Verification notes
which tests pass and fail"] - Work --> Commit["Git checkpoint
exact repo state"] - - Progress --> Rebuild["Session 2 rebuild"] - Decisions --> Rebuild - Verify --> Rebuild - Commit --> Rebuild - - Rebuild --> Resume["New session picks up quickly"] -``` - -## Zentrale Konzepte - -- **Kontextfenster sind endlich**: Egal welche Fenstergröße behauptet wird (128K, 200K, 1M) — lange Aufgaben werden sie schließlich erschöpfen. Nach der Erschöpfung ist entweder Kompression (Informationsverlust) oder ein Reset (neue Session) erforderlich. Beide verlieren etwas. -- **Kontinuitäts-Artefakte**: Persistierte Zustandsdateien, die es einer neuen Session ermöglichen, eindeutig dort fortzufahren, wo die letzte aufgehört hat. Die Grundform: Fortschrittsprotokoll + Verifizierungsdatensatz + nächste Aktionen. Das Tagebuch dieses Handwerkers. -- **Rebuild-Kosten**: Die Zeit, die eine neue Session benötigt, um einen ausführbaren Zustand zu erreichen. Gute harness-Konstruktionen können die Rebuild-Kosten von 15 Minuten auf 3 Minuten komprimieren. -- **Drift**: Die Lücke zwischen dem Verständnis des Agenten und dem tatsächlichen Zustand des Code-Repositorys. Jede Sitzungsgrenze führt Drift ein; ohne Kontrolle summiert er sich. -- **Context Anxiety**: Ein von Anthropic beobachtetes Phänomen — Agenten zeigen voreiliges Konvergenzverhalten, wenn sie sich wahrgenommenen Kontextgrenzen nähern, und beenden Aufgaben vorzeitig, um Informationsverlust zu vermeiden. Es ist eine irrationale Ressourcenangst. -- **Kompression vs. Reset**: Kompression fasst den Kontext innerhalb derselben Session zusammen (behält das „was", verliert möglicherweise das „warum"); Reset öffnet eine neue Session, die aus persistiertem Zustand wieder aufgebaut wird (sauber, aber abhängig von der Vollständigkeit der Artefakte). - -## Was passiert, wenn die Kontinuität abbricht - -Die vorherige Session hat bedeutende Kontextbudgets für die Analyse dreier Ansätze und die Wahl von Option B aufgewendet. Der Agent dieser Session weiß nichts von dieser Analyse und könnte auf Basis unvollständiger Informationen neu entscheiden — möglicherweise wählt er Option A. Wie der amnestische Handwerker, der sich nicht erinnert, warum rote Ziegel gewählt wurden, die blauen heute schöner findet und die Mauer von gestern abreißt, um sie neu zu bauen. - -Noch schlimmer ist doppelte Arbeit. Der Agent ist sich nicht sicher, ob bestimmte Arbeit bereits erledigt wurde, und macht sie noch einmal. Oder noch schlimmer — erledigt die Hälfte, entdeckt einen Konflikt mit der bestehenden Implementierung und muss nacharbeiten. Auf einer Baustelle können nicht zwei Teams gleichzeitig dieselbe Mauer bauen — aber ohne Fortschrittsaufzeichnungen hat die neue Mannschaft keine Ahnung, dass bereits jemand daran arbeitet. - -Über mehrere Sessions hinweg kann die Implementierungsrichtung still von den ursprünglichen Anforderungen abgedriftet sein. Jede neue Session hat ein leicht anderes Verständnis der Projektziele. Wie bei einem Flüsternachrichtenspiel — nachdem zehn Menschen die Nachricht weitergegeben haben, könnte „hol mir einen Kaffee" zu „kauf mir eine Kaffeemaschine" geworden sein. - -Es gibt auch die Verifizierungslücke. Die Verifizierungsergebnisse der vorherigen Session (welche Tests durchlaufen, welche fehlschlagen, warum sie fehlschlagen) wurden nicht dokumentiert. Die neue Session muss alle Verifizierungen erneut ausführen, um den aktuellen Zustand zu verstehen. Jede Session diagnostiziert von Grund auf neu, jedes Mal wertvollen Kontext verschwendend. - -Sowohl OpenAI als auch Anthropic betonen strukturierte Zustandspersistenz in ihrer Dokumentation. OpenAIs Artikel zum harness Engineering behandelt das Repository als „operational record" — die Ergebnisse jeder Operation sollten nachvollziehbare Spuren im Repo hinterlassen. Anthropics Dokumentation zu langlebigen Agenten empfiehlt ausdrücklich „handoff files" — strukturierte Dokumente mit aktuellem Zustand, bekannten Problemen und nächsten Aktionen. - -## Ein Tagebuch für den amnestischen Handwerker - -Kernansatz: **Behandeln Sie den Agenten wie einen brillanten Ingenieur mit Amnesie.** Bevor er „aussticht", muss er kritische Informationen aufschreiben, damit der Agent der nächsten „Schicht" schnell aufsetzen kann. - -**Tool 1: Fortschrittsdatei (PROGRESS.md).** Das grundlegendste Kontinuitäts-Artefakt — der Kern des Tagebuchs: - -```markdown -# Project Progress - -## Current State -- Latest commit: abc1234 (feat: add user preferences endpoint) -- Test status: 42/43 passing (test_pagination_edge_case failing) -- Lint: passing - -## Completed -- [x] User model and database migration -- [x] Basic CRUD endpoints -- [x] Auth middleware integration - -## In Progress -- [ ] Pagination feature (90% - edge case test failing) - -## Known Issues -- test_pagination_edge_case returns 500 on empty result sets -- Need to confirm whether deleted users should appear in listings - -## Next Steps -1. Fix pagination edge case bug -2. Add "include deleted users" query parameter -3. Update API documentation -``` - -**Tool 2: Entscheidungsprotokoll (DECISIONS.md).** Dokumentiert wichtige Designentscheidungen und deren Gründe. Keine detaillierten Designdokumente nötig — nur „welche Entscheidung, warum, wann" — die Notizen im Tagebuch: - -```markdown -# Design Decisions - -## 2024-01-15: Use Redis for user preferences caching -- Reason: High read frequency (every API call), small data size -- Rejected alternative: PostgreSQL materialized view (high change frequency makes maintenance cost not worthwhile) -- Constraint: Cache TTL of 5 minutes, active invalidation on write -``` - -**Tool 3: Git-Commits als Kontrollpunkte.** Committen Sie nach Abschluss jeder atomaren Arbeitseinheit. Commit-Nachrichten sollten erklären, was getan wurde und warum. Diese sind kostenlose, automatisch versionierte Zustandsschnappschüsse. - -**Tool 4: init.sh oder harness-Initialisierungsfluss.** Spezifizieren Sie in `AGENTS.md` die „Einstich"- und „Ausstich"-Routinen: - -```markdown -## At session start (clock in) -1. Read PROGRESS.md for current state -2. Read DECISIONS.md for important decisions -3. Run make check to confirm repo is in consistent state -4. Continue from PROGRESS.md "Next Steps" section - -## Before session end (clock out) -1. Update PROGRESS.md -2. Run make check to confirm consistent state -3. Commit all completed work -``` - -**Gemischte Strategie**: Nicht jede Aufgabe benötigt einen Kontext-Reset. Kurze Aufgaben (unter 30 Minuten) können innerhalb einer Session abgeschlossen werden. Lange Aufgaben (sessionübergreifend) müssen Fortschrittsdateien und Entscheidungsprotokolle für die Kontinuität verwenden. Entscheidungsregel: Wenn eine Aufgabe mehr als 60% des Fensters benötigt, beginnen Sie mit der Vorbereitung der Übergabe. - -### Tieferer Einblick in Context Anxiety - -Anthropics Forschung von März 2026 offenbarte weitere Details zur Context Anxiety: Bei Sonnet 4.5 zeigt der Agent starkes „voreilige Konvergenz"-Verhalten, wenn der Kontext sich der Fenstergrenze nähert. Es ist, als würde man merken, dass die Zeit fast abgelaufen ist, und schnell zufällige Antworten bei der Multiple Choice ausfüllen. - -Zwei Strategien helfen dagegen: - -**Kompression**: Frühzeitige Gesprächszusammenfassung innerhalb derselben Session. Vorteil: erhält Kontinuität, der Agent kann das „was" sehen. Nachteil: das „warum" geht in Zusammenfassungen oft verloren — warum Option B gegenüber A gewählt wurde, warum eine bestimmte Optimierung übersprungen wurde. Kritischer noch: Kompression beseitigt die Context Anxiety nicht — der Agent weiß, dass der Kontext einmal groß war, und neigt psychologisch weiterhin zum voreiligen Abschluss. - -**Kontext-Reset**: Vollständiges Leeren des Kontexts, Öffnen einer neuen Session, Wiederherstellung aus persistierten Artefakten. Vorteil: sauberer mentaler Zustand — die neue Session hat keine „mir geht die Zeit aus"-Angst. Nachteil: abhängig von der Vollständigkeit der Übergabe-Artefakte. Wenn im Tagebuch kritische Informationen fehlen, kann die neue Session Zeit in die falsche Richtung verschwenden. - -Anthropics tatsächliche Daten: Bei Sonnet 4.5 ist die Context Anxiety so stark, dass Kompression allein nicht ausreicht — Kontext-Reset wird zu einer kritischen Komponente des harness-Designs. Bei Opus 4.5 ist dieses Verhalten jedoch stark abgeschwächt, und Kompression kann den Kontext verwalten, ohne auf Resets angewiesen zu sein. Das bedeutet: **harness-Design benötigt spezifisches Verständnis des Zielmodells, keine Einheitslösung.** - -> Quelle: [Anthropic: Harness design for long-running application development](https://www.anthropic.com/engineering/harness-design-long-running-apps) - -## Praxisbeispiel - -Ein Agent wurde beauftragt, ein Blog-System mit Benutzerauthentifizierung zu implementieren — 12 Feature-Punkte, geschätzte 5 Sessions nötig. - -**Baseline ohne Tagebuch**: Session 1 implementierte das Benutzermodell und grundlegende Routen. Session 2 startete ohne dass sich der Agent an den Interface-Vertrag der Auth-Middleware erinnerte und verbrachte ~15 Minuten damit, die vorherige Designabsicht zu erschließen. In Session 3 führte akkumulierter Drift dazu, dass der Agent bereits abgeschlossene Features neu zu implementieren begann. In Session 5 enthielt das Repo viel redundanten Code, aber das Kern-Auth-Feature hatte die End-to-End-Tests noch nicht bestanden. Nur 7 von 12 Feature-Punkten abgeschlossen, 3 mit versteckten Korrektheitsproblemen. Wie der Handwerker, der nie in sein Tagebuch schreibt — an Tag fünf ist die Baustelle das Chaos, einige Mauern doppelt gebaut, einige, die hätten gebaut werden sollen, nie begonnen. - -**Mit Tagebuch**: Unter Verwendung von Fortschrittsdateien, Entscheidungsprotokollen, Verifizierungsaufzeichnungen und Git-Kontrollpunkten. Zustandsbericht automatisch am Ende jeder Session aktualisiert. Session 2s Rebuild-Kosten fielen auf ~3 Minuten. Bis Session 5 waren alle 12 Feature-Punkte abgeschlossen und verifiziert. - -Quantitativer Vergleich: Rebuild-Zeit um ~78% reduziert, Feature-Abschlussrate von 58% auf 100%, versteckte Fehlerrate von 43% auf 8%. Der Handwerker ist immer noch amnestisch, aber mit dem Tagebuch beginnt jeder Tag dort, wo der vorherige aufgehört hat — nicht bei null. - -## Wichtigste Erkenntnisse - -- Kontextfenster sind eine endliche Ressource. Lange Aufgaben werden Sessions überspannen, und Sessions werden Informationen verlieren — wie der Handwerker, der jeden Tag vergisst, ist das objektive Realität. -- Die Lösung sind nicht größere Fenster — sondern bessere Zustandspersistenz. Fortschrittsdateien + Entscheidungsprotokolle + Git-Kontrollpunkte — geben Sie dem amnestischen Handwerker ein zuverlässiges Tagebuch. -- Behandeln Sie den Agenten wie einen Ingenieur mit Amnesie: Bevor er „aussticht", schreibt er auf, was getan wurde, warum und was als Nächstes ansteht. -- Rebuild-Kosten sind die zentrale Kennzahl. Gute harness-Konstruktionen sollten neue Sessions innerhalb von 3 Minuten in einen ausführbaren Zustand bringen. -- Gemischte Strategie: kurze Aufgaben innerhalb von Sessions, lange Aufgaben mit strukturierten Artefakten für Kontinuität. - -## Weiterführende Literatur - -- [Anthropic: Effective Harnesses for Long-Running Agents](https://www.anthropic.com/engineering/effective-harnesses-for-long-running-agents) -- [OpenAI: Harness Engineering](https://openai.com/index/harness-engineering/) -- [Lost in the Middle: How Language Models Use Long Contexts](https://arxiv.org/abs/2307.03172) -- [Claude Code Documentation](https://docs.anthropic.com/de/docs/claude-code) -- [HumanLayer: Harness Engineering for Coding Agents](https://humanlayer.dev/articles/harness-engineering-for-coding-agents/) - -## Übungen - -1. **Kontinuitätsverlust-Messung**: Wählen Sie eine Entwicklungsaufgabe, die mindestens 3 Sessions benötigt. Ohne Kontinuitäts-Artefakte anzubieten, notieren Sie zu Beginn jeder Session, wie viel Kontext der Agent darauf verwendet, „herauszufinden, was beim letzten Mal passiert ist." Erstellen Sie nach jeder Session eine Fortschrittsdatei und lassen Sie die nächste Session daraus starten. Vergleichen Sie die Rebuild-Kosten mit und ohne Fortschrittsdateien. - -2. **Übergabe-Vorlage entwerfen**: Entwerfen Sie eine minimale Übergabe-Vorlage mit vier Feldern: Repo-Zustand (Commit-Hash), Laufzeitzustand (Test-Pass-Rate), Blockaden, nächste Aktionen. Lassen Sie eine komplett neue Agenten-Session den Projektzustand nur anhand dieser Vorlage wiederherstellen. Dokumentieren Sie Mehrdeutigkeiten bei der Wiederherstellung und iterieren Sie zur Verbesserung der Vorlage. - -3. **Gemischte Strategie experimentieren**: In einer 5-Session-Entwicklungsaufgabe vergleichen Sie drei Strategien: (a) immer neue Sessions starten + Fortschrittsdateien, (b) so viel wie möglich in einer Session erledigen (Kontext-Kompression), (c) gemischte Strategie (kurze Aufgaben in der Session, lange Aufgaben sessionübergreifend + Fortschrittsdateien). Vergleichen Sie Rebuild-Zeit, Feature-Abschlussrate und Entscheidungs konsistenz. diff --git a/courses/learn-harness-engineering/docs/de/lectures/lecture-06-why-initialization-needs-its-own-phase/code/index.md b/courses/learn-harness-engineering/docs/de/lectures/lecture-06-why-initialization-needs-its-own-phase/code/index.md deleted file mode 100644 index 6969b246..00000000 --- a/courses/learn-harness-engineering/docs/de/lectures/lecture-06-why-initialization-needs-its-own-phase/code/index.md +++ /dev/null @@ -1,8 +0,0 @@ -# Code für Vorlesung 06 - -Verwenden Sie diesen Ordner für Beispiele zu: - -- Initialisierer-Ausgaben -- Init-Skripten -- Fortschrittsdateien -- Erstlauf-Scaffolding diff --git a/courses/learn-harness-engineering/docs/de/lectures/lecture-06-why-initialization-needs-its-own-phase/code/init-check.ts b/courses/learn-harness-engineering/docs/de/lectures/lecture-06-why-initialization-needs-its-own-phase/code/init-check.ts deleted file mode 100644 index 52dcd985..00000000 --- a/courses/learn-harness-engineering/docs/de/lectures/lecture-06-why-initialization-needs-its-own-phase/code/init-check.ts +++ /dev/null @@ -1,296 +0,0 @@ -/** - * init-check.ts - * - * Programmatically checks initialization prerequisites: - * - Node.js version - * - Dependencies installed (node_modules exists) - * - Data directory exists - * - Config files present - * - * Simulates running with and without an explicit init phase, - * showing how missing prerequisites cause silent failures later. - * - * Run: npx tsx docs/lectures/lecture-06-why-initialization-needs-its-own-phase/code/init-check.ts - */ - -import * as fs from "node:fs"; -import * as path from "node:path"; -import * as childProcess from "node:child_process"; - -// --------------------------------------------------------------------------- -// Types -// --------------------------------------------------------------------------- - -interface CheckItem { - name: string; - category: string; - check: () => { pass: boolean; detail: string }; - impactIfMissing: string; -} - -interface CheckResult { - name: string; - category: string; - passed: boolean; - detail: string; - impactIfMissing: string; -} - -// --------------------------------------------------------------------------- -// Checks -// --------------------------------------------------------------------------- - -function createChecks(targetDir: string): CheckItem[] { - return [ - { - name: "Node.js version >= 18", - category: "Runtime", - check: () => { - try { - const version = process.version; // e.g. "v20.11.0" - const major = parseInt(version.replace("v", "").split(".")[0], 10); - return { - pass: major >= 18, - detail: `Detected: ${version} (need >= v18)`, - }; - } catch { - return { pass: false, detail: "Could not detect Node.js version" }; - } - }, - impactIfMissing: "TypeScript features and built-in APIs unavailable", - }, - { - name: "package.json exists", - category: "Config", - check: () => { - const p = path.join(targetDir, "package.json"); - const exists = fs.existsSync(p); - return { - pass: exists, - detail: exists ? `Found: ${p}` : "Not found in target directory", - }; - }, - impactIfMissing: "Cannot install dependencies or run scripts", - }, - { - name: "Dependencies installed (node_modules)", - category: "Dependencies", - check: () => { - const p = path.join(targetDir, "node_modules"); - const exists = fs.existsSync(p); - return { - pass: exists, - detail: exists ? `Found: ${p}` : "node_modules/ not found -- run npm install", - }; - }, - impactIfMissing: "All imports fail at runtime", - }, - { - name: "TypeScript available", - category: "Toolchain", - check: () => { - try { - const result = childProcess.execSync("npx tsc --version", { - encoding: "utf-8", - timeout: 10000, - stdio: ["pipe", "pipe", "pipe"], - }).trim(); - return { pass: true, detail: `Detected: ${result}` }; - } catch { - return { pass: false, detail: "TypeScript not available via npx" }; - } - }, - impactIfMissing: "Cannot compile TypeScript files", - }, - { - name: "tsconfig.json exists", - category: "Config", - check: () => { - const p = path.join(targetDir, "tsconfig.json"); - const exists = fs.existsSync(p); - return { - pass: exists, - detail: exists ? `Found: ${p}` : "Not found", - }; - }, - impactIfMissing: "TypeScript compiler uses defaults, may not match project needs", - }, - { - name: "Source directory exists", - category: "Structure", - check: () => { - const candidates = ["src", "lib", "app"]; - const found = candidates.filter((d) => { - const p = path.join(targetDir, d); - return fs.existsSync(p) && fs.statSync(p).isDirectory(); - }); - return { - pass: found.length > 0, - detail: found.length > 0 ? `Found: ${found.join(", ")}` : "No src/ lib/ or app/ directory found", - }; - }, - impactIfMissing: "Agent cannot locate source files to modify", - }, - { - name: "Test directory exists", - category: "Structure", - check: () => { - const candidates = ["test", "tests", "__tests__", "spec"]; - const found = candidates.filter((d) => { - const p = path.join(targetDir, d); - return fs.existsSync(p) && fs.statSync(p).isDirectory(); - }); - return { - pass: found.length > 0, - detail: found.length > 0 ? `Found: ${found.join(", ")}` : "No test directory found", - }; - }, - impactIfMissing: "Agent cannot find or run existing tests", - }, - { - name: "Git repository initialized", - category: "Version Control", - check: () => { - const gitDir = path.join(targetDir, ".git"); - const exists = fs.existsSync(gitDir); - return { - pass: exists, - detail: exists ? "Git repository detected" : "No .git directory found", - }; - }, - impactIfMissing: "No rollback capability, no change history", - }, - ]; -} - -// --------------------------------------------------------------------------- -// Simulation: with and without init phase -// --------------------------------------------------------------------------- - -interface SimResult { - scenario: string; - checksRun: boolean; - failuresBeforeWork: number; - workAttempted: boolean; - workSucceeded: boolean; - timeWastedMs: number; -} - -function simulateWithoutInit(): SimResult { - // Agent skips init, goes straight to work. - // Encounters failures one at a time as it hits missing prerequisites. - const checks = createChecks(process.cwd()); - let failures = 0; - let timeWasted = 0; - - for (const check of checks) { - const result = check.check(); - if (!result.pass) { - failures++; - timeWasted += 200; // Agent spends time discovering each missing piece - } - } - - return { - scenario: "NO INIT PHASE", - checksRun: false, - failuresBeforeWork: 0, // Didn't check upfront - workAttempted: true, - workSucceeded: failures === 0, - timeWastedMs: timeWasted, - }; -} - -function simulateWithInit(): SimResult { - // Agent runs init phase first, discovers all issues upfront. - const checks = createChecks(process.cwd()); - let failures = 0; - - for (const check of checks) { - if (!check.check().pass) { - failures++; - } - } - - return { - scenario: "WITH INIT PHASE", - checksRun: true, - failuresBeforeWork: failures, - workAttempted: failures === 0, - workSucceeded: failures === 0, - timeWastedMs: 0, - }; -} - -// --------------------------------------------------------------------------- -// Reporting -// --------------------------------------------------------------------------- - -function pad(s: string, len: number): string { - return s.length >= len ? s : s + " ".repeat(len - s.length); -} - -function run(): void { - const targetDir = process.cwd(); - console.log("\n" + "=".repeat(80)); - console.log(" INITIALIZATION PREREQUISITE CHECK"); - console.log("=".repeat(80)); - console.log(` Target: ${targetDir}\n`); - - const checks = createChecks(targetDir); - const results: CheckResult[] = checks.map((c) => { - const r = c.check(); - return { - name: c.name, - category: c.category, - passed: r.pass, - detail: r.detail, - impactIfMissing: c.impactIfMissing, - }; - }); - - // Detailed report - const header = `| ${pad("Check", 35)}| ${pad("Category", 12)}| ${pad("Status", 6)}| Detail`; - const sep = `|${"-".repeat(37)}|${"-".repeat(14)}|${"-".repeat(8)}|${"-".repeat(40)}`; - console.log(header); - console.log(sep); - - for (const r of results) { - const status = r.passed ? "PASS" : "FAIL"; - console.log(`| ${pad(r.name, 35)}| ${pad(r.category, 12)}| ${pad(status, 6)}| ${r.detail}`); - } - - const passCount = results.filter((r) => r.passed).length; - const failCount = results.filter((r) => !r.passed).length; - - console.log("\n" + "-".repeat(80)); - console.log(` Results: ${passCount} passed, ${failCount} failed out of ${results.length} checks`); - - if (failCount > 0) { - console.log("\n FAILED CHECKS -- Impact if not resolved:"); - for (const r of results.filter((r) => !r.passed)) { - console.log(` - ${r.name}: ${r.impactIfMissing}`); - } - } - - // Comparison: with vs without init - const noInit = simulateWithoutInit(); - const withInit = simulateWithInit(); - - console.log("\n" + "=".repeat(80)); - console.log(" INIT PHASE COMPARISON"); - console.log("=".repeat(80) + "\n"); - - const cHeader = `| ${pad("Metric", 35)}| ${pad("No Init Phase", 18)}| ${pad("With Init Phase", 18)}|`; - const cSep = `|${"-".repeat(37)}|${"-".repeat(20)}|${"-".repeat(20)}|`; - console.log(cHeader); - console.log(cSep); - console.log(`| ${pad("Prerequisites checked upfront", 35)}| ${pad("No", 18)}| ${pad("Yes", 18)}|`); - console.log(`| ${pad("Work attempted despite issues", 35)}| ${pad(String(noInit.workAttempted), 18)}| ${pad(String(withInit.workAttempted), 18)}|`); - console.log(`| ${pad("Time wasted discovering issues late", 35)}| ${pad(noInit.timeWastedMs + "ms", 18)}| ${pad(withInit.timeWastedMs + "ms", 18)}|`); - console.log(`| ${pad("Work succeeded", 35)}| ${pad(String(noInit.workSucceeded), 18)}| ${pad(String(withInit.workSucceeded), 18)}|`); - - console.log("\n An explicit init phase catches problems before they waste time.\n"); -} - -run(); diff --git a/courses/learn-harness-engineering/docs/de/lectures/lecture-06-why-initialization-needs-its-own-phase/code/init.sh b/courses/learn-harness-engineering/docs/de/lectures/lecture-06-why-initialization-needs-its-own-phase/code/init.sh deleted file mode 100644 index 2c6e1b0c..00000000 --- a/courses/learn-harness-engineering/docs/de/lectures/lecture-06-why-initialization-needs-its-own-phase/code/init.sh +++ /dev/null @@ -1,10 +0,0 @@ -#!/usr/bin/env bash -set -euo pipefail - -echo "[init] installing dependencies" -npm install - -echo "[init] starting the docs site is optional" -echo "[init] use npm run docs:dev for course docs" - -echo "[init] project-specific startup would go here" diff --git a/courses/learn-harness-engineering/docs/de/lectures/lecture-06-why-initialization-needs-its-own-phase/code/initializer-output-checklist.md b/courses/learn-harness-engineering/docs/de/lectures/lecture-06-why-initialization-needs-its-own-phase/code/initializer-output-checklist.md deleted file mode 100644 index caafe1ba..00000000 --- a/courses/learn-harness-engineering/docs/de/lectures/lecture-06-why-initialization-needs-its-own-phase/code/initializer-output-checklist.md +++ /dev/null @@ -1,7 +0,0 @@ -# Initialisierer-Ausgabe Checkliste - -- Gibt es einen kanonischen Startbefehl? -- Gibt es einen kanonischen Verifikationsbefehl? -- Gibt es ein erstes Fortschritts-Artefakt? -- Gibt es einen stabilen ersten Commit? -- Gibt es eine sichtbare Feature-Oberfläche für spätere Sessions? diff --git a/courses/learn-harness-engineering/docs/de/lectures/lecture-06-why-initialization-needs-its-own-phase/index.md b/courses/learn-harness-engineering/docs/de/lectures/lecture-06-why-initialization-needs-its-own-phase/index.md deleted file mode 100644 index 82481f87..00000000 --- a/courses/learn-harness-engineering/docs/de/lectures/lecture-06-why-initialization-needs-its-own-phase/index.md +++ /dev/null @@ -1,157 +0,0 @@ -[中文版本 →](../../../zh/lectures/lecture-06-why-initialization-needs-its-own-phase/) - -> Codebeispiele: [code/](https://github.com/walkinglabs/learn-harness-engineering/blob/main/docs/de/lectures/lecture-06-why-initialization-needs-its-own-phase/code/) -> Praxisprojekt: [Project 03. Multi-session continuity](./../../projects/project-03-multi-session-continuity/index.md) - -# Lektion 06. Vor jeder Agenten-Session initialisieren - -Sie starten eine neue Agenten-Session und sagen „Füge eine Suchfunktion hinzu." Es stürzt sich direkt ins Programmieren — bewundernswerte Begeisterung. Nach 20 Minuten stellt es fest, dass das Test-Framework nicht richtig konfiguriert ist, verbringt weitere 10 Minuten mit der Reparatur, dann ist das Format des Datenbankmigrationsskripts falsch, weiteres Herumgefummel. Die Suchfunktion wird schließlich hinzugefügt, aber die gesamte Session war ineffizient — die meiste Zeit floss in „herausfinden, wie dieses Projekt funktioniert" anstatt die Suchfunktion zu schreiben. - -Der bessere Ansatz: Bevor der Agent mit der Arbeit beginnt, eine separate Phase nutzen, um die Basisumgebung vorzubereiten, Verifizierungsbefehle zum Laufen zu bringen und die Projektstruktur zu verstehen. Es ist wie beim Hausbau — man gießt nicht das Fundament und stellt gleichzeitig die Wände auf. Wenn man es doch tut, stehen die Wände, bevor das Fundament ausgehärtet ist, und das gesamte Gebäude muss abgerissen und neu begonnen werden. Erst das Fundament gießen, aushärten lassen, dann die Wände bauen — sauber und effizient. - -Diese Lektion erklärt, warum die Initialisierung eine separate Phase sein muss, nicht mit der Implementierung vermischt. - -## Fundament und Wände: Zwei grundverschiedene Aufgaben - -Initialisierung und Implementierung haben völlig unterschiedliche Optimierungsziele. Die Implementierungsphase optimiert auf: Maximierung der Menge und Qualität verifizierter Features. Die Initialisierungsphase optimiert auf: Maximierung der Zuverlässigkeit und Effizienz aller nachfolgenden Implementierungen. - -Wenn man Initialisierung und Implementierung vermischt, steht der Agent vor einem Multi-Objective-Optimierungsproblem — gleichzeitig Infrastruktur aufbauen und Feature-Code schreiben. Ohne explizite Prioritätensetzung neigt der Agent natürlicherweise zum Schreiben von Code (weil das direkt sichtbare Ausgabe ist) und opfert die Infrastruktur (weil ihr Wert sich erst in nachfolgenden Sessions zeigt). Es ist, als würde man einer Baumannschaft sagen, sie soll gleichzeitig das Fundament gießen und die Wände bauen — sie werden wahrscheinlich eilig Wände bauen, weil Wände sichtbar und vorzeigbar sind. Aber ein Haus mit einem schlechten Fundament hat systemische Probleme später. - -## Initialisierungs-Lebenszyklus - -```mermaid -flowchart TB - subgraph Wrong["Mixed session (wrong)"] - W1["Start feature work immediately"] --> W2["Discover env and test gaps mid-task"] - W2 --> W3["Accumulate unverified code"] - W3 --> W4["Next session must rediscover project state"] - end - - subgraph Right["Dedicated initialization (right)"] - R1["Session 1: environment runnable"] --> R2["Example test passing"] - R2 --> R3["Bootstrap contract + task list written"] - R3 --> R4["Clean checkpoint committed"] - R4 --> R5["Later sessions start directly on verified tasks"] - end -``` - -## Was passiert, wenn man beides vermischt - -Das direkteste Problem: Das Fundament härtet nicht richtig aus. Der Agent verbringt 80% seiner Mühe auf Feature-Code und 20% mit beiläufigem Aufbau etwas Infrastruktur. Das Test-Framework ist konfiguriert, aber nie verifiziert, Lint-Regeln sind gesetzt, aber zu lasch, keine Fortschrittsdatei erstellt. Diese Mängel sind in der ersten Session nicht offensichtlich (weil der Agent sich noch an das erinnert, was er getan hat), aber sie tauchen in der zweiten Session auf — der neue Agent weiß nicht, wie man ausführt, testet oder wo die Dinge stehen. Schlechtes Fundament, wackeliges Gebäude. - -Ein noch verborgenerer Kostenfaktor ist die „unverifizierte Akkumulation" — Feature-Code, der geschrieben wurde, bevor das Test-Framework konfiguriert ist, ist Code ohne Verifizierung. Wenn man schließlich zurückgeht, um Tests für diesen Code hinzuzufügen, könnte man feststellen, dass das Design von Anfang an falsch war — hätte man es gewusst, hätte man es anders implementiert. Wie Fliesen auf nassem Beton — wenn man entdeckt, dass der Boden nicht eben ist, müssen alle Fliesen herausgebrochen und neu verlegt werden. - -Das Session-Budget wird ebenfalls verschwendet. Initialisierungsarbeit (Umgebungen konfigurieren, Tests einrichten, Projektstruktur verstehen) verbraucht signifikantes Budget und lässt weniger für die eigentliche Feature-Implementierung. Ergebnis: Die erste Session schließt nur die Hälfte der Features ab, und die zweite Session muss das Projektverständnis von vorne beginnen. Budget für das Fundament ausgegeben, aber das Fundament ist auch nicht solide — keines der beiden Ziele erreicht. - -Das am leichtesten übersehene Problem sind implizite Annahmen-Landminen. Entscheidungen, die der Agent während der Initialisierung trifft (welches Test-Framework, wie Verzeichnisse organisieren, Abhängigkeitsverwaltung) — wenn sie nicht explizit dokumentiert werden, können nachfolgende Sessions diese Entscheidungen nicht nachvollziehen. Schlimmer noch, nachfolgende Sessions könnten widersprüchliche Entscheidungen treffen. Die erste Baumannschaft hat ein Betonfundament verwendet, die zweite weiß es nicht und hat Holzpfähle hineingetrieben — das Fundament reißt. - -Anthropics Forschung zur Entwicklung langlebiger Anwendungen empfiehlt ausdrücklich die Trennung von Initialisierung und Implementierung. Ihre experimentellen Daten: Projekte mit einer dedizierten Initialisierungsphase zeigten in Multi-Session-Szenarien eine um 31% höhere Feature-Abschlussrate im Vergleich zu gemischten Ansätzen. Die zentrale Erkenntnis — die in die Initialisierungsphase investierte Zeit wird in den nächsten 3–4 Sessions vollständig zurückgewonnen. Je solider das Fundament, desto schneller gehen die Wände hoch. - -OpenAIs Codex harness Engineering-Leitfaden betont ebenfalls das Prinzip „Repository als operativer Datensatz" — etablieren Sie von Anfang an eine klare Betriebsstruktur, sonst muss jede neue Session Projektkonventionen neu erschließen. - -## Zentrale Konzepte - -- **Initialisierungsphase**: Die erste Phase im Lebenszyklus des Agenten — keine Feature-Implementierung, nur die Schaffung von Voraussetzungen für alle nachfolgenden Implementierungsphasen. Die Ausgabe ist kein Code, sondern Infrastruktur. -- **Bootstrap-Vertrag**: Die Bedingungen, unter denen ein Projekt von einer neuen Agenten-Session eindeutig betrieben werden kann — kann starten, kann testen, kann Fortschritt sehen, kann nächste Schritte aufnehmen. Vier Bedingungen, alle erforderlich. -- **Kaltstart vs. Warmstart**: Kaltstart bedeutet ein leeres Verzeichnis, in dem der Agent die Projektstruktur erraten muss; Warmstart bedeutet eine Vorlage oder ein bestehendes Projekt, in dem die Infrastruktur bereits vorhanden ist. Warmstart schneidet deutlich besser ab — wie das Arbeiten auf einer Baustelle mit fließendem Wasser und Strom versus dem Beginn auf einer kargen Brache. -- **Übergabebereitschaft**: Das Projekt befindet sich jederzeit in einem Zustand, in dem ein neuer Agent übernehmen kann. Keine mündliche Erklärung nötig — nur der Repo-Inhalt. -- **Zeit bis zur ersten Verifizierung**: Die Zeit vom Projektstart bis zum ersten bestandenen Feature-Verifizierungspunkt. Dies ist die Kernkennzahl zur Messung der Initialisierungseffizienz. -- **Downstream-Verwendbarkeit**: Das beste Maß für die Initialisierungsqualität — der Anteil nachfolgender Sessions, die erfolgreich Aufgaben ausführen können, ohne auf implizites Wissen angewiesen zu sein. - -## Wie man Initialisierung richtig durchführt - -**Behandeln Sie Initialisierung als dedizierte Phase.** Die erste Session führt nur Initialisierung durch — überhaupt kein Geschäfts-Feature-Code. Initialisierung produziert: - -**1. Ausführbare Umgebung.** Das Projekt startet, Abhängigkeiten sind installiert, keine Umgebungsprobleme. Fundament gegossen, keine Risse. - -**2. Verifizierbares Test-Framework.** Mindestens ein Beispieltest ist bestanden. Das beweist, dass das Test-Framework selbst richtig konfiguriert ist — wie das Aufstellen einer Säule auf dem Fundament, um zu beweisen, dass es Gewicht tragen kann. - -**3. Bootstrap-Vertragsdokument.** Ein klares Dokument, das nachfolgenden Sessions sagt: -```markdown -# Initialization Contract - -## Start Commands -- Install dependencies: `make setup` -- Start dev server: `make dev` -- Run tests: `make test` -- Full verification: `make check` - -## Current State -- All dependencies installed and locked -- Test framework configured (Vitest + React Testing Library) -- Example test passing (1/1) -- Lint rules configured (ESLint + Prettier) - -## Project Structure -- src/ — Source code -- src/components/ — React components -- src/api/ — API client -- tests/ — Test files -``` - -**4. Aufgaben-Aufschlüsselung.** Teilen Sie das gesamte Projekt in eine geordnete Aufgabenliste auf, jede Aufgabe mit klaren Akzeptanzkriterien: -```markdown -# Task Breakdown - -## Task 1: User Authentication Basics -- Implement JWT auth middleware -- Add login/register endpoints -- Acceptance: pytest tests/test_auth.py all passing - -## Task 2: User Profile Page -- Implement user profile CRUD -- Add profile edit form -- Acceptance: pytest tests/test_profile.py all passing - -## Task 3: Search Feature -- ... -``` - -**5. Git-Commit als Kontrollpunkt.** Nach Abschluss der Initialisierung einen sauberen Kontrollpunkt committen. Alle nachfolgende Arbeit beginnt ab diesem Kontrollpunkt. - -**Warmstart-Strategie**: Nicht mit einem leeren Verzeichnis beginnen. Verwenden Sie eine Projektvorlage (create-react-app, fastapi-template, etc.), um eine Standardverzeichnisstruktur, Abhängigkeitskonfiguration und Test-Framework vorzugeben. Backen Sie häufige Initialisierungsschritte in die Vorlage ein und lassen Sie nur projektspezifische Initialisierungsarbeit übrig. Wie das Arbeiten auf einer Baustelle mit fließendem Wasser und Strom — zehntausendmal besser als der Beginn auf einer kargen Brache. - -**Initialisierungs-Abschlusskriterien**: Nicht „wie viel Code geschrieben wurde", sondern ob die vier Bedingungen des Bootstrap-Vertrags erfüllt sind — kann starten, kann testen, kann Fortschritt sehen, kann nächste Schritte aufnehmen. Verwenden Sie diese Checkliste zur Validierung der Initialisierung: - -```markdown -## Initialization Acceptance Checklist -- [ ] `make setup` succeeds from scratch -- [ ] `make test` has at least one passing test -- [ ] A new agent session can answer "how to run" and "how to test" from repo contents alone -- [ ] Task breakdown file exists with at least 3 tasks -- [ ] Everything committed to git -``` - -## Praxisbeispiel - -Zwei Initialisierungsansätze für ein React-Frontend-Projekt: - -**Gemischter Ansatz (Fundament gießen und Wände gleichzeitig bauen)**: Der Agent erstellte gleichzeitig Projektgerüst und implementierte das erste Feature in Session 1. Am Session-Ende hatte das Repo ausführbaren Code, aber: keine explizite Start-/Test-Befehlsdokumentation, keine Fortschritts-Tracking-Datei, keine Aufgaben-Aufschlüsselung. Session 2 verbrachte ~20 Minuten damit, Projektstruktur, Test-Framework und Build-Prozess zu erschließen — wie eine neue Baumannschaft, die auf einer Baustelle ankommt, nicht weiß, wie weit das Fundament ist oder wo die Rohre verlaufen, und Loch für Loch graben muss, um es herauszufinden. - -**Dedizierte Initialisierung (erst das Fundament)**: Session 1 führte nur Initialisierung durch — erstellte Verzeichnisstruktur aus einer Vorlage, konfigurierte das Test-Framework (Vitest + React Testing Library), schrieb und verifizierte einen Beispieltest, erstellte das Bootstrap-Vertragsdokument und die Aufgaben-Aufschlüsselungsdatei, committete den initialen Kontrollpunkt. Session 2s Rebuild-Zeit lag unter 3 Minuten, und sie begann direkt aus der Aufgabenliste zu arbeiten — die Mannschaft kommt an, wirft einen Blick auf den Bauplan und weiß genau, wo sie ansetzen muss. - -Vergleich über den gesamten Projektzyklus: Die gesamte Rebuild-Zeit des gemischten Ansatzes (über alle Sessions) lag ~60% höher als beim dedizierten Initialisierungsansatz. Die zusätzlichen 20 Minuten für die Initialisierung wurden in nachfolgenden Sessions vielfach zurückgewonnen. Wie ein solides Fundament, das die Wände schneller hochgehen lässt — langsam ist schnell. - -## Wichtigste Erkenntnisse - -- Initialisierung und Implementierung haben unterschiedliche Optimierungsziele — ihre Vermischung zieht nur beide herunter. Erst das Fundament gießen, dann die Wände bauen. -- Die Ausgabe der Initialisierung ist kein Code, sondern Infrastruktur: ausführbare Umgebung, verifizierbare Tests, Bootstrap-Vertrag, Aufgaben-Aufschlüsselung. -- Validieren Sie die Initialisierung anhand der vier Bedingungen des Bootstrap-Vertrags: kann starten, kann testen, kann Fortschritt sehen, kann nächste Schritte aufnehmen. -- Warmstart schlägt Kaltstart. Verwenden Sie Projektvorlagen, um standardisierte Infrastruktur vorzugeben. -- Die in die Initialisierung investierte Zeit wird in den nächsten 3–4 Sessions vollständig zurückgewonnen. Das sind keine Mehrkosten — sondern eine Vorab-Investition. Je solider das Fundament, desto schneller geht das Gebäude hoch. - -## Weiterführende Literatur - -- [Anthropic: Effective Harnesses for Long-Running Agents](https://www.anthropic.com/engineering/effective-harnesses-for-long-running-agents) -- [OpenAI: Harness Engineering](https://openai.com/index/harness-engineering/) -- [HumanLayer: Harness Engineering for Coding Agents](https://humanlayer.dev/articles/harness-engineering-for-coding-agents/) -- [Infrastructure as Code — Martin Fowler](https://martinfowler.com/bliki/InfrastructureAsCode.html) -- [SWE-agent: Agent-Computer Interfaces](https://github.com/princeton-nlp/SWE-agent) - -## Übungen - -1. **Bootstrap-Vertrag entwerfen**: Schreiben Sie einen vollständigen Bootstrap-Vertrag für ein Projekt, das Sie entwickeln. Öffnen Sie dann eine komplett neue Agenten-Session, zeigen Sie ihr nur den Repo-Inhalt (kein mündlicher Kontext), und lassen Sie sie versuchen, das Projekt zu starten, Tests auszuführen und den aktuellen Fortschritt zu verstehen. Notieren Sie jedes Problem, auf das sie stößt — jedes entspricht einer fehlenden Klausel in Ihrem Bootstrap-Vertrag. - -2. **Vergleichsexperiment**: Wählen Sie ein moderat komplexes neues Projekt. Ansatz A: Den Agenten gleichzeitig initialisieren und die erste Implementierung durchführen lassen. Ansatz B: Eine Session für dedizierte Initialisierung aufwenden, Implementierung in Session 2 beginnen. Nach 4 Sessions vergleichen: Zeit bis zur ersten Verifizierung, Rebuild-Kosten, Feature-Abschlussrate. - -3. **Initialisierungs-Akzeptanz-Checkliste**: Entwerfen Sie eine Initialisierungs-Akzeptanz-Checkliste für Ihr Projekt. Lassen Sie eine neue Agenten-Session jeden Checklistenpunkt ausführen und notieren Sie, welche bestehen und welche fehlschlagen. Die fehlschlagenden Punkte sind die Stellen, an denen Ihr harness verstärkt werden muss. diff --git a/courses/learn-harness-engineering/docs/de/lectures/lecture-07-why-agents-overreach-and-under-finish/code/index.md b/courses/learn-harness-engineering/docs/de/lectures/lecture-07-why-agents-overreach-and-under-finish/code/index.md deleted file mode 100644 index 14179569..00000000 --- a/courses/learn-harness-engineering/docs/de/lectures/lecture-07-why-agents-overreach-and-under-finish/code/index.md +++ /dev/null @@ -1,8 +0,0 @@ -# Code für Vorlesung 07 - -Verwenden Sie diesen Ordner für Beispiele zu: - -- One-Shot-Fehlern -- übergroßen Task-Prompts -- inkrementeller Aufgabenformung -- strukturierten Feature-Oberflächen diff --git a/courses/learn-harness-engineering/docs/de/lectures/lecture-07-why-agents-overreach-and-under-finish/code/next-task-template.md b/courses/learn-harness-engineering/docs/de/lectures/lecture-07-why-agents-overreach-and-under-finish/code/next-task-template.md deleted file mode 100644 index fd96d405..00000000 --- a/courses/learn-harness-engineering/docs/de/lectures/lecture-07-why-agents-overreach-and-under-finish/code/next-task-template.md +++ /dev/null @@ -1,6 +0,0 @@ -# Nächste-Aufgabe-Vorlage - -- Aktuelles Feature mit höchster Priorität: -- Warum dieses Feature als nächstes kommt: -- Was als bestanden gilt: -- Was während dieses Schritts nicht geändert werden darf: diff --git a/courses/learn-harness-engineering/docs/de/lectures/lecture-07-why-agents-overreach-and-under-finish/code/scope-surface-example.md b/courses/learn-harness-engineering/docs/de/lectures/lecture-07-why-agents-overreach-and-under-finish/code/scope-surface-example.md deleted file mode 100644 index 4510fa18..00000000 --- a/courses/learn-harness-engineering/docs/de/lectures/lecture-07-why-agents-overreach-and-under-finish/code/scope-surface-example.md +++ /dev/null @@ -1,17 +0,0 @@ -# Scope-Oberfläche Beispiel - -Aufgabe: - -- Indexierung zur Electron Knowledge-App hinzufügen - -Schlechte Scope-Form: - -- "Indexierung implementieren" - -Bessere Scope-Form: - -- Importierte Dokumente parsen -- Dokumente in Chunks aufteilen -- Chunk-Metadaten persistieren -- Indexierungsstatus in der UI anzeigen -- Eine Neu-Indexierungs-Aktion hinzufügen diff --git a/courses/learn-harness-engineering/docs/de/lectures/lecture-07-why-agents-overreach-and-under-finish/code/scope-tracker.ts b/courses/learn-harness-engineering/docs/de/lectures/lecture-07-why-agents-overreach-and-under-finish/code/scope-tracker.ts deleted file mode 100644 index c587a1dc..00000000 --- a/courses/learn-harness-engineering/docs/de/lectures/lecture-07-why-agents-overreach-and-under-finish/code/scope-tracker.ts +++ /dev/null @@ -1,151 +0,0 @@ -/** - * scope-tracker.ts - * - * Reads a feature list and a change log. Enforces single-active-feature - * policy. Given a log of changes, flags any changes outside the active - * feature scope. Demonstrates how scope drift happens and how the tracker - * catches it. - * - * Run: npx tsx docs/lectures/lecture-07-why-agents-overreach-and-under-finish/code/scope-tracker.ts - */ - -// --------------------------------------------------------------------------- -// Types -// --------------------------------------------------------------------------- - -interface Feature { - id: string; - name: string; - status: "active" | "pending" | "done"; -} - -interface ChangeLogEntry { - step: number; - file: string; - description: string; - featureId: string; // The feature this change claims to belong to -} - -// --------------------------------------------------------------------------- -// Sample data -// --------------------------------------------------------------------------- - -const features: Feature[] = [ - { id: "F-001", name: "Search endpoint", status: "active" }, - { id: "F-002", name: "Delete endpoint", status: "pending" }, - { id: "F-003", name: "Rate limiting", status: "pending" }, - { id: "F-004", name: "User dashboard", status: "pending" }, -]; - -// A realistic change log where the agent gradually drifts from the active feature -const changeLog: ChangeLogEntry[] = [ - { step: 1, file: "src/routes/search.ts", description: "Add search route handler", featureId: "F-001" }, - { step: 2, file: "src/routes/search.ts", description: "Add query parameter validation", featureId: "F-001" }, - { step: 3, file: "src/routes/search.ts", description: "Add search results pagination", featureId: "F-001" }, - { step: 4, file: "src/routes/delete.ts", description: "Add delete route handler", featureId: "F-002" }, // DRIFT - { step: 5, file: "src/middleware/rate-limit.ts", description: "Add rate limiter middleware", featureId: "F-003" }, // DRIFT - { step: 6, file: "src/routes/search.ts", description: "Integrate rate limiter into search", featureId: "F-001" }, - { step: 7, file: "src/dashboard/ui.tsx", description: "Create dashboard layout component", featureId: "F-004" }, // DRIFT - { step: 8, file: "src/routes/search.ts", description: "Add search response formatting", featureId: "F-001" }, - { step: 9, file: "src/routes/delete.ts", description: "Add delete confirmation logic", featureId: "F-002" }, // DRIFT - { step: 10, file: "src/routes/search.ts", description: "Add search tests", featureId: "F-001" }, -]; - -// --------------------------------------------------------------------------- -// Scope tracker -// --------------------------------------------------------------------------- - -interface ScopeCheckResult { - step: number; - file: string; - description: string; - featureId: string; - inScope: boolean; - activeFeature: string; -} - -function trackScope( - featureList: Feature[], - changes: ChangeLogEntry[] -): ScopeCheckResult[] { - const activeFeatures = featureList.filter((f) => f.status === "active"); - const activeIds = new Set(activeFeatures.map((f) => f.id)); - const activeNames = activeFeatures.map((f) => f.name).join(", "); - - return changes.map((change) => ({ - step: change.step, - file: change.file, - description: change.description, - featureId: change.featureId, - inScope: activeIds.has(change.featureId), - activeFeature: activeNames, - })); -} - -// --------------------------------------------------------------------------- -// Reporting -// --------------------------------------------------------------------------- - -function pad(s: string, len: number): string { - return s.length >= len ? s : s + " ".repeat(len - s.length); -} - -function run(): void { - const results = trackScope(features, changeLog); - - console.log("\n" + "=".repeat(100)); - console.log(" SCOPE TRACKER -- Single Active Feature Enforcement"); - console.log("=".repeat(100)); - - console.log("\n Active feature: " + features.filter((f) => f.status === "active").map((f) => `${f.id} (${f.name})`).join(", ")); - console.log(" Pending features: " + features.filter((f) => f.status === "pending").map((f) => `${f.id} (${f.name})`).join(", ")); - - // Detailed change log - console.log("\n" + "-".repeat(100)); - const header = `| ${pad("Step", 5)}| ${pad("File", 35)}| ${pad("Description", 40)}| ${pad("Feature", 8)}| ${pad("In Scope", 10)}|`; - const sep = `|${"-".repeat(7)}|${"-".repeat(37)}|${"-".repeat(42)}|${"-".repeat(10)}|${"-".repeat(12)}|`; - console.log(header); - console.log(sep); - - let inScopeCount = 0; - let driftCount = 0; - - for (const r of results) { - const scopeLabel = r.inScope ? "OK" : "DRIFT"; - if (r.inScope) inScopeCount++; - else driftCount++; - - const marker = r.inScope ? " " : ">>"; - console.log(`${marker}| ${pad(String(r.step), 5)}| ${pad(r.file, 35)}| ${pad(r.description, 40)}| ${pad(r.featureId, 8)}| ${pad(scopeLabel, 10)}|`); - } - - // Summary - console.log("\n" + "=".repeat(100)); - console.log(" SCOPE DRIFT SUMMARY"); - console.log("=".repeat(100) + "\n"); - - const sHeader = `| ${pad("Metric", 40)}| ${pad("Value", 15)}|`; - const sSep = `|${"-".repeat(42)}|${"-".repeat(17)}|`; - console.log(sHeader); - console.log(sSep); - console.log(`| ${pad("Total changes", 40)}| ${pad(String(results.length), 15)}|`); - console.log(`| ${pad("Changes within active scope (F-001)", 40)}| ${pad(String(inScopeCount), 15)}|`); - console.log(`| ${pad("Changes outside active scope (DRIFT)", 40)}| ${pad(String(driftCount), 15)}|`); - console.log(`| ${pad("Features touched (total)", 40)}| ${pad(String(new Set(results.map((r) => r.featureId)).size), 15)}|`); - - // Drift detail - const driftFeatures = [...new Set(results.filter((r) => !r.inScope).map((r) => r.featureId))]; - if (driftFeatures.length > 0) { - console.log("\n DRIFTED FEATURES:"); - for (const fid of driftFeatures) { - const feat = features.find((f) => f.id === fid); - const driftChanges = results.filter((r) => r.featureId === fid); - console.log(` ${fid} (${feat?.name}): ${driftChanges.length} unauthorized changes`); - } - } - - console.log("\n Without a scope tracker, the agent silently worked on " + driftFeatures.length + " unrelated features."); - console.log(" The tracker catches this drift and enforces the single-active-feature policy.\n"); -} - -run(); diff --git a/courses/learn-harness-engineering/docs/de/lectures/lecture-07-why-agents-overreach-and-under-finish/index.md b/courses/learn-harness-engineering/docs/de/lectures/lecture-07-why-agents-overreach-and-under-finish/index.md deleted file mode 100644 index c052b159..00000000 --- a/courses/learn-harness-engineering/docs/de/lectures/lecture-07-why-agents-overreach-and-under-finish/index.md +++ /dev/null @@ -1,134 +0,0 @@ -[中文版本 →](../../../zh/lectures/lecture-07-why-agents-overreach-and-under-finish/) - -> Codebeispiele: [code/](https://github.com/walkinglabs/learn-harness-engineering/blob/main/docs/de/lectures/lecture-07-why-agents-overreach-and-under-finish/code/) -> Praxisprojekt: [Project 04. Runtime feedback and scope control](./../../projects/project-04-incremental-indexing/index.md) - -# Lektion 07. Klare Aufgabengrenzen für Agenten ziehen - -Sie sagen Claude Code, es soll „Benutzerauthentifizierung zu diesem Projekt hinzufügen", und es beginnt, das Datenbankschema zu ändern, Routen zu schreiben, Frontend-Komponenten zu bearbeiten und — während es gerade dabei ist — die Fehlerbehandlungs-Middleware zu refactoren. Zwei Stunden später überprüfen Sie: 12 Dateien geändert, 800 Zeilen neuer Code, und kein einziges Feature funktioniert End-to-End. - -Sich mehr auf den Teller laden, als man bewältigen kann — dieses Sprichwort gilt für KI-Agenten ganz besonders. Agenten werden mit einem Impuls geboren, „noch etwas Extra zu erledigen" — sie sehen verwandte Dinge und erledigen sie einfach nebenbei, wie jemand, der für eine Flasche Sojasauce in den Supermarkt geht und mit einem vollen Einkaufswagen herauskommt. Das Problem ist: Menschen, die zu viel kaufen, verschwenden nur Geld; Agenten, die zu viele Dinge gleichzeitig tun, bedeuten, dass keines davon richtig erledigt wird. - -Anthropics Engineering-Blog „Effective harnesses for long-running agents" stellt klar: Wenn Prompts zu breit gefasst sind, neigen Agenten dazu, „mehrere Dinge gleichzeitig zu beginnen" anstatt „erst eins zu Ende zu bringen." OpenAIs Codex-Engineering-Praktiken fanden dasselbe — Aufgaben ohne explizite Scope-Kontrollen sehen Abschlussraten einbrechen. Das ist kein Modellproblem — es ist ein harness-Problem. Sie haben die Grenze nicht gezogen. - -## Aufmerksamkeit ist eine endliche Ressource - -Das ist keine Metapher — es ist Mathematik. Angenommen, die Kontextkapazität des Agenten ist C und er aktiviert gleichzeitig k Aufgaben. Jede Aufgabe bekommt durchschnittlich C/k Reasoning-Ressourcen. Wenn C/k unter den Mindestschwellenwert fällt, der zum Abschluss einer einzelnen Aufgabe benötigt wird, wird keine davon fertig. Ihr Magen ist nur so groß — stopfen Sie zehn Knödel auf einmal hinein, und Sie verdauen sie nicht alle, Sie bekommen einfach zehnmal Verdauungsprobleme. - -Claude Codes tatsächliches Verhalten ist aufschlussreich. Bitten Sie es, „Benutzerregistrierung hinzuzufügen", und es könnte: - -1. Ein User-Modell erstellen -2. Die Registrierungsroute schreiben -3. Bemerken, dass E-Mail-Verifizierung nötig ist, also einen Mail-Service hinzufügen -4. Sehen, dass Passwörter gehasht werden müssen, also bcrypt einbinden -5. Bemerken, dass die Fehlerbehandlung inkonsistent ist, also die globale Error-Middleware refactoren -6. Sehen, dass die Testdateistruktur unordentlich ist, also das Verzeichnis reorganisieren - -Sechs Schritte später ist jeder halb fertig. Keine End-to-End-Verifizierung, komplexe Kopplung zwischen dem halbfertigen Code, und die nächste Session, die alles aufarbeiten muss, wird völlig verloren sein. Wie jemand, der sechs Gerichte gleichzeitig kocht — jedes Gericht ist in der Pfanne, aber keines ist angerichtet. Sie alle verbrennen. - -Anthropics experimentelle Daten unterstützen dies direkt: Agenten mit einer „kleiner nächster Schritt"-Strategie (entspricht WIP=1) zeigen eine um 37% höhere Aufgaben-Abschlussrate als Agenten mit breiten Prompts. Noch interessanter: Die Anzahl der vom Agenten generierten Codezeilen ist schwach negativ korreliert mit dem tatsächlichen Feature-Abschluss — mehr Code geschrieben, weniger Features abgeschlossen. Sich mehr auf den Teller laden, als man bewältigen kann, datenbewiesen. - -## WIP=1-Workflow - -```mermaid -flowchart LR - Queue["Feature queue"] --> Pick["Pick exactly one task"] - Pick --> Active["Only one active item"] - Active --> Verify["Run end-to-end verification"] - Verify -->|pass| Commit["Commit and unlock next task"] - Verify -->|fail| Active - Commit --> Queue -``` - -```mermaid -flowchart TB - Budget["Available reasoning budget = C"] --> One["WIP = 1
C / 1 per task"] - Budget --> Many["WIP = 5
C / 5 per task"] - - One --> Finish["One feature reaches passing state"] - Many --> Partial["Five partial implementations"] - Partial --> VCR["Low verified completion rate
high rework next session"] -``` - -## Zentrale Konzepte - -- **Overreach**: Der Agent aktiviert in einer einzelnen Session mehr Aufgaben als optimal. Es ist quantifizierbar — 5 Features mit 0 bestandenen End-to-End-Tests ist Overreach. -- **Under-finish**: Das Verhältnis von Aufgaben, die End-to-End-Verifizierung bestehen, zu allen aktivierten Aufgaben, fällt unter den Schwellenwert. Geschriebener Code, aber keine bestandenen Tests, ist Under-finish. -- **WIP-Limit (Work-in-Progress-Limit)**: Aus der Kanban-Methodik. Kernidee: Begrenzen Sie, wie viele Aufgaben gleichzeitig in Bearbeitung sind. Für Agenten ist WIP=1 die sicherste Standardeinstellung — erst eins beenden, dann das nächste beginnen. Wie an einem Buffet — nicht den Teller überladen, erst einen Teller essen, dann zum nächsten gehen. -- **Abschluss-Evidenz**: Die verifizierbare Bedingung, die eine Aufgabe erfüllen muss, um von „in Bearbeitung" auf „erledigt" zu wechseln. Ohne dies substituieren Agenten „der Code sieht gut aus" durch „das Verhalten besteht die Tests." -- **Scope Surface**: Eine DAG-Struktur, bei der jeder Knoten eine Arbeitseinheit und die Kanten Abhängigkeiten sind. Zustände sind auf vier beschränkt: not_started, active, blocked, passing. -- **Abschluss-Druck**: Die einschränkende Kraft, die das harness durch WIP-Limits und Abschluss-Evidenz-Anforderungen ausübt und den Agenten zwingt, die aktuelle Aufgabe abzuschließen, bevor eine neue begonnen wird. - -## Overreach und Under-finish sind symbiotisch - -Diese beiden Probleme sind nicht unabhängig — sie verstärken sich gegenseitig. Overreach verdünnt die Aufmerksamkeit, verdünnte Aufmerksamkeit verursacht Under-finish, und der halbfertige Code, der zurückbleibt, erhöht die Systemkomplexität, was den Overreach bei der nächsten Aufgabe weiter treibt. Ein Teufelskreis. - -In Kanban-Begriffen: Littles Gesetz besagt L = lambda * W. Wenn die Work-in-Progress L zu hoch ist (zu viele Dinge gleichzeitig), erhöht sich die Durchlaufzeit W für jede Aufgabe unvermeidlich. Für Agenten bedeutet das: Jedes Feature dauert länger vom Start bis zur verifizierten Fertigstellung, und die Wahrscheinlichkeit des Scheiterns wächst. - -Dies ist auch in der menschlichen Welt ein altes Problem — Steve McConnell dokumentierte in *Rapid Development*, dass Scope Creep die häufigste Ursache für Projektversagen ist. Aber Menschen haben zumindest die Intuition „ich habe genug getan." Agenten haben das nicht. Die Generierung der nächsten Idee kostet das Modell fast keine zusätzlichen Token — „lass mich das auch noch reparieren, während ich hier bin" fällt kaum auf — aber jede zusätzliche Modifikation verdünnt die Aufmerksamkeit des Agenten. Wie an einem Buffet, wo jeder zusätzliche Teller nahezu grenzkostenfrei ist, aber Ihr Magen nur begrenzte Kapazität hat. - -## Wie man es richtig macht - -### 1. WIP=1 erzwingen - -Das ist die direkteste und wirksamste Methode. Sagen Sie dem Agenten in Ihrem harness explizit: **zu jedem Zeitpunkt ist nur eine Aufgabe im Status „active" erlaubt.** In Claude Codes `CLAUDE.md` oder Codex' `AGENTS.md` schreiben Sie: - -``` -## Work Rules -- Work on one feature at a time -- Only start the next feature after the current one passes end-to-end verification -- Don't "also refactor" feature B while implementing feature A -``` - -Wie am Buffet essen — ein Teller nach dem anderen, erst essen, dann nachholen. - -### 2. Explizite Abschluss-Evidenz für jede Aufgabe definieren - -Fertig ist nicht „Code ist geschrieben" — es ist „Verhaltensverifizierung bestanden." In Ihrer feature list benötigt jeder Eintrag einen Verifizierungsbefehl: - -``` -F01: User Registration - Verification: curl -X POST /api/register -d '{"email":"test@example.com","password":"123456"}' | jq .status == 201 - State: passing -``` - -### 3. Die Scope Surface externalisieren - -Verwenden Sie eine maschinenlesbare Datei (JSON oder Markdown), um alle Aufgaben zustände zu dokumentieren. Jede neue Session kann diese Datei lesen und sofort wissen: Welche Aufgabe ist aktiv? Welches Verhalten gilt als erledigt? Welche Verifizierungen wurden bestanden? - -### 4. Die verifizierte Abschlussrate überwachen - -Das harness sollte kontinuierlich VCR (Verified Completion Rate) = verifizierte Aufgaben / aktivierte Aufgaben verfolgen. Blockieren Sie neue Aufgabenaktivierungen, wenn VCR < 1.0. - -## Praxisbeispiel - -Ein REST-API-Projekt mit 8 Features, zwei Strategien im Vergleich: - -**Buffet-Modus (uneingeschränkt)**: Der Agent aktiviert 5 Features gleichzeitig in Session 1. Produziert ~800 Zeilen über 12 Dateien. End-to-End-Test-Bestehensrate: 20% — nur Benutzerregistrierung funktioniert. Die anderen 4 Features: Datenbankschema erstellt, aber Validierungslogik fehlt, Routen definiert, aber falsches Antwortformat. Wie jemand, der sechs Gerichte gleichzeitig kocht, nur eines ist kaum essbar. Ende Session 3 sind nur 3 von 8 Features abgeschlossen. - -**Ein-Teller-Modus (WIP=1)**: Der Agent arbeitet in Session 1 nur an der Benutzerregistrierung. Produziert ~200 Zeilen über 4 Dateien. End-to-End-Tests: 100% bestanden. Committet eine saubere, verifizierte Implementierung. Ende Session 4 sind 7 von 8 Features abgeschlossen (das 8. blockiert durch eine externe Abhängigkeit). - -Ergebnis: weniger Gesamtcodes (800 vs. 1200 Zeilen), aber mehr wirksamer Code. Abschlussrate: 87,5% vs. 37,5%. Einen Bissen nach dem anderen, und man isst tatsächlich mehr. - -## Wichtigste Erkenntnisse - -- **WIP=1 ist die sichere Standardeinstellung für Agenten-harnesses** — erst eines beenden, dann das nächste beginnen; nicht versuchen zu parallelisieren. Man wird nicht in einem Biss dick. -- **Abschluss-Evidenz muss ausführbar sein** — „der Code sieht gut aus" zählt nicht; „curl liefert 201" schon. -- **Die Scope Surface muss als Datei externalisiert werden** — nicht nur im Gespräch erwähnt, sondern in einem maschinenlesbaren Format im Repo dokumentiert. -- **Overreach und Under-finish sind symbiotisch** — die Lösung des einen löst das andere. -- **„Weniger tun, aber beenden" schlägt immer „mehr tun, aber halbfertig lassen"** — Agenten-Codezeilen und Feature-Abschlussrate sind negativ korreliert. Qualität schlägt immer Quantität. - -## Weiterführende Literatur - -- [Effective harnesses for long-running agents - Anthropic](https://www.anthropic.com/engineering/effective-harnesses-for-long-running-agents) — Anthropics Engineering-Blog, detaillierte Diskussion der „kleiner nächster Schritt"-Strategie -- [Harness Engineering - OpenAI](https://openai.com/index/harness-engineering/) — OpenAIs umfassende Abhandlung zum harness Engineering -- [Kanban: Successful Evolutionary Change - David Anderson](https://www.goodreads.com/book/show/1070822.Kanban) — Die klassische Quelle zu WIP-Limits -- [Rapid Development - Steve McConnell](https://www.goodreads.com/book/show/125171.Rapid_Development) — Empirische Daten zu Scope Creep als häufigster Ursache für Projektversagen - -## Übungen - -1. **Aufgaben-Atomisierung**: Wählen Sie eine breite Anforderung (z. B. „implementiere ein Benutzerverwaltungssystem") und brechen Sie sie in mindestens 5 atomare Arbeitseinheiten auf. Geben Sie für jede Einheit an: (a) eine einzelne Verhaltensbeschreibung, (b) einen ausführbaren Verifizierungsbefehl, (c) Abhängigkeiten. Prüfen Sie, ob die Zerlegung der WIP=1-Einschränkung genügt. - -2. **Vergleichsexperiment**: Führen Sie dasselbe Projekt zweimal aus — einmal ohne Einschränkungen, einmal mit erzwungenem WIP=1. Vergleichen Sie: verifizierte Abschlussrate, Gesamtcodeszeilen, effektives Code-Verhältnis. - -3. **Abschluss-Evidenz-Audit**: Überprüfen Sie die Ausgabe eines recenten Agenten-Laufs und klassifizieren Sie jede Code-Änderung als „abgeschlossenes Verhalten", „unvollständiges Verhalten" oder „Gerüstbau". Fügen Sie fehlende Verifizierungsbefehle für jedes unvollständige Verhalten hinzu. diff --git a/courses/learn-harness-engineering/docs/de/lectures/lecture-08-why-feature-lists-are-harness-primitives/code/feature-list-validator.ts b/courses/learn-harness-engineering/docs/de/lectures/lecture-08-why-feature-lists-are-harness-primitives/code/feature-list-validator.ts deleted file mode 100644 index 52d6a2b9..00000000 --- a/courses/learn-harness-engineering/docs/de/lectures/lecture-08-why-feature-lists-are-harness-primitives/code/feature-list-validator.ts +++ /dev/null @@ -1,218 +0,0 @@ -/** - * feature-list-validator.ts - * - * Reads a feature_list.json, validates its schema, and checks for features - * marked "pass" without verification evidence. Outputs a structured report. - * Can run against any project directory that has a feature_list.json. - * - * Usage: - * npx tsx docs/lectures/lecture-08.../code/feature-list-validator.ts [path-to-dir] - * (defaults to the directory containing this script) - * - * Run: npx tsx docs/lectures/lecture-08-why-feature-lists-are-harness-primitives/code/feature-list-validator.ts - */ - -import * as fs from "node:fs"; -import * as path from "node:path"; - -// --------------------------------------------------------------------------- -// Types -// --------------------------------------------------------------------------- - -interface FeatureEntry { - id?: string; - category?: string; - description?: string; - verification?: string[]; - passes?: boolean; - // Allow unknown fields for flexibility - [key: string]: unknown; -} - -interface ValidationResult { - featureId: string; - schemaValid: boolean; - schemaErrors: string[]; - hasVerification: boolean; - markedPassWithoutEvidence: boolean; - passes: boolean; - verificationCount: number; -} - -// --------------------------------------------------------------------------- -// Schema validation -// --------------------------------------------------------------------------- - -function validateSchema(entry: FeatureEntry, index: number): string[] { - const errors: string[] = []; - const label = entry.id ?? `entry ${index + 1}`; - - if (!entry.id || typeof entry.id !== "string") { - errors.push(`[${label}] Missing or invalid 'id' field`); - } - if (!entry.category || typeof entry.category !== "string") { - errors.push(`[${label}] Missing or invalid 'category' field`); - } - if (!entry.description || typeof entry.description !== "string") { - errors.push(`[${label}] Missing or invalid 'description' field`); - } - if (entry.verification !== undefined && !Array.isArray(entry.verification)) { - errors.push(`[${label}] 'verification' must be an array if present`); - } - if (entry.passes !== undefined && typeof entry.passes !== "boolean") { - errors.push(`[${label}] 'passes' must be a boolean if present`); - } - - return errors; -} - -// --------------------------------------------------------------------------- -// Evidence validation -// --------------------------------------------------------------------------- - -function checkEvidence(entry: FeatureEntry): { - hasVerification: boolean; - markedPassWithoutEvidence: boolean; -} { - const hasVerification = Array.isArray(entry.verification) && entry.verification.length > 0; - const markedPassWithoutEvidence = entry.passes === true && !hasVerification; - - return { hasVerification, markedPassWithoutEvidence }; -} - -// --------------------------------------------------------------------------- -// Process feature list -// --------------------------------------------------------------------------- - -function processFeatureList(entries: FeatureEntry[]): ValidationResult[] { - return entries.map((entry, index) => { - const schemaErrors = validateSchema(entry, index); - const evidence = checkEvidence(entry); - - return { - featureId: (entry.id as string) ?? `entry-${index + 1}`, - schemaValid: schemaErrors.length === 0, - schemaErrors, - hasVerification: evidence.hasVerification, - markedPassWithoutEvidence: evidence.markedPassWithoutEvidence, - passes: entry.passes === true, - verificationCount: Array.isArray(entry.verification) ? entry.verification.length : 0, - }; - }); -} - -// --------------------------------------------------------------------------- -// Reporting -// --------------------------------------------------------------------------- - -function pad(s: string, len: number): string { - return s.length >= len ? s : s + " ".repeat(len - s.length); -} - -function run(): void { - // Resolve target directory - const scriptDir = path.dirname(new URL(import.meta.url).pathname); - const targetDir = process.argv[2] - ? path.resolve(process.argv[2]) - : scriptDir; - - const filePath = path.join(targetDir, "feature_list.json"); - - console.log("\n" + "=".repeat(90)); - console.log(" FEATURE LIST VALIDATOR"); - console.log("=".repeat(90)); - console.log(` Reading: ${filePath}\n`); - - if (!fs.existsSync(filePath)) { - console.error(` ERROR: feature_list.json not found at ${filePath}`); - console.error(" Usage: npx tsx feature-list-validator.ts [path-to-directory-containing-feature_list.json]\n"); - process.exit(1); - } - - let entries: FeatureEntry[]; - try { - const raw = fs.readFileSync(filePath, "utf-8"); - entries = JSON.parse(raw); - } catch (err) { - console.error(` ERROR: Could not parse feature_list.json: ${err}`); - process.exit(1); - } - - if (!Array.isArray(entries)) { - console.error(" ERROR: feature_list.json must contain a JSON array at the top level."); - process.exit(1); - } - - // For demo purposes, also validate an extended test set - const demoEntries: FeatureEntry[] = [ - ...entries, - { - id: "qna-002", - category: "import", - description: "User can import a PDF document.", - verification: ["Upload a PDF file", "Verify it appears in the document list"], - passes: true, - }, - { - id: "qna-003", - category: "grounded_qa", - description: "System hallucination rate is below 5%.", - verification: [], // Empty -- no evidence - passes: true, // Marked as pass WITHOUT evidence - }, - { - id: "missing-fields", - // Missing 'category' and 'description' - passes: true, - } as FeatureEntry, - ]; - - const results = processFeatureList(demoEntries); - - // Print report - const header = `| ${pad("Feature ID", 14)}| ${pad("Schema", 8)}| ${pad("Passes", 7)}| ${pad("Verifications", 14)}| ${pad("Evidence?", 12)}| Notes`; - const sep = `|${"-".repeat(16)}|${"-".repeat(10)}|${"-".repeat(9)}|${"-".repeat(16)}|${"-".repeat(14)}|${"-".repeat(30)}`; - console.log(header); - console.log(sep); - - for (const r of results) { - const schemaLabel = r.schemaValid ? "OK" : "INVALID"; - const passesLabel = r.passes ? "PASS" : "FAIL"; - const evidenceLabel = r.hasVerification ? "Present" : "MISSING"; - const notes = r.markedPassWithoutEvidence - ? "FLAGGED: passes without evidence!" - : r.schemaErrors.length > 0 - ? r.schemaErrors[0] - : ""; - - const marker = r.markedPassWithoutEvidence ? ">>" : r.schemaValid ? " " : "!!"; - console.log( - `${marker}| ${pad(r.featureId, 14)}| ${pad(schemaLabel, 8)}| ${pad(passesLabel, 7)}| ${pad(String(r.verificationCount), 14)}| ${pad(evidenceLabel, 12)}| ${notes}` - ); - } - - // Summary - const total = results.length; - const schemaOk = results.filter((r) => r.schemaValid).length; - const passing = results.filter((r) => r.passes).length; - const flagged = results.filter((r) => r.markedPassWithoutEvidence).length; - const withEvidence = results.filter((r) => r.hasVerification).length; - - console.log("\n" + "-".repeat(90)); - console.log(" SUMMARY"); - console.log("-".repeat(90)); - console.log(` Total features: ${total}`); - console.log(` Schema valid: ${schemaOk}/${total}`); - console.log(` Marked as passing: ${passing}/${total}`); - console.log(` With verification evidence: ${withEvidence}/${total}`); - console.log(` Flagged (pass without evidence): ${flagged}`); - - if (flagged > 0) { - console.log(`\n WARNING: ${flagged} feature(s) marked as "pass" without any verification evidence.`); - console.log(" These features need verification before they can be trusted.\n"); - } else { - console.log("\n All passing features have verification evidence. Feature list is healthy.\n"); - } -} - -run(); diff --git a/courses/learn-harness-engineering/docs/de/lectures/lecture-08-why-feature-lists-are-harness-primitives/code/feature_list.json b/courses/learn-harness-engineering/docs/de/lectures/lecture-08-why-feature-lists-are-harness-primitives/code/feature_list.json deleted file mode 100644 index 8b973e31..00000000 --- a/courses/learn-harness-engineering/docs/de/lectures/lecture-08-why-feature-lists-are-harness-primitives/code/feature_list.json +++ /dev/null @@ -1,15 +0,0 @@ -[ - { - "id": "qna-001", - "category": "grounded_qa", - "description": "User can ask a question about an imported document and receive an answer with visible citations.", - "verification": [ - "Import a markdown document", - "Open the Q&A panel", - "Ask a question about known content", - "Verify the answer is returned", - "Verify one or more citations are displayed" - ], - "passes": false - } -] diff --git a/courses/learn-harness-engineering/docs/de/lectures/lecture-08-why-feature-lists-are-harness-primitives/code/index.md b/courses/learn-harness-engineering/docs/de/lectures/lecture-08-why-feature-lists-are-harness-primitives/code/index.md deleted file mode 100644 index 901fea7a..00000000 --- a/courses/learn-harness-engineering/docs/de/lectures/lecture-08-why-feature-lists-are-harness-primitives/code/index.md +++ /dev/null @@ -1,8 +0,0 @@ -# Code für Vorlesung 08 - -Verwenden Sie diesen Ordner für Beispiele zu: - -- Pass-State-Gating -- End-to-End-Verifikation -- Schwache vs. starke Abschlusskriterien -- Evaluator-Loop-Beispielen diff --git a/courses/learn-harness-engineering/docs/de/lectures/lecture-08-why-feature-lists-are-harness-primitives/code/pass-gate-policy.md b/courses/learn-harness-engineering/docs/de/lectures/lecture-08-why-feature-lists-are-harness-primitives/code/pass-gate-policy.md deleted file mode 100644 index 92f1740e..00000000 --- a/courses/learn-harness-engineering/docs/de/lectures/lecture-08-why-feature-lists-are-harness-primitives/code/pass-gate-policy.md +++ /dev/null @@ -1,8 +0,0 @@ -# Pass-Gate-Richtlinie - -Ein Feature darf nur von `passes: false` zu `passes: true` wechseln, wenn: - -- der erwartete Workflow ausgeführt wurde -- der Erfolgsnachweis erfasst wurde -- kein blockierender Fehler im getesteten Pfad vorliegt -- die Implementierung die App nicht in einem defekten oder mehrdeutigen Zustand hinterlässt diff --git a/courses/learn-harness-engineering/docs/de/lectures/lecture-08-why-feature-lists-are-harness-primitives/index.md b/courses/learn-harness-engineering/docs/de/lectures/lecture-08-why-feature-lists-are-harness-primitives/index.md deleted file mode 100644 index 2b885784..00000000 --- a/courses/learn-harness-engineering/docs/de/lectures/lecture-08-why-feature-lists-are-harness-primitives/index.md +++ /dev/null @@ -1,138 +0,0 @@ -[中文版本 →](../../../zh/lectures/lecture-08-why-feature-lists-are-harness-primitives/) - -> Codebeispiele: [code/](https://github.com/walkinglabs/learn-harness-engineering/blob/main/docs/de/lectures/lecture-08-why-feature-lists-are-harness-primitives/code/) -> Praxisprojekt: [Projekt 04. Runtime-Feedback und Scope-Kontrolle](./../../projects/project-04-incremental-indexing/index.md) - -# Lektion 08. Feature-Listen nutzen, um Agentenarbeit zu begrenzen - -Du bittest einen Agenten, eine E-Commerce-Seite zu bauen. Nachdem er fertig ist, sagt er "done". Du schaust in den Code: Die Benutzeranmeldung funktioniert, aber der Checkout-Button im Warenkorb macht nichts, und der Zahlungsfluss ist gar nicht verbunden. Das Problem: Du hast nie gesagt, was "done" bedeutet. Also verwendet der Agent seinen eigenen Standard: "Ich habe viel Code geschrieben, und es sieht ziemlich vollständig aus." - -In den Augen vieler Menschen sind Feature-Listen nur Notizzettel: Dinge aufschreiben, damit man sie nicht vergisst, und dann beiseitelegen. In der Harness-Welt aber ist eine Feature-Liste kein Memo für Menschen, sondern das Rückgrat des gesamten Harness. Der Scheduler nutzt sie, um Aufgaben auszuwählen; der Verifier nutzt sie, um Abschluss zu beurteilen; der Handoff Reporter nutzt sie, um Zusammenfassungen zu erzeugen. Bricht das Rückgrat, ist der ganze Körper gelähmt. - -Anthropic und OpenAI betonen beide: **Artefakte müssen externalisiert werden.** Feature-Zustand muss in einer maschinenlesbaren Datei im Repo liegen, nicht in unstrukturiertem Konversationstext. - -## Agenten wissen nicht, was "done" bedeutet - -Weder Claude Code noch Codex wissen automatisch, was du mit "done" meinst. Du sagst "füge eine Warenkorb-Funktion hinzu", und das Modell interpretiert das vielleicht als "schreibe eine Cart-Komponente und eine addToCart-Methode". Du meinst aber: "Der Nutzer kann Produkte ansehen, in den Warenkorb legen und den Checkout end-to-end abschließen." Ohne Feature-Liste bleibt diese Verständnislücke bestehen. Der Agent nutzt seinen impliziten Standard, meistens "der Code hat keine offensichtlichen Syntaxfehler". Was du brauchst, ist end-to-end-Verifikation des Verhaltens. Wie wenn du einen Freund bittest, Obst zu kaufen: Du sagst "bring etwas Obst mit", und er kommt mit Zitronen zurück. Sein Obst und dein Obst sind nicht dasselbe. - -Sieh dir diese typische Fortschrittsnotiz an: - -``` -Did user auth, shopping cart mostly done, still need payments -``` - -Kann eine neue Agentensitzung daraus diese Fragen beantworten? Was bedeutet "mostly done"? Welche Tests hat der Warenkorb bestanden? Was blockiert Payments? Die Antwort auf alles lautet: "Niemand weiß es." Wie wenn du deinem Arzt sagst: "Mein Bauch tut weh, war in letzter Zeit schon okay" - welches Medikament soll er verschreiben? - -Das Ergebnis: Die neue Sitzung verbringt 20 Minuten damit, den Projektzustand zu erschließen, und implementiert möglicherweise bereits fertige Features erneut. Anthropics Engineering-Daten zeigen, dass gute Fortschrittsaufzeichnungen die Diagnosezeit beim Sitzungsstart um 60-80% reduzieren. - -## Feature-Zustandsmaschine - -```mermaid -flowchart LR - Feature["Eine Feature-Zeile"] --> Behavior["Verhalten
z. B.: POST /cart/items returns 201"] - Feature --> Check["Verifikationsbefehl
der exakte Check"] - Feature --> State["Zustand
not_started / active / blocked / passing"] - - Behavior --> Complete["Erst mit allen drei Feldern
ist die Feature-Zeile nutzbar"] - Check --> Complete - State --> Complete -``` - -```mermaid -flowchart LR - List["feature_list.json / features.md"] --> Scheduler["Nächstes not_started-Element wählen"] - Scheduler --> Agent["Agent arbeitet an genau diesem Element"] - Agent --> Verifier["Verifikationsbefehl dieses Elements ausführen"] - Verifier -->|pass| Passing["Als passing markieren
und Evidenz schreiben"] - Verifier -->|fail| Active["Active lassen"] - Verifier -->|dependency issue| Blocked["Als blocked markieren"] - Passing --> Handoff["Handoff-Notiz und aktuellen Fortschritt aktualisieren"] - Active --> Agent -``` - -## Zentrale Konzepte - -- **Feature-Listen sind Harness-Primitives**: Keine "optionalen Planungstools", sondern grundlegende Datenstrukturen, von denen alle anderen Harness-Komponenten abhängen. Wie Datenbanktabellenstrukturen: Man kann nicht sagen "lassen wir Primärschlüssel weg". -- **Dreifachstruktur**: Jedes Feature-Element ist ein Tripel `(Verhaltensbeschreibung, Verifikationsbefehl, aktueller Zustand)`. Fehlt ein Element, ist das Element unvollständig. -- **Zustandsmaschinenmodell**: Jedes Feature-Element hat vier Zustände: `not_started`, `active`, `blocked`, `passing`. Zustandsübergänge werden vom Harness kontrolliert, nicht frei vom Agenten geändert. -- **Pass-state gating**: Der einzige Weg von `active` zu `passing` ist ein erfolgreich ausgeführter Verifikationsbefehl. Das ist irreversibel: Einmal `passing`, kein Zurück. Wie bei einer bestandenen Prüfung: Bestanden ist bestanden; die Note wird nicht rückwirkend geändert. -- **Single source of truth**: Alle Informationen darüber, "was zu tun ist", müssen aus einer einzigen Feature-Liste stammen. Keine Widersprüche zwischen Feature-Liste und Gesprächsverlauf. -- **Back-pressure**: Die Anzahl der noch nicht bestandenen Features ist der Druck, den der Harness auf den Agenten ausübt. Druck null = Projekt abgeschlossen. - -## Warum Feature-Listen "Primitives" sein müssen - -Dokumente sind zum Lesen durch Menschen da; Primitives sind zur Ausführung durch Systeme da. Dokumente können ignoriert werden; Primitives kann man nicht umgehen. - -Denke an Datenbank-Trigger-Constraints im Vergleich zu Checks auf Anwendungsebene: Erstere werden von der Datenbank-Engine erzwungen, kein SQL kann sie überspringen; Letztere hängen von der Korrektheit des Anwendungscodes ab und können versehentlich umgangen werden. Als Harness-Primitive dient die Feature-Liste vier Komponenten: - -1. **Scheduler**: Liest Zustände und wählt das nächste `not_started`-Feature. Wie ein Produktionsplanungssystem in einer Fabrik. -2. **Verifier**: Führt Verifikationsbefehle aus und entscheidet, ob Zustandsübergänge erlaubt sind. Wie Qualitätskontrolle. -3. **Handoff reporter**: Erzeugt automatisch Übergabezusammenfassungen aus der Feature-Liste. Wie ein automatischer Schichtwechselbericht. -4. **Progress tracker**: Zählt Zustandsverteilungen und liefert Gesundheitsmetriken des Projekts. Wie ein Dashboard. - -## Wie man es richtig macht - -### 1. Ein minimales Feature-Listen-Format definieren - -Du brauchst kein komplexes System. Eine strukturierte Markdown- oder JSON-Datei reicht. Entscheidend ist, dass jeder Eintrag das Tripel enthält: - -```json -{ - "id": "F03", - "behavior": "POST /cart/items with {product_id, quantity} returns 201", - "verification": "curl -X POST http://localhost:3000/api/cart/items -H 'Content-Type: application/json' -d '{\"product_id\":1,\"quantity\":2}' | jq .status == 201", - "state": "passing", - "evidence": "commit abc123, test output log" -} -``` - -### 2. Den Harness Zustandsübergänge kontrollieren lassen - -Der Agent kann den Zustand eines Features nicht direkt auf `passing` setzen. Er kann nur eine Verifikationsanfrage stellen. Der Harness führt den Verifikationsbefehl aus und entscheidet, ob der Übergang erlaubt ist. Das ist pass-state gating. - -### 3. Die Regeln in CLAUDE.md schreiben - -``` -## Feature List Rules -- Feature list file: /docs/features.md -- Only one feature active at a time -- Verification command must pass before marking as passing -- Don't modify feature list states yourself — the verification script updates them automatically -``` - -### 4. Granularität kalibrieren - -Jedes Feature-Element sollte auf "in einer Sitzung abschließbar" zugeschnitten sein. Zu breit, und es wird nicht fertig; zu eng, und der Verwaltungsaufwand steigt. "Nutzer kann Artikel in den Warenkorb legen" ist gute Granularität. "Den Warenkorb implementieren" ist zu breit. "Das name-Feld im Cart-Modell erstellen" ist zu eng. Wie ein Steak schneiden: nicht das ganze Stück und nicht Hackfleisch. - -## Praxisfall - -Eine E-Commerce-Plattform mit 10 Features. Zwei Tracking-Ansätze wurden verglichen: - -**Memo-Modus**: Der Agent nutzt unstrukturierte Notizen. Nach 3 Sitzungen lauten sie: "user auth und product list erledigt, shopping cart mostly done, aber Bugs, payments nicht begonnen." Eine neue Sitzung braucht 20 Minuten, um den Zustand zu erschließen, und implementiert am Ende abgeschlossene Features erneut. Wie eine Einkaufsliste mit "Milch, Brot und dieses Ding" - im Laden weißt du immer noch nicht, was du kaufen sollst. - -**Rückgrat-Modus**: Jedes Feature hat einen klaren Zustand und einen Verifikationsbefehl. Eine neue Sitzung liest die Feature-Liste und weiß in 3 Minuten: F01-F05 sind `passing`, F06 ist `active`, F07-F10 sind `not_started`. Sie macht direkt bei F06 weiter, ohne Nacharbeit. - -Quantifiziertes Ergebnis: Projekte mit strukturierten Feature-Listen zeigen eine 45% höhere Feature-Abschlussrate als Freiform-Tracking, bei null doppelten Implementierungen. - -## Wichtigste Erkenntnisse - -- **Feature-Listen sind das Rückgrat des Harness**, keine Memos für Menschen. Scheduler, Verifier und Handoff Reporter hängen alle davon ab. -- **Jedes Feature-Element braucht das Tripel**: Verhaltensbeschreibung + Verifikationsbefehl + aktueller Zustand. Fehlt ein Element, ist es unvollständig - wie ein dreibeiniger Hocker, dem ein Bein fehlt. -- **Zustandsübergänge werden vom Harness kontrolliert**; der Agent kann Zustände nicht selbst ändern. Erfolgreiche Verifikation ist der einzige Aufstiegspfad. -- **Die Feature-Liste ist die single source of truth des Projekts**; alle "was tun"-Informationen stammen aus einer Liste. -- **Kalibriere Granularität auf "in einer Sitzung abschließbar".** - -## Weiterführende Literatur - -- [Building Effective Agents - Anthropic](https://www.anthropic.com/research/building-effective-agents) — Identifiziert die Feature-Liste ausdrücklich als "core data structure" zur Kontrolle des Agenten-Scopes -- [Harness Engineering - OpenAI](https://openai.com/index/harness-engineering/) — Betont das Prinzip der "externalizing artifacts" -- [Design by Contract - Bertrand Meyer](https://www.goodreads.com/book/show/130439.Object_Oriented_Software_Construction) — Contract-Design-Prinzipien, theoretische Grundlage von Feature-Listen -- [How Google Tests Software](https://www.goodreads.com/book/show/13563030-how-google-tests-software) — Testpyramide und Praktiken für verhaltensbasierte Spezifikation - -## Übungen - -1. **Feature-Listen-Design**: Definiere ein minimales JSON-Schema für Feature-Listen. Enthalten: id, Verhaltensbeschreibung, Verifikationsbefehl, aktueller Zustand, Evidenzreferenz. Beschreibe damit ein echtes Projekt mit 5 Features. - -2. **Vergleich der Verifikationsstrenge**: Wähle 3 Features und entwirf sowohl eine "lockere" Verifikation (z. B. "Code hat keine Syntaxfehler") als auch eine "strenge" Verifikation (z. B. "End-to-End-Test besteht"). Vergleiche die False-Positive-Rate. - -3. **Audit des Single-Source-Prinzips**: Prüfe ein bestehendes Agentenprojekt auf Scope-Informationen, die der Feature-Liste widersprechen (implizite Anforderungen in Gesprächen, TODO-Kommentare im Code usw.). Entwirf einen Plan, um alle Informationen in der Feature-Liste zu vereinheitlichen. diff --git a/courses/learn-harness-engineering/docs/de/lectures/lecture-09-why-agents-declare-victory-too-early/code/clean-state-checklist.md b/courses/learn-harness-engineering/docs/de/lectures/lecture-09-why-agents-declare-victory-too-early/code/clean-state-checklist.md deleted file mode 100644 index 99630617..00000000 --- a/courses/learn-harness-engineering/docs/de/lectures/lecture-09-why-agents-declare-victory-too-early/code/clean-state-checklist.md +++ /dev/null @@ -1,6 +0,0 @@ -# Clean-State-Checkliste - -- App startet ohne manuelle Reparatur -- Aktueller Fortschritt ist dokumentiert -- Kein halbfertiger Import- oder Indexierungsschritt bleibt undokumentiert -- Der nächste Agent kann den Standard-Start- und Verifikationspfad sofort ausführen diff --git a/courses/learn-harness-engineering/docs/de/lectures/lecture-09-why-agents-declare-victory-too-early/code/index.md b/courses/learn-harness-engineering/docs/de/lectures/lecture-09-why-agents-declare-victory-too-early/code/index.md deleted file mode 100644 index a9c30d5a..00000000 --- a/courses/learn-harness-engineering/docs/de/lectures/lecture-09-why-agents-declare-victory-too-early/code/index.md +++ /dev/null @@ -1,8 +0,0 @@ -# Code für Vorlesung 09 - -Verwenden Sie diesen Ordner für Beispiele zu: - -- Logs als Feedback -- Laufzeitzustand-Sichtbarkeit -- Clean-State-Prüfungen -- Wiederherstellungsbeispielen diff --git a/courses/learn-harness-engineering/docs/de/lectures/lecture-09-why-agents-declare-victory-too-early/code/victory-detector.ts b/courses/learn-harness-engineering/docs/de/lectures/lecture-09-why-agents-declare-victory-too-early/code/victory-detector.ts deleted file mode 100644 index fad1a3ba..00000000 --- a/courses/learn-harness-engineering/docs/de/lectures/lecture-09-why-agents-declare-victory-too-early/code/victory-detector.ts +++ /dev/null @@ -1,241 +0,0 @@ -/** - * victory-detector.ts - * - * Simulates an agent that claims task completion, then checks the claimed - * state against actual verification. Outputs: claimed vs actual, highlighting - * gaps between what the agent said and what's really true. - * - * Run: npx tsx docs/lectures/lecture-09-why-agents-declare-victory-too-early/code/victory-detector.ts - */ - -// --------------------------------------------------------------------------- -// Types -// --------------------------------------------------------------------------- - -interface Task { - name: string; - claimedComplete: boolean; - checks: VerificationCheck[]; -} - -interface VerificationCheck { - description: string; - claimed: boolean; // What the agent says - actual: boolean; // What is actually true - severity: "critical" | "warning"; -} - -// --------------------------------------------------------------------------- -// Simulated tasks with claimed vs actual states -// --------------------------------------------------------------------------- - -const tasks: Task[] = [ - { - name: "Add search endpoint", - claimedComplete: true, - checks: [ - { - description: "Route handler exists", - claimed: true, - actual: true, - severity: "critical", - }, - { - description: "Authentication middleware applied", - claimed: true, - actual: false, // Agent forgot auth - severity: "critical", - }, - { - description: "Input validation added", - claimed: true, - actual: true, - severity: "critical", - }, - { - description: "Unit tests written", - claimed: true, - actual: false, // Agent skipped tests - severity: "critical", - }, - { - description: "Integration tests pass", - claimed: true, - actual: false, // No tests to run - severity: "critical", - }, - { - description: "Documentation updated", - claimed: true, - actual: false, // Agent skipped docs - severity: "warning", - }, - ], - }, - { - name: "Fix pagination bug", - claimedComplete: true, - checks: [ - { - description: "Bug reproduction case confirmed", - claimed: true, - actual: true, - severity: "critical", - }, - { - description: "Root cause identified", - claimed: true, - actual: true, - severity: "critical", - }, - { - description: "Fix applied", - claimed: true, - actual: true, - severity: "critical", - }, - { - description: "Edge cases tested", - claimed: true, - actual: false, // Only happy path tested - severity: "warning", - }, - { - description: "Regression tests added", - claimed: true, - actual: false, - severity: "critical", - }, - ], - }, - { - name: "Refactor auth module", - claimedComplete: true, - checks: [ - { - description: "Old code removed", - claimed: true, - actual: false, // Old code still present - severity: "warning", - }, - { - description: "All existing tests still pass", - claimed: true, - actual: false, // Two tests broke - severity: "critical", - }, - { - description: "New auth flow works", - claimed: true, - actual: true, - severity: "critical", - }, - { - description: "Migration guide written", - claimed: true, - actual: false, - severity: "warning", - }, - ], - }, -]; - -// --------------------------------------------------------------------------- -// Verification -// --------------------------------------------------------------------------- - -interface TaskVerification { - taskName: string; - claimedComplete: boolean; - actuallyComplete: boolean; - totalChecks: number; - claimedPassing: number; - actualPassing: number; - gaps: { description: string; severity: string }[]; -} - -function verifyTask(task: Task): TaskVerification { - const claimedPassing = task.checks.filter((c) => c.claimed).length; - const actualPassing = task.checks.filter((c) => c.actual).length; - const gaps = task.checks.filter((c) => c.claimed && !c.actual); - - return { - taskName: task.name, - claimedComplete: task.claimedComplete, - actuallyComplete: gaps.length === 0, - totalChecks: task.checks.length, - claimedPassing, - actualPassing, - gaps: gaps.map((g) => ({ description: g.description, severity: g.severity })), - }; -} - -// --------------------------------------------------------------------------- -// Reporting -// --------------------------------------------------------------------------- - -function pad(s: string, len: number): string { - return s.length >= len ? s : s + " ".repeat(len - s.length); -} - -function run(): void { - console.log("\n" + "=".repeat(90)); - console.log(" VICTORY DETECTOR -- Claimed vs Actual Verification"); - console.log("=".repeat(90)); - - const verifications = tasks.map(verifyTask); - - for (const v of verifications) { - console.log("\n Task: " + v.taskName); - console.log(" " + "-".repeat(70)); - console.log(` Agent claimed: ${v.claimedComplete ? "COMPLETE" : "NOT COMPLETE"}`); - console.log(` Actually is: ${v.actuallyComplete ? "COMPLETE" : "INCOMPLETE"}`); - - if (!v.actuallyComplete) { - console.log(` MISMATCH: Agent declared victory but ${v.gaps.length} check(s) failed!`); - } - - // Detailed checks for this task - const task = tasks.find((t) => t.name === v.taskName)!; - console.log("\n Check detail:"); - const header = ` | ${pad("Check", 40)}| ${pad("Claimed", 9)}| ${pad("Actual", 9)}| ${pad("Gap?", 6)}|`; - const sep = ` |${"-".repeat(42)}|${"-".repeat(11)}|${"-".repeat(11)}|${"-".repeat(8)}|`; - console.log(header); - console.log(sep); - - for (const check of task.checks) { - const claimedLabel = check.claimed ? "PASS" : "FAIL"; - const actualLabel = check.actual ? "PASS" : "FAIL"; - const gapLabel = check.claimed && !check.actual ? "GAP" : ""; - const marker = gapLabel ? ">>" : " "; - console.log(`${marker}| ${pad(check.description, 40)}| ${pad(claimedLabel, 9)}| ${pad(actualLabel, 9)}| ${pad(gapLabel, 6)}|`); - } - } - - // Overall summary - console.log("\n" + "=".repeat(90)); - console.log(" OVERALL SUMMARY"); - console.log("=".repeat(90) + "\n"); - - const sHeader = `| ${pad("Task", 28)}| ${pad("Claimed", 10)}| ${pad("Actual", 10)}| ${pad("Gaps", 6)}| ${pad("Accuracy", 10)}|`; - const sSep = `|${"-".repeat(30)}|${"-".repeat(12)}|${"-".repeat(12)}|${"-".repeat(8)}|${"-".repeat(12)}|`; - console.log(sHeader); - console.log(sSep); - - for (const v of verifications) { - const accuracy = Math.round((v.actualPassing / v.totalChecks) * 100); - const cLabel = v.claimedComplete ? "Done" : "Pending"; - const aLabel = v.actuallyComplete ? "Done" : "Incomplete"; - console.log(`| ${pad(v.taskName, 28)}| ${pad(cLabel, 10)}| ${pad(aLabel, 10)}| ${pad(String(v.gaps.length), 6)}| ${pad(accuracy + "%", 10)}|`); - } - - const totalGaps = verifications.reduce((s, v) => s + v.gaps.length, 0); - const falseVictories = verifications.filter((v) => v.claimedComplete && !v.actuallyComplete).length; - - console.log("\n False victories: " + falseVictories + " of " + verifications.length + " tasks"); - console.log(" Total undetected gaps: " + totalGaps); - console.log("\n The agent declared 'done' on every task, but verification reveals " + totalGaps + " unmet criteria."); - console.log(" Without explicit verification, premature victory declarations go unchecked.\n"); -} - -run(); diff --git a/courses/learn-harness-engineering/docs/de/lectures/lecture-09-why-agents-declare-victory-too-early/index.md b/courses/learn-harness-engineering/docs/de/lectures/lecture-09-why-agents-declare-victory-too-early/index.md deleted file mode 100644 index 6cbcdb17..00000000 --- a/courses/learn-harness-engineering/docs/de/lectures/lecture-09-why-agents-declare-victory-too-early/index.md +++ /dev/null @@ -1,145 +0,0 @@ -[中文版本 →](../../../zh/lectures/lecture-09-why-agents-declare-victory-too-early/) - -> Codebeispiele für diese Lektion: [code/](https://github.com/walkinglabs/learn-harness-engineering/blob/main/docs/de/lectures/lecture-09-why-agents-declare-victory-too-early/code/) -> Praxisprojekt: [Project 05. Lassen Sie den Agenten seine eigene Arbeit überprüfen](./../../projects/project-05-grounded-qa-verification/index.md) - -# Lektion 09. Verhindern, dass Agenten zu früh Erfolg melden - -Sie bitten einen Agenten, eine „Passwort zurücksetzen"-Funktion zu implementieren. Er ändert das Datenbankschema, schreibt den API-Endpunkt, fügt die E-Mail-Vorlage hinzu, führt Unit-Tests aus (alle bestanden) und teilt Ihnen zuversichtlich mit: „Es ist fertig." Wenn Sie es tatsächlich ausprobieren — der Link zum Zurücksetzen des Passworts kann nicht gesendet werden (E-Mail-Service-Konfiguration fehlt), die Datenbankmigration scheitert zur Hälfte (Schemainkonsistenz), und der End-to-End-Ablauf wurde kein einziges Mal ausgeführt. - -Dieses Gefühl sollte nicht un vertraut sein — es ist, als würde man das gesamte Klausurheft ausfüllen, zuversichtlich als Erster abgeben und dann durchfallen, wenn die Noten kommen. Nur weil das Heft voll ist, heißt das nicht, dass die Antworten richtig sind. - -Dies ist kein Einzelfall. Das klassische ICML-Papier von Guo et al. (2017) hat bewiesen: **Moderne neuronale Netze sind systematisch über confident** — die von Modellen berichtete Zuversicht ist deutlich höher als ihre tatsächliche Genauigkeit. Dasselbe gilt für KI-Coding-Agenten: Sie „fühlen", dass sie fertig sind, aber in Wirklichkeit sind sie weit davon entfernt. Ihr Harness muss die „Gefühle" des Agenten durch externalisierte, ausführungsbasierte Verifikation ersetzen. - -## Die rutschige Abwärtsspirale - -Voreilige Fertigstellungsmeldungen folgen fast immer demselben Muster: Der Code sieht in Ordnung aus — Syntax ist korrekt, Logik erscheint plausibel, und die statische Analyse zeigt keine offensichtlichen Fehler. Aber der Harness erzwingt keine umfassende Ausführungs verifikation, also überspringt der Agent das tatsächliche Ausführen oder führt nur teilweise Tests durch. Er führt Unit-Tests durch, überspringt aber Integrationstests; er führt Tests durch, prüft aber keine Abdeckung. Letztendlich wird „der Code sieht gut aus" als Beweis dafür genommen, dass „die Funktion vollständig ist". Und das Klausurheft wird abgegeben. - -Bei jedem Schritt geht Information verloren. Von der Aufgaben spezifikation über die Code-Implementierung bis zum Laufzeitverhalten — jede Transformation kann Verzerrungen einführen, und jede übersprungene Verifikation verschärft die Informationsasymmetrie. - -## Drei-Schichten-Abschlussprüfung - -```mermaid -flowchart LR - Claim["Agent sagt: fertig"] --> L1["Zuerst ausführen
lint / typecheck"] - L1 --> L2["Dann ausführen
Tests und Startup-Checks"] - L2 --> L3["Schließlich ausführen
kompletten User-Flow"] - L3 --> Done["Alle drei bestanden = fertig"] -``` - -```mermaid -flowchart LR - A["Code ist geschrieben
Unit-Tests sind grün"] --> B["Aber die App startete nicht wirklich
kompletter Flow nie ausgeführt"] - B --> C["Config-, DB-, externe Service-Probleme
bleiben alle verborgen"] - C --> D["Also meldet der Agent zu früh Erfolg"] -``` - -## Zentrale Konzepte - -- **Voreilige Fertigstellungsmeldung**: Der Agent behauptet, die Aufgabe sei abgeschlossen, aber es existieren noch immer nicht erfüllte Korrektheitsspezifikationen. Das Kernproblem: Der Agent urteilt auf Basis lokaler Zuversicht auf Code-Ebene, während systemweite Korrektheit globale Verifikation erfordert. -- **Confidence-Calibration-Bias**: Die systematische Lücke zwischen der selbst berichteten Zuversicht des Agenten bezüglich der Fertigstellung und der tatsächlichen Qualitäts der Fertigstellung. Bei komplexen Multi-Datei-Aufgaben ist dieser Bias signifikant positiv — der Agent ist immer zuversichtlicher, als er tatsächlich leistet. Wie ein Student, der seine Klausurnote nach der Prüfung immer überschätzt. -- **Terminierungskriterien**: Eine klare, ausführbare Menge von Beurteilungsbedingungen, die im Harness definiert sind. Der Agent muss alle Bedingungen erfüllen, bevor er die Fertigstellung meldet. „Fertig" wandelt sich von einem subjektiven Urteil zu einer objektiven Feststellung. -- **Verifikation-Validierung-Doppeltor**: Die erste Verifikationsschicht prüft „hat der Code das spezifizierte Verhalten korrekt implementiert"; die zweite Validierungsschicht prüft „entspricht das Systemverhalten den End-to-End-Anforderungen". Beide müssen bestanden werden, um als vollständig zu gelten. -- **Laufzeit-Feedback-Signale**: Logs, Prozesszustände und Health-Checks aus der Programmausführung. Dies ist die objektive Grundlage für den Harness, um die Qualität der Fertigstellung zu beurteilen. -- **Abschlussprioritäts-Einschränkung**: Zuerst funktionale Korrektheit verifizieren, dann Performance behandeln und schließlich Stil ansprechen. Refactoring ist verboten, bis die Kernfunktionalität verifiziert ist. - -## Bestandene Unit-Tests ≠ Aufgabe erledigt - -Das ist die häufigste Falle und die gefährlichste. Der Agent hat den Code geschrieben, die Unit-Tests ausgeführt, alles grün bekommen und „fertig" gesagt. Aber die Entwurfsphilosophie von Unit-Tests — Isolierung der getesteten Einheit und Mocking von Abhängigkeiten — ist genau das, was sie unfähig macht, komponentenübergreifende Probleme zu erkennen: - -**Schnittstellen-Inkonsistenz**: Der vom Render-Prozess an das Preload-Skript übergebene Dateipfad ist ein relativer Pfad, aber das Preload-Skript erwartet einen absoluten Pfad. Ihre jeweiligen Unit-Tests haben beide Mocks verwendet und bestanden. Das Problem wird erst beim End-to-End-Testing entdeckt. Wie Musiker in einer Band, die jeweils für sich perfekt proben, nur um beim Zusammenspiel festzustellen, dass sie in verschiedenen Tonarten spielen. - -**Zustandspropagationsfehler**: Eine Datenbankmigration ändert das Tabellenschema, aber die ORM-Zwischenspeicher schicht enthält noch Cache-Einträge für das alte Schema. Unit-Tests stellen jedes Mal eine frische Mock-Umgebung bereit, was diese schichtenübergreifende Zustandsinkonsistenz nicht aufdeckt. - -**Umgebungsabhängigkeit**: Der Code verhält sich in der Testumgebung korrekt (wo alles gemockt ist), scheitert aber in der realen Umgebung aufgrund von Konfigurationsunterschieden, Netzwerklatenz oder Dienstverfügbarkeit. Wie man im Probenraum perfekt singt, aber auf der Bühne mit Audioequipment-Problemen kämpft. - -### „Beim Refactoring gleich mit erledigen" ist Gift für die Abschlussbeurteilung - -Claude Code zeigt ein häufiges Verhaltensmuster: Es beginnt mit dem Refactoring von Code, der Optimierung der Performance und der Verbesserung des Stils, bevor die Kernfunktionalität die Verifikation bestanden hat. Knuths Zitat, „Premature optimization is the root of all evil", bekommt im Agenten-Szenario eine neue Bedeutung — Refactoring verschiebt die Grenze zwischen verifiziertem und nicht verifiziertem Code und kann zuvor implizit korrekte Codepfade zerstören. Es ist, als würde man seine Multiple-Choice-Antworten für eine bessere Formatierung neu abschreiben, bevor man die Matheaufgabe beendet hat — nicht nur verschwendet es Zeit, man könnte sie auch noch falsch abschreiben. - -### Systematischer Bias bei der Selbstevaluation - -Anthropic entdeckte 2026 in seiner Forschung ein tieferes Fehlermuster: **Wenn ein Agent gebeten wird, seine eigene Arbeit zu evaluieren, liefert er systematisch zu positive Bewertungen — selbst wenn ein menschlicher Beobachter die Qualität als eindeutig mangelhaft betrachten würde.** Das ist, als würde man einen Studenten bitten, seine eigene Klausur zu benoten — er wird bei seinen eigenen Antworten immer besonders nachsichtig sein. - -Dieses Problem ist besonders gravierend bei subjektiven Aufgaben (wie Design-Ästhetik) — ob ein „Layout exquisit" ist, ist eine Frage der Beurteilung, und der Agent neigt zuverlässig zu positiven Bewertungen. Selbst bei Aufgaben mit überprüfbaren Ergebnissen kann die Leistung des Agenten durch schlechtes Urteilsvermögen beeinträchtigt werden. - -Die Lösung besteht nicht darin, den Agenten „objektiver" zu machen — dasselbe Modell, das generiert und evaluiert, begünstigt von Natur aus Nachsicht gegenüber sich selbst. **Die Lösung ist die Trennung von „Arbeiter" und „Prüfer".** Wie ein Student seine eigene Klausur nicht benoten sollte — man braucht einen unabhängigen Korrektor. - -Ein unabhängiger evaluierender Agent, der speziell darauf abgestimmt ist, „pingelig" zu sein, ist weitaus effektiver, als den generierenden Agenten sich selbst evaluieren zu lassen. Experimentelle Daten von Anthropic: - -| Architektur | Laufzeit | Kosten | Kernfunktionen funktionsfähig? | -|--------------|---------|------|------------------------| -| Einzelner Agent (ohne Harness) | 20 Min. | $9 | Nein (Spiel-Entitäten reagieren nicht auf Eingabe) | -| Drei Agenten (Planer + Generator + Evaluator) | 6 Stunden | $200 | Ja (Spiel ist vollständig spielbar) | - -Dies ist exakt dasselbe Modell (Opus 4.5) mit exakt demselben Prompt („build a 2D retro game editor"). Der einzige Unterschied ist der Harness — vom „nackten Ausführen" zu „Planer entfaltet Anforderungen → Generator implementiert Feature für Feature → Evaluator führt tatsächliches Klick-Testing mit Playwright durch". - -> Quelle: [Anthropic: Harness design for long-running application development](https://www.anthropic.com/engineering/harness-design-long-running-apps) - -## Wie man voreilige Abgaben verhindert - -### 1. Abschlussbeurteilung externalisieren - -Die Abschlussbeurteilung sollte nicht vom Agenten selbst vorgenommen werden. Der Harness muss unabhängig eine Terminierungsvalidierung durchführen, wobei Laufzeit-Signale als Eingabe dienen, nicht die Zuversicht des Agenten. Schreiben Sie dies klar in `CLAUDE.md`: - -``` -## Definition of Done -- Feature complete = end-to-end verification passed, not "code is written" -- Required verification levels: - 1. Unit tests pass - 2. Integration tests pass - 3. End-to-end flow verification passes -- Do not proceed to level 2 if level 1 fails -- Do not proceed to level 3 if level 2 fails -``` - -### 2. Eine Drei-Schichten-Terminierungsvalidierung aufbauen - -- **Schicht 1: Syntax und statische Analyse**. Geringste Kosten, wenigste Informationen, aber muss bestanden werden. Das ist die Mindestprüfung — man muss die Wörter erst einmal richtig schreiben, bevor wir uns etwas anderes ansehen. -- **Schicht 2: Laufzeitverhaltens-Verifikation**. Testausführung, App-Startup-Checks, Validierung kritischer Pfade. Dies ist der zentrale Beweis für die Fertigstellung. Es reicht nicht, es nur zu schreiben; es muss laufen. -- **Schicht 3: Systemebene-Bestätigung**. End-to-End-Testing, Integrationsvalidierung, Nutzer szenario-Simulation. Die letzte Verteidigungslinie gegen voreilige Meldungen. Es reicht nicht zu laufen; es muss korrekt laufen. - -### 3. Gute „Rotstift-Korrekturen" für Agenten entwerfen - -OpenAI hat während seiner Codex-Praxis ein besonders wirksames Muster eingeführt: **Fehlermeldungen für Agenten sollten Korrekturanweisungen enthalten**. Nicht nur ein großes rotes Kreuz wie ein fauler Korrektor ziehen; wie ein guter Lehrer an den Rand schreiben „So sollten Sie das ändern". Nicht `"Test failed"` verwenden, sondern `"Test failed: POST /api/reset-password returned 500. Check that the email service config exists in environment variables. The template file should be at templates/reset-email.html."` Dieses spezifische, umsetzbare Feedback ermöglicht dem Agenten, sich ohne menschliches Eingreifen selbst zu korrigieren. - -### 4. Laufzeit-Signale erfassen - -Wirksame Laufzeit-Signale umfassen: -- Ist die Anwendung erfolgreich gestartet und hat einen bereiten Zustand erreicht? -- Haben die Pfade der kritischen Funktionen zur Laufzeit erfolgreich ausgeführt? -- Waren Datenbank-Schreibvorgänge, Dateioperationen und andere Seiteneffekte korrekt? -- Wurden temporäre Ressourcen bereinigt? - -## Fallbeispiel aus der Praxis - -**Aufgabe**: Passwort-Zurücksetzen-Funktionalität für Benutzer implementieren. Umfasst Datenbankoperationen, E-Mail-Versand und API-Endpunkt-Änderungen. - -**Voreiliger Abgabeweg**: Agent ändert Datenbankschema, schreibt API-Endpunkt, fügt E-Mail-Vorlage hinzu, führt Unit-Tests durch (bestanden) und meldet Fertigstellung. Das Klausurheft ist komplett ausgefüllt. - -**Tatsächliche Punkteabzüge**: (1) End-to-End-Ablauf ungetestet — das tatsächliche Senden und Verifizieren des Zurücksetzungslinks wurde nie bestätigt. (2) Datenbankmigration scheiterte nach teilweiser Ausführung, was eine Schemainkonsistenz verursachte. (3) E-Mail-Service-Konfiguration fehlte in der Zielumgebung. - -**Harness-Intervention**: Terminierungsvalidierung erzwungen — (1) Vollständige App starten, um die Erreichbarkeit des Reset-Endpunkts zu verifizieren; (2) Den vollständigen Reset-Flow ausführen; (3) Datenbankzustandskonsistenz verifizieren. Alle Defekte wurden innerhalb der Session gefunden, was 5-10x der Kosten für spätere Reparaturen einsparte. Der unabhängige Korrektor fand die echten Probleme. - -## Wichtigste Erkenntnisse - -- **Agenten sind systematisch über confident** — der Confidence-Calibration-Bias ist eine objektive Realität. Das Klausurheft ausgefüllt zu haben bedeutet nicht, dass man alles richtig gemacht hat. -- **Die Abschlussbeurteilung muss externalisiert werden** — der Harness verifiziert unabhängig; vertrauen Sie nicht den „Gefühlen" des Agenten. Studenten können ihre eigenen Klausuren nicht benoten. -- **Alle drei Validierungsschichten sind unerlässlich** — Syntax bestanden, Verhalten bestanden, System bestanden, Schicht für Schicht fortschreitend. -- **Fehlermeldungen sollten wie die Rotstift-Korrektur eines guten Lehrers sein** — spezifische Korrekturschritte enthalten, damit der Agent sich selbst korrigieren kann. -- **Kein Refactoring, bis die Kernfunktionalität verifiziert ist** — die Abschlussprioritäts-Einschränkung ist der Schlüssel zur Verhinderung vorzeitiger Optimierung. - -## Weiterführende Literatur - -- [On Calibration of Modern Neural Networks - Guo et al.](https://arxiv.org/abs/1706.04599) — Beweist, dass moderne Deep Networks systematisch über confident sind -- [Building Effective Agents - Anthropic](https://www.anthropic.com/research/building-effective-agents) — Die kritische Rolle von Laufzeit-Evidenz bei der Abschlussbeurteilung -- [Harness Engineering - OpenAI](https://openai.com/index/harness-engineering/) — Voreilige Fertigstellungsmeldung ist einer der Haupt-Fehlmodi von Agenten -- [The Art of Software Testing - Myers](https://www.goodreads.com/book/show/137543.The_Art_of_Software_Testing) — Klassisches Referenzwerk über Testmethoden-Hierarchien und deren Wirksamkeit - -## Übungen - -1. **Terminierungsvalidierungs-Funktion entwerfen**: Entwerfen Sie eine vollständige Terminierungsvalidierung für eine Aufgabe, die eine Datenbankmigration und API-Änderung umfasst. Listen Sie die erforderlichen Laufzeit-Signale und die Bestehens-/Durchfall-Kriterien für jedes Signal auf. Führen Sie es mit einer realen Aufgabe durch und dokumentieren Sie, welche verborgenen Probleme es findet. - -2. **Calibration-Bias-Messung**: Wählen Sie 10 verschiedene Arten von Codierungsaufgaben und dokumentieren Sie die selbst berichtete Abschlusszuversicht des Agenten versus die tatsächliche Qualität des Abschlusses. Berechnen Sie den Bias-Wert und analysieren Sie seine Beziehung zur Aufgabenkomplexität. - -3. **Mehrschichtige-Verteidigung-Experiment**: Führen Sie drei Konfigurationen mit demselben Aufgabensatz durch — (a) nur statische Analyse, (b) zusätzlich Unit-Testing, (c) vollständige Drei-Schichten-Validierung. Vergleichen Sie den Anteil voreiliger Fertigstellungsmeldungen und die Anzahl unentdeckter Defekte. diff --git a/courses/learn-harness-engineering/docs/de/lectures/lecture-10-why-end-to-end-testing-changes-results/code/architecture-rules.md b/courses/learn-harness-engineering/docs/de/lectures/lecture-10-why-end-to-end-testing-changes-results/code/architecture-rules.md deleted file mode 100644 index 449ee3ae..00000000 --- a/courses/learn-harness-engineering/docs/de/lectures/lecture-10-why-end-to-end-testing-changes-results/code/architecture-rules.md +++ /dev/null @@ -1,6 +0,0 @@ -# Electron-Architekturregeln - -- Renderer-Code darf nicht direkt auf das Dateisystem zugreifen. -- Preload ist die einzige Brücke zwischen Renderer und Electron Main. -- Retrieval- und Indexierungslogik leben in Service-Modulen, nicht in UI-Komponenten. -- Logging sollte strukturiert sein und von Service-Grenzen ausgehen. diff --git a/courses/learn-harness-engineering/docs/de/lectures/lecture-10-why-end-to-end-testing-changes-results/code/e2e-runner.ts b/courses/learn-harness-engineering/docs/de/lectures/lecture-10-why-end-to-end-testing-changes-results/code/e2e-runner.ts deleted file mode 100644 index 24284165..00000000 --- a/courses/learn-harness-engineering/docs/de/lectures/lecture-10-why-end-to-end-testing-changes-results/code/e2e-runner.ts +++ /dev/null @@ -1,253 +0,0 @@ -/** - * e2e-runner.ts - * - * A minimal E2E test harness. Defines test cases as user action sequences - * (import doc -> index -> ask question -> verify citation). Simulates - * running them and shows the difference between "unit tests pass" and - * "full pipeline works". - * - * Run: npx tsx docs/lectures/lecture-10-why-end-to-end-testing-changes-results/code/e2e-runner.ts - */ - -// --------------------------------------------------------------------------- -// Types -// --------------------------------------------------------------------------- - -interface PipelineStep { - name: string; - unitTestPasses: boolean; - // Simulated actual behavior in the pipeline - actualBehavior: "works" | "fails" | "partial"; - failureReason?: string; -} - -interface TestCase { - name: string; - steps: PipelineStep[]; -} - -interface TestResult { - testCase: string; - unitTestsPassed: number; - unitTestsTotal: number; - unitTestResult: "PASS" | "FAIL"; - e2eResult: "PASS" | "FAIL"; - e2eFailureStep?: string; - e2eFailureReason?: string; -} - -// --------------------------------------------------------------------------- -// Test cases -- realistic scenarios where unit tests pass but E2E fails -// --------------------------------------------------------------------------- - -const testCases: TestCase[] = [ - { - name: "Import document and ask question", - steps: [ - { - name: "Parse uploaded document", - unitTestPasses: true, - actualBehavior: "works", - }, - { - name: "Store document chunks", - unitTestPasses: true, - actualBehavior: "works", - }, - { - name: "Index chunks for retrieval", - unitTestPasses: true, - actualBehavior: "partial", // Indexes but with wrong embedding dimensions - failureReason: "Embedding dimension mismatch between indexer and retriever", - }, - { - name: "Retrieve relevant chunks", - unitTestPasses: true, // Unit test uses mock data with correct dimensions - actualBehavior: "fails", - failureReason: "Empty results due to dimension mismatch from previous step", - }, - { - name: "Generate answer with citations", - unitTestPasses: true, // Unit test provides pre-retrieved chunks - actualBehavior: "fails", - failureReason: "No chunks retrieved, so answer has no citations", - }, - ], - }, - { - name: "Delete document and verify removal", - steps: [ - { - name: "Find document by ID", - unitTestPasses: true, - actualBehavior: "works", - }, - { - name: "Delete document record", - unitTestPasses: true, - actualBehavior: "works", - }, - { - name: "Remove indexed chunks", - unitTestPasses: true, - actualBehavior: "fails", // Orphaned chunks remain in the index - failureReason: "Index cleanup query timed out, chunks remain orphaned", - }, - { - name: "Verify document not in search results", - unitTestPasses: true, // Unit test mocks the search - actualBehavior: "fails", - failureReason: "Orphaned chunks from previous step still appear in results", - }, - ], - }, - { - name: "Multi-user concurrent access", - steps: [ - { - name: "User A imports document", - unitTestPasses: true, - actualBehavior: "works", - }, - { - name: "User B imports document", - unitTestPasses: true, - actualBehavior: "works", - }, - { - name: "User A queries their document", - unitTestPasses: true, - actualBehavior: "partial", // Cross-contamination of results - failureReason: "No user-scoping on retrieval, returns chunks from User B's doc", - }, - { - name: "Verify only User A's results returned", - unitTestPasses: true, - actualBehavior: "fails", - failureReason: "Results include documents from other users", - }, - ], - }, -]; - -// --------------------------------------------------------------------------- -// Run tests -// --------------------------------------------------------------------------- - -function runUnitTests(tc: TestCase): { passed: number; total: number } { - const passed = tc.steps.filter((s) => s.unitTestPasses).length; - return { passed, total: tc.steps.length }; -} - -function runE2ETest(tc: TestCase): { pass: boolean; failureStep?: string; failureReason?: string } { - // Pipeline: if any step actually fails, the whole E2E fails - for (const step of tc.steps) { - if (step.actualBehavior === "fails") { - return { - pass: false, - failureStep: step.name, - failureReason: step.failureReason ?? "Unknown failure", - }; - } - // "partial" means the step technically completes but creates problems downstream - // We let it continue but track it - } - // Check if any step was "partial" (which may cause downstream issues) - const partialSteps = tc.steps.filter((s) => s.actualBehavior === "partial"); - if (partialSteps.length > 0) { - // The partial steps may or may not cause overall failure - // In our simulation, partial steps always lead to failure downstream - // unless there's an explicit "fails" step that already caught it - // This case means all steps were either "works" or "partial" - return { - pass: false, - failureStep: partialSteps[partialSteps.length - 1].name, - failureReason: partialSteps[partialSteps.length - 1].failureReason ?? "Partial completion", - }; - } - - return { pass: true }; -} - -// --------------------------------------------------------------------------- -// Reporting -// --------------------------------------------------------------------------- - -function pad(s: string, len: number): string { - return s.length >= len ? s : s + " ".repeat(len - s.length); -} - -function run(): void { - console.log("\n" + "=".repeat(95)); - console.log(" E2E TEST RUNNER -- Unit Tests vs Full Pipeline"); - console.log("=".repeat(95)); - - const results: TestResult[] = testCases.map((tc) => { - const unit = runUnitTests(tc); - const e2e = runE2ETest(tc); - - return { - testCase: tc.name, - unitTestsPassed: unit.passed, - unitTestsTotal: unit.total, - unitTestResult: unit.passed === unit.total ? "PASS" : "FAIL", - e2eResult: e2e.pass ? "PASS" : "FAIL", - e2eFailureStep: e2e.failureStep, - e2eFailureReason: e2e.failureReason, - }; - }); - - // Per-test-case detail - for (let i = 0; i < testCases.length; i++) { - const tc = testCases[i]; - const r = results[i]; - - console.log("\n Test Case: " + tc.name); - console.log(" " + "-".repeat(70)); - - const stepHeader = ` | ${pad("Step", 40)}| ${pad("Unit Test", 11)}| ${pad("E2E Actual", 12)}|`; - const stepSep = ` |${"-".repeat(42)}|${"-".repeat(13)}|${"-".repeat(14)}|`; - console.log(stepHeader); - console.log(stepSep); - - for (const step of tc.steps) { - const utLabel = step.unitTestPasses ? "PASS" : "FAIL"; - let e2eLabel: string; - if (step.actualBehavior === "works") e2eLabel = "PASS"; - else if (step.actualBehavior === "partial") e2eLabel = "PARTIAL*"; - else e2eLabel = "FAIL"; - - const marker = step.actualBehavior !== "works" ? ">>" : " "; - console.log(`${marker}| ${pad(step.name, 40)}| ${pad(utLabel, 11)}| ${pad(e2eLabel, 12)}|`); - } - - if (r.e2eFailureStep) { - console.log(`\n E2E Failure at: ${r.e2eFailureStep}`); - console.log(` Reason: ${r.e2eFailureReason}`); - } - } - - // Summary comparison - console.log("\n" + "=".repeat(95)); - console.log(" COMPARISON: Unit Tests vs E2E Tests"); - console.log("=".repeat(95) + "\n"); - - const header = `| ${pad("Test Case", 35)}| ${pad("Unit Tests", 15)}| ${pad("E2E Result", 12)}| Discrepancy`; - const sep = `|${"-".repeat(37)}|${"-".repeat(17)}|${"-".repeat(14)}|${"-".repeat(30)}`; - console.log(header); - console.log(sep); - - for (const r of results) { - const utLabel = `${r.unitTestsPassed}/${r.unitTestsTotal} ${r.unitTestResult}`; - const discrepancy = r.unitTestResult === "PASS" && r.e2eResult === "FAIL"; - const discLabel = discrepancy ? "UNIT PASS BUT E2E FAIL" : "Consistent"; - - console.log(`| ${pad(r.testCase, 35)}| ${pad(utLabel, 15)}| ${pad(r.e2eResult, 12)}| ${discLabel}`); - } - - const falseConfidence = results.filter((r) => r.unitTestResult === "PASS" && r.e2eResult === "FAIL").length; - console.log("\n False confidence count: " + falseConfidence + " of " + results.length); - console.log(" Unit tests pass but E2E reveals integration failures that units cannot catch.\n"); -} - -run(); diff --git a/courses/learn-harness-engineering/docs/de/lectures/lecture-10-why-end-to-end-testing-changes-results/code/index.md b/courses/learn-harness-engineering/docs/de/lectures/lecture-10-why-end-to-end-testing-changes-results/code/index.md deleted file mode 100644 index 2b6578e1..00000000 --- a/courses/learn-harness-engineering/docs/de/lectures/lecture-10-why-end-to-end-testing-changes-results/code/index.md +++ /dev/null @@ -1,8 +0,0 @@ -# Code für Vorlesung 10 - -Verwenden Sie diesen Ordner für Beispiele zu: - -- Architekturbeschränkungen -- Strukturellen Tests -- Geschmacks-Invarianten (Taste Invariants) -- Remediation-orientierten Lint-Nachrichten diff --git a/courses/learn-harness-engineering/docs/de/lectures/lecture-10-why-end-to-end-testing-changes-results/code/review-feedback-to-rule.md b/courses/learn-harness-engineering/docs/de/lectures/lecture-10-why-end-to-end-testing-changes-results/code/review-feedback-to-rule.md deleted file mode 100644 index fb502473..00000000 --- a/courses/learn-harness-engineering/docs/de/lectures/lecture-10-why-end-to-end-testing-changes-results/code/review-feedback-to-rule.md +++ /dev/null @@ -1,10 +0,0 @@ -# Beispiel: Review-Feedback in eine Regel umwandeln - -Wiederholter Review-Kommentar: - -> Verwende keine Dateisystem-Dienstprogramme aus dem Renderer. Nutze die Preload-Brücke. - -Beförderte Harness-Regel: - -- Eine Lint- oder Import-Regel hinzufügen, die `fs`-Verwendung in Renderer-Code verhindert -- Remediation-Text hinzufügen, der die Preload-Grenze erklärt diff --git a/courses/learn-harness-engineering/docs/de/lectures/lecture-10-why-end-to-end-testing-changes-results/index.md b/courses/learn-harness-engineering/docs/de/lectures/lecture-10-why-end-to-end-testing-changes-results/index.md deleted file mode 100644 index d8bc53cf..00000000 --- a/courses/learn-harness-engineering/docs/de/lectures/lecture-10-why-end-to-end-testing-changes-results/index.md +++ /dev/null @@ -1,158 +0,0 @@ -[中文版本 →](../../../zh/lectures/lecture-10-why-end-to-end-testing-changes-results/) - -> Codebeispiele für diese Lektion: [code/](https://github.com/walkinglabs/learn-harness-engineering/blob/main/docs/de/lectures/lecture-10-why-end-to-end-testing-changes-results/code/) -> Praxisprojekt: [Project 05. Lassen Sie den Agenten seine eigene Arbeit überprüfen](./../../projects/project-05-grounded-qa-verification/index.md) - -# Lektion 10. Nur End-to-End-Tests sind echte Verifikation - -Sie bitten den Agenten, eine Datei-Export-Funktion zu einer Electron-App hinzuzufügen. Er schreibt die Render-Prozess-Komponente, das Preload-Skript und die Service-Layer-Logik. Die Unit-Tests für jede Komponente bestehen perfekt. Der Agent sagt: „Es ist fertig." Wenn Sie tatsächlich auf den Export-Button klicken — das Dateipfad-Format ist falsch, die Fortschrittsanzeige aktualisiert sich nicht und der Export großer Dateien verursacht einen Memory-Leak. Fünf Komponenten-Grenzfläche-Defekte, und die Unit-Tests haben keinen einzigen davon erkannt. - -Es ist wie bei einer Chorprobe — jede Stimme klingt einzeln perfekt, aber wenn sie zusammen singen, sind die Soprane einen halben Takt schneller als die Bässe, und die Begleitung ist einen Halbton von der Hauptmelodie entfernt. Jede Stimme ist für sich „korrekt", aber das Ganze ist nicht harmonisch. - -Googles Testing-Pyramid sagt uns: Eine große Anzahl von Unit-Tests ist das Fundament, aber wenn man dort stehen bleibt, wird man systematisch Komponenten-Interaktionsprobleme übersehen. Für KI-Coding-Agenten ist dieses Problem noch gravierender — Agenten neigen dazu, nur die schnellsten Tests auszuführen und dann die Fertigstellung zu melden. **Nur End-to-End-Testing kann beweisen, dass systemweite Defekte nicht existieren.** - -## Die blinden Flecken von Unit-Tests - -Die Entwurfsphilosophie von Unit-Tests ist Isolation — Abhängigkeiten werden gemockt und der Fokus liegt ausschließlich auf der zu testenden Einheit. Diese Philosophie macht Unit-Tests schnell und präzise, erzeugt aber auch systematische blinde Flecken. Es ist, als würde bei einer Chorprobe jede Stimme mit Kopfhörern proben — es klingt für sie gut, aber die Probleme tauchen erst auf, wenn sie zusammen kommen: - -**Schnittstellen-Inkonsistenz**: Der vom Render-Prozess an das Preload-Skript übergebene Dateipfad ist ein relativer Pfad, aber das Preload-Skript erwartet einen absoluten Pfad. Ihre jeweiligen Unit-Tests haben beide Mocks verwendet und bestanden. Das Problem wird erst entdeckt, wenn der End-to-End-Flow ausgeführt wird — wie zwei Stimmen, die unabhängig voneinander proben und sich wohl fühlen, nur um beim Ensemble festzustellen, dass die eine im 4/4-Takt und die andere im 3/4-Takt singt. - -**Zustandspropagationsfehler**: Eine Datenbankmigration ändert das Tabellenschema, aber die ORM-Cache-Schicht enthält noch Cache-Einträge für das alte Schema. Unit-Tests stellen jedes Mal eine völlig neue Mock-Umgebung bereit, was diese schichtenübergreifende Zustandsinkonsistenz nicht aufdeckt. Es ist, als würde man den Text eines Liedes ändern, aber jemand singt immer noch die alte Version. - -**Ressourcen-Lebenszyklus-Probleme**: Die Beschaffung und Freigabe von Datei-Handles, Datenbankverbindungen und Netzwerk-Sockets erstreckt sich über mehrere Komponenten. Unit-Tests erstellen und zerstören für jeden Test unabhängige Ressourcen, was Ressourcenkonkurrenz oder Lecks nicht aufdeckt. Es ist, als würde bei der Probe jede Stimme abwechselnd die Mikrofone nutzen, aber wenn alle zusammen auf die Bühne gehen, gibt es nicht genug Mikros. - -**Umgebungsabhängigkeit**: Der Code verhält sich in der Testumgebung korrekt (wo alles gemockt ist), scheitert aber in der realen Umgebung aufgrund von Konfigurationsunterschieden, Netzwerklatenz oder Dienstverfügbarkeit. Wie man im Probenraum perfekt singt, aber auf einem Festival im Freien mit Audio-Rückkopplung und Windeinfluss kämpft. - -## End-to-End-Testing verändert nicht nur Ergebnisse, es verändert das Verhalten - -Das ist etwas, das viele Menschen nicht erkennen: Wenn ein Agent weiß, dass seine Arbeit einem End-to-End-Test unterzogen wird, ändert sich sein Codierungsverhalten. - -1. **Komponenten-Interaktionen berücksichtigen**: Beim Schreiben von Code wird er darüber nachdenken, „wie sich diese Schnittstelle mit Upstream verbindet", anstatt sich nur auf eine einzelne Funktion zu konzentrieren. Wie wenn man weiß, dass man schließlich zusammen singen wird — man achtet beim Üben auf die anderen Stimmen. -2. **Architektur-Grenzen respektieren**: In Systemen mit Architektur-Einschränkungen erzwingt End-to-End-Testing, dass der Agent die Grenzregeln einhält. Wie in einer Partitur, die mit „hier Crescendo" markiert ist — man muss sich daran halten. -3. **Fehlerpfade behandeln**: End-to-End-Tests umfassen in der Regel Fehler-Szenarien und zwingen den Agenten, die Ausnahmebehandlung zu berücksichtigen. Es ist, als würde man bei der Probe simulieren, „was wäre, wenn das Mikro plötzlich ausfällt", damit man weiß, was zu tun ist. - -## Testing-Pyramid und Review-Feedback-Beförderung - -```mermaid -flowchart TB - subgraph Unit["Unit tests only check isolated parts"] - U1["Renderer tests"] - U2["Preload tests"] - U3["Service tests"] - end - - subgraph E2E["E2E runs through the real system"] - R["Click renderer button"] --> P["Preload bridge"] - P --> S["Service layer"] - S --> F["File System / OS"] - F --> Result["Actual exported file"] - end -``` - -```mermaid -flowchart LR - Review["Review feedback:
renderer cannot import fs directly"] --> Rule["Add a direct fs import check"] - Rule --> Message["Tell agent in error message
to move file access to preload"] - Message --> Harness["Add this check to harness"] - Harness --> Stronger["Will fail immediately next time"] -``` - -In den Codex-Engineering-Praktiken betont OpenAI: **Fehlermeldungen für Agenten müssen Korrekturanweisungen enthalten.** Schreiben Sie nicht nur `"Direct filesystem access in renderer"`; schreiben Sie `"Direct filesystem access in renderer. All file operations must go through the preload bridge. Move this call to preload/file-ops.ts and invoke it via window.api."` Dies verwandelt Architekturregeln in eine automatische Korrekturschleife. Wie ein Chorleiter, der nicht nur sagt „das hast du falsch gesungen", sondern stattdessen sagt „du warst hier einen halben Takt zu schnell, hör auf den Rhythmus der Altstimmen und setze bei Takt 32 ein". - -## Zentrale Konzepte - -- **Komponenten-Grenzflächen-Defekte**: Komponente A und B bestehen jeweils ihre Unit-Tests, aber ihre Interaktion erzeugt fehlerhaftes Verhalten. Dies ist die Art von Problem, die End-to-End-Testing am besten erkennt — wie Chorstimmen, die einzeln korrekt sind, aber zusammen nicht harmonisch klingen. -- **Test-Angemessenheits-Gradient**: Von Unit-Tests erkannte Defekte <= von Integrationstests erkannte Defekte <= von End-to-End-Tests erkannte Defekte. Jede Schicht nach oben erhöht die Erkennungsfähigkeit. -- **Architektur-Grenz-Erzwingungsregeln**: Umwandlung von Regeln aus Architektur-Dokumenten (wie „Render-Prozess kann nicht direkt auf das Dateisystem zugreifen") in ausführbare, automatisierte Checks. Vom „auf Papier geschrieben" zum „laufend in CI". -- **Review-Feedback-Beförderung**: Umwandlung wiederholter Code-Review-Kommentare in automatisierte Tests. Jedes Mal, wenn ein wiederkehrendes Problem gefunden wird, eine Regel hinzufügen, und der Harness wird automatisch stärker. Wie ein Dirigent, der häufige Probenfehler in Aufwärmübungen verwandelt — beim nächsten Mal, wenn derselbe Fehler gemacht wird, deckt die Übung selbst ihn auf, ohne dass der Dirigent ein Wort sagen muss. -- **Agenten-orientierte Fehlermeldungen**: Fehlermeldungen sollten nicht nur „was schiefgelaufen ist" feststellen, sondern dem Agenten auch genau sagen, wie er es beheben kann. Dies verwandelt Testfehler in selbst-korrigierende Feedback-Schleifen. - -## Vorgehen - -### 0. Zuerst Architektur-Grenzen definieren, dann E2E-Tests schreiben - -Voraussetzung für End-to-End-Testing sind klare Systemgrenzen. Wenn die Architektur ein Teller Spaghetti ist, wird End-to-End-Testing nur beweisen, dass „dieser Teller Spaghetti läuft", aber nicht aufzeigen, wo Design-Absichten verletzt wurden. Es ist wie ein Chor, der sich noch nicht einmal in Stimmen aufgeteilt hat — keine Menge an Probe wird gut klingen lassen. - -OpenAIs Erfahrung: **Für von Agenten generierte Codebases müssen Architektur-Einschränkungen von Anfang an als frühe Voraussetzungen etabliert werden, nicht erst in Betracht gezogen werden, wenn das Team wächst.** Der Grund ist einfach — Agenten kopieren existierende Muster im Repository, selbst wenn diese Muster uneinheitlich oder suboptimal sind. Ohne Architektur-Einschränkungen wird der Agent in jeder Session weitere Abweichungen einführen. - -OpenAI übernahm eine „Layered Domain Architecture" — jede Geschäftsdomäne ist in feste Schichten unterteilt: Types → Config → Repo → Service → Runtime → UI. Abhängigkeiten fließen streng vorwärts, und domänenübergreifende Belange treten über explizite Provider-Schnittstellen ein. Alle anderen Abhängigkeiten sind verboten und werden durch Custom-Linting mechanisch erzwungen. - -Schlüsselprinzip: **Invarianten erzwingen, nicht Implementierung mikromanagen.** Zum Beispiel verlangen, dass „Daten an der Grenze geparst werden", aber nicht vorschreiben, welche Bibliothek verwendet werden soll. Fehlermeldungen müssen Korrekturanweisungen enthalten — nicht nur „Verletzung" sagen, sondern dem Agenten genau sagen, wie er es ändern soll. - -> Quelle: [OpenAI: Harness engineering: leveraging Codex in an agent-first world](https://openai.com/index/harness-engineering/) - -### 1. Der Harness muss eine End-to-End-Schicht enthalten - -Machen Sie es in Ihrem Validierungsflow explizit: Für Aufgaben mit komponentenübergreifenden Änderungen ist das Bestehen von End-to-End-Tests eine Voraussetzung für die Fertigstellung: - -``` -## Validation Hierarchy -- Level 1: Unit tests (Must pass) -- Level 2: Integration tests (Must pass) -- Level 3: End-to-end tests (Must pass when cross-component changes are involved) -- Skipping any required level = Not Complete -``` - -### 2. Architektur-Regeln in ausführbare Checks umwandeln - -Jede Architektur-Einschränkung sollte einen entsprechenden Test oder eine Lint-Regel haben: - -```bash -# Check if the render process directly calls Node.js APIs -grep -r "require('fs')" src/renderer/ && exit 1 || echo "OK: no direct fs access in renderer" -``` - -### 3. Agenten-orientierte Fehlermeldungen entwerfen - -Fehlermeldungen sollten drei Elemente enthalten: was schiefgelaufen ist, warum, und wie man es behebt: - -``` -ERROR: Found direct import of 'fs' in src/renderer/App.tsx:12 -WHY: Renderer process has no access to Node.js APIs for security -FIX: Move file operations to src/preload/file-ops.ts and call via window.api.readFile() -``` - -### 4. Einen Review-Feedback-Beförderungsprozess etablieren - -Jedes Mal, wenn eine neue Art von Agentenfehler beim Code-Review gefunden wird, wandeln Sie ihn in einen automatisierten Check um. Einen Monat später wird Ihr Harness deutlich stärker sein als zu Monatsbeginn. Es ist wie Probenotizen für einen Chor — Probleme aus jeder Probe aufzeichnen, damit sie vor der nächsten überprüft werden können. Im Laufe der Zeit nehmen häufige Fehler ab, und die Musik wird harmonischer. - -## Fallbeispiel aus der Praxis - -**Aufgabe**: Implementierung einer Datei-Export-Funktion in einer Electron-App. Umfasst Render-Prozess-UI, Preload-Skript-Dateisystem-Proxy und Service-Layer-Datentransformation. - -**Stimmen einzeln singen (Unit-Tests bestanden)**: Render-Komponenten-Tests (bestanden, Dateioperationen gemockt), Preload-Skript-Tests (bestanden, Dateisystem gemockt), Service-Layer-Tests (bestanden, Datenquelle gemockt). Agent meldet Fertigstellung. - -**Zusammen singen (Defekte durch End-to-End-Tests aufgedeckt)**: - -| Defekt | Beschreibung | Unit-Test | E2E | -|--------|-------------|-----------|-----| -| Schnittstellen-Inkonsistenz | Inkonsistentes Dateipfad-Format | Verpasst | Gefunden | -| Zustandspropagation | Export-Fortschritt wird nicht über IPC an UI zurückgesendet | Verpasst | Gefunden | -| Ressourcen-Leak | Datei-Export-Handles großer Dateien nicht freigegeben | Verpasst | Gefunden | -| Berechtigungsproblem | Unterschiedliche Berechtigungen in der Paketumgebung | Verpasst | Gefunden | -| Fehlerpropagation | Service-Layer-Exceptions erreichten UI-Schicht nicht | Verpasst | Gefunden | - -Alle 5 Defekte wurden von End-to-End-Tests gefunden, während Unit-Tests keinen einzigen entdeckten. Die Kosten waren ein Anstieg der Testzeit von 2 Sekunden auf 15 Sekunden — in einem Agenten-Workflow völlig akzeptabel. Egal wie gut jeder Teil einzeln singt, es kann nicht mit einer vollständigen Ensemble-Probe mithalten. - -## Wichtigste Erkenntnisse - -- **Unit-Tests sind systematisch blind für Komponenten-Grenzflächen-Defekte** — ihr Isolationsdesign ist genau das, was sie daran hindert, Interaktionsprobleme zu erkennen. Dass jeder richtig singt, bedeutet nicht, dass der Chor harmonisch klingt. -- **End-to-End-Testing erkennt nicht nur Defekte, es verändert das Codierungsverhalten des Agenten** — es lässt ihn mehr auf Integration und Grenzen fokussieren. -- **Architektur-Regeln müssen ausführbar sein** — nicht in einem Dokument geschrieben, das darauf wartet, gelesen zu werden, sondern automatisch bei jedem Commit überprüft. -- **Fehlermeldungen müssen für Agenten entworfen werden** — einschließlich spezifischer Schritte zur „Behebung", um eine selbst-korrigierende Schleife zu bilden. -- **Review-Feedback-Beförderung macht den Harness automatisch stärker** — jede Kategorie erfasster Defekte wird zu einer permanenten Verteidigungslinie. - -## Weiterführende Literatur - -- [How Google Tests Software - Whittaker et al.](https://www.goodreads.com/book/show/13563030-how-google-tests-software) — Die klassische Quelle des Testing-Pyramid-Modells -- [Harness Engineering - OpenAI](https://openai.com/index/harness-engineering/) — Engineering-Praktiken für die automatisierte Ausführung von Architektur-Einschränkungen -- [Chaos Engineering - Netflix (Basiri et al.)](https://ieeexplore.ieee.org/document/7466237) — Proaktives Injizieren von Fehlern zur Überprüfung der Systemresilienz -- [QuickCheck - Claessen & Hughes](https://www.cs.tufts.edu/~nr/cs257/archive/john-hughes/quick.pdf) — Property-Testing-Methodik, angesiedelt zwischen Beispiel-Testing und formaler Verifikation - -## Übungen - -1. **Komponentenübergreifende Defekterkennung**: Wählen Sie eine Änderungsaufgabe, die mindestens drei Komponenten umfasst. Führen Sie zuerst nur Unit-Tests durch und dokumentieren Sie die Ergebnisse, dann führen Sie End-to-End-Tests durch. Analysieren Sie, zu welcher Art von schichtenübergreifendem Interaktionsproblem jeder zusätzlich entdeckte Defekt gehört. - -2. **Architektur-Regel-Automatisierung**: Wählen Sie eine Architektur-Einschränkung aus Ihrem Projekt und wandeln Sie sie in einen ausführbaren Check um (mit einer agenten-orientierten Fehlermeldung). Integrieren Sie ihn in den Harness und verifizieren Sie seine Wirksamkeit mit einer Baseline-Aufgabe. - -3. **Review-Feedback-Beförderung**: Finden Sie einen wiederkehrenden Kommentar-Typ aus Ihrem Code-Review-Verlauf und wandeln Sie ihn in einen automatisierten Check im Fünf-Schritte-Prozess um. Vergleichen Sie die Häufigkeit des Problems vor und nach der Beförderung. diff --git a/courses/learn-harness-engineering/docs/de/lectures/lecture-11-why-observability-belongs-inside-the-harness/code/evaluator-rubric.md b/courses/learn-harness-engineering/docs/de/lectures/lecture-11-why-observability-belongs-inside-the-harness/code/evaluator-rubric.md deleted file mode 100644 index 567dae84..00000000 --- a/courses/learn-harness-engineering/docs/de/lectures/lecture-11-why-observability-belongs-inside-the-harness/code/evaluator-rubric.md +++ /dev/null @@ -1,8 +0,0 @@ -# Evaluator-Rubrik Beispiel - -Verwenden Sie 1-5 Bewertung für jede Dimension: - -- Fundierung: Sind die Antworten klar mit importierten Quellen verknüpft? -- Zitierqualität: Sind die Quellverweise sichtbar und spezifisch? -- Funktionalität: Kann der Benutzer den Frage-Antwort-Flow abschließen? -- Produktkohärenz: Fühlt sich der Workflow integriert an? diff --git a/courses/learn-harness-engineering/docs/de/lectures/lecture-11-why-observability-belongs-inside-the-harness/code/index.md b/courses/learn-harness-engineering/docs/de/lectures/lecture-11-why-observability-belongs-inside-the-harness/code/index.md deleted file mode 100644 index 110d617b..00000000 --- a/courses/learn-harness-engineering/docs/de/lectures/lecture-11-why-observability-belongs-inside-the-harness/code/index.md +++ /dev/null @@ -1,8 +0,0 @@ -# Code für Vorlesung 11 - -Verwenden Sie diesen Ordner für Beispiele zu: - -- Planner-Ausgaben -- Evaluator-Rubriken -- Generator/Evaluator-Schleifen -- Single-Agent- vs. Multi-Role-Vergleichen diff --git a/courses/learn-harness-engineering/docs/de/lectures/lecture-11-why-observability-belongs-inside-the-harness/code/runtime-logger.ts b/courses/learn-harness-engineering/docs/de/lectures/lecture-11-why-observability-belongs-inside-the-harness/code/runtime-logger.ts deleted file mode 100644 index 213dc198..00000000 --- a/courses/learn-harness-engineering/docs/de/lectures/lecture-11-why-observability-belongs-inside-the-harness/code/runtime-logger.ts +++ /dev/null @@ -1,260 +0,0 @@ -/** - * runtime-logger.ts - * - * A structured logging module demo. Shows ad-hoc console.log output vs - * structured JSON log output when diagnosing a failure. Includes a seeded - * failure scenario and demonstrates how structured logs pinpoint the issue - * faster. - * - * Run: npx tsx docs/lectures/lecture-11-why-observability-belongs-inside-the-harness/code/runtime-logger.ts - */ - -// --------------------------------------------------------------------------- -// Types -// --------------------------------------------------------------------------- - -interface StructuredLogEntry { - timestamp: string; - level: "info" | "warn" | "error" | "debug"; - component: string; - action: string; - durationMs?: number; - input?: unknown; - output?: unknown; - error?: string; - correlationId: string; -} - -// --------------------------------------------------------------------------- -// Simulated pipeline with a seeded failure -// --------------------------------------------------------------------------- - -const CORRELATION_ID = "req-" + Math.random().toString(36).slice(2, 8); - -// Simulated stages of a document Q&A pipeline -interface PipelineStage { - component: string; - action: string; - durationMs: number; - success: boolean; - errorMessage?: string; - input?: unknown; - output?: unknown; -} - -function runPipeline(): PipelineStage[] { - return [ - { - component: "DocumentLoader", - action: "parse_upload", - durationMs: 45, - success: true, - input: { filename: "report.pdf", size: "2.3MB" }, - output: { chunks: 47 }, - }, - { - component: "ChunkIndexer", - action: "embed_and_store", - durationMs: 230, - success: true, - input: { chunks: 47 }, - output: { indexed: 47, embeddingDim: 1536 }, - }, - { - component: "QueryRouter", - action: "route_query", - durationMs: 12, - success: true, - input: { query: "What is the revenue target?" }, - output: { routedTo: "RetrievalEngine" }, - }, - { - // SEEDED FAILURE: Retrieval returns 0 results due to dimension mismatch - component: "RetrievalEngine", - action: "semantic_search", - durationMs: 180, - success: false, - errorMessage: "Vector dimension mismatch: query embedding dim=768, index embedding dim=1536", - input: { query: "What is the revenue target?", topK: 5 }, - output: { results: 0 }, - }, - { - component: "AnswerGenerator", - action: "generate_with_citations", - durationMs: 1500, - success: true, // Doesn't crash, but produces a bad answer - input: { context: [], query: "What is the revenue target?" }, - output: { answer: "I could not find relevant information.", citations: 0 }, - }, - ]; -} - -// --------------------------------------------------------------------------- -// Ad-hoc logging (console.log style) -// --------------------------------------------------------------------------- - -function printAdHocLog(stages: PipelineStage[]): void { - console.log("Starting document Q&A pipeline..."); - console.log("User uploaded report.pdf"); - - for (const stage of stages) { - if (stage.success) { - console.log(`${stage.component}: ${stage.action} done (${stage.durationMs}ms)`); - } else { - console.log(`${stage.component}: something went wrong`); - } - } - - console.log("Pipeline finished. Answer: I could not find relevant information."); -} - -// --------------------------------------------------------------------------- -// Structured logging (JSON) -// --------------------------------------------------------------------------- - -function printStructuredLog(stages: PipelineStage[]): StructuredLogEntry[] { - const entries: StructuredLogEntry[] = []; - - for (const stage of stages) { - entries.push({ - timestamp: new Date().toISOString(), - level: stage.success ? "info" : "error", - component: stage.component, - action: stage.action, - durationMs: stage.durationMs, - input: stage.input, - output: stage.output, - error: stage.errorMessage, - correlationId: CORRELATION_ID, - }); - } - - return entries; -} - -// --------------------------------------------------------------------------- -// Diagnose failure from structured logs -// --------------------------------------------------------------------------- - -function diagnoseFromStructured(entries: StructuredLogEntry[]): string[] { - const diagnosis: string[] = []; - - // Find errors - const errors = entries.filter((e) => e.level === "error"); - if (errors.length > 0) { - diagnosis.push("ERRORS FOUND:"); - for (const err of errors) { - diagnosis.push(` - ${err.component}.${err.action}: ${err.error}`); - } - } - - // Check for latency spikes - const slowStages = entries.filter((e) => (e.durationMs ?? 0) > 1000); - if (slowStages.length > 0) { - diagnosis.push("LATENCY SPIKES:"); - for (const s of slowStages) { - diagnosis.push(` - ${s.component}.${s.action}: ${s.durationMs}ms`); - } - } - - // Check for cascading failures - const emptyOutputs = entries.filter((e) => { - const out = e.output as Record | undefined; - return out && typeof out === "object" && "results" in out && (out.results as number) === 0; - }); - if (emptyOutputs.length > 0) { - diagnosis.push("EMPTY OUTPUTS (possible cascade):"); - for (const e of emptyOutputs) { - diagnosis.push(` - ${e.component}.${e.action}: returned 0 results`); - } - } - - return diagnosis; -} - -// --------------------------------------------------------------------------- -// Reporting -// --------------------------------------------------------------------------- - -function pad(s: string, len: number): string { - return s.length >= len ? s : s + " ".repeat(len - s.length); -} - -function run(): void { - const pipeline = runPipeline(); - - console.log("\n" + "=".repeat(90)); - console.log(" OBSERVABILITY DEMO -- Ad-hoc vs Structured Logging"); - console.log("=".repeat(90)); - - // --- Ad-hoc output --- - console.log("\n" + "-".repeat(90)); - console.log(" SCENARIO A: Ad-hoc console.log output"); - console.log("-".repeat(90) + "\n"); - printAdHocLog(pipeline); - console.log("\n Diagnosis from ad-hoc logs: ??? Hard to tell what went wrong."); - console.log(" The failure message is vague: 'something went wrong'."); - console.log(" No dimensions, no input/output data, no correlation ID."); - - // --- Structured output --- - console.log("\n" + "-".repeat(90)); - console.log(" SCENARIO B: Structured JSON log output"); - console.log("-".repeat(90) + "\n"); - - const structuredEntries = printStructuredLog(pipeline); - - const header = `| ${pad("Timestamp", 26)}| ${pad("Level", 6)}| ${pad("Component", 20)}| ${pad("Action", 25)}| ${pad("Duration", 9)}| Error?`; - const sep = `|${"-".repeat(28)}|${"-".repeat(8)}|${"-".repeat(22)}|${"-".repeat(27)}|${"-".repeat(11)}|${"-".repeat(30)}`; - console.log(header); - console.log(sep); - - for (const entry of structuredEntries) { - const hasError = entry.error ? entry.error.slice(0, 30) : ""; - const marker = entry.level === "error" ? ">>" : " "; - console.log( - `${marker}| ${pad(entry.timestamp, 26)}| ${pad(entry.level, 6)}| ${pad(entry.component, 20)}| ${pad(entry.action, 25)}| ${pad(String(entry.durationMs) + "ms", 9)}| ${hasError}` - ); - } - - // --- Diagnosis --- - console.log("\n" + "-".repeat(90)); - console.log(" AUTOMATED DIAGNOSIS FROM STRUCTURED LOGS"); - console.log("-".repeat(90) + "\n"); - - const errors = structuredEntries.filter((e) => e.level === "error"); - for (const err of errors) { - console.log(` ROOT CAUSE: ${err.component}.${err.action}`); - console.log(` Error: ${err.error}`); - console.log(` Input: ${JSON.stringify(err.input)}`); - console.log(` Output: ${JSON.stringify(err.output)}`); - console.log(` Correlation ID: ${err.correlationId}`); - } - - // Downstream impact - console.log("\n DOWNSTREAM IMPACT:"); - const answerGen = structuredEntries.find((e) => e.component === "AnswerGenerator"); - if (answerGen) { - const out = answerGen.output as Record; - console.log(` AnswerGenerator received empty context (${JSON.stringify(answerGen.input)})`); - console.log(` Produced answer: "${out.answer}" with ${out.citations} citations`); - } - - // Comparison summary - console.log("\n" + "=".repeat(90)); - console.log(" COMPARISON"); - console.log("=".repeat(90) + "\n"); - - const cHeader = `| ${pad("Metric", 35)}| ${pad("Ad-hoc Logs", 18)}| ${pad("Structured Logs", 18)}|`; - const cSep = `|${"-".repeat(37)}|${"-".repeat(20)}|${"-".repeat(20)}|`; - console.log(cHeader); - console.log(cSep); - console.log(`| ${pad("Root cause identifiable", 35)}| ${pad("No", 18)}| ${pad("Yes", 18)}|`); - console.log(`| ${pad("Input/output traceable", 35)}| ${pad("No", 18)}| ${pad("Yes", 18)}|`); - console.log(`| ${pad("Correlation across steps", 35)}| ${pad("No", 18)}| ${pad("Yes", 18)}|`); - console.log(`| ${pad("Machine-parseable", 35)}| ${pad("No", 18)}| ${pad("Yes", 18)}|`); - console.log(`| ${pad("Time to diagnose", 35)}| ${pad("Minutes (manual)", 18)}| ${pad("Seconds (auto)", 18)}|`); - - console.log("\n Structured logging transforms debugging from guesswork into a deterministic lookup.\n"); -} - -run(); diff --git a/courses/learn-harness-engineering/docs/de/lectures/lecture-11-why-observability-belongs-inside-the-harness/code/sprint-contract.md b/courses/learn-harness-engineering/docs/de/lectures/lecture-11-why-observability-belongs-inside-the-harness/code/sprint-contract.md deleted file mode 100644 index 7e8afb93..00000000 --- a/courses/learn-harness-engineering/docs/de/lectures/lecture-11-why-observability-belongs-inside-the-harness/code/sprint-contract.md +++ /dev/null @@ -1,12 +0,0 @@ -# Sprint-Vertrag Beispiel - -Sprint-Ziel: - -- Sichtbare Zitate zu fundierten Q&A-Ergebnissen hinzufügen - -"Fertig" bedeutet: - -- Benutzer stellt eine Frage -- App gibt eine Antwort zurück -- Mindestens ein Zitat wird angezeigt -- Klicken auf ein Zitat öffnet die Quellposition in der Dokumentansicht diff --git a/courses/learn-harness-engineering/docs/de/lectures/lecture-11-why-observability-belongs-inside-the-harness/index.md b/courses/learn-harness-engineering/docs/de/lectures/lecture-11-why-observability-belongs-inside-the-harness/index.md deleted file mode 100644 index 321ea271..00000000 --- a/courses/learn-harness-engineering/docs/de/lectures/lecture-11-why-observability-belongs-inside-the-harness/index.md +++ /dev/null @@ -1,154 +0,0 @@ -[中文版本 →](../../../zh/lectures/lecture-11-why-observability-belongs-inside-the-harness/) - -> Codebeispiele: [code/](https://github.com/walkinglabs/learn-harness-engineering/blob/main/docs/de/lectures/lecture-11-why-observability-belongs-inside-the-harness/code/) -> Praxisprojekt: [Projekt 06. Vollständiger harness (Capstone)](./../../projects/project-06-runtime-observability-and-debugging/index.md) - -# Lektion 11. Die Runtime des Agenten beobachtbar machen - -## Welches Problem löst diese Lektion? - -Du bittest einen Agenten, ein Feature zu implementieren. Er läuft 20 Minuten, ändert viele Dateien und sagt dann: "fertig, aber zwei Tests schlagen fehl." Du fragst, warum sie fehlschlagen: "nicht sicher, vielleicht ein Timing-Problem." Du fragst, welche kritischen Pfade geändert wurden: "ich schaue in den Code..." - -Das liegt nicht an fehlender Agentenfähigkeit. Dein harness stellt nicht genug Beobachtbarkeit bereit. **Ohne Beobachtbarkeit treffen Agenten Entscheidungen unter Unsicherheit, Bewertungen werden subjektiv, und retries werden blindes Herumprobieren.** OpenAI und Anthropic definieren Zuverlässigkeit als Evidenzproblem: Der harness muss Runtime-Verhalten und Bewertungssignale in einer Form offenlegen, die die nächste Entscheidung leitet. - -## Zentrale Konzepte - -- **Runtime-Beobachtbarkeit**: Systemsignale wie Logs, Traces, Prozessereignisse und Health Checks. Beantwortet "was hat das System getan". -- **Prozess-Beobachtbarkeit**: Sichtbarkeit in Entscheidungsartefakte des harness, etwa Pläne, Bewertungsrubriken und Akzeptanzkriterien. Beantwortet "warum sollte diese Änderung akzeptiert werden". -- **Task trace**: Vollständiger Entscheidungsweg vom Start bis zur Fertigstellung, analog zu Request-Tracing in verteilten Systemen. -- **Sprint contract**: Kurzfristige Vereinbarung vor dem Coding, die Scope, Verifikationsstandards und Ausschlüsse festlegt. -- **Evaluator-Rubrik**: Wandelt Qualitätsbewertung von subjektivem Urteil in evidenzbasierte strukturierte Bewertung um. -- **Geschichtete Beobachtbarkeit**: System- und Prozess-Beobachtbarkeit werden gemeinsam entworfen und verstärken sich gegenseitig. - -## Geschichtete Beobachtbarkeit - -```mermaid -flowchart LR - Contract["Zuerst die Aufgabe festhalten
was ändern / was nicht ändern / Bestehenskriterien"] --> Generator["Generator"] - Generator --> Signals["App-Logs, Traces
und Health Checks während der Ausführung sammeln"] - Contract --> Review["Ergebnis Punkt für Punkt prüfen
Verhalten / Tests / Grenzen"] - Signals --> Review - Review --> Verdict["Fehlgeschlagenen Check zeigen
und wo zu fixen ist"] - Verdict --> Generator -``` - -## Warum das passiert - -### Die echten Kosten fehlender Beobachtbarkeit - -Wenn einem harness Beobachtbarkeit fehlt, treten vier Problemtypen systematisch auf: - -**"Korrekt" lässt sich nicht von "sieht korrekt aus" unterscheiden**: Eine Funktion kann im Code-Review perfekt wirken. Zur Laufzeit erzeugt aber ein Edge-Case-Fehler falsche Ergebnisse. Nur Runtime-Traces zeigen, dass der tatsächliche Ausführungspfad von der Erwartung abweicht. - -**Bewertung wird Mystik**: Ohne Rubriken und Akzeptanzkriterien verlassen sich Evaluatoren, ob Mensch oder Agent, auf implizite Annahmen. Dieselbe Ausgabe kann stark unterschiedlich bewertet werden. Qualitätsbewertung wird nicht reproduzierbar. - -**Retries werden blinde Vermutungen**: Wenn der Agent nicht weiß, warum etwas fehlschlägt, ist die Retry-Richtung zufällig. Er kann irrelevante Codepfade reparieren, während die eigentliche Ursache unberührt bleibt. Jeder blinde retry kostet Tokens und Zeit. - -**Informationsklippe beim Handoff**: Wenn unvollständige Arbeit an die nächste Session übergeben wird, muss die neue Session ohne Beobachtbarkeit den Systemzustand von Grund auf diagnostizieren. Anthropic beobachtet, dass diese redundante Diagnose 30-50 % der Sessionzeit verbrauchen kann. - -### Ein realistisches Claude-Code-Szenario - -Stell dir einen harness mit planner-generator-evaluator-Workflow vor, der "Dark Mode zur App hinzufügen" ausführt. - -**Ohne Beobachtbarkeit**: Der planner liefert eine vage Beschreibung. Der generator implementiert Dark Mode auf dieser Basis, trifft aber die impliziten Erwartungen nicht. Der evaluator lehnt nach eigenen impliziten Standards ab, kann aber nicht konkret sagen, was falsch ist. Der generator versucht blind erneut. Der Zyklus wiederholt sich 3-4 Mal, dauert etwa 45 Minuten und liefert ein kaum akzeptables Ergebnis. - -**Mit vollständiger Beobachtbarkeit**: Der planner liefert einen sprint contract mit zu ändernden Komponenten, Verifikationsstandards und Ausschlüssen. Der generator implementiert gemäß Vertrag. Runtime-Beobachtbarkeit zeichnet das Laden und Anwenden der Styles jeder Komponente auf. Der evaluator bewertet Dimension für Dimension anhand einer Rubrik und konkreter Evidenz. Eine Iteration liefert ein hochwertiges Ergebnis in etwa 15 Minuten. - -3x Effizienzunterschied. Die einzige Änderung ist Beobachtbarkeit. - -### Warum Agenten das nicht selbst lösen können - -Vielleicht denkst du: "Kann der Agent nicht einfach eigene Logs ausgeben?" Die Probleme sind: - -1. Der Agent weiß nicht, was er nicht weiß; er zeichnet Signale nicht proaktiv auf, deren Bedarf er nicht erkennt. -2. Logformate sind inkonsistent; verschiedene Sessions nutzen unterschiedliche Formate, was systematische Analyse verhindert. -3. Prozess-Beobachtbarkeit lässt sich nicht mit Logs lösen; sprint contracts und Bewertungsrubriken sind strukturierte Artefakte auf harness-Ebene. - -## Wie man es richtig macht - -### 1. Runtime-Signalsammlung in den harness einbauen - -Verlass dich nicht darauf, dass der Agent eigene Logs ausgibt. Der harness sollte diese Signale automatisch sammeln: - -- **Anwendungslebenszyklus**: Startup, ready, running, shutdown -- **Featurepfad-Ausführung**: Kritische Pfade mit Einstiegspunkten, Checkpoints und Ausgängen -- **Datenfluss**: Datenbewegungen zwischen Komponenten -- **Ressourcennutzung**: Abnormale Muster, etwa stetig wachsender Speicher -- **Fehler und Ausnahmen**: Vollständiger Kontext, nicht nur Fehlermeldungen - -### 2. Sprint contracts implementieren - -Vor jeder Aufgabe verhandeln generator und evaluator, möglicherweise verschiedene Aufrufe desselben Agenten, einen Vertrag: - -```markdown -# Sprint Contract: Dark Mode Support - -## Scope -- Modify the theme toggle component -- Update global CSS variables -- Add dark mode tests - -## Verification Standards -- Visual regression tests pass for each component -- Main flow end-to-end tests pass -- No flash of unstyled content (FOUC) - -## Exclusions -- Not handling print styles -- Not handling third-party component dark mode -``` - -### 3. Eine Evaluator-Rubrik einführen - -Mache aus "ist es gut oder nicht" eine quantifizierbare Bewertung: - -```markdown -# Scoring Rubric - -| Dimension | A | B | C | D | -|-----------|---|---|---|---| -| Code correctness | All tests pass | Main flow passes | Partial pass | Build fails | -| Architecture compliance | Fully compliant | Minor deviations | Obvious deviations | Serious violations | -| Test coverage | Main + edge cases | Main flow only | Only skeleton | No tests | -``` - -### 4. Mit OpenTelemetry standardisieren - -Erzeuge einen trace für jede harness-Session, einen span für jede Aufgabe und sub-spans für jeden Verifikationsschritt. Standardattribute annotieren wichtige Informationen. So integrieren sich Beobachtbarkeitsdaten in Tools wie Jaeger oder Zipkin. - -## Praxisfall - -Ein harness mit planner-generator-evaluator-Workflow führt "Dark Mode Support hinzufügen" aus: - -**Nicht beobachtbare Version**: 3-4 blinde retry-Runden, 45 Minuten, kaum akzeptables Ergebnis. Der evaluator sagt "fühlt sich nicht richtig an", kann aber nicht konkret werden. Der generator verschwendet Zeit in falschen Richtungen. - -**Voll beobachtbare Version**: -- Sprint contract klärt Scope, Standards und Ausschlüsse -- Runtime-Traces zeichnen das Laden der Styles jeder Komponente auf -- Bewertungsrubrik liefert strukturierte Bewertung pro Dimension -- Eine Iteration liefert hochwertige Ergebnisse in 15 Minuten - -3x Effizienzgewinn, stabilere Qualität, reproduzierbare Bewertungen. - -## Wichtigste Erkenntnisse - -- **Beobachtbarkeit ist eine Architektur-Eigenschaft des harness**, keine nachträglich hinzugefügte Funktion. -- **Beide Beobachtbarkeitsschichten sind wesentlich**: Runtime-Signale erklären "was passiert ist", Prozessartefakte erklären "warum es so gemacht wurde". -- **Sprint contracts richten früh aus** und verhindern vorhersehbare Ablehnungen durch evaluator. -- **Rubriken machen Bewertung reproduzierbar**, sodass verschiedene Evaluatoren ähnliche Scores liefern. -- **Fehlende Beobachtbarkeit verschwendet 30-50 % der Sessionzeit für redundante Diagnose.** - -## Weiterführende Literatur - -- [Observability Engineering - Charity Majors](https://www.honeycomb.io/blog/observability-engineering-book) — Theorie und Praxis moderner observability engineering -- [Dapper - Google (Sigelman et al.)](https://research.google/pubs/pub36356/) — wegweisende Praxis in großskaligem Distributed Tracing -- [Harness Design - Anthropic](https://www.anthropic.com/engineering/harness-design-long-running-apps) — Einführung von sprint contracts und evaluator rubrics -- [Site Reliability Engineering - Google](https://sre.google/sre-book/table-of-contents/) — systematische Anwendung von Beobachtbarkeit in Produktionssystemen - -## Übungen - -1. **Observability-Gap-Analyse**: Prüfe deinen aktuellen harness auf System- und Prozess-Beobachtbarkeit. Finde Systemzustände, die mit bestehenden Signalen nicht unterscheidbar sind, und schlage Ergänzungen vor. - -2. **Sprint-contract-Praxis**: Schreibe einen sprint contract für eine reale Aufgabe. Lass den Agenten danach arbeiten und vergleiche Effizienz und Qualität mit und ohne Vertrag. - -3. **Task-trace-Konstruktion**: Zeichne jeden Schritt eines Agenten während einer vollständigen Coding-Aufgabe auf. Annotiere mit OpenTelemetry-Konventionen und analysiere Informationsengpässe. diff --git a/courses/learn-harness-engineering/docs/de/lectures/lecture-12-why-every-session-must-leave-a-clean-state/code/benchmark-comparison-template.md b/courses/learn-harness-engineering/docs/de/lectures/lecture-12-why-every-session-must-leave-a-clean-state/code/benchmark-comparison-template.md deleted file mode 100644 index 8e0c184f..00000000 --- a/courses/learn-harness-engineering/docs/de/lectures/lecture-12-why-every-session-must-leave-a-clean-state/code/benchmark-comparison-template.md +++ /dev/null @@ -1,18 +0,0 @@ -# Benchmark-Vergleichsvorlage - -Harness A: - -- Abschlussrate -- Durchschnittliche Wiederholungen -- Vor menschlicher Review gefundene Bugs - -Harness B: - -- Abschlussrate -- Durchschnittliche Wiederholungen -- Vor menschlicher Review gefundene Bugs - -Interpretation: - -- Welches Harness hat das Ergebnis verändert? -- Welches Harness hat die Kosten für das Ergebnis verändert? diff --git a/courses/learn-harness-engineering/docs/de/lectures/lecture-12-why-every-session-must-leave-a-clean-state/code/benchmark-runner.ts b/courses/learn-harness-engineering/docs/de/lectures/lecture-12-why-every-session-must-leave-a-clean-state/code/benchmark-runner.ts deleted file mode 100644 index 0388cad7..00000000 --- a/courses/learn-harness-engineering/docs/de/lectures/lecture-12-why-every-session-must-leave-a-clean-state/code/benchmark-runner.ts +++ /dev/null @@ -1,216 +0,0 @@ -/** - * benchmark-runner.ts - * - * Reads a benchmark task definition (JSON array of tasks with pass criteria), - * "executes" each task, records timing and pass/fail, outputs a comparison - * report showing which tasks pass and which fail. - * - * Run: npx tsx docs/lectures/lecture-12-why-every-session-must-leave-a-clean-state/code/benchmark-runner.ts - */ - -// --------------------------------------------------------------------------- -// Types -// --------------------------------------------------------------------------- - -interface BenchmarkTask { - id: string; - name: string; - category: string; - passCriteria: string[]; - expectedDurationMs: number; - // Simulated actual results - actualDurationMs: number; - actualPass: boolean; - failureReason?: string; -} - -interface BenchmarkResult { - id: string; - name: string; - category: string; - criteriaTotal: number; - criteriaPassed: number; - pass: boolean; - expectedMs: number; - actualMs: number; - durationDelta: number; - failureReason?: string; -} - -// --------------------------------------------------------------------------- -// Benchmark task definitions -// --------------------------------------------------------------------------- - -const benchmarkTasks: BenchmarkTask[] = [ - { - id: "bench-001", - name: "Import markdown document", - category: "Document Pipeline", - passCriteria: ["File accepted", "Chunks created", "Metadata stored"], - expectedDurationMs: 100, - actualDurationMs: 95, - actualPass: true, - }, - { - id: "bench-002", - name: "Import PDF document", - category: "Document Pipeline", - passCriteria: ["File accepted", "Text extracted", "Chunks created", "Metadata stored"], - expectedDurationMs: 200, - actualDurationMs: 310, - actualPass: false, - failureReason: "PDF text extraction failed on page 3 -- encoding issue", - }, - { - id: "bench-003", - name: "Ask grounded question", - category: "Q&A Pipeline", - passCriteria: ["Query received", "Relevant chunks retrieved", "Answer generated", "Citations present"], - expectedDurationMs: 500, - actualDurationMs: 480, - actualPass: true, - }, - { - id: "bench-004", - name: "Ask question with no relevant docs", - category: "Q&A Pipeline", - passCriteria: ["Query received", "Graceful 'no results' message", "No hallucinated citations"], - expectedDurationMs: 400, - actualDurationMs: 420, - actualPass: false, - failureReason: "Model hallucinated a citation instead of saying 'no results'", - }, - { - id: "bench-005", - name: "Delete imported document", - category: "Document Pipeline", - passCriteria: ["Document removed from list", "Chunks removed from index", "No orphan data"], - expectedDurationMs: 150, - actualDurationMs: 145, - actualPass: true, - }, - { - id: "bench-006", - name: "Concurrent user access", - category: "Security", - passCriteria: ["Users isolated", "No cross-user data leak", "Performance within SLA"], - expectedDurationMs: 300, - actualDurationMs: 850, - actualPass: false, - failureReason: "Cross-user data leak detected + response time exceeded SLA (850ms > 500ms)", - }, - { - id: "bench-007", - name: "Session continuity after restart", - category: "Reliability", - passCriteria: ["State persisted", "Session recovers", "No data loss"], - expectedDurationMs: 200, - actualDurationMs: 180, - actualPass: true, - }, - { - id: "bench-008", - name: "API rate limiting", - category: "Security", - passCriteria: ["Rate limit enforced", "429 response after limit", "Legitimate traffic unaffected"], - expectedDurationMs: 100, - actualDurationMs: 100, - actualPass: true, - }, -]; - -// --------------------------------------------------------------------------- -// Execution simulation -// --------------------------------------------------------------------------- - -function executeBenchmark(tasks: BenchmarkTask[]): BenchmarkResult[] { - return tasks.map((task) => ({ - id: task.id, - name: task.name, - category: task.category, - criteriaTotal: task.passCriteria.length, - criteriaPassed: task.actualPass ? task.passCriteria.length : Math.floor(task.passCriteria.length * 0.5), - pass: task.actualPass, - expectedMs: task.expectedDurationMs, - actualMs: task.actualDurationMs, - durationDelta: task.actualDurationMs - task.expectedDurationMs, - failureReason: task.failureReason, - })); -} - -// --------------------------------------------------------------------------- -// Reporting -// --------------------------------------------------------------------------- - -function pad(s: string, len: number): string { - return s.length >= len ? s : s + " ".repeat(len - s.length); -} - -function run(): void { - console.log("\n" + "=".repeat(100)); - console.log(" BENCHMARK RUNNER -- Task Execution Report"); - console.log("=".repeat(100)); - - const results = executeBenchmark(benchmarkTasks); - - // Detailed results - const header = `| ${pad("ID", 10)}| ${pad("Task", 35)}| ${pad("Category", 18)}| ${pad("Pass?", 6)}| ${pad("Criteria", 10)}| ${pad("Expected", 10)}| ${pad("Actual", 10)}| ${pad("Delta", 8)}|`; - const sep = `|${"-".repeat(12)}|${"-".repeat(37)}|${"-".repeat(20)}|${"-".repeat(8)}|${"-".repeat(12)}|${"-".repeat(12)}|${"-".repeat(12)}|${"-".repeat(10)}|`; - console.log("\n" + header); - console.log(sep); - - for (const r of results) { - const passLabel = r.pass ? "PASS" : "FAIL"; - const criteriaLabel = `${r.criteriaPassed}/${r.criteriaTotal}`; - const deltaLabel = r.durationDelta >= 0 ? `+${r.durationDelta}ms` : `${r.durationDelta}ms`; - const marker = r.pass ? " " : ">>"; - - console.log( - `${marker}| ${pad(r.id, 10)}| ${pad(r.name, 35)}| ${pad(r.category, 18)}| ${pad(passLabel, 6)}| ${pad(criteriaLabel, 10)}| ${pad(r.expectedMs + "ms", 10)}| ${pad(r.actualMs + "ms", 10)}| ${pad(deltaLabel, 8)}|` - ); - } - - // Failure details - const failures = results.filter((r) => !r.pass); - if (failures.length > 0) { - console.log("\n" + "-".repeat(100)); - console.log(" FAILURE DETAILS"); - console.log("-".repeat(100)); - for (const f of failures) { - console.log(`\n [${f.id}] ${f.name}`); - console.log(` Category: ${f.category}`); - console.log(` Criteria: ${f.criteriaPassed}/${f.criteriaTotal} passed`); - console.log(` Reason: ${f.failureReason ?? "Unknown"}`); - console.log(` Timing: Expected ${f.expectedMs}ms, actual ${f.actualMs}ms (delta: ${f.durationDelta >= 0 ? "+" : ""}${f.durationDelta}ms)`); - } - } - - // Summary - console.log("\n" + "=".repeat(100)); - console.log(" SUMMARY BY CATEGORY"); - console.log("=".repeat(100) + "\n"); - - const categories = [...new Set(results.map((r) => r.category))]; - const catHeader = `| ${pad("Category", 20)}| ${pad("Total", 8)}| ${pad("Passed", 8)}| ${pad("Failed", 8)}| ${pad("Pass Rate", 12)}|`; - const catSep = `|${"-".repeat(22)}|${"-".repeat(10)}|${"-".repeat(10)}|${"-".repeat(10)}|${"-".repeat(14)}|`; - console.log(catHeader); - console.log(catSep); - - for (const cat of categories) { - const catResults = results.filter((r) => r.category === cat); - const catPassed = catResults.filter((r) => r.pass).length; - const catFailed = catResults.filter((r) => !r.pass).length; - const rate = Math.round((catPassed / catResults.length) * 100); - - console.log(`| ${pad(cat, 20)}| ${pad(String(catResults.length), 8)}| ${pad(String(catPassed), 8)}| ${pad(String(catFailed), 8)}| ${pad(rate + "%", 12)}|`); - } - - // Overall - const totalPassed = results.filter((r) => r.pass).length; - const totalFailed = results.filter((r) => !r.pass).length; - console.log("\n" + "-".repeat(100)); - console.log(` OVERALL: ${totalPassed}/${results.length} passed (${Math.round((totalPassed / results.length) * 100)}%), ${totalFailed} failures`); - console.log("=".repeat(100) + "\n"); -} - -run(); diff --git a/courses/learn-harness-engineering/docs/de/lectures/lecture-12-why-every-session-must-leave-a-clean-state/code/cleanup-loop.md b/courses/learn-harness-engineering/docs/de/lectures/lecture-12-why-every-session-must-leave-a-clean-state/code/cleanup-loop.md deleted file mode 100644 index 3f940b1c..00000000 --- a/courses/learn-harness-engineering/docs/de/lectures/lecture-12-why-every-session-must-leave-a-clean-state/code/cleanup-loop.md +++ /dev/null @@ -1,9 +0,0 @@ -# Aufräum-Schleife Beispiel - -Wiederkehrende Aufräumaufgaben: - -- Nach veralteten Dokumenten scannen -- Nach Strukturverletzungen scannen -- Qualitätsnoten aktualisieren -- Gezielte Aufräum-PRs öffnen -- Ein festes Benchmark-Slice nach dem Aufräumen erneut ausführen diff --git a/courses/learn-harness-engineering/docs/de/lectures/lecture-12-why-every-session-must-leave-a-clean-state/code/cleanup-scanner.ts b/courses/learn-harness-engineering/docs/de/lectures/lecture-12-why-every-session-must-leave-a-clean-state/code/cleanup-scanner.ts deleted file mode 100644 index 6b35905c..00000000 --- a/courses/learn-harness-engineering/docs/de/lectures/lecture-12-why-every-session-must-leave-a-clean-state/code/cleanup-scanner.ts +++ /dev/null @@ -1,336 +0,0 @@ -/** - * cleanup-scanner.ts - * - * Scans a project directory for stale artifacts, dead code, structural - * violations, and outputs a cleanup report. Helps enforce the "clean state - * at end of every session" principle. - * - * Usage: - * npx tsx docs/lectures/lecture-12.../code/cleanup-scanner.ts [path] - * (defaults to current working directory) - * - * Run: npx tsx docs/lectures/lecture-12-why-every-session-must-leave-a-clean-state/code/cleanup-scanner.ts - */ - -import * as fs from "node:fs"; -import * as path from "node:path"; - -// --------------------------------------------------------------------------- -// Types -// --------------------------------------------------------------------------- - -interface ScanResult { - category: string; - check: string; - severity: "critical" | "warning" | "info"; - found: string[]; - description: string; -} - -// --------------------------------------------------------------------------- -// Scanner checks -// --------------------------------------------------------------------------- - -interface ScannerCheck { - category: string; - name: string; - severity: "critical" | "warning" | "info"; - description: string; - scan: (dir: string) => string[]; -} - -function createChecks(): ScannerCheck[] { - return [ - { - category: "Stale Artifacts", - name: "Temporary files (*.tmp, *.bak, *.swp)", - severity: "warning", - description: "Leftover temp files from editing or processing", - scan: (dir) => findFiles(dir, [".tmp", ".bak", ".swp", "~"]), - }, - { - category: "Stale Artifacts", - name: "Debug/log files in source", - severity: "warning", - description: "Log files that should not be in the source tree", - scan: (dir) => { - const found: string[] = []; - const logPatterns = [".log"]; - for (const p of findFiles(dir, logPatterns)) { - // Only flag log files in source directories - if (p.includes("/src/") || p.includes("/lib/") || p.includes("/app/")) { - found.push(p); - } - } - return found; - }, - }, - { - category: "Dead Code", - name: "Unused imports (placeholder detection)", - severity: "info", - description: "Files that may contain unused code (heuristic scan)", - scan: (dir) => { - const found: string[] = []; - const srcDirs = ["src", "lib", "app"]; - for (const srcDir of srcDirs) { - const srcPath = path.join(dir, srcDir); - if (fs.existsSync(srcPath)) { - scanForPatterns(srcPath, ["TODO:", "FIXME:", "HACK:", "XXX:"], found, dir); - } - } - return found; - }, - }, - { - category: "Structural Violations", - name: "Missing .gitignore", - severity: "warning", - description: "No .gitignore file found -- risk of committing build artifacts", - scan: (dir) => fs.existsSync(path.join(dir, ".gitignore")) ? [] : [".gitignore (MISSING)"], - }, - { - category: "Structural Violations", - name: "Build artifacts in source", - severity: "critical", - description: "Compiled output (dist/, build/) mixed with source", - scan: (dir) => { - const found: string[] = []; - // Check if dist/ or build/ exists inside src/ - const dirs = ["src/dist", "src/build", "lib/dist", "lib/build"]; - for (const d of dirs) { - if (fs.existsSync(path.join(dir, d))) { - found.push(d + "/"); - } - } - return found; - }, - }, - { - category: "Structural Violations", - name: "node_modules in source tree", - severity: "critical", - description: "node_modules directory found outside root (nested dependency)", - scan: (dir) => { - const found: string[] = []; - const srcDirs = ["src", "lib", "app", "test", "tests"]; - for (const srcDir of srcDirs) { - const nmPath = path.join(dir, srcDir, "node_modules"); - if (fs.existsSync(nmPath)) { - found.push(srcDir + "/node_modules/"); - } - } - return found; - }, - }, - { - category: "Session Cleanliness", - name: "Uncommitted changes indicator", - severity: "info", - description: "Checks for common artifacts of incomplete sessions", - scan: (dir) => { - const found: string[] = []; - const indicators = [ - "WIP.md", "IN_PROGRESS.md", "scratch.ts", "scratch.js", - "debug.ts", "test-manual.ts", "temp.ts", - ]; - for (const f of indicators) { - if (fs.existsSync(path.join(dir, f))) { - found.push(f); - } - } - return found; - }, - }, - { - category: "Session Cleanliness", - name: "Empty directories", - severity: "info", - description: "Empty directories that may be leftover from incomplete work", - scan: (dir) => { - const found: string[] = []; - const commonDirs = ["src", "lib", "app", "test", "tests", "docs"]; - for (const d of commonDirs) { - const dp = path.join(dir, d); - if (fs.existsSync(dp) && fs.statSync(dp).isDirectory()) { - try { - const contents = fs.readdirSync(dp); - if (contents.length === 0) { - found.push(d + "/"); - } - } catch { - // Skip directories we can't read - } - } - } - return found; - }, - }, - { - category: "Configuration", - name: "Environment files in source", - severity: "critical", - description: ".env files that may contain secrets", - scan: (dir) => { - const found: string[] = []; - const envFiles = [".env", ".env.local", ".env.production", ".env.staging"]; - for (const f of envFiles) { - if (fs.existsSync(path.join(dir, f))) { - found.push(f); - } - } - return found; - }, - }, - ]; -} - -// --------------------------------------------------------------------------- -// Helper functions -// --------------------------------------------------------------------------- - -function findFiles(dir: string, extensions: string[]): string[] { - const found: string[] = []; - - function walk(current: string, depth: number): void { - if (depth > 4) return; // Limit recursion depth - try { - const entries = fs.readdirSync(current, { withFileTypes: true }); - for (const entry of entries) { - // Skip common non-source directories - if (entry.isDirectory() && ["node_modules", ".git", "dist", "build", ".next", "coverage"].includes(entry.name)) { - continue; - } - - const fullPath = path.join(current, entry.name); - const relativePath = path.relative(dir, fullPath); - - if (entry.isDirectory()) { - walk(fullPath, depth + 1); - } else if (entry.isFile()) { - if (extensions.some((ext) => entry.name.endsWith(ext))) { - found.push(relativePath); - } - } - } - } catch { - // Skip directories we can't read - } - } - - walk(dir, 0); - return found; -} - -function scanForPatterns(dir: string, patterns: string[], results: string[], baseDir: string): void { - try { - const entries = fs.readdirSync(dir, { withFileTypes: true }); - for (const entry of entries) { - if (entry.isDirectory()) { - if (!["node_modules", ".git", "dist", "build"].includes(entry.name)) { - scanForPatterns(path.join(dir, entry.name), patterns, results, baseDir); - } - continue; - } - - const fullPath = path.join(dir, entry.name); - if (!entry.name.endsWith(".ts") && !entry.name.endsWith(".tsx") && !entry.name.endsWith(".js")) { - continue; - } - - try { - const content = fs.readFileSync(fullPath, "utf-8"); - const lines = content.split("\n"); - for (let i = 0; i < Math.min(lines.length, 50); i++) { - for (const pattern of patterns) { - if (lines[i].includes(pattern)) { - const relative = path.relative(baseDir, fullPath); - results.push(`${relative}:${i + 1} contains ${pattern}`); - } - } - } - } catch { - // Skip files we can't read - } - } - } catch { - // Skip directories we can't read - } -} - -// --------------------------------------------------------------------------- -// Reporting -// --------------------------------------------------------------------------- - -function pad(s: string, len: number): string { - return s.length >= len ? s : s + " ".repeat(len - s.length); -} - -function run(): void { - const targetDir = process.argv[2] ? path.resolve(process.argv[2]) : process.cwd(); - - console.log("\n" + "=".repeat(90)); - console.log(" CLEANUP SCANNER -- Stale Artifact & Violation Report"); - console.log("=".repeat(90)); - console.log(` Scanning: ${targetDir}\n`); - - const checks = createChecks(); - const results: ScanResult[] = []; - - for (const check of checks) { - const found = check.scan(targetDir); - results.push({ - category: check.category, - check: check.name, - severity: check.severity, - found, - description: check.description, - }); - } - - // Report - const header = `| ${pad("Category", 20)}| ${pad("Check", 40)}| ${pad("Severity", 10)}| ${pad("Count", 6)}| Details`; - const sep = `|${"-".repeat(22)}|${"-".repeat(42)}|${"-".repeat(12)}|${"-".repeat(8)}|${"-".repeat(30)}`; - console.log(header); - console.log(sep); - - for (const r of results) { - const count = r.found.length; - const detail = count > 0 ? r.found.slice(0, 2).join(", ") : "(clean)"; - const marker = count > 0 && r.severity === "critical" ? ">>" : count > 0 ? " * " : " "; - - console.log(`${marker}| ${pad(r.category, 20)}| ${pad(r.check, 40)}| ${pad(r.severity, 10)}| ${pad(String(count), 6)}| ${detail}`); - } - - // Summary - const criticals = results.filter((r) => r.severity === "critical" && r.found.length > 0); - const warnings = results.filter((r) => r.severity === "warning" && r.found.length > 0); - const infos = results.filter((r) => r.severity === "info" && r.found.length > 0); - const clean = results.filter((r) => r.found.length === 0); - - console.log("\n" + "=".repeat(90)); - console.log(" CLEANUP SUMMARY"); - console.log("=".repeat(90) + "\n"); - - console.log(` Critical issues: ${criticals.length}`); - console.log(` Warnings: ${warnings.length}`); - console.log(` Info items: ${infos.length}`); - console.log(` Clean checks: ${clean.length}/${results.length}`); - - if (criticals.length > 0) { - console.log("\n ACTION REQUIRED (Critical):"); - for (const c of criticals) { - console.log(` - ${c.check}: ${c.found.length} item(s) found`); - console.log(` ${c.description}`); - } - } - - const totalIssues = results.reduce((s, r) => s + r.found.length, 0); - if (totalIssues === 0) { - console.log("\n Project is in a clean state. No issues found.\n"); - } else { - console.log(`\n Total issues found: ${totalIssues}. Run this scanner at the end of every session to maintain clean state.\n`); - } -} - -run(); diff --git a/courses/learn-harness-engineering/docs/de/lectures/lecture-12-why-every-session-must-leave-a-clean-state/code/index.md b/courses/learn-harness-engineering/docs/de/lectures/lecture-12-why-every-session-must-leave-a-clean-state/code/index.md deleted file mode 100644 index 67281fd8..00000000 --- a/courses/learn-harness-engineering/docs/de/lectures/lecture-12-why-every-session-must-leave-a-clean-state/code/index.md +++ /dev/null @@ -1,8 +0,0 @@ -# Code für Vorlesung 12 - -Verwenden Sie diesen Ordner für Beispiele zu: - -- Benchmark-Slices -- Aufräumaufgaben -- Entropie-Reduktions-Beispielen -- Wiederholbaren Harness-Läufen diff --git a/courses/learn-harness-engineering/docs/de/lectures/lecture-12-why-every-session-must-leave-a-clean-state/index.md b/courses/learn-harness-engineering/docs/de/lectures/lecture-12-why-every-session-must-leave-a-clean-state/index.md deleted file mode 100644 index 4b283d2c..00000000 --- a/courses/learn-harness-engineering/docs/de/lectures/lecture-12-why-every-session-must-leave-a-clean-state/index.md +++ /dev/null @@ -1,184 +0,0 @@ -[中文版本 →](../../../zh/lectures/lecture-12-why-every-session-must-leave-a-clean-state/) - -> Codebeispiele: [code/](https://github.com/walkinglabs/learn-harness-engineering/blob/main/docs/de/lectures/lecture-12-why-every-session-must-leave-a-clean-state/code/) -> Praxisprojekt: [Project 06. Vollständiger Harness (Capstone)](./../../projects/project-06-runtime-observability-and-debugging/index.md) - -# Lektion 12. Sauberes Handoff am Ende jeder Session - -Ihr Agent läuft den ganzen Nachmittag, ändert 20 Dateien, committet den Code, die Session endet. Die nächste Agenten-Session startet und stellt sofort fest: der Build ist kaputt, die Tests sind rot, temporäre Debug-Dateien sind überall, die Feature-Liste wurde nicht aktualisiert und der Fortschritt ist völlig unklar. Die neue Session verbringt ihre ersten 30 Minuten nur damit herauszufinden, „was die letzte Session eigentlich gemacht hat." - -Sowohl OpenAI als auch Anthropic stellen klar: **Langfristige Zuverlässigkeit hängt von operativer Disziplin ab, nicht nur vom Erfolg einzelner Läufe.** Die Qualität des Zustands beim Session-Exit bestimmt direkt die Effizienz der nächsten Session. Betrachten Sie es wie Git-Best-Practices — jeder Commit sollte eine atomare, kompilierbare Änderung sein, kein Haufen halbfertigen Codes. - -## Zentrale Konzepte - -- **Clean state**: Das System erfüllt am Session-Ende fünf Bedingungen — Build bestanden, Tests bestanden, Fortschritt dokumentiert, keine veralteten Artefakte, Startup-Pfad verfügbar. Fehlt eine davon, ist die Session nicht „abgeschlossen". -- **Session-Integrität**: Analog zu Datenbanktransaktionen — entweder vollständig committen und einen sauberen Zustand hinterlassen oder auf den letzten konsistenten Zustand zurückrollen. Kein Mittelweg. -- **Qualitätsdokument**: Ein aktives Artefakt, das kontinuierlich Qualitätsbewertungen für jedes Modul aufzeichnet. Keine einmalige Bewertung, sondern ein Tracker, der zeigt, ob die Codebase im Laufe der Zeit stärker oder schwächer wird. -- **Cleanup-Schleife**: Eine regelmäßige Wartungssession, die darauf abzielt, die Entropie in der Codebase systematisch zu reduzieren. Keine Notfallreparatur, sondern routinemäßiger Betrieb. -- **Harness-Vereinfachung**: Wenn sich die Modellfähigkeiten verbessern, regelmäßig Harness-Komponenten entfernen, die nicht mehr notwendig sind. Eine Einschränkung, die heute essenziell ist, kann in drei Monaten unnötiger Overhead sein. -- **Idempotenter Cleanup**: Cleanup-Operationen liefern dasselbe Ergebnis, unabhängig davon, wie oft sie ausgeführt werden. Stellt sicher, dass Cleanup auch in Fehler-Wiederholungs-Szenarien sicher bleibt. - -## Fünf Dimensionen des Clean State - -```mermaid -flowchart LR - Work["Feature work complete"] --> Build{"Build passes?"} - Build -->|yes| Test{"Tests pass?"} - Build -->|no| Fix["Fix before exit"] - Test -->|yes| Record["Update feature list + progress"] - Test -->|no| Fix - Record --> Cleanup["Remove temp artifacts / debug code"] - Cleanup --> Startup{"Standard startup path works?"} - Startup -->|yes| Clean["Clean handoff"] - Startup -->|no| Fix - Fix --> Build -``` - -```mermaid -flowchart LR - Dirty["Session ends with
red tests / temp files / no progress update"] --> Diagnose["Next session first has to
figure out what happened"] - Diagnose --> Fragile["New work starts on a messy repo"] - Fragile --> More["More debug files, more broken checks,
more unclear progress"] - More --> Dirty - - Clean["Session ends with
green tests / updated progress / temp files removed"] --> Fast["Next session can start coding immediately"] - Fast --> Stable["No need to rescue the repo first"] - Stable --> Clean -``` - -## Warum das passiert - -### Entropiewachstum ist der Standardzustand - -Lehmans Gesetze der Software-Evolution sagen uns: Systeme, die kontinuierlichen Änderungen unterliegen, werden unausweichlich an Komplexität zunehmen, es sei denn, sie werden aktiv verwaltet. Dies gilt insbesondere für KI-Coding-Agenten — jede Session führt Änderungen ein, und ohne Cleanup beim Exit akkumuliert sich technische Schuld exponentiell. - -Echte Daten sind aussagekräftig. Ein Projekt, das 12 Wochen lang mit Agenten entwickelt wurde, ohne Cleanup-Strategie: - -- Woche 1: Build-Pass-Rate 100%, Test-Pass-Rate 100%, neue Session Startup 5 Min. -- Woche 4: Build 95%, Tests 92%, Startup 15 Min. -- Woche 8: Build 82%, Tests 78%, Startup 35 Min. -- Woche 12: Build 68%, Tests 61%, Startup 60+ Min. - -Dasselbe Projekt mit einer Cleanup-Strategie: - -- Woche 1: 100%, 100%, 5 Min. -- Woche 12: 97%, 95%, 9 Min. - -Nach 12 Wochen: Die Build-Pass-Rate unterscheidet sich um 29 Prozentpunkte, die Startup-Zeit für neue Sessions um 85%. Das ist nicht theoretisch — es ist ein beobachteter Unterschied. - -### Fünf Dimensionen des Clean State - -Clean State bedeutet nicht nur „der Code kompiliert". Es sind fünf Dimensionen, die zusammen bewertet werden: - -**Build-Dimension**: Lässt sich der Code fehlerfrei bauen? Das ist das Grundlegendste — die nächste Session sollte nicht zuerst Build-Fehler beheben müssen. - -**Test-Dimension**: Bestehen alle Tests? Einschließlich der Tests, die vor der Session existierten — die Session ist dafür verantwortlich, bestehende Funktionalität nicht zu beschädigen. Und es sollte in CI verifiziert werden, nicht nur „funktioniert auf meinem Rechner". - -**Fortschritts-Dimension**: Ist der aktuelle Fortschritt in einem maschinenlesbaren Artefakt dokumentiert? Abgeschlossene Teilaufgaben mit ihren Bestehenskriterien, in Bearbeitung befindliche aber unvollständige Teilaufgaben mit aktuellem Zustand, noch nicht gestartete Teilaufgaben. Gute Fortschrittsaufzeichnungen reduzieren 60-80% der Diagnosezeit beim Session-Startup. - -**Artefakt-Dimension**: Gibt es veraltete oder mehrdeutige temporäre Artefakte? Debug-Logs, temporäre Dateien, auskommentierter Code, TODO-Marker — all diese erhöhen die kognitive Belastung für die nächste Session. - -**Startup-Dimension**: Ist der Standard-Startup-Pfad verfügbar? Kann die nächste Session ohne manuelles Eingreifen mit der Arbeit beginnen? Umgebungsinitialisierung, Codebase-Laden, Kontextakquise, Aufgabenauswahl — diese Pfade dürfen nicht beschädigt sein. - -### „Später aufräumen" bedeutet niemals aufräumen - -Die häufigste mentale Falle ist „keine Zeit zum Aufräumen in dieser Session, das mache ich das nächste Mal." Aber die nächste Agenten-Session weiß nicht, was Sie hinterlassen haben — sie sieht ein Chaos aus Code und unsicherem Zustand. Sie wird erhebliche Zeit damit verbringen zu erschließen, „welche Teile dieses Codes beabsichtigt sind und welche temporär." - -Schlimmer noch: Jede Session hat ihre eigenen Aufgabenziele. Die neue Session ist da, um neue Arbeit zu erledigen, nicht das Chaos der vorherigen Session aufzuräumen. Sie wird das Chaos ignorieren und neue Arbeit darauf aufbauen, was mehr Chaos über Chaos einführt. Das ist die positive Rückkopplungsschleife der Entropie. - -## Wie man es richtig macht - -### 1. Clean State als Abschlussvoraussetzung - -Definieren Sie explizit im Harness: **Session-Abschluss = Aufgabe besteht die Verifikation UND Clean-State-Check besteht.** Fehlt eines von beiden, ist die Session nicht abgeschlossen. Schreiben Sie in CLAUDE.md: - -``` -## Session Exit Checklist -- [ ] Build passes (npm run build) -- [ ] All tests pass (npm test) -- [ ] Feature list updated -- [ ] No debug code remaining (console.log, debugger, TODO) -- [ ] Standard startup path available (npm run dev) -``` - -### 2. Zwei-Modi-Cleanup-Strategie - -Kombinieren Sie zwei Cleanup-Modi: - -**Sofortiger Cleanup (am Ende jeder Session)**: Temporäre Artefakte bereinigen, die während der Session erstellt wurden, Feature-Liste-Status aktualisieren, sicherstellen, dass Build und Tests bestehen. Das ist „Reference-Counting"-Cleanup. - -**Periodischer Cleanup (wöchentlich)**: Vollständiger System-Scan — akkumulierte strukturelle Probleme behandeln, Qualitätsdokumente aktualisieren, Benchmark-Tests ausführen, um Drift zu erkennen. Das ist „Tracing"-Cleanup. - -### 3. Ein Qualitätsdokument pflegen - -Ein Qualitätsdokument ist ein aktives Artefakt, das jedes Modul kontinuierlich bewertet: - -```markdown -# Quality Document - -## User Authentication Module (Quality: A) -- Verification passing: Yes -- Agent understandable: Yes -- Test stability: Stable -- Architecture boundaries: Compliant -- Code conventions: Followed - -## Payment Module (Quality: C) -- Verification passing: Partial (payment callback untested) -- Agent understandable: Difficult (logic spread across 3 files) -- Test stability: Unstable (2 flaky tests) -- Architecture boundaries: Violations present -- Code conventions: Partially followed -``` - -Neue Sessions lesen dieses Dokument und wissen sofort, wo Prioritäten gesetzt werden sollten. Das Modul mit der niedrigsten Bewertung zuerst reparieren. - -### 4. Den Harness regelmäßig vereinfachen - -Eine wichtige Erkenntnis von Anthropic: **Jede Harness-Komponente existiert, weil das Modell etwas nicht zuverlässig alleine kann. Aber wenn sich Modelle verbessern, veralten diese Annahmen.** Eine Einschränkung, die vor drei Monaten essenziell war, kann heute unnötiger Overhead sein. - -Empfohlene Praxis: Jeden Monat eine Harness-Komponente auswählen, vorübergehend deaktivieren und Benchmark-Aufgaben ausführen. Wenn die Ergebnisse nicht schlechter werden, dauerhaft entfernen. Wenn doch, wiederherstellen oder durch eine leichtere Alternative ersetzen. - -### 5. Cleanup-Operationen müssen idempotent sein - -Cleanup-Skripte sollten sicher sein, wiederholt ausgeführt zu werden: - -```bash -# Idempotent cleanup operations -rm -f /tmp/debug-*.log # -f ensures no error when files don't exist -git checkout -- .env.local # Restore to known state -npm run test # Verify cleanup didn't break anything -``` - -## Fallbeispiel aus der Praxis - -Eine Electron-App, die über 12 Wochen mit Agenten entwickelt wurde, im Vergleich zweier Ansätze: - -**Ohne Cleanup-Strategie** (Kontrollgruppe): Woche 12, Build-Pass-Rate 68%, Test-Pass-Rate 61%, neue Session Startup 60+ Min., veraltete Artefakte 103. - -**Mit Cleanup-Strategie** (Versuchsgruppe): Vollständiger Clean-State-Check am Ende jeder Session + wöchentliche Cleanup-Schleife. Woche 12, Build-Pass-Rate 97%, Test-Pass-Rate 95%, neue Session Startup 9 Min., veraltete Artefakte 11. - -In Woche 12 liegt die Build-Pass-Rate der Versuchsgruppe um 29 Prozentpunkte höher, die Test-Pass-Rate um 34 Punkte höher und die Startup-Zeit für neue Sessions um 85% niedriger. - -## Wichtigste Erkenntnisse - -- **Clean State ist eine notwendige Bedingung für den Session-Abschluss** — kein optionales Aufräumen, sondern Teil der „Definition of Done". -- **Alle fünf Dimensionen sind erforderlich** — Build, Tests, Fortschritt, Artefakte, Startup — jede muss explizit überprüft werden. -- **Qualitätsdokumente machen die Codebase-Gesundheit messbar** — man kann nur reparieren, von dem man weiß, dass es sich verschlechtert. -- **Den Harness regelmäßig vereinfachen** — wenn sich die Modellfähigkeiten verbessern, Einschränkungen entfernen, die nicht mehr benötigt werden. -- **„Später aufräumen" bedeutet niemals aufräumen** — Entropiewachstum ist der Standard; nur aktiver Cleanup wirkt dagegen. - -## Weiterführende Literatur - -- [Clean Code - Robert C. Martin](https://www.goodreads.com/book/show/3735293-clean-code) — Systematische Prinzipien von Code-Sauberkeit -- [Harness Engineering - OpenAI](https://openai.com/index/harness-engineering/) — Reproduzierbarkeit als Kernanforderung des Harness-Designs -- [Effective Harnesses - Anthropic](https://www.anthropic.com/engineering/effective-harnesses-for-long-running-agents) — Die kritische Rolle sauberer Session-Exits für langfristige Zuverlässigkeit -- [Programs, Life Cycles, and Laws of Software Evolution - Lehman](https://ieeexplore.ieee.org/document/1702314) — Software-Evolutionsgesetze, die beweisen, dass Systemkomplexität ohne aktive Wartung unausweichlich wächst - -## Übungen - -1. **Clean-State-Checkliste**: Entwerfen Sie eine Session-Exit-Checkliste für Ihre Codebase, die alle fünf Dimensionen abdeckt. Wenden Sie sie über 5 aufeinanderfolgende Sessions an und dokumentieren Sie Verstöße pro Dimension. - -2. **Benchmark-Vergleich**: Verwenden Sie einen festen Aufgabensatz mit zwei Harness-Varianten (mit/ohne Clean-State-Anforderungen). Vergleichen Sie Abschlussrate, Wiederholungsanzahl und Defekt-Escape-Rate. - -3. **Harness-Vereinfachung üben**: Wählen Sie eine Harness-Komponente aus, deaktivieren Sie sie vorübergehend und führen Sie Benchmark-Aufgaben durch. Vergleichen Sie die Ergebnisse mit und ohne sie. Entscheiden Sie, ob Sie sie beibehalten, entfernen oder ersetzen. diff --git a/courses/learn-harness-engineering/docs/de/projects/index.md b/courses/learn-harness-engineering/docs/de/projects/index.md deleted file mode 100644 index 23ec693d..00000000 --- a/courses/learn-harness-engineering/docs/de/projects/index.md +++ /dev/null @@ -1,23 +0,0 @@ -# Willkommen bei den Projekten - -Dies ist der praktische Teil von Learn Harness Engineering. Die Lektionen zu lesen reicht nicht aus: Du musst die Umgebungen selbst bauen und beobachten, wie Codex, Claude Code oder andere KI-Agenten unter verschiedenen Regeln reagieren. - -## Projektübersicht - -Der Kurs enthält 6 aufeinander aufbauende Praxisprojekte, die zeigen, wie man eine zuverlässige agentische Arbeitsumgebung von Grund auf baut: - -1. **Nur Prompt vs Regeln zuerst**: Vergleiche, wie ein Agent nur mit Prompt und mit einfachem harness arbeitet. -2. **Agent-lesbarer Workspace**: Lerne, ein Repository KI-freundlich zu strukturieren und Handoff-Mechanismen einzurichten. -3. **Kontinuität über Sessions**: Entwirf Zustandsdateien und Initialisierungsskripte, damit der Agent über Sessions hinweg nahtlos weiterarbeiten kann. -4. **Runtime-Feedback und Scope-Kontrolle**: Führe Werkzeuge ein, mit denen der Agent seinen eigenen Code testen und Fehler während der Ausführung korrigieren kann. -5. **Selbstverifikation und Rollentrennung**: Baue einen unabhängigen Review-Mechanismus, um Halluzinationen und zu frühe Erfolgsmeldungen zu vermeiden. -6. **Vollständiger harness (Capstone)**: Setze eine finale, beobachtbare, end-to-end agentische Arbeitsumgebung zusammen. - -## Vorgehen - -Jeder Projektordner enthält typischerweise: -- `starter/`: deinen Start-Workspace. -- `solution/`: eine Referenzimplementierung, falls du feststeckst. -- Aufgabenanweisungen mit Hintergrund und konkreten Zielen. - -Nutze deinen bevorzugten AI Coding Agent, zum Beispiel Claude Code, Cursor oder Trae, um die Aufgaben im Verzeichnis `starter/` abzuschließen. diff --git a/courses/learn-harness-engineering/docs/de/projects/project-01-baseline-vs-minimal-harness/index.md b/courses/learn-harness-engineering/docs/de/projects/project-01-baseline-vs-minimal-harness/index.md deleted file mode 100644 index b133f097..00000000 --- a/courses/learn-harness-engineering/docs/de/projects/project-01-baseline-vs-minimal-harness/index.md +++ /dev/null @@ -1,25 +0,0 @@ -[中文版本 →](../../../zh/projects/project-01-baseline-vs-minimal-harness/) - -> Zugehörige Lektionen: [Lektion 01. Starke Modelle bedeuten keine zuverlässige Ausführung](./../../lectures/lecture-01-why-capable-agents-still-fail/index.md) · [Lektion 02. Was harness wirklich bedeutet](./../../lectures/lecture-02-what-a-harness-actually-is/index.md) -> Vorlagendateien: [templates/](https://github.com/walkinglabs/learn-harness-engineering/blob/main/docs/de/resources/templates/) - -# Projekt 01. Nur Prompt vs Regeln zuerst: Wie groß ist der Unterschied? - -## Was du tust - -Baue das minimale Gerüst einer Electron-Wissensdatenbank-App: ein Fenster mit Dokumentliste links, Q&A-Panel rechts und lokalem Datenverzeichnis. Die Aufgabe selbst ist nicht komplex. Komplex ist, wie du den Agenten dazu bringst, sie zuverlässig abzuschließen. - -Du führst sie zweimal aus. Beim ersten Mal: nur ein Prompt, keine Vorbereitung. Beim zweiten Mal: `AGENTS.md`, `init.sh` und `feature_list.json` liegen bereits im Repository. Danach vergleichst du. - -Der Kern dieses Projekts ist nicht das Schreiben von Code, sondern herauszufinden, wie groß der Abstand zwischen "15 Minuten Regeln vorbereiten" und "den Agenten einfach loslaufen lassen" ist. - -## Werkzeuge - -- Claude Code oder Codex (wähle eines und nutze es für beide Läufe) -- Git (Branches verwalten und vergleichen) -- Node.js + Electron (Projektstack) -- Ein Timer (Dauer jedes Laufs erfassen) - -## Harness-Mechanismus - -Minimaler harness: `AGENTS.md` + `init.sh` + `feature_list.json` diff --git a/courses/learn-harness-engineering/docs/de/projects/project-02-agent-readable-workspace/index.md b/courses/learn-harness-engineering/docs/de/projects/project-02-agent-readable-workspace/index.md deleted file mode 100644 index 87e3c40a..00000000 --- a/courses/learn-harness-engineering/docs/de/projects/project-02-agent-readable-workspace/index.md +++ /dev/null @@ -1,22 +0,0 @@ -[中文版本 →](../../../zh/projects/project-02-agent-readable-workspace/) - -> Zugehörige Lektionen: [Lektion 03. Mache das Repository zur einzigen Quelle der Wahrheit](./../../lectures/lecture-03-why-the-repository-must-become-the-system-of-record/index.md) · [Lektion 04. Teile Anweisungen auf mehrere Dateien auf](./../../lectures/lecture-04-why-one-giant-instruction-file-fails/index.md) -> Vorlagendateien: [templates/](https://github.com/walkinglabs/learn-harness-engineering/blob/main/docs/de/resources/templates/) - -# Projekt 02. Das Projekt lesbar machen und dort weiterarbeiten, wo du aufgehört hast - -## Was du tust - -Füge dem Repository "Lesbarkeit" hinzu, damit ein neuer Agent die Projektstruktur schnell versteht, den aktuellen Fortschritt kennt und Arbeit aufnehmen kann. Konkret: Implementiere Dokumentimport, Detailansicht und lokale Persistenz über zwei Sessions hinweg. - -Du führst es zweimal aus: zuerst ohne Hilfe, dann mit bereits abgelegten `ARCHITECTURE.md`, `PRODUCT.md` und `session-handoff.md`. - -## Werkzeuge - -- Claude Code oder Codex -- Git -- Node.js + Electron - -## Harness-Mechanismus - -Agent-lesbarer Workspace + persistente Zustandsdateien diff --git a/courses/learn-harness-engineering/docs/de/projects/project-03-multi-session-continuity/index.md b/courses/learn-harness-engineering/docs/de/projects/project-03-multi-session-continuity/index.md deleted file mode 100644 index 4d7b1531..00000000 --- a/courses/learn-harness-engineering/docs/de/projects/project-03-multi-session-continuity/index.md +++ /dev/null @@ -1,22 +0,0 @@ -[中文版本 →](../../../zh/projects/project-03-multi-session-continuity/) - -> Zugehörige Lektionen: [Lektion 05. Kontext über Sessions hinweg lebendig halten](./../../lectures/lecture-05-why-long-running-tasks-lose-continuity/index.md) · [Lektion 06. Vor jeder Agenten-Session initialisieren](./../../lectures/lecture-06-why-initialization-needs-its-own-phase/index.md) -> Vorlagendateien: [templates/](https://github.com/walkinglabs/learn-harness-engineering/blob/main/docs/de/resources/templates/) - -# Projekt 03. Den Agenten über Session-Neustarts hinweg weiterarbeiten lassen - -## Was du tust - -Füge dem Agenten Scope-Kontrolle und Verifikationsgates hinzu. Implementiere Dokument-Chunking, Metadatenextraktion, Indexierungsfortschritt und einen Q&A-Flow mit Zitaten. Nutze `feature_list.json`, um Feature-Status zu verfolgen: ein Feature nach dem anderen, kein `pass` ohne Verifikationsnachweis. - -Du führst es zweimal aus: zuerst ohne Einschränkungen, dann mit strikter Durchsetzung. - -## Werkzeuge - -- Claude Code oder Codex -- Git -- Node.js + Electron - -## Harness-Mechanismus - -Fortschrittslog + Session-Handoff + Multi-Session-Kontinuität diff --git a/courses/learn-harness-engineering/docs/de/projects/project-04-incremental-indexing/index.md b/courses/learn-harness-engineering/docs/de/projects/project-04-incremental-indexing/index.md deleted file mode 100644 index 9489dbb8..00000000 --- a/courses/learn-harness-engineering/docs/de/projects/project-04-incremental-indexing/index.md +++ /dev/null @@ -1,22 +0,0 @@ -[中文版本 →](../../../zh/projects/project-04-incremental-indexing/) - -> Zugehörige Lektionen: [Lektion 07. Klare Aufgabengrenzen für Agenten ziehen](./../../lectures/lecture-07-why-agents-overreach-and-under-finish/index.md) · [Lektion 08. Feature-Listen nutzen, um Agentenarbeit zu begrenzen](./../../lectures/lecture-08-why-feature-lists-are-harness-primitives/index.md) -> Vorlagendateien: [templates/](https://github.com/walkinglabs/learn-harness-engineering/blob/main/docs/de/resources/templates/) - -# Projekt 04. Runtime-Feedback nutzen, um Agentenverhalten zu korrigieren - -## Was du tust - -Füge Runtime-Beobachtbarkeit hinzu, etwa Startlogs, Import-/Indexierungslogs und Fehlerzustände, sowie Architekturregeln, um Verstöße zwischen Schichten zu verhindern. Baue einen Runtime-Bug ein, den der Agent beheben soll. - -Du führst es zweimal aus: zuerst ohne Logs oder Regeln, dann mit passenden Werkzeugen und Regeln. - -## Werkzeuge - -- Claude Code oder Codex -- Git -- Node.js + Electron - -## Harness-Mechanismus - -Runtime-Feedback + Scope-Kontrolle + inkrementelle Indexierung diff --git a/courses/learn-harness-engineering/docs/de/projects/project-05-grounded-qa-verification/index.md b/courses/learn-harness-engineering/docs/de/projects/project-05-grounded-qa-verification/index.md deleted file mode 100644 index 7b20e17b..00000000 --- a/courses/learn-harness-engineering/docs/de/projects/project-05-grounded-qa-verification/index.md +++ /dev/null @@ -1,22 +0,0 @@ -[中文版本 →](../../../zh/projects/project-05-grounded-qa-verification/) - -> Zugehörige Lektionen: [Lektion 09. Agenten daran hindern, zu früh Erfolg zu melden](./../../lectures/lecture-09-why-agents-declare-victory-too-early/index.md) · [Lektion 10. Nur ein vollständiger Pipeline-Lauf zählt als echte Verifikation](./../../lectures/lecture-10-why-end-to-end-testing-changes-results/index.md) -> Vorlagendateien: [templates/](https://github.com/walkinglabs/learn-harness-engineering/blob/main/docs/de/resources/templates/) - -# Projekt 05. Den Agenten seine eigene Arbeit verifizieren lassen - -## Was du tust - -Implementiere Rollentrennung: einen generator für die Umsetzung, einen evaluator für die Prüfung und optional einen planner. Führe drei Läufe durch, um den Effekt jeder zusätzlichen Rolle zu messen. - -Wähle eine substanzielle Feature-Erweiterung, etwa Multi-Turn-Konversation, Neugestaltung des Zitat-Panels oder Dokumentfilterung, und halte sie über alle Läufe hinweg konstant. - -## Werkzeuge - -- Claude Code oder Codex -- Git -- Node.js + Electron - -## Harness-Mechanismus - -Selbstverifikation + begründete Q&A + evidenzbasierter Abschluss diff --git a/courses/learn-harness-engineering/docs/de/projects/project-06-runtime-observability-and-debugging/index.md b/courses/learn-harness-engineering/docs/de/projects/project-06-runtime-observability-and-debugging/index.md deleted file mode 100644 index e59f71d0..00000000 --- a/courses/learn-harness-engineering/docs/de/projects/project-06-runtime-observability-and-debugging/index.md +++ /dev/null @@ -1,25 +0,0 @@ -[中文版本 →](../../../zh/projects/project-06-runtime-observability-and-debugging/) - -> Zugehörige Lektionen: [Lektion 11. Die Runtime des Agenten beobachtbar machen](./../../lectures/lecture-11-why-observability-belongs-inside-the-harness/index.md) · [Lektion 12. Sauberes Handoff am Ende jeder Session](./../../lectures/lecture-12-why-every-session-must-leave-a-clean-state/index.md) -> Vorlagendateien: [templates/](https://github.com/walkinglabs/learn-harness-engineering/blob/main/docs/de/resources/templates/) - -# Projekt 06. Einen vollständigen Agenten-harness bauen (Capstone) - -## Was du tust - -Dies ist das Abschlussprojekt. Setze alles zusammen, was du in den ersten fünf Projekten gelernt hast, führe einen vollständigen Benchmark aus und mache danach einen Cleanup-Pass, um zu prüfen, ob die Qualität wartbar bleibt. - -Nutze ein festes Multi-Feature-Aufgabenset, das einen vollständigen Produktschnitt abdeckt: Dokumentimport, Indexierung, Q&A mit Zitaten, Runtime-Beobachtbarkeit und einen lesbaren, wiederaufnehmbaren Repository-Zustand. Führe zuerst einen schwachen harness-Baseline-Lauf aus, dann deinen stärksten harness, danach Cleanup und erneuten Lauf. Zum Schluss machst du ein harness-Ablationsexperiment: Entferne jeweils eine Komponente und beobachte, welche wirklich wichtig sind. - -## Werkzeuge - -- Claude Code oder Codex -- Git -- Node.js + Electron -- Vorlage für Qualitätsdokument -- Evaluator-Rubrik -- Alle harness-Komponenten aus den ersten fünf Projekten - -## Harness-Mechanismus - -Vollständiger harness: alle Mechanismen + Beobachtbarkeit + Ablationsstudie diff --git a/courses/learn-harness-engineering/docs/de/resources/index.md b/courses/learn-harness-engineering/docs/de/resources/index.md deleted file mode 100644 index 9eb3b170..00000000 --- a/courses/learn-harness-engineering/docs/de/resources/index.md +++ /dev/null @@ -1,50 +0,0 @@ -# Deutsche Ressourcenbibliothek - -Dieser Ordner übersetzt die Kursmethoden in kopierfertige Vorlagen und kompakte Referenzen, die du in einem echten Repository verwenden kannst. - -## Wann du sie nutzt - -Beginne hier, wenn Codex, Claude Code oder ein anderer Coding-Agent über mehrere Sessions arbeiten soll, ohne Setup, Status und Scope ständig neu herzuleiten. - -Besonders nützlich ist das, wenn: - -- Arbeit über mehrere Sessions läuft -- viele Features vorhanden sind und leicht halb fertig bleiben -- Agenten zu früh Erfolg melden -- Startschritte jedes Mal neu gesucht werden - -## Hier starten - -Für eine minimale Einrichtung beginne mit: - -- Root-Anweisungen: [`templates/AGENTS.md`](./templates/AGENTS.md) oder [`templates/CLAUDE.md`](./templates/CLAUDE.md) -- Feature-Zustand: [`templates/feature_list.json`](./templates/feature_list.json) -- Fortschrittslog: [`templates/claude-progress.md`](./templates/claude-progress.md) -- Bootstrap-Skriptreferenz: `docs/de/resources/templates/init.sh` - -Ergänze danach: - -- Session-Handoff: [`templates/session-handoff.md`](./templates/session-handoff.md) -- Clean-Exit-Checkliste: [`templates/clean-state-checklist.md`](./templates/clean-state-checklist.md) -- Evaluator-Rubrik: [`templates/evaluator-rubric.md`](./templates/evaluator-rubric.md) - -Wenn du die vollständigere OpenAI-artige Repository-Struktur aus dem "Harness engineering"-Beitrag möchtest, nutze das erweiterte Paket: - -- [`openai-advanced/index.md`](./openai-advanced/index.md) - -## Struktur der Bibliothek - -- [`templates/`](./templates/index.md): Vorlagen zum Kopieren in ein echtes Repository -- [`reference/`](./reference/index.md): Methodennotizen, Startablauf und Karten von Fehlermodi -- [`openai-advanced/`](./openai-advanced/index.md): erweitertes Repository-Gerüst, system-of-record-Dokumente und agent-first Governance-Vorlagen - -## Empfohlenes Minimalpaket - -- `AGENTS.md` oder `CLAUDE.md` -- `feature_list.json` -- `claude-progress.md` -- `init.sh` - -Diese vier Dateien reichen aus, um die meisten Agenten-Workflows deutlich stabiler zu machen. - -Wenn das Repository zu einem länger laufenden System mit mehreren Domänen, aktiven Plänen, Qualitätsscores und Zuverlässigkeitsrichtlinien wächst, wechsle zum Paket [`openai-advanced/`](./openai-advanced/index.md), statt das Minimalpaket zu weit zu dehnen. diff --git a/courses/learn-harness-engineering/docs/de/resources/openai-advanced/index.md b/courses/learn-harness-engineering/docs/de/resources/openai-advanced/index.md deleted file mode 100644 index 1ba7f632..00000000 --- a/courses/learn-harness-engineering/docs/de/resources/openai-advanced/index.md +++ /dev/null @@ -1,83 +0,0 @@ -# OpenAI Advanced Pack - -Dieser Ordner verpackt die stärker meinungsbasierte Repository-Struktur, die in -OpenAIs Artikel "Harness engineering: leveraging Codex in an agent-first world" -beschrieben wird, in kopierfertige Starterdateien. - -Verwenden Sie dieses Pack, wenn das minimale Harness nicht mehr ausreicht und -Ihr Repository nun Folgendes benötigt: - -- eine kurze, routing-artige `AGENTS.md` -- dauerhafte System-of-Record-Dokumentation innerhalb des Repos -- aktive und abgeschlossene Ausführungspläne -- explizite Produkt-, Zuverlässigkeits-, Sicherheits- und Frontend-Richtliniendateien -- Qualitätsbewertung nach Produktdomäne und Architektur-Schicht -- modellfreundliche Referenzmaterial-Ordner -- Standardarbeitsanweisungen (SOPs) für Architektur, Wissenserfassung und Laufzeitvalidierung - -## Enthaltene Starter-Struktur - -Das Starter-Pack unter [`repo-template/`](./repo-template/index.md) spiegelt die -folgende Struktur wider: - -```text -AGENTS.md -ARCHITECTURE.md -docs/ -├── design-docs/ -│ ├── index.md -│ └── core-beliefs.md -├── exec-plans/ -│ ├── active/ -│ ├── completed/ -│ └── tech-debt-tracker.md -├── generated/ -│ └── db-schema.md -├── product-specs/ -│ ├── index.md -│ └── new-user-onboarding.md -├── references/ -│ ├── design-system-reference-llms.txt -│ ├── nixpacks-llms.txt -│ └── uv-llms.txt -├── DESIGN.md -├── FRONTEND.md -├── PLANS.md -├── PRODUCT_SENSE.md -├── QUALITY_SCORE.md -├── RELIABILITY.md -└── SECURITY.md -``` - -## Wie Sie es übernehmen - -1. Beginnen Sie mit dem minimalen Pack, wenn Ihr Repo noch klein ist. -2. Kopieren Sie die Dateien aus [`repo-template/`](./repo-template/index.md) in - Ihr eigenes Repository, sobald Sie eine stärkere Struktur benötigen. -3. Halten Sie `AGENTS.md` kurz. Behandeln Sie sie als Router zu den tieferen Docs, - nicht als Enzyklopädie. -4. Aktualisieren Sie die Qualitäts-, Zuverlässigkeits- und Plan-Dokumente als - Teil der normalen Arbeit, nicht an einem separaten Aufräumtag. -5. Halten Sie generierte Artefakte und externe Referenzen explizit, damit Agenten - sie finden können, ohne sich auf Chat-Verlauf zu verlassen. - -## SOP-Bibliothek - -Der Ordner [`sops/`](./sops/index.md) wandelt die Diagramme des Artikels in -schrittweise Arbeitsanweisungen um: - -- Einrichtung einer geschichteten Domänenarchitektur -- Codierung von unsichtbarem Wissen in das Repository -- lokaler Observability-Stack und Feedback-Schleifen-Workflow -- Chrome DevTools-Validierungsschleife für UI-Arbeit - -## Design-Prinzipien - -- Kurzer Einstiegspunkt, tiefer verlinkte Docs -- Repository als System of Record -- Mechanische Prüfungen schlagen erinnerte Regeln -- Pläne und Qualitätsverlauf leben neben dem Code -- Aufräumen und Vereinfachung sind Aufgaben erster Klasse - -Dieses Pack ist bewusst meinungsbasiert, sollte aber an Ihr Projekt angepasst -werden, anstatt blind kopiert zu werden. diff --git a/courses/learn-harness-engineering/docs/de/resources/openai-advanced/repo-template/AGENTS.md b/courses/learn-harness-engineering/docs/de/resources/openai-advanced/repo-template/AGENTS.md deleted file mode 100644 index 778ea190..00000000 --- a/courses/learn-harness-engineering/docs/de/resources/openai-advanced/repo-template/AGENTS.md +++ /dev/null @@ -1,57 +0,0 @@ -# AGENTS.md - -Dieses Repository ist für langlaufende Coding-Agent-Arbeit optimiert. Halten Sie diese Datei -kurz. Verwenden Sie sie als Routing-Schicht zu den System-of-Record-Docs, nicht als -gigantische Instruktionsablage. - -## Start-Workflow - -Bevor Code geändert wird: - -1. Das Repo-Root mit `pwd` bestätigen. -2. `ARCHITECTURE.md` für die aktuelle Systemübersicht und harten Abhängigkeitsregeln lesen. -3. `docs/QUALITY_SCORE.md` lesen, um zu sehen, welche Domänen oder Schichten am schwächsten sind. -4. `docs/PLANS.md` lesen, dann den aktiven Plan öffnen, an dem gearbeitet wird. -5. Die relevante Produktspezifikation in `docs/product-specs/` lesen. -6. Den Standard-Bootstrap- und Verifikationspfad für dieses Repo ausführen. -7. Wenn die Baseline-Verifikation fehlschlägt, die Baseline reparieren, bevor neuer Scope hinzugefügt wird. - -## Routing-Map - -- `ARCHITECTURE.md`: Domänen-Map, Schichtenmodell, Abhängigkeitsregeln -- `docs/design-docs/index.md`: Designentscheidungen und Kernüberzeugungen -- `docs/product-specs/index.md`: aktuelles Produktverhalten und Abnahmeziele -- `docs/PLANS.md`: Plan-Lebenszyklus und Ausführungsplan-Policy -- `docs/QUALITY_SCORE.md`: Produktdomänen- und Schichtengesundheit -- `docs/RELIABILITY.md`: Laufzeitsignale, Benchmarks und Neustart-Erwartungen -- `docs/SECURITY.md`: Secrets, Sandbox, Daten und externe Aktionsregeln -- `docs/FRONTEND.md`: UI-Einschränkungen, Designsystem-Regeln, Barrierefreiheits-Checks - -## Arbeitsvertrag - -- Von einem begrenzten Plan oder Feature-Slice gleichzeitig arbeiten. -- Arbeit nicht als fertig markieren nur aufgrund von Code-Inspektion; ausführbarer Nachweis ist erforderlich. -- Wenn Sie Verhalten ändern, die zugehörigen Produkt-, Plan- oder Zuverlässigkeitsdocs in derselben Session aktualisieren. -- Wenn Sie wiederholtes Review-Feedback sehen, es zu einer mechanischen Regel, einem Check oder Linter befördern, anstatt es im Chat erneut zu erklären. -- Generiertes Material in `docs/generated/` und Quellreferenzen in `docs/references/` aufbewahren. -- Kleine, aktuelle Docs hinzufügen anstatt diese Datei wachsen zu lassen. - -## Definition of Done - -Eine Änderung ist erst fertig, wenn all dies zutrifft: - -- Zielverhalten ist implementiert -- Erforderliche Verifikation wurde tatsächlich ausgeführt -- Nachweis ist vom relevanten Plan- oder Qualitätsdokument verlinkt -- Betroffene Docs bleiben aktuell -- Das Repository kann sauber über den Standard-Startpfad neugestartet werden - -## Session-Ende - -Vor dem Beenden einer Session: - -1. Den aktiven Ausführungsplan aktualisieren. -2. `docs/QUALITY_SCORE.md` aktualisieren, falls sich eine Domäne oder Schicht signifikant geändert hat. -3. Neue Schulden in `docs/exec-plans/tech-debt-tracker.md` dokumentieren, falls zurückgestellt. -4. Fertige Planks nach `docs/exec-plans/completed/` verschieben, wenn angemessen. -5. Das Repo in einem neustartfähigen Zustand mit klarer nächster Aktion hinterlassen. diff --git a/courses/learn-harness-engineering/docs/de/resources/openai-advanced/repo-template/ARCHITECTURE.md b/courses/learn-harness-engineering/docs/de/resources/openai-advanced/repo-template/ARCHITECTURE.md deleted file mode 100644 index c65ad234..00000000 --- a/courses/learn-harness-engineering/docs/de/resources/openai-advanced/repo-template/ARCHITECTURE.md +++ /dev/null @@ -1,57 +0,0 @@ -# ARCHITECTURE.md - -Diese Datei ist die Top-Level-Übersicht des Systems. Sie sollte prägnant bleiben und -auf tiefere Dokumente verweisen, wenn nötig. - -## Systemform - -- Produkt: `[mit Produktnamen ersetzen]` -- Primärer Benutzer-Workflow: `[mit Hauptworkflow ersetzen]` -- Laufzeitoberflächen: `[Desktop / Web / CLI / Services / Worker]` -- Source of Truth für Produktverhalten: `docs/product-specs/` - -## Domänen-Map - -| Domäne | Zweck | Primäre Einstiegspunkte | Zugehörige Spec | -|--------|-------|------------------------|----------------| -| `[domäne-a]` | `[was sie besitzt]` | `[Module / Routen / Befehle]` | `[Spec-Pfad]` | -| `[domäne-b]` | `[was sie besitzt]` | `[Module / Routen / Befehle]` | `[Spec-Pfad]` | - -## Schichtenmodell - -Verwenden Sie ein festes gerichtetes Modell, damit Agenten keine ad-hoc-Architektur erfinden: - -`Types -> Config -> Repo -> Service -> Runtime -> UI` - -Querschnittliche Belange sollten über explizite Provider- oder Adapter-Grenzen -eintreten, anstatt Schichten direkt zu überspringen. - -## Harte Abhängigkeitsregeln - -- Untere Schichten dürfen nicht von oberen Schichten abhängen. -- UI darf Runtime- oder Service-Verträge nicht umgehen. -- Datenzugriff muss über Repositories oder äquivalente Adapter erfolgen. -- Gemeinsame Utilities müssen generisch bleiben und dürfen keine Domänenlogik ansammeln. -- Neue Abhängigkeiten sollten im entsprechenden Plan oder Design-Doc begründet werden. - -## Querschnittsschnittstellen - -| Belang | Genehmigte Grenze | Notizen | -|--------|-------------------|---------| -| Logging und Tracing | `[Provider / Utility-Pfad]` | `[nur strukturiert, kein ad-hoc Console-Use]` | -| Auth | `[Provider-Pfad]` | `[Token/Session-Regeln]` | -| Externe APIs | `[Client oder Provider-Pfad]` | `[Rate-Limit / Retry-Guidance]` | -| Feature-Flags | `[Flag-Grenze]` | `[Ownership]`` | - -## Aktuelle Hot Spots - -- `[Bereich, der für Agenten am schwersten sicher zu ändern ist]` -- `[Bereich mit schwachen Grenzen oder fragilen Tests]` - -## Änderungs-Checkliste - -Wenn Sie architekturrelevanten Code berühren: - -1. Diese Datei aktualisieren, falls sich die Domänen-Map oder erlaubten Grenzen geändert haben. -2. Das zugehörige Design-Doc in `docs/design-docs/` aktualisieren, falls sich die Begründung geändert hat. -3. Einen ausführbaren Check hinzufügen oder aktualisieren, falls die Regel mechanisch durchgesetzt werden soll. diff --git a/courses/learn-harness-engineering/docs/de/resources/openai-advanced/repo-template/docs/DESIGN.md b/courses/learn-harness-engineering/docs/de/resources/openai-advanced/repo-template/docs/DESIGN.md deleted file mode 100644 index 2a89971f..00000000 --- a/courses/learn-harness-engineering/docs/de/resources/openai-advanced/repo-template/docs/DESIGN.md +++ /dev/null @@ -1,28 +0,0 @@ -# DESIGN.md - -Diese Datei ist der Design-Einstiegspunkt. Halten Sie sie kurz und verwenden Sie sie, -um zu den detaillierteren Dateien unter `docs/design-docs/` zu routen. - -## Zweck - -Dauerhafte Produkt- und Systemdesignentscheidungen dokumentieren, die einen einzelnen -Chat, Sprint oder die Erinnerung eines Reviewers überdauern sollten. - -## Lesen Sie dies, wenn - -- Sie die aktuelle Designphilosophie benötigen -- Sie dabei sind, ein neues Muster einzuführen -- Sie wissen müssen, welche Designentscheidungen endgültig sind und welche noch offen sind - -## Kanonische Design-Dokumente - -- `docs/design-docs/index.md`: Index der akzeptierten, vorgeschlagenen und veralteten Dokumente -- `docs/design-docs/core-beliefs.md`: projektweite Agenten-first-Überzeugungen - -## Designregeln - -- Halten Sie Design-Dokumente klein und aktuell. -- Bevorzugen Sie ein Dokument pro Entscheidungsbereich. -- Verknüpfen Sie Design-Dokumente aus Plänen und Spezifikationen, wenn eine Änderung von ihnen abhängt. -- Wenn eine Designregel operationell kritisch wird, zu einer automatisierten Prüfung - befördern oder `ARCHITECTURE.md` aktualisieren. diff --git a/courses/learn-harness-engineering/docs/de/resources/openai-advanced/repo-template/docs/FRONTEND.md b/courses/learn-harness-engineering/docs/de/resources/openai-advanced/repo-template/docs/FRONTEND.md deleted file mode 100644 index 441fe8c1..00000000 --- a/courses/learn-harness-engineering/docs/de/resources/openai-advanced/repo-template/docs/FRONTEND.md +++ /dev/null @@ -1,24 +0,0 @@ -# FRONTEND.md - -Diese Datei definiert stabile Frontend-Erwartungen, damit Agenten nicht unvorhersehbar -UI-Muster erfinden. - -## UI-Prinzipien - -- Optimieren Sie auf Klarheit vor Neuheit. -- Halten Sie Interaktionsabläufe auffindbar und neustartbar. -- Bevorzugen Sie eine kleine Anzahl wiederverwendbarer Komponenten gegenüber einmaligen Varianten. -- Barrierefreiheitsprüfungen sind Teil der normalen Verifikation, keine Polierarbeit. - -## Leitplanken - -- Dokumentieren Sie das Design-System oder die Komponentenbibliothek in `docs/references/`. -- Erfassen Sie wichtige benutzerseitige Zustände: leer, laden, Erfolg, Fehler, erneut versuchen. -- Halten Sie Text, Tastaturverhalten und visuelle Hierarchie über alle Abläufe hinweg konsistent. -- Wenn ein UI-Fehler behoben wird, den zugehörigen Validierungsschritt hinzufügen oder aktualisieren. - -## Verifikationserwartungen - -- Beweise für kritische Benutzer-Journeys erfassen. -- Browser- oder Laufzeit-Validierungsschritte im entsprechenden Plan dokumentieren. -- Wenn visuelle Regressionen häufig auftreten, Screenshot- oder DOM-Prüfungen standardisieren. diff --git a/courses/learn-harness-engineering/docs/de/resources/openai-advanced/repo-template/docs/PLANS.md b/courses/learn-harness-engineering/docs/de/resources/openai-advanced/repo-template/docs/PLANS.md deleted file mode 100644 index d87dedfb..00000000 --- a/courses/learn-harness-engineering/docs/de/resources/openai-advanced/repo-template/docs/PLANS.md +++ /dev/null @@ -1,37 +0,0 @@ -# PLANS.md - -Diese Datei definiert, wie Ausführungspläne erstellt, aktualisiert, abgeschlossen -und archiviert werden. - -## Wann ein Plan erforderlich ist - -Erstellen Sie einen Ausführungsplan, wenn die Arbeit: - -- mehr als eine Sitzung umfasst -- mehr als ein Subsystem verändert -- nicht-triviale Verifikations- oder Rollout-Risiken birgt -- von offenen Entscheidungen abhängt, die dokumentiert werden sollten - -## Plan-Speicherorte - -- `docs/exec-plans/active/`: Pläne, die aktuell die Arbeit steuern -- `docs/exec-plans/completed/`: abgeschlossene Pläne, die für zukünftigen Agenten-Kontext behalten werden -- `docs/exec-plans/tech-debt-tracker.md`: aufgeschobene Arbeit und Folgeaufgaben - -## Mindest-Abschnitte eines Plans - -- Zielsetzung -- Umfang und Nicht-Umfang -- Verifikationspfad -- Risiken und Blocker -- Fortschrittslog -- Offene Entscheidungen - -## Betriebsregeln - -- Ein aktiver Plan sollte einen klar verantwortlichen aktuellen Schritt haben. -- Den Plan aktualisieren, während die Arbeit voranschreitet; nicht als statischen - Text behandeln. -- Wenn eine Entscheidung die Implementierungsrichtung ändert, im Plan dokumentieren. -- Abgeschlossene Pläne nach `completed/` verschieben, damit Agenten den bisherigen - Kontext weiterhin finden können. diff --git a/courses/learn-harness-engineering/docs/de/resources/openai-advanced/repo-template/docs/PRODUCT_SENSE.md b/courses/learn-harness-engineering/docs/de/resources/openai-advanced/repo-template/docs/PRODUCT_SENSE.md deleted file mode 100644 index 20f410ff..00000000 --- a/courses/learn-harness-engineering/docs/de/resources/openai-advanced/repo-template/docs/PRODUCT_SENSE.md +++ /dev/null @@ -1,26 +0,0 @@ -# PRODUCT_SENSE.md - -Diese Datei erfasst dauerhaftes Produkturteil, das Agenten nicht zuverlässig nur -aus dem Code ableiten können. - -## Produktkern - -- Hauptbenutzer: `[ersetzen]` -- Zu erledigende Aufgabe: `[ersetzen]` -- Hauptfrustration, die behoben werden soll: `[ersetzen]` -- Qualitätsmaßstab für Akzeptanz: `[ersetzen]` - -## Produktregeln - -- Bevorzugen Sie für den Benutzer sichtbare Zuverlässigkeit gegenüber Feature-Anzahl. -- Behandeln Sie mehrdeutiges Verhalten als Spezifikationslücke, nicht als Erlaubnis zum Raten. -- Wenn die Implementierung ändert, was Benutzer sehen oder vertrauen, aktualisieren Sie die zugehörige Spezifikation. -- Verwenden Sie Produktspezifikationen für konkrete Abläufe und diese Datei für - produktübergreifende Prioritäten. - -## No-Go-Muster - -- Versteckte destruktive Aktionen -- Stilles Scheitern ohne Benutzer-Feedback -- Unklare Wahrheitsquelle für sichtbaren Zustand -- Features, die nicht in einem Satz erklärt werden können diff --git a/courses/learn-harness-engineering/docs/de/resources/openai-advanced/repo-template/docs/QUALITY_SCORE.md b/courses/learn-harness-engineering/docs/de/resources/openai-advanced/repo-template/docs/QUALITY_SCORE.md deleted file mode 100644 index d6c66099..00000000 --- a/courses/learn-harness-engineering/docs/de/resources/openai-advanced/repo-template/docs/QUALITY_SCORE.md +++ /dev/null @@ -1,39 +0,0 @@ -# QUALITY_SCORE.md - -Dieses Dokument verfolgt, ob das Repository mit der Zeit stärker oder schwächer wird. - -## Bewertungsskala - -- `A`: verifiziert, lesbar, stabil, Grenzen durchgesetzt -- `B`: funktionsfähig mit kleinen Lücken -- `C`: teilweise funktionsfähig, merkliche Verwirrung oder Instabilität -- `D`: kaputt, unsicher oder strukturell unklar - -## Produktdomänen - -| Domäne | Note | Verifikation | Agenten-Lesbarkeit | Teststabilität | Wesentliche Lücken | Zuletzt aktualisiert | -|--------|------|-------------|-------------------|---------------|-------------------|---------------------| -| `[domäne-a]` | - | - | - | - | - | - | -| `[domäne-b]` | - | - | - | - | - | - | -| `[domäne-c]` | - | - | - | - | - | - | - -## Architektur-Schichten - -| Schicht | Note | Grenzdurchsetzung | Agenten-Lesbarkeit | Wesentliche Lücken | Zuletzt aktualisiert | -|---------|------|-------------------|-------------------|-------------------|---------------------| -| Types | - | - | - | - | - | -| Services | - | - | - | - | - | -| Runtime | - | - | - | - | - | -| UI | - | - | - | - | - | - -## Benchmark-Momentaufnahmen - -| Datum | Harness-Variante | Abschlussrate | Wiederholungen | Fehler vor Review | Notizen | -|-------|-----------------|--------------|---------------|-------------------|---------| -| YYYY-MM-DD | `[Baseline / verbessert / vereinfacht]` | - | - | - | - | - -## Vereinfachungsprotokoll - -| Datum | Entfernte Komponente | Ergebnis | Entscheidung | -|-------|---------------------|----------|-------------| -| YYYY-MM-DD | `[Komponente]` | `[verschlechtert / unverändert]` | `[wiederherstellen / entfernt behalten]` | diff --git a/courses/learn-harness-engineering/docs/de/resources/openai-advanced/repo-template/docs/RELIABILITY.md b/courses/learn-harness-engineering/docs/de/resources/openai-advanced/repo-template/docs/RELIABILITY.md deleted file mode 100644 index 42de6e1f..00000000 --- a/courses/learn-harness-engineering/docs/de/resources/openai-advanced/repo-template/docs/RELIABILITY.md +++ /dev/null @@ -1,33 +0,0 @@ -# RELIABILITY.md - -Diese Datei definiert, wie das System beweist, dass es gesund und neu startbar ist. - -## Standardpfade - -- Bootstrap: `[Befehl]` -- Verifikation: `[Befehl]` -- App oder Dienst starten: `[Befehl]` -- Debugging oder Laufzeit-Inspektion: `[Befehl]` - -## Erforderliche Laufzeitsignale - -- strukturierte Logs für Start und kritische Abläufe -- Health-Checks für wichtige Dienste -- Trace- oder Timing-Daten für langsame Pfade, wenn verfügbar -- für den Benutzer sichtbare Fehlerzustände bei behebbaren Ausfällen - -## Golden Journeys - -- `[Journey 1]` -- `[Journey 2]` -- `[Journey 3]` - -Jede Golden Journey sollte einen wiederholbaren Verifikationspfad und klare -Fehlersignale haben. - -## Zuverlässigkeitsregeln - -- Kein Feature ist vollständig, wenn das System danach nicht sauber neu starten kann. -- Laufzeitfehler sollten aus repo-lokalen Signalen diagnostizierbar sein. -- Wenn ein wiederholtes Fehlermuster auftritt, einen Benchmark oder eine Leitplanke dafür hinzufügen. -- Aufräumen ist Teil der Zuverlässigkeit, keine separate concern. diff --git a/courses/learn-harness-engineering/docs/de/resources/openai-advanced/repo-template/docs/SECURITY.md b/courses/learn-harness-engineering/docs/de/resources/openai-advanced/repo-template/docs/SECURITY.md deleted file mode 100644 index c9ca4ba7..00000000 --- a/courses/learn-harness-engineering/docs/de/resources/openai-advanced/repo-template/docs/SECURITY.md +++ /dev/null @@ -1,28 +0,0 @@ -# SECURITY.md - -Diese Datei definiert die Sicherheits- und Sicherheitsregeln, bei denen Agenten nicht -raten dürfen. - -## Geheimnisse und Anmeldeinformationen - -- Niemals Geheimnisse in Quellcode oder Dokumentation hardcoden. -- Zugelassene Pfade zum Laden von Geheimnissen hier dokumentieren. -- Tokens, API-Schlüssel und personenbezogene Daten aus Logs und Screenshots schwärzen. - -## Nicht vertrauenswürdige Eingaben - -- Externe Inhalte als nicht vertrauenswürdig behandeln, bis sie validiert sind. -- Zulässige Fetch- oder Ausführungsgrenzen hier dokumentieren. -- Wenn ein Risiko für Prompt-Injection oder Befehlsinjektion besteht, die Leitplanke dokumentieren. - -## Externe Aktionen - -- Auflisten, welche Aktionen eine explizite Genehmigung erfordern. -- Produktions- oder destruktive Befehle dokumentieren, die Agenten standardmäßig nicht ausführen dürfen. -- Sandbox-sichere Workflows für Debugging und Verifikation bevorzugen. - -## Abhängigkeits- und Review-Regeln - -- Neue Abhängigkeiten benötigen eine Begründung im aktiven Plan. -- Sicherheitsempfindliche Änderungen erfordern explizite Verifikationsschritte. -- Wiederholte Security-Review-Kommentare sollten zu Prüfungen werden, nicht zu implizitem Wissen. diff --git a/courses/learn-harness-engineering/docs/de/resources/openai-advanced/repo-template/docs/design-docs/core-beliefs.md b/courses/learn-harness-engineering/docs/de/resources/openai-advanced/repo-template/docs/design-docs/core-beliefs.md deleted file mode 100644 index 8491f16e..00000000 --- a/courses/learn-harness-engineering/docs/de/resources/openai-advanced/repo-template/docs/design-docs/core-beliefs.md +++ /dev/null @@ -1,10 +0,0 @@ -# Grundüberzeugungen - -- Das Repository ist das System of Record für Agenten. -- `AGENTS.md` ist ein Router, keine Enzyklopädie. -- Verifikationsnachweise sind wichtiger als Selbstvertrauen. -- Eine begrenzte Aufgabe ist besser als viele halbfertige Aufgaben. -- Wiederholtes menschliches Feedback sollte zu wiederverwendbaren Harness-Regeln werden. -- Aufräumen und Vereinfachung sind Teil des Auslieferns, keine Nachgedanken. -- Wenn ein Agent eine Tatsache nicht im Repo finden kann, behandeln Sie diese - Tatsache als operationell nicht verfügbar. diff --git a/courses/learn-harness-engineering/docs/de/resources/openai-advanced/repo-template/docs/design-docs/index.md b/courses/learn-harness-engineering/docs/de/resources/openai-advanced/repo-template/docs/design-docs/index.md deleted file mode 100644 index a8d653e3..00000000 --- a/courses/learn-harness-engineering/docs/de/resources/openai-advanced/repo-template/docs/design-docs/index.md +++ /dev/null @@ -1,21 +0,0 @@ -# Design-Dokumente Index - -Verwenden Sie diesen Index als auffindbare Karte der Design-Historie. - -## Akzeptiert - -- `core-beliefs.md`: Agenten-first-Betriebsüberzeugungen und dauerhafte Projektnormen - -## Vorgeschlagen - -- `[neue Design-Dokument-Pfade hier einfügen]` - -## Veraltet - -- `[alte oder ersetzte Design-Dokumente hier mit Ersetzungslinks verschieben]` - -## Wartungsregeln - -- Jedes Design-Dokument sollte einen Eigentümer oder einen Auslöser für Aktualisierungen haben. -- Veraltete Dokumente entfernen oder als veraltet markieren, anstatt sie abdriften zu lassen. -- Aktive Ausführungspläne mit den Design-Dokumenten verknüpfen, von denen sie abhängen. diff --git a/courses/learn-harness-engineering/docs/de/resources/openai-advanced/repo-template/docs/exec-plans/active/index.md b/courses/learn-harness-engineering/docs/de/resources/openai-advanced/repo-template/docs/exec-plans/active/index.md deleted file mode 100644 index 0b0bb1bb..00000000 --- a/courses/learn-harness-engineering/docs/de/resources/openai-advanced/repo-template/docs/exec-plans/active/index.md +++ /dev/null @@ -1,10 +0,0 @@ -# Aktive Pläne - -Bewahren Sie eine Markdown-Datei pro aktivem Ausführungsplan in diesem Ordner auf. - -Vorgeschlagenes Dateinamenmuster: - -- `YYYY-MM-DD-kurzes-thema.md` - -Jeder aktive Plan sollte aktuell genug sein, damit eine neue Agenten-Sitzung die -Arbeit allein aus dem Repository fortsetzen kann. diff --git a/courses/learn-harness-engineering/docs/de/resources/openai-advanced/repo-template/docs/exec-plans/completed/index.md b/courses/learn-harness-engineering/docs/de/resources/openai-advanced/repo-template/docs/exec-plans/completed/index.md deleted file mode 100644 index 4fa9e5cc..00000000 --- a/courses/learn-harness-engineering/docs/de/resources/openai-advanced/repo-template/docs/exec-plans/completed/index.md +++ /dev/null @@ -1,5 +0,0 @@ -# Abgeschlossene Pläne - -Verschieben Sie fertige Pläne hierher, anstatt sie zu löschen. Abgeschlossene Pläne -sind Teil der Repository-Gedächtnisoberfläche und helfen späteren Agenten-Sitzungen -zu verstehen, warum der Code so aussieht, wie er aussieht. diff --git a/courses/learn-harness-engineering/docs/de/resources/openai-advanced/repo-template/docs/exec-plans/tech-debt-tracker.md b/courses/learn-harness-engineering/docs/de/resources/openai-advanced/repo-template/docs/exec-plans/tech-debt-tracker.md deleted file mode 100644 index 56b72ec3..00000000 --- a/courses/learn-harness-engineering/docs/de/resources/openai-advanced/repo-template/docs/exec-plans/tech-debt-tracker.md +++ /dev/null @@ -1,8 +0,0 @@ -# Tech-Schulden-Tracker - -Verwenden Sie diese Datei für Schulden, die real sind, anerkannt sind und bewusst -aufgeschoben wurden. - -| Datum | Bereich | Schuld | Warum aufgeschoben | Risiko | Nächster Auslöser | -|-------|---------|--------|-------------------|--------|------------------| -| YYYY-MM-DD | `[Bereich]` | `[Schuld]` | `[Grund]` | `[Risiko]` | `[wann erneut prüfen]` | diff --git a/courses/learn-harness-engineering/docs/de/resources/openai-advanced/repo-template/docs/generated/db-schema.md b/courses/learn-harness-engineering/docs/de/resources/openai-advanced/repo-template/docs/generated/db-schema.md deleted file mode 100644 index 0f30b5e3..00000000 --- a/courses/learn-harness-engineering/docs/de/resources/openai-advanced/repo-template/docs/generated/db-schema.md +++ /dev/null @@ -1,14 +0,0 @@ -# Datenbankschema - -Verwenden Sie diesen Ordner für generierte oder abgeleitete Artefakte, die Agenten -inspizieren können sollten, ohne sie aus dem Code rekonstruieren zu müssen. - -## Quelle - -- Generiert aus: `[Befehl oder Quellpfad]` -- Zuletzt aktualisiert: `YYYY-MM-DD` - -## Hinweise - -- Generierte Abschnitte nicht manuell bearbeiten. -- Diese Datei neu generieren, wenn sich das zugrunde liegende Schema ändert. diff --git a/courses/learn-harness-engineering/docs/de/resources/openai-advanced/repo-template/docs/product-specs/index.md b/courses/learn-harness-engineering/docs/de/resources/openai-advanced/repo-template/docs/product-specs/index.md deleted file mode 100644 index 7f63822b..00000000 --- a/courses/learn-harness-engineering/docs/de/resources/openai-advanced/repo-template/docs/product-specs/index.md +++ /dev/null @@ -1,15 +0,0 @@ -# Produktspezifikationen Index - -Verwenden Sie diesen Ordner für aktuelle benutzerseitige Verhaltensspezifikationen. - -## Aktive Spezifikationen - -- `new-user-onboarding.md` - -## Regeln - -- Spezifikationen sollten benutzerseitiges Verhalten und Akzeptanzkriterien beschreiben. -- Wenn die Implementierung von der Spezifikation abweicht, aktualisieren Sie eine von - beiden in derselben Sitzung. -- Halten Sie diesen Index aktuell, damit ein neuer Agent den Produktumfang schnell - erfassen kann. diff --git a/courses/learn-harness-engineering/docs/de/resources/openai-advanced/repo-template/docs/product-specs/new-user-onboarding.md b/courses/learn-harness-engineering/docs/de/resources/openai-advanced/repo-template/docs/product-specs/new-user-onboarding.md deleted file mode 100644 index 1797764e..00000000 --- a/courses/learn-harness-engineering/docs/de/resources/openai-advanced/repo-template/docs/product-specs/new-user-onboarding.md +++ /dev/null @@ -1,26 +0,0 @@ -# Onboarding neuer Benutzer - -## Ziel - -Beschreiben Sie das Erlebnis bei der ersten Ausführung, das ein neuer Benutzer haben sollte. - -## Startbedingungen - -- `[Zustand bevor der Ablauf beginnt]` - -## Benutzerablauf - -1. `[Schritt eins]` -2. `[Schritt zwei]` -3. `[Schritt drei]` - -## Akzeptanzkriterien - -- `[beobachtbares Ergebnis]` -- `[beobachtbares Ergebnis]` -- `[beobachtbares Ergebnis]` - -## Fehlerzustände - -- `[behebbarer Fehler und Benutzer-Feedback]` -- `[blockierter Zustand und Ausweichoption]` diff --git a/courses/learn-harness-engineering/docs/de/resources/openai-advanced/repo-template/docs/references/design-system-reference-llms.txt b/courses/learn-harness-engineering/docs/de/resources/openai-advanced/repo-template/docs/references/design-system-reference-llms.txt deleted file mode 100644 index 6f1c612c..00000000 --- a/courses/learn-harness-engineering/docs/de/resources/openai-advanced/repo-template/docs/references/design-system-reference-llms.txt +++ /dev/null @@ -1,9 +0,0 @@ -Purpose: store model-friendly reference material for the design system. - -Suggested contents: -- component naming rules -- spacing and typography tokens -- state variants -- accessibility expectations - -Keep this file concise and refresh it when the upstream design system changes. diff --git a/courses/learn-harness-engineering/docs/de/resources/openai-advanced/repo-template/docs/references/nixpacks-llms.txt b/courses/learn-harness-engineering/docs/de/resources/openai-advanced/repo-template/docs/references/nixpacks-llms.txt deleted file mode 100644 index c30cc9c2..00000000 --- a/courses/learn-harness-engineering/docs/de/resources/openai-advanced/repo-template/docs/references/nixpacks-llms.txt +++ /dev/null @@ -1,8 +0,0 @@ -Purpose: store a clean agent-readable extract of the deployment or packaging -rules your repository depends on. - -Suggested contents: -- build entrypoints -- runtime assumptions -- environment variable expectations -- common failure signatures diff --git a/courses/learn-harness-engineering/docs/de/resources/openai-advanced/repo-template/docs/references/uv-llms.txt b/courses/learn-harness-engineering/docs/de/resources/openai-advanced/repo-template/docs/references/uv-llms.txt deleted file mode 100644 index f2ceba9a..00000000 --- a/courses/learn-harness-engineering/docs/de/resources/openai-advanced/repo-template/docs/references/uv-llms.txt +++ /dev/null @@ -1,8 +0,0 @@ -Purpose: store a compact reference for your Python package and environment -workflow when `uv` or similar tooling matters to the repo. - -Suggested contents: -- install and sync commands -- lockfile policy -- virtualenv expectations -- verification commands diff --git a/courses/learn-harness-engineering/docs/de/resources/openai-advanced/repo-template/index.md b/courses/learn-harness-engineering/docs/de/resources/openai-advanced/repo-template/index.md deleted file mode 100644 index ffac1218..00000000 --- a/courses/learn-harness-engineering/docs/de/resources/openai-advanced/repo-template/index.md +++ /dev/null @@ -1,24 +0,0 @@ -# Erweitertes Repo-Template - -Kopieren Sie diesen Starter in ein echtes Repository, wenn Sie eine OpenAI-Stil -agenten-first Dokumentationsoberfläche anstelle von nur einem minimalen Harness möchten. - -## Kopierreihenfolge - -1. `AGENTS.md` und `ARCHITECTURE.md` ins Repo-Root kopieren. -2. Den gesamten `docs/`-Baum kopieren. -3. Zuerst `docs/PRODUCT_SENSE.md`, `docs/QUALITY_SCORE.md` und - `docs/RELIABILITY.md` ausfüllen. -4. Den ersten aktiven Plan unter `docs/exec-plans/active/` hinzufügen. -5. Die Einstiegspunktdateien kurz halten und Detail in die verlinkten Docs routen. - -## Wofür dieses Template optimiert - -- dauerhaften repo-lokalen Kontext -- progressive disclosure statt einer gigantischen Instruktionsdatei -- expliziten Plan-Lebenszyklus -- Qualitäts-Tracking über Zeit -- lesbare Grenzen für Agenten und Menschen - -Behandeln Sie jede Datei hier als Starter. Ersetzen Sie Platzhalter, Beispiele und -Beispielbefehle mit Ihren echten Projektspezifika, bevor Sie sich darauf verlassen. diff --git a/courses/learn-harness-engineering/docs/de/resources/openai-advanced/sops/chrome-devtools-validation-loop.md b/courses/learn-harness-engineering/docs/de/resources/openai-advanced/sops/chrome-devtools-validation-loop.md deleted file mode 100644 index 798c03f9..00000000 --- a/courses/learn-harness-engineering/docs/de/resources/openai-advanced/sops/chrome-devtools-validation-loop.md +++ /dev/null @@ -1,51 +0,0 @@ -# SOP: Chrome DevTools-Validierungsschleife - -Verwenden Sie diese SOP, wenn UI-Arbeit von tatsächlicher Laufzeit-Interaktion abhängt -und Screenshots, DOM-Zustand und Konsolenausgabe wichtiger sind als reine Code-Inspektion. - -## Ziel - -UI-Validierung in eine wiederholbare Interaktionsschleife verwandeln, die der Agent -ausführen kann, bis die Journey fehlerfrei ist. - -## Kern-Schleife - -1. Zielseite oder App-Instanz auswählen. -2. Veralteten Konsolenlärm bereinigen. -3. Den VORHER-Zustand erfassen. -4. Den UI-Pfad auslösen. -5. Laufzeitereignisse während der Interaktion beobachten. -6. Den NACHHER-Zustand erfassen. -7. Den Fix anwenden und die App bei Bedarf neustarten. -8. Validierung erneut ausführen, bis die Journey fehlerfrei ist. - -## Erforderliche Eingaben - -- ein stabiler Startbefehl -- eine reproduzierbare UI-Journey -- eine Methode zum Erstellen von Snapshots von DOM, Konsole oder Screenshots -- eine Regel, was als "fehlerfrei" gilt - -## Ausführungs-SOP - -1. Die Ziel-Journey im aktiven Plan aufschreiben. -2. Erfolg in beobachtbaren Begriffen definieren: Text vorhanden, Button aktiviert, - Fehler verschwunden, Konsole sauber, Anfrage erfolgreich. -3. Snapshot des Anfangszustands vor der Interaktion erstellen. -4. Genau einen Pfad gleichzeitig auslösen. -5. Laufzeitereignisse, DOM-Änderungen und sichtbare Ausgabe aufzeichnen. -6. Wenn die Journey fehlschlägt, die kleinste verantwortliche Schicht reparieren und neustarten. -7. Denselben Pfad erneut ausführen und VORHER/NACHHER-Nachweise vergleichen. - -## Fehlerfreiheitskriterien - -- beabsichtigter sichtbarer Zustand ist vorhanden -- unerwartete Fehler sind abwesend -- Konsolenlärm ist verstanden oder bereinigt -- erneutes Ausführen desselben Pfads liefert dasselbe Ergebnis - -## Zu aktualisierende Repo-Artefakte - -- aktiver Ausführungsplan -- `docs/RELIABILITY.md`, wenn die Journey zu einer Golden Path wird -- Produktspezifikation, wenn sich das sichtbare Verhalten geändert hat diff --git a/courses/learn-harness-engineering/docs/de/resources/openai-advanced/sops/encode-knowledge-into-repo.md b/courses/learn-harness-engineering/docs/de/resources/openai-advanced/sops/encode-knowledge-into-repo.md deleted file mode 100644 index 7c17c5d3..00000000 --- a/courses/learn-harness-engineering/docs/de/resources/openai-advanced/sops/encode-knowledge-into-repo.md +++ /dev/null @@ -1,45 +0,0 @@ -# SOP: Unsichtbares Wissen im Repo codieren - -Verwenden Sie diese SOP, wenn wichtiger Kontext noch in Google Docs, Chat-Threads, -Tickets oder in den Köpfen von Menschen lebt. - -## Ziel - -Agenten-unsichtbares Wissen in der Codebasis auffindbar machen, damit eine neue -Sitzung darauf reagieren kann, ohne sich auf vorherige Gespräche zu verlassen. - -## Auslösesignale - -- Der Agent fragt ständig, wie das System funktioniert. -- Menschen sagen "wir haben das in Slack entschieden" oder "folgen Sie dem, was X letzte Woche gesagt hat." -- Reviews verweisen auf Produkt- oder Sicherheitsregeln, die nicht im Repo dokumentiert sind. -- Neue Sitzungen wiederholen Entdeckungsarbeit, die bereits geklärt sein sollte. - -## Ausführungs-SOP - -1. Die unsichtbaren Wissensquellen auflisten: Docs, Chats, unausgesprochene Teamregeln, mündliche Entscheidungen. -2. Für jede Quelle fragen: Ist das Architektur, Produktverhalten, Sicherheitsrichtlinie, - Zuverlässigkeitserwartung, Plankontext oder Referenzmaterial? -3. Im entsprechenden Repo-Artefakt codieren: - - Architektur -> `ARCHITECTURE.md` - - Produktverhalten -> `docs/product-specs/` - - Designbegründung -> `docs/design-docs/` - - Ausführungszustand -> `docs/exec-plans/` - - wiederholte externe Referenzen -> `docs/references/` - - Qualitäts- oder Zuverlässigkeitserwartungen -> `docs/QUALITY_SCORE.md` oder `docs/RELIABILITY.md` -4. Vage Aussagen durch operationell nützliche Formulierungen ersetzen. -5. Veraltete Kopien entfernen oder als veraltet markieren, damit das Repo eine auffindbare Wahrheit behält. - -## Gute Codierungsregeln - -- Für Auffindbarkeit schreiben, nicht für literarische Vollständigkeit. -- Kurze Dokumente mit klaren Dateinamen bevorzugen. -- Verwandte Artefakte miteinander verknüpfen. -- Dauerhafte Regeln speichern, keine Meeting-Protokolle. -- Das Repo in derselben Sitzung aktualisieren, in der die Entscheidung getroffen wird. - -## Definition von Fertig - -- Ein neuer Agent kann die relevante Regel finden, ohne einen Menschen zu fragen. -- Dieselbe Tatsache ist nicht über mehrere widersprüchliche Dateien verstreut. -- Das neue Artefakt liegt nahe am Code oder Workflow, den es steuert. diff --git a/courses/learn-harness-engineering/docs/de/resources/openai-advanced/sops/index.md b/courses/learn-harness-engineering/docs/de/resources/openai-advanced/sops/index.md deleted file mode 100644 index e75f63e2..00000000 --- a/courses/learn-harness-engineering/docs/de/resources/openai-advanced/sops/index.md +++ /dev/null @@ -1,26 +0,0 @@ -# OpenAI Advanced SOPs - -Diese SOPs übersetzen die Betriebsmuster des Artikels in konkrete Ausführungs- -Playbooks, denen Sie folgen oder die Sie anpassen können. - -## Enthaltene SOPs - -- [`layered-domain-architecture.md`](./layered-domain-architecture.md): - explizite Schichten und produktübergreifende Grenzen etablieren -- [`encode-knowledge-into-repo.md`](./encode-knowledge-into-repo.md): - unsichtbares Wissen aus Chat, Docs und Erinnerung in repo-lokale Dateien überführen -- [`observability-feedback-loop.md`](./observability-feedback-loop.md): - Agenten Logs, Metriken, Traces und eine wiederholbare Debug-Schleife geben -- [`chrome-devtools-validation-loop.md`](./chrome-devtools-validation-loop.md): - Browser-Automatisierung und Snapshots verwenden, um UI-Verhalten bis zur - Fehlerfreiheit zu validieren - -## Wie Sie sie verwenden - -1. Wählen Sie die SOP, die zu Ihrem aktuellen Engpass passt. -2. Verwenden Sie die Checkliste, um die fehlenden Artefakte oder Werkzeuge einzurichten. -3. Codieren Sie die resultierenden Regeln in Ihre kopierten `repo-template/`-Docs. -4. Wandeln Sie wiederholte Review-Kommentare in Prüfungen, Skripte oder Leitplanken um. - -Diese SOPs sind nicht dazu gedacht, blind befolgt zu werden. Sie sollen das Harness -lesbarer, durchsetzbar und wiederholbar machen. diff --git a/courses/learn-harness-engineering/docs/de/resources/openai-advanced/sops/layered-domain-architecture.md b/courses/learn-harness-engineering/docs/de/resources/openai-advanced/sops/layered-domain-architecture.md deleted file mode 100644 index 671f5b3d..00000000 --- a/courses/learn-harness-engineering/docs/de/resources/openai-advanced/sops/layered-domain-architecture.md +++ /dev/null @@ -1,51 +0,0 @@ -# SOP: Geschichtete Domänenarchitektur - -Verwenden Sie diese SOP, wenn der Agent ständig Grenzen verletzt, Logik über Schichten -hinweg dupliziert oder Code erzeugt, der nach wenigen Sitzungen schwer zu reviewen ist. - -## Ziel - -Domänengrenzen so explizit machen, dass Agenten schnell arbeiten können, ohne -unsichtbar die Struktur zu beeinträchtigen. - -## Zielmodell - -Bevorzugen Sie innerhalb einer Geschäftsdomäne diesen gerichteten Fluss: - -`Types -> Config -> Repo -> Service -> Runtime -> UI` - -Produktübergreifende Concerns sollten durch explizite Provider oder Adapter eintreten. -Gemeinsame Utils bleiben außerhalb der Domäne und sollten keine Domänenlogik ansammeln. - -## Einrichtungs-Checkliste - -- Die aktuellen Domänen in `ARCHITECTURE.md` definieren. -- Erlaubte Abhängigkeitsrichtungen in `ARCHITECTURE.md` dokumentieren. -- Produktübergreifende Schnittstellen wie Auth, Telemetrie und externe APIs erfassen. -- Eine kurze Notiz für die aktuell schwerste Grenzverletzung hinzufügen. -- Entscheiden, was mechanisch durch Lint, Tests oder Skripte durchgesetzt werden soll. - -## Ausführungs-SOP - -1. Die Codebasis in Domänen aufteilen, bevor der Implementierungsstil angefasst wird. -2. Für jede Domäne die erlaubte Schichtenfolge identifizieren. -3. Alle produktübergreifenden Concerns identifizieren und durch Provider oder Adapter routen. -4. Mehrdeutige gemeinsame Logik entweder in die besitzende Domäne oder in wirklich - generische Utils verschieben. -5. Die Regeln in `ARCHITECTURE.md` dokumentieren. -6. Eine ausführbare Leitplanke für die teuerste Verletzung hinzufügen. -7. Qualitätsbewertung nach der Änderung aktualisieren. - -## Definition von Fertig - -- Ein neuer Agent kann erkennen, welche Schicht eine Änderung besitzt. -- UI-Code greift nicht mehr direkt auf Repo oder externe Seiteneffekte zu. -- Produktübergreifende Concerns haben benannte Einstiegspunkte. -- Mindestens eine wichtige Grenze wird mechanisch durchgesetzt. - -## Zu aktualisierende Repo-Artefakte - -- `ARCHITECTURE.md` -- `docs/QUALITY_SCORE.md` -- `docs/design-docs/`, wenn sich die Begründung geändert hat -- `docs/PLANS.md` oder der aktive Ausführungsplan diff --git a/courses/learn-harness-engineering/docs/de/resources/openai-advanced/sops/observability-feedback-loop.md b/courses/learn-harness-engineering/docs/de/resources/openai-advanced/sops/observability-feedback-loop.md deleted file mode 100644 index f9f4a81c..00000000 --- a/courses/learn-harness-engineering/docs/de/resources/openai-advanced/sops/observability-feedback-loop.md +++ /dev/null @@ -1,46 +0,0 @@ -# SOP: Observability-Feedback-Schleife - -Verwenden Sie diese SOP, wenn Debugging langsam ist, Agenten ständig Erfolg ohne -Nachweise behaupten oder das Laufzeitverhalten schwerer zu inspizieren ist als der -Code selbst. - -## Ziel - -Dem Agenten eine lokale Feedback-Schleife über Logs, Metriken, Traces und ausführbare -Workloads geben, damit er aus der Ausführung heraus argumentieren kann, nicht nur aus -der Code-Inspektion. - -## Minimaler Stack - -- Anwendung sendet strukturierte Logs -- Anwendung sendet Metriken und Traces, wenn machbar -- lokaler Fan-out- oder Sammelschicht -- Abfrageschnittstellen für Logs, Metriken und Traces -- wiederholbarer Workload oder Benutzer-Journey, um nach jeder Änderung erneut auszuführen - -## Ausführungs-SOP - -1. Die Golden Runtime-Journeys definieren, die am wichtigsten sind. -2. Strukturierte Logs zum Start und zum kritischen Pfad hinzufügen. -3. Metriken für Latenz, Fehleranzahlen oder Warteschlangentiefe hinzufügen, wo nützlich. -4. Traces oder Timing-Marker für langsame oder mehrstufige Abläufe hinzufügen. -5. Die Signale aus der lokalen Entwicklungsumgebung abfragbar machen. -6. Dem Agenten einen wiederholbaren Workload oder ein Szenario zum erneuten Ausführen geben. -7. Die Schleife einfordern: abfragen -> korrelieren -> argumentieren -> implementieren -> - neustarten -> erneut ausführen -> verifizieren. - -## Debug-Sitzungs-Checkliste - -- Was ist fehlgeschlagen? -- Welches Signal beweist den Fehler? -- Welche Schicht ist für den Fehler verantwortlich? -- Was hat sich nach dem Fix geändert? -- Ist die App sauber neugestartet? -- Hat derselbe Workload nach erneutem Ausfahren bestanden? - -## Definition von Fertig - -- Der Agent kann einen Fehlermodus aus Laufzeitnachweisen erklären. -- Derselbe Workload kann nach jeder Änderung erneut ausgeführt werden. -- Neustart und erneutes Ausführen sind Teil der normalen Aufgaben-Schleife. -- Zuverlässigkeitssignale sind in `docs/RELIABILITY.md` dokumentiert. diff --git a/courses/learn-harness-engineering/docs/de/resources/reference/coding-agent-startup-flow.md b/courses/learn-harness-engineering/docs/de/resources/reference/coding-agent-startup-flow.md deleted file mode 100644 index 0a341b5a..00000000 --- a/courses/learn-harness-engineering/docs/de/resources/reference/coding-agent-startup-flow.md +++ /dev/null @@ -1,33 +0,0 @@ -# Coding-Agent-Startflow - -Verwenden Sie dies zu Beginn jeder Session, nachdem die Initialisierung abgeschlossen ist. - -## Feste Startvorlage - -1. `pwd` ausführen und das Repository-Root bestätigen. -2. `claude-progress.md` lesen. -3. `feature_list.json` lesen. -4. Letzte Commits mit `git log --oneline -5` überprüfen. -5. `./init.sh` ausführen. -6. Einen Baseline-Smoke- oder End-to-End-Pfad ausführen. -7. Wenn die Baseline kaputt ist, diese zuerst beheben. -8. Das Feature mit höchster unvollständiger Priorität auswählen. -9. Nur an diesem Feature arbeiten, bis es verifiziert oder explizit blockiert ist. - -## Warum diese Reihenfolge wichtig ist - -- `pwd` verhindert versehentliche Arbeit im falschen Verzeichnis. -- Fortschritts- und Feature-Dateien stellen dauerhaften Zustand wieder her, bevor neue Edits beginnen. -- Letzte Commits erklären, was sich kürzlich geändert hat. -- `init.sh` standardisiert den Start anstatt sich auf Erinnerung zu verlassen. -- Baseline-Verifikation fängt kaputte Startzustände ab, bevor neue Arbeit sie verbirgt. - -## Session-Ende-Spiegel - -Die gleiche Session sollte beenden mit: - -1. Fortschritt dokumentieren -2. Feature-Status aktualisieren -3. Einen Handoff schreiben falls nötig -4. Sichere Arbeit committen -5. Einen sauberen Neustart-Pfad hinterlassen diff --git a/courses/learn-harness-engineering/docs/de/resources/reference/index.md b/courses/learn-harness-engineering/docs/de/resources/reference/index.md deleted file mode 100644 index 483fbe6c..00000000 --- a/courses/learn-harness-engineering/docs/de/resources/reference/index.md +++ /dev/null @@ -1,54 +0,0 @@ -# Deutsche Referenz - -Diese Notizen erklären, wie man die Templates als funktionierenden Harness statt als -lose Dateisammlung verwendet. - -## Interne Referenznotizen - -- [`method-map.md`](./method-map.md): ordnet häufige langlaufende Fehlermodi dem - Artefakt oder der Policy zu, die sie zuerst adressiert -- [`initializer-agent-playbook.md`](./initializer-agent-playbook.md): was der - Initializer hinterlassen sollte, bevor Feature-Arbeit beginnt -- [`coding-agent-startup-flow.md`](./coding-agent-startup-flow.md): fester - Session-Start-Flow für spätere Coding-Läufe -- [`prompt-calibration.md`](./prompt-calibration.md): wie man Root-Instruktionen - scharf hält, ohne sie aufzublähen und brüchig zu machen - -## Primäre Artikel - -Diese Liste ist bewusst eng. Ein Harness bedeutet das Ausführungssystem rund um -das Modell: die Agent-Schleife, Tool-Ausführung, Sandboxing, Zustand, Kontext, -Verifikation, Terminierung, Orchestrierung und Observabilität. Allgemeine Prompt- -Engineering- oder breite Agent-Framework-Artikel gehören nicht in die primäre Liste. - -Die ursprünglichen drei Artikel bleiben das Rückgrat des Kurses: - -- [OpenAI: Harness engineering: leveraging Codex in an agent-first world](https://openai.com/index/harness-engineering/) (2026-02-11): agent-first Repositories, repo-lokaler Kontext, Custom-Linting und strukturelle Guardrails. -- [Anthropic: Effective harnesses for long-running agents](https://www.anthropic.com/engineering/effective-harnesses-for-long-running-agents) (2025-11-26): Initializer-Agent, Coding-Agent, Feature-Liste, Fortschrittslog und Handoff über Kontextfenster. -- [Anthropic: Harness design for long-running application development](https://www.anthropic.com/engineering/harness-design-long-running-apps) (2026-03-24): Planner-/Generator-/Evaluator-Rollen, Kontext-Resets, Harness-Vereinfachung und veraltete Annahmen. - -## 2026 Erweiterte Referenzen - -Dies sind keine Kernquellen des Kurses, aber nützlich beim Entwurf spezifischer -Harness-Module. - -- [OpenAI: Unlocking the Codex harness](https://openai.com/index/unlocking-the-codex-harness/) (2026-02-04) -- [OpenAI Developers: Run long horizon tasks with Codex](https://developers.openai.com/blog/run-long-horizon-tasks-with-codex) (2026-02-23) -- [OpenAI: The next evolution of the Agents SDK](https://openai.com/index/the-next-evolution-of-the-agents-sdk/) (2026-04-15) -- [OpenAI: An open-source spec for Codex orchestration: Symphony](https://openai.com/index/open-source-codex-orchestration-symphony/) (2026-04-27) -- [Anthropic: Building a C compiler with a team of parallel Claudes](https://www.anthropic.com/engineering/building-c-compiler) (2026-02-05) -- [Anthropic: Scaling Managed Agents](https://www.anthropic.com/engineering/managed-agents) (2026-04-08) -- [Anthropic: An update on recent Claude Code quality reports](https://www.anthropic.com/engineering/april-23-postmortem) (2026-04-23) -- [LangChain: Context Management for Deep Agents](https://www.langchain.com/blog/context-management-for-deepagents) (2026-01-28) -- [LangChain: Tuning Deep Agents to Work Well with Different Models](https://www.langchain.com/blog/tuning-deep-agents-different-models) (2026-04-29) -- [Microsoft: Agent Harness in Agent Framework](https://devblogs.microsoft.com/agent-framework/agent-harness-in-agent-framework/) (2026-03-12) - -## Empfohlene Lesereihenfolge - -1. `method-map.md` -2. `initializer-agent-playbook.md` -3. `coding-agent-startup-flow.md` -4. `prompt-calibration.md` -5. OpenAI Harness Engineering -6. Anthropic Effective Harnesses -7. Anthropic Harness Design diff --git a/courses/learn-harness-engineering/docs/de/resources/reference/initializer-agent-playbook.md b/courses/learn-harness-engineering/docs/de/resources/reference/initializer-agent-playbook.md deleted file mode 100644 index 75c81635..00000000 --- a/courses/learn-harness-engineering/docs/de/resources/reference/initializer-agent-playbook.md +++ /dev/null @@ -1,38 +0,0 @@ -# Initializer-Agenten-Playbook - -Verwenden Sie dieses Playbook für die erste ernsthafte Session in einem Repository, -bevor inkrementelle Feature-Arbeit beginnt. - -## Ziel - -Eine stabile Betriebsoberfläche schaffen, sodass spätere Sessions Verhalten -implementieren können, ohne Startbefehle, aktuellen Status oder Task-Grenzen -neu abzuleiten. - -## Erforderliche Outputs - -Der Initializer sollte mindestens diese Artefakte hinterlassen: - -- eine Root-Instruktionsdatei wie `AGENTS.md` oder `CLAUDE.md` -- eine maschinenlesbare Feature-Oberfläche wie `feature_list.json` -- ein dauerhaftes Fortschrittsartefakt wie `claude-progress.md` -- einen Standard-Starthelfer wie `init.sh` -- einen ersten sicheren Commit, der das Baseline-Gerüst erfasst - -## Checkliste - -1. Den Standard-Startpfad definieren. -2. Den Standard-Verifikationspfad definieren. -3. Das Fortschrittslog erstellen und den Startzustand dokumentieren. -4. Die Arbeit in explizite Features mit Status zerlegen. -5. Den ersten sauberen Baseline-Commit erstellen. - -## Erfolgstest - -Eine frische Session ohne vorherigen Chat-Kontext sollte folgende Fragen beantworten können: - -- Was dieses Repository macht -- Wie man es startet -- Wie man es verifiziert -- Was unvollendet ist -- Was der nächste beste Schritt ist diff --git a/courses/learn-harness-engineering/docs/de/resources/reference/method-map.md b/courses/learn-harness-engineering/docs/de/resources/reference/method-map.md deleted file mode 100644 index a27111d8..00000000 --- a/courses/learn-harness-engineering/docs/de/resources/reference/method-map.md +++ /dev/null @@ -1,19 +0,0 @@ -# Methoden-Map - -Diese Tabelle ordnet die häufigsten Fehlermodi von langlaufenden Coding-Agenten dem -Artefakt oder der Betriebsregel zu, die sie normalerweise zuerst behebt. - -| Fehlermodus | Wie es in der Praxis aussieht | Primäre Lösung | Unterstützendes Artefakt | -| --- | --- | --- | --- | -| Kaltstart-Verwirrung | Eine neue Session verbringt die meiste Zeit damit, Setup und Status neu zu entdecken | Das Repository zum System of Record machen | `claude-progress.md` | -| Scope-Ausdehnung | Der Agent startet mehrere Features und schließt keines sauber ab | Aktiven Scope einschränken | `feature_list.json` | -| Vorzeitiger Abschluss | Der Agent behauptet Fertig nach Code-Edits, aber vor ausführbarem Nachweis | Abschluss an Nachweis binden | `clean-state-checklist.md` | -| Fragiler Start | Jede Session lernt neu, wie das Projekt bootet | Setup und Verifikation standardisieren | `init.sh` | -| Schwacher Handoff | Die nächste Session kann nicht erkennen, was verifiziert, kaputt oder als nächstes dran ist | Mit einem expliziten Handoff beenden | `session-handoff.md` | -| Subjektives Review | Review-Qualität hängt von Geschmack oder Erinnerung ab | Output mit festen Kategorien bewerten | `evaluator-rubric.md` | - -## Betriebsprinzip - -Das kleinste Artefakt hinzufügen, das den beobachteten Fehlermodus direkt adressiert. -Vermeiden Sie, jedes Zuverlässigkeitsproblem durch das Einfügen von mehr Text in eine -globale Instruktionsdatei zu lösen. diff --git a/courses/learn-harness-engineering/docs/de/resources/reference/prompt-calibration.md b/courses/learn-harness-engineering/docs/de/resources/reference/prompt-calibration.md deleted file mode 100644 index b59f3eda..00000000 --- a/courses/learn-harness-engineering/docs/de/resources/reference/prompt-calibration.md +++ /dev/null @@ -1,25 +0,0 @@ -# Prompt-Kalibrierung - -Root-Instruktionen sollten den Betriebsrahmen definieren, nicht jeden möglichen Zug. - -## In der Root-Datei behalten - -- Repository-Zweck und Scope -- Startpfad -- Verifikationspfad -- Nicht-verhandelbare Einschränkungen -- Erforderliche Zustandsartefakte -- Session-Ende-Regeln - -## Aus der Root-Datei auslagern - -- Lange historische Edge Cases -- Themenspezifische Implementierungsdetails -- Lokale Architektur-Notizen, die zum Code gehören -- Beispiele, die nur für ein Subsystem gelten - -## Arbeitsregel - -Die Root-Datei sollte einer frischen Session helfen, sich schnell zu orientieren. Wenn die -Datei zum Sammelbecken für jeden vergangenen Fehlschlag wird, das Detail in kleinere -Dokumente aufteilen und stattdessen dorthin verlinken. diff --git a/courses/learn-harness-engineering/docs/de/resources/templates/AGENTS.md b/courses/learn-harness-engineering/docs/de/resources/templates/AGENTS.md deleted file mode 100644 index f1d070d2..00000000 --- a/courses/learn-harness-engineering/docs/de/resources/templates/AGENTS.md +++ /dev/null @@ -1,55 +0,0 @@ -# AGENTS.md - -Dieses Repository ist für langfristige Coding-Agent-Arbeit konzipiert. Ziel ist nicht -die Maximierung der reinen Code-Produktion. Ziel ist es, das Repo in einem Zustand zu -hinterlassen, in dem die nächste Session ohne Raten weiterarbeiten kann. - -## Start-Workflow - -Bevor Code geschrieben wird: - -1. Arbeitsverzeichnis mit `pwd` bestätigen. -2. `claude-progress.md` für den neuesten verifizierten Zustand und nächsten Schritt lesen. -3. `feature_list.json` lesen und das Feature mit höchster Priorität auswählen. -4. Letzte Commits mit `git log --oneline -5` überprüfen. -5. `./init.sh` ausführen. -6. Erforderlichen Smoke- oder End-to-End-Test vor neuer Arbeit ausführen. - -Wenn die Baseline-Verifikation bereits fehlschlägt, diese zuerst beheben. Keine neue -Feature-Arbeit auf einen kaputten Startzustand stapeln. - -## Arbeitsregeln - -- Jeweils an einem Feature arbeiten. -- Ein Feature nicht als abgeschlossen markieren, nur weil Code hinzugefügt wurde. -- Änderungen im gewählten Feature-Scope belassen, es sei denn, ein Blocker erzwingt - eine enge unterstützende Korrektur. -- Verifikationsregeln während der Implementierung nicht stillschweigend ändern. -- Dauerhafte Repo-Artefakte gegenüber Chat-Zusammenfassungen bevorzugen. - -## Erforderliche Artefakte - -- `feature_list.json`: Source of Truth für Feature-Status -- `claude-progress.md`: Session-Log und aktueller verifizierter Status -- `init.sh`: Standard-Start- und Verifikationspfad -- `session-handoff.md`: Optionaler kompakter Handoff für größere Sessions - -## Definition of Done - -Ein Feature ist erst fertig, wenn all dies zutrifft: - -- das Zielverhalten ist implementiert -- die erforderliche Verifikation wurde tatsächlich ausgeführt -- der Nachweis ist in `feature_list.json` oder `claude-progress.md` dokumentiert -- das Repository bleibt über den Standard-Startpfad neustartfähig - -## Session-Ende - -Vor dem Beenden einer Session: - -1. `claude-progress.md` aktualisieren. -2. `feature_list.json` aktualisieren. -3. Alle ungelösten Risiken oder Blocker dokumentieren. -4. Mit einer beschreibenden Nachricht committen, sobald die Arbeit in einem sicheren Zustand ist. -5. Das Repo sauber genug hinterlassen, damit die nächste Session sofort `./init.sh` - ausführen kann. diff --git a/courses/learn-harness-engineering/docs/de/resources/templates/CLAUDE.md b/courses/learn-harness-engineering/docs/de/resources/templates/CLAUDE.md deleted file mode 100644 index b378a57f..00000000 --- a/courses/learn-harness-engineering/docs/de/resources/templates/CLAUDE.md +++ /dev/null @@ -1,47 +0,0 @@ -# CLAUDE.md - -Sie arbeiten in einem Repository für langfristige Implementierungsarbeit. -Priorisieren Sie zuverlässigen Abschluss, Kontinuität über Sessions und explizite -Verifikation gegenüber Geschwindigkeit. - -## Betriebsablauf - -Zu Beginn jeder Session: - -1. `pwd` ausführen und das erwartete Repository-Root bestätigen. -2. `claude-progress.md` lesen. -3. `feature_list.json` lesen. -4. Letzte Commits mit `git log --oneline -5` überprüfen. -5. `./init.sh` ausführen. -6. Prüfen, ob der Baseline-Smoke- oder End-to-End-Pfad bereits kaputt ist. - -Dann genau ein unfertiges Feature auswählen und nur an diesem Feature arbeiten, bis -es verifiziert ist oder dokumentiert ist, warum es blockiert ist. - -## Regeln - -- Ein aktives Feature gleichzeitig. -- Keinen Abschluss ohne ausführbaren Nachweis behaupten. -- Die Feature-Liste nicht umschreiben, um unfertige Arbeit zu verbergen. -- Tests nicht entfernen oder schwächen, nur damit die Aufgabe fertig aussieht. -- Repo-Artefakte als System of Record verwenden. - -## Erforderliche Dateien - -- `feature_list.json` -- `claude-progress.md` -- `init.sh` -- `session-handoff.md` wenn ein kompakter Handoff nützlich ist - -## Abschluss-Gate - -Ein Feature kann nur zu `passing` wechseln, nachdem die erforderliche Verifikation -erfolgreich war und das Ergebnis dokumentiert ist. - -## Vor dem Stoppen - -1. Das Fortschrittslog aktualisieren. -2. Den Feature-Status aktualisieren. -3. Was noch kaputt oder unverifiziert ist, dokumentieren. -4. Committen, sobald das Repository sicher fortgesetzt werden kann. -5. Einen sauberen Neustart-Pfad für die nächste Session hinterlassen. diff --git a/courses/learn-harness-engineering/docs/de/resources/templates/claude-progress.md b/courses/learn-harness-engineering/docs/de/resources/templates/claude-progress.md deleted file mode 100644 index 97db44ae..00000000 --- a/courses/learn-harness-engineering/docs/de/resources/templates/claude-progress.md +++ /dev/null @@ -1,35 +0,0 @@ -# Fortschrittslog - -## Aktueller Verifizierter Zustand - -- Repository-Root: -- Standard-Startpfad: -- Standard-Verifikationspfad: -- Aktuelles Feature mit höchster Priorität (unvollendet): -- Aktueller Blocker: - -## Session-Log - -### Session 001 - -- Datum: -- Ziel: -- Abgeschlossen: -- Verifikationslauf: -- Nachweis erfasst: -- Commits: -- Dateien oder Artefakte aktualisiert: -- Bekanntes Risiko oder ungelöstes Problem: -- Nächster bester Schritt: - -### Session 002 - -- Datum: -- Ziel: -- Abgeschlossen: -- Verifikationslauf: -- Nachweis erfasst: -- Commits: -- Dateien oder Artefakte aktualisiert: -- Bekanntes Risiko oder ungelöstes Problem: -- Nächster bester Schritt: diff --git a/courses/learn-harness-engineering/docs/de/resources/templates/clean-state-checklist.md b/courses/learn-harness-engineering/docs/de/resources/templates/clean-state-checklist.md deleted file mode 100644 index a01faa98..00000000 --- a/courses/learn-harness-engineering/docs/de/resources/templates/clean-state-checklist.md +++ /dev/null @@ -1,8 +0,0 @@ -# Clean-State-Checkliste - -- [ ] Der Standard-Startpfad funktioniert noch. -- [ ] Der Standard-Verifikationspfad läuft noch. -- [ ] Der aktuelle Fortschritt ist im Fortschrittslog dokumentiert. -- [ ] Der Feature-Status entspricht dem, was tatsächlich bestanden vs. unverifiziert ist. -- [ ] Kein halbfertiger Schritt bleibt undokumentiert. -- [ ] Die nächste Session kann ohne manuelle Reparatur weiterarbeiten. diff --git a/courses/learn-harness-engineering/docs/de/resources/templates/evaluator-rubric.md b/courses/learn-harness-engineering/docs/de/resources/templates/evaluator-rubric.md deleted file mode 100644 index a9aef94b..00000000 --- a/courses/learn-harness-engineering/docs/de/resources/templates/evaluator-rubric.md +++ /dev/null @@ -1,24 +0,0 @@ -# Evaluator-Rubrik - -Verwenden Sie diese Rubrik nach der Implementierung und vor der finalen Abnahme. - -| Kategorie | Frage | Bewertung (0-2) | Notizen | -| --- | --- | --- | --- | -| Korrektheit | Entspricht das implementierte Verhalten dem angeforderten Feature? | | | -| Verifikation | Wurden die erforderlichen Checks tatsächlich mit Nachweis ausgeführt? | | | -| Scope-Disziplin | Blieb die Session im gewählten Feature-Scope? | | | -| Zuverlässigkeit | Übersteht das Ergebnis einen Neustart ohne Reparatur? | | | -| Wartbarkeit | Sind Code und Dokumentation klar genug für die nächste Session? | | | -| Handoff-Bereitschaft | Kann eine frische Session nur aus Repo-Artefakten weiterarbeiten? | | | - -## Urteil - -- Akzeptieren -- Überarbeiten -- Blockieren - -## Erforderliches Follow-Up - -- Fehlende Nachweise: -- Erforderliche Korrekturen: -- Nächster Review-Trigger: diff --git a/courses/learn-harness-engineering/docs/de/resources/templates/feature_list.json b/courses/learn-harness-engineering/docs/de/resources/templates/feature_list.json deleted file mode 100644 index 138adc6a..00000000 --- a/courses/learn-harness-engineering/docs/de/resources/templates/feature_list.json +++ /dev/null @@ -1,64 +0,0 @@ -{ - "project": "replace-with-project-name", - "last_updated": "YYYY-MM-DD", - "rules": { - "single_active_feature": true, - "passing_requires_evidence": true, - "do_not_skip_verification": true - }, - "status_legend": { - "not_started": "Work has not begun.", - "in_progress": "The feature is the current active task.", - "blocked": "Work cannot continue until a documented blocker is resolved.", - "passing": "Required verification has passed and evidence is recorded." - }, - "features": [ - { - "id": "chat-001", - "priority": 1, - "area": "chat", - "title": "Create a new conversation", - "user_visible_behavior": "A user can click New Chat and see a fresh empty conversation.", - "status": "not_started", - "verification": [ - "Open the app.", - "Click New Chat.", - "Verify a new conversation appears in the sidebar.", - "Verify the main panel shows an empty conversation state." - ], - "evidence": [], - "notes": "" - }, - { - "id": "chat-002", - "priority": 2, - "area": "chat", - "title": "Send a message in the current conversation", - "user_visible_behavior": "A user can submit a message and see it appear in the active thread.", - "status": "not_started", - "verification": [ - "Open an existing conversation.", - "Type a message into the input.", - "Submit the message.", - "Verify the new message appears in the thread." - ], - "evidence": [], - "notes": "" - }, - { - "id": "chat-003", - "priority": 3, - "area": "chat", - "title": "Persist the active conversation list", - "user_visible_behavior": "A user sees previously created conversations after restarting the app.", - "status": "not_started", - "verification": [ - "Create two conversations.", - "Restart the app.", - "Verify both conversations still appear in the sidebar." - ], - "evidence": [], - "notes": "" - } - ] -} diff --git a/courses/learn-harness-engineering/docs/de/resources/templates/index.md b/courses/learn-harness-engineering/docs/de/resources/templates/index.md deleted file mode 100644 index 8946bc66..00000000 --- a/courses/learn-harness-engineering/docs/de/resources/templates/index.md +++ /dev/null @@ -1,255 +0,0 @@ -# Vorlagen-Leitfaden - -Diese Vorlagen sind bereit, in Ihr eigenes Projekt kopiert zu werden. Jede erfüllt -einen bestimmten Zweck im Workflow des Agenten. Bearbeiten Sie die Inhalte, um sie -an die Befehle, Pfade, Feature-Namen und Verifikationsschritte Ihres Projekts -anzupassen. - -## Erste Schritte - -Kopieren Sie diese vier Dateien zuerst in Ihr Projektverzeichnis: - -1. `AGENTS.md` oder `CLAUDE.md` -2. `init.sh` -3. `claude-progress.md` -4. `feature_list.json` - -Fügen Sie die restlichen Dateien hinzu, wenn Ihr Projekt wächst. - ---- - -## AGENTS.md - -Die Haupt-Instruktionsdatei. Dies ist das Erste, was der Agent liest, wenn er eine -Sitzung startet. Sie definiert die Betriebsregeln: was vor dem Schreiben von Code -zu tun ist, wie gearbeitet wird und wie abgeschlossen wird. - -**Verwendung:** - -- In Ihr Projektverzeichnis kopieren -- Die Start-Workflow-Schritte durch Ihre tatsächlichen Projektpfade und -befehle ersetzen -- Die Arbeitsregeln an die Konventionen Ihres Teams anpassen -- Den Abschnitt zur Definition von Fertig beibehalten -- er ist der wichtigste Teil - -**Was sie für den Agenten tut:** - -- Sagt ihm, dass er Fortschritt und Feature-Status lesen soll, bevor er mit der Arbeit beginnt -- Zwingt ihn, an nur einem Feature gleichzeitig zu arbeiten -- Verlangt Nachweise, bevor etwas als fertig markiert wird -- Definiert, wie ein sauberes Sitzungsende aussieht - -Verwenden Sie `AGENTS.md` für Codex oder andere Agenten. Verwenden Sie `CLAUDE.md`, -wenn Sie mit Claude Code arbeiten -- die Struktur ist dieselbe, nur formatiert für -Claudes Instruktionsstil. - -## init.sh - -Das Startskript. Führt Abhängigkeitsinstallation, Verifikation und Ausgabe des -Startbefehls aus -- alles in einem Durchgang. - -**Verwendung:** - -- In Ihr Projektverzeichnis kopieren -- Diese drei Variablen oben bearbeiten: - - `INSTALL_CMD` -- Ihr Abhängigkeits-Installationsbefehl (z. B. `npm install`, `pip install -r requirements.txt`) - - `VERIFY_CMD` -- Ihr grundlegender Verifikationsbefehl (z. B. `npm test`, `pytest`) - - `START_CMD` -- Ihr Dev-Server-Startbefehl (z. B. `npm run dev`) -- Ausführbar machen: `chmod +x init.sh` - -**Was es tut:** - -1. Gibt das aktuelle Verzeichnis aus (damit Sie bestätigen können, dass es am richtigen Ort läuft) -2. Installiert Abhängigkeiten -3. Führt den Verifikationsbefehl aus -4. Gibt den Startbefehl aus (oder führt ihn aus, wenn `RUN_START_COMMAND=1` gesetzt ist) - -Wenn die Verifikation fehlschlägt, sollte der Agent anhalten und die Baseline -korrigieren, bevor er etwas anderes tut. - -## claude-progress.md - -Das Fortschrittslog. Jede Sitzung schreibt in diese Datei, und jede neue Sitzung -liest sie zuerst. - -**Verwendung:** - -- In Ihr Projektverzeichnis kopieren -- Den Abschnitt "Aktueller verifizierter Zustand" mit Ihren Projektinformationen ausfüllen -- Nach jeder Sitzung den Sitzungs-Eintrag aktualisieren - -**Bedeutung der einzelnen Felder:** - -- **Aktueller verifizierter Zustand** -- die einzige Wahrheitsquelle für den Projektstand - - `Repository-Root-Verzeichnis` -- wo das Projekt liegt - - `Standard-Startpfad` -- der Befehl, um das Projekt zu starten - - `Standard-Verifikationspfad` -- der Befehl, um Tests auszuführen - - `Höchste Priorität unfertiges Feature` -- woran die nächste Sitzung arbeiten sollte - - `Aktueller Blocker` -- alles, was feststeckt -- **Sitzungs-Eintrag** -- ein Eintrag pro Sitzung - - `Ziel` -- was Sie vorhatten zu tun - - `Abgeschlossen` -- was tatsächlich erledigt wurde - - `Verifikation ausgeführt` -- welche Tests ausgeführt wurden - - `Nachweise erfasst` -- welche Beweise dokumentiert wurden - - `Commits` -- was committet wurde - - `Bekannte Risiken` -- was kaputt sein könnte - - `Nächste beste Aktion` -- wo die nächste Sitzung beginnen sollte - -## feature_list.json - -Der Feature-Tracker. Eine maschinenlesbare Liste aller Features, die der Agent -implementieren muss, zusammen mit Status, Verifikationsschritten und Nachweisen. - -**Verwendung:** - -- In Ihr Projektverzeichnis kopieren -- Die Beispielfeatures durch Ihre eigenen ersetzen -- Jedes Feature benötigt: - - `id` -- eine kurze eindeutige Kennung - - `priority` -- Ganzzahl, niedriger = höhere Priorität - - `area` -- welcher Teil der App (z. B. "chat", "import", "search") - - `title` -- kurze Beschreibung - - `user_visible_behavior` -- was der Benutzer sehen soll, wenn es funktioniert - - `status` -- einer von `not_started`, `in_progress`, `blocked`, `passing` - - `verification` -- Schritt-für-Schritt-Anleitung zur Bestätigung, dass es funktioniert - - `evidence` -- dokumentierter Nachweis, dass die Verifikation bestanden hat (vom Agenten ausgefüllt) - - `notes` -- jeglicher zusätzlicher Kontext - -**Statusregeln:** - -- `not_started` -- noch nicht angefasst -- `in_progress` -- das aktuell bearbeitete Feature (nur eines gleichzeitig) -- `blocked` -- kann aufgrund eines dokumentierten Problems nicht fortgesetzt werden -- `passing` -- Verifikation bestanden und Nachweise sind dokumentiert - -Der Agent sollte zu jedem Zeitpunkt nur ein Feature im Status `in_progress` haben. - -## session-handoff.md - -Eine kompakte Übergabenotiz zwischen Sitzungen. Verwenden Sie diese, wenn eine -Sitzung endet und Sie möchten, dass die nächste schnell anknüpfen kann. - -**Verwendung:** - -- In Ihr Projektverzeichnis kopieren -- Am Ende jeder Sitzung ausfüllen (oder den Agenten ausfüllen lassen) - -**Was jeder Abschnitt abdeckt:** - -- **Aktuell verifiziert** -- was als funktionierend bestätigt ist und welche Verifikation lief -- **Änderungen dieser Sitzung** -- welcher Code oder welche Infrastruktur sich geändert hat -- **Noch kaputt oder unverifiziert** -- bekannte Probleme und riskante Bereiche -- **Nächste beste Aktion** -- was die nächste Sitzung tun sollte und was nicht angefasst werden sollte -- **Befehle** -- Start-, Verifikations- und Debug-Befehle zur schnellen Referenz - -Diese Datei ist optional für kleine Sitzungen. Sie wird wichtig, wenn Sitzungen lang -sind oder das Projekt mehrere aktive Bereiche hat. - -## clean-state-checklist.md - -Eine Checkliste, die vor dem Beenden jeder Sitzung durchzugehen ist. Stellt sicher, -dass das Repo in einem guten Zustand ist, damit die nächste Sitzung sauber starten kann. - -**Verwendung:** - -- In Ihr Projektverzeichnis kopieren -- Vor dem Schließen einer Sitzung durchgehen -- Der Agent sollte diese Punkte auch als Teil seiner Sitzungsabschluss-Routine prüfen - -**Was sie prüft:** - -- Standard-Start funktioniert noch -- Standard-Verifikation läuft noch -- Fortschrittslog ist aktualisiert -- Feature-Liste spiegelt den tatsächlichen Zustand wider (keine falschen `passing`-Einträge) -- Keine halbfertige Arbeit undokumentiert gelassen -- Nächste Sitzung kann ohne manuelle Korrekturen fortgesetzt werden - -## evaluator-rubric.md - -Ein Bewertungsbogen zur Überprüfung der Agenten-Ausgabequalität. Verwenden Sie diesen -nach einer Sitzung oder bei Projektmeilensteinen, um zu bewerten, ob die Arbeit den -Anforderungen entspricht. - -**Verwendung:** - -- In Ihr Projektverzeichnis kopieren -- Nach einer Sitzung (oder mehreren Sitzungen) die Arbeit des Agenten über sechs Dimensionen bewerten -- Jede Dimension wird mit 0-2 bewertet - -**Die sechs Dimensionen:** - -1. **Korrektheit** -- entspricht die Implementierung dem Zielverhalten? -2. **Verifikation** -- wurden die erforderlichen Prüfungen tatsächlich mit Nachweisen ausgeführt? -3. **Umfangdisziplin** -- ist der Agent innerhalb des ausgewählten Features geblieben? -4. **Zuverlässigkeit** -- übersteht das Ergebnis einen Neustart oder erneute Ausführung? -5. **Wartbarkeit** -- sind Code und Dokumentation klar genug für die nächste Sitzung? -6. **Übergabebereitschaft** -- kann eine neue Sitzung nur mit Repo-Artefakten fortgesetzt werden? - -**Schlussfolgerungsoptionen:** - -- Akzeptieren -- erfüllt die Anforderungen -- Überarbeiten -- benötigt Korrekturen vor der Akzeptanz -- Blockieren -- grundlegende Probleme, die zuerst gelöst werden müssen - -**Wichtig: Der Evaluator muss kalibriert werden.** Im Standardzustand sind Agenten -schlechte Selbstbewerter -- sie identifizieren Probleme und reden sich dann in eine -Genehmigung. Sie müssen iterieren: - -1. Den Evaluator auf einen abgeschlossenen Sprint anwenden. -2. Seine Bewertungen mit Ihrem eigenen menschlichen Urteil vergleichen. -3. Wo sie abweichen, die Rubrik spezifischer zu Bestehen/Fehler-Kriterien machen. -4. Erneut ausführen und die Übereinstimmung prüfen. -5. Wiederholen, bis der Evaluator konsistent mit dem menschlichen Review übereinstimmt. - -Planen Sie 3-5 Kalibrierungsrunden. Dokumentieren Sie jede Änderung, damit Sie -verfolgen können, was die Übereinstimmung verbessert hat. - -## quality-document.md - -Eine Qualitätsmomentaufnahme, die jede Produktdomäne und jede Architektur-Schicht in -Ihrem Projekt bewertet. Verfolgt die Codebasis-Gesundheit über die Zeit, nicht nur -die Ausgabe einzelner Sitzungen. - -**Verwendung:** - -- In Ihr Projektverzeichnis kopieren -- Vor Beginn einer Sitzung: lesen, um zu verstehen, wo die Codebasis am schwächsten ist -- Nach einer Sitzung: Noten basierend auf den Änderungen aktualisieren -- Über die Zeit: Momentaufnahmen vergleichen, um zu sehen, welche Harness-Änderungen - die Codebasis-Gesundheit tatsächlich verbessert haben - -**Was bewertet wird:** - -- **Produktdomänen** (z. B. Dokumentimport, Q&A-Ablauf, Indexierung): jede Domäne erhält - eine Note (A-D) für Verifikationsstatus, Agenten-Lesbarkeit, Teststabilität und - wesentliche Lücken -- **Architektur-Schichten** (z. B. Hauptprozess, Preload, Renderer, Services): jede - Schicht erhält eine Note für Grenzdurchsetzung und Agenten-Lesbarkeit - -**Warum es wichtig ist:** - -Die Evaluator-Rubrik bewertet einzelne Agenten-Ausgaben. Das Qualitätsdokument bewertet -die Codebasis selbst. Sie beantworten unterschiedliche Fragen: - -- Evaluator-Rubrik: "Hat der Agent in dieser Sitzung gute Arbeit geleistet?" -- Qualitätsdokument: "Wird das Projekt mit der Zeit stärker oder schwächer?" - -**Wann aktualisieren:** - -- Nach jeder bedeutenden Sitzung -- Vor Benchmark-Vergleichen -- Nach Aufräum- oder Vereinfachungsdurchläufen -- Bei Onboarding eines neuen Agenten oder Modells in das Projekt - -**Harness-Vereinfachungs-Bezug:** - -Das Qualitätsdokument unterstützt auch die Harness-Vereinfachung. Jede Harness-Komponente -codiert eine Annahme darüber, was das Modell nicht kann. Wenn sich Modelle verbessern, -veralten diese Annahmen. Um zu prüfen, ob eine Komponente noch benötigt wird: - -1. Eine Qualitätsdokument-Momentaufnahme erstellen. -2. Eine Harness-Komponente entfernen. -3. Die Benchmark-Aufgabensuite ausführen. -4. Eine weitere Momentaufnahme erstellen. -5. Vergleichen -- wenn die Noten nicht gefallen sind, war die Komponente Overhead. - Wenn doch, wiederherstellen. diff --git a/courses/learn-harness-engineering/docs/de/resources/templates/init.sh b/courses/learn-harness-engineering/docs/de/resources/templates/init.sh deleted file mode 100755 index d7522d66..00000000 --- a/courses/learn-harness-engineering/docs/de/resources/templates/init.sh +++ /dev/null @@ -1,29 +0,0 @@ -#!/usr/bin/env bash - -set -euo pipefail - -ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" -cd "$ROOT_DIR" - -# Replace these commands with the correct commands for your repository. -INSTALL_CMD=(npm install) -VERIFY_CMD=(npm test) -START_CMD=(npm run dev) - -echo "==> Working directory: $PWD" -echo "==> Syncing dependencies" -"${INSTALL_CMD[@]}" - -echo "==> Running baseline verification" -"${VERIFY_CMD[@]}" - -echo "==> Startup command" -printf ' %q' "${START_CMD[@]}" -printf '\n' - -if [ "${RUN_START_COMMAND:-0}" = "1" ]; then - echo "==> Starting the app" - exec "${START_CMD[@]}" -fi - -echo "Set RUN_START_COMMAND=1 if you want init.sh to launch the app directly." diff --git a/courses/learn-harness-engineering/docs/de/resources/templates/quality-document.md b/courses/learn-harness-engineering/docs/de/resources/templates/quality-document.md deleted file mode 100644 index e16cfbf5..00000000 --- a/courses/learn-harness-engineering/docs/de/resources/templates/quality-document.md +++ /dev/null @@ -1,43 +0,0 @@ -# Qualitätsdokument - -Eine Qualitätsmomentaufnahme für jede Produktdomäne und Architekturschicht. Sowohl Agenten als auch Menschen können dieses Dokument nutzen, um schnell zu verstehen, wo die Codebasis stark ist und wo sie Arbeit braucht. - -**Aktualisierungskadenz:** Nach jeder signifikanten Session oder vor dem Start einer neuen Arbeitsphase. - -**Benotungsskala:** - -- **A**: Alle Verifikationen bestanden, saubere Architektur, agentenlesbar, stabile Tests -- **B**: Verifikationen bestanden, größtenteils sauber, kleinere Lücken in Lesbarkeit oder Testabdeckung -- **C**: Teils funktionierend, bekannte Lücken, einige Code-Bereiche schwer für Agenten zu verstehen -- **D**: Nicht funktionierend oder größere strukturelle Probleme - ---- - -## Produktdomänen - -| Domäne | Note | Verifikation | Agenten-Lesbarkeit | Test-Stabilität | Wichtigste Lücken | Zuletzt aktualisiert | -|--------|------|-------------|-------------------|----------------|-------------------|---------------------| -| Dokumentenimport | - | - | - | - | - | - | -| Dokumentenverwaltung | - | - | - | - | - | - | -| Dokumentenindexierung | - | - | - | - | - | - | -| Q&A-Flow | - | - | - | - | - | - | -| Begründete Antworten | - | - | - | - | - | - | - -## Architekturschichten - -| Schicht | Note | Grenz-Durchsetzung | Agenten-Lesbarkeit | Wichtigste Lücken | Zuletzt aktualisiert | -|---------|------|-------------------|-------------------|-------------------|---------------------| -| Main-Prozess | - | - | - | - | - | -| Preload | - | - | - | - | - | -| Renderer | - | - | - | - | - | -| Services | - | - | - | - | - | - -## Änderungsverlauf - -### YYYY-MM-DD - -- Änderungen: -- Domänen hochgestuft: -- Domänen herabgestuft: -- Neue Lücken identifiziert: -- Lücken geschlossen: diff --git a/courses/learn-harness-engineering/docs/de/resources/templates/session-handoff.md b/courses/learn-harness-engineering/docs/de/resources/templates/session-handoff.md deleted file mode 100644 index 4a5ef291..00000000 --- a/courses/learn-harness-engineering/docs/de/resources/templates/session-handoff.md +++ /dev/null @@ -1,30 +0,0 @@ -# Session-Handoff - -## Aktuell Verifiziert - -- Was aktuell funktioniert: -- Welche Verifikation tatsächlich lief: - -## Diese Session Geändert - -- Code oder Verhalten hinzugefügt: -- Infrastruktur- oder Harness-Änderungen: - -## Kaputt oder Unverifiziert - -- Bekannter Defekt: -- Unverifizierter Pfad: -- Risiko für die nächste Session: - -## Nächster Bester Schritt - -- Feature mit höchster unvollständiger Priorität: -- Warum es als nächstes kommt: -- Was als bestanden gilt: -- Was sich während dieses Schritts nicht ändern darf: - -## Befehle - -- Start: -- Verifikation: -- Fokussierter Debug-Befehl: diff --git a/courses/learn-harness-engineering/docs/de/skills/index.md b/courses/learn-harness-engineering/docs/de/skills/index.md deleted file mode 100644 index 6022b3b6..00000000 --- a/courses/learn-harness-engineering/docs/de/skills/index.md +++ /dev/null @@ -1,49 +0,0 @@ -# Skills - -Dieses Verzeichnis enthält die mit dem Kurs ausgelieferten KI-Agenten-Skills. Skills sind eigenständige Prompt-Vorlagen, die KI-Coding-Agenten wie Claude Code, Codex, Cursor oder Windsurf laden können, um spezialisierte Aufgaben zu erledigen. - -## harness-creator - -Ein produktionsreifer harness-engineering-Skill für KI-Coding-Agenten. Er hilft beim Erstellen, Bewerten und Verbessern der fünf zentralen harness-Subsysteme: Anweisungen, Zustand, Verifikation, Scope und Session-Lebenszyklus. - -### Was er tut - -- **Harnesses von Grund auf erstellen** — AGENTS.md, Feature-Listen, Verifikations-Workflows -- **Bestehende Harnesses verbessern** — Bewertung der fünf Subsysteme mit priorisierten Verbesserungen -- **Session-Kontinuität entwerfen** — persistente Erinnerung, Fortschrittsverfolgung, Handoff-Prozeduren -- **Produktionsmuster anwenden** — Memory, Context Engineering, Tool-Sicherheit, Multi-Agent-Koordination - -### Schnellstart - -Die Skill-Dateien liegen im Repository unter [`skills/harness-creator/`](https://github.com/walkinglabs/learn-harness-engineering/tree/main/skills/harness-creator). - -Um ihn mit Claude Code zu verwenden, kopiere das Verzeichnis `harness-creator/` in den Skill-Pfad deines Projekts oder zeige deinen Agenten direkt auf die Datei SKILL.md. - -### Referenzmuster - -Der Skill enthält 6 ausführliche Referenzdokumente: - -| Muster | Wann verwenden | -|---------|----------------| -| Memory Persistence | Der Agent vergisst zwischen Sessions | -| Context Engineering | Kontextbudget, JIT-Laden | -| Tool Registry | Tool-Sicherheit, Nebenläufigkeitskontrolle | -| Multi-Agent Coordination | Parallelität, Spezialisierungs-Workflows | -| Lifecycle & Bootstrap | Hooks, Hintergrundaufgaben, Initialisierung | -| Gotchas | 15 nicht offensichtliche Fehlermodi mit Lösungen | - -### Vorlagen - -Der Skill enthält sofort nutzbare Vorlagen: - -- `agents.md` — AGENTS.md-Gerüst mit Arbeitsregeln -- `feature-list.json` — JSON Schema und Beispiel-Feature-Liste -- `init.sh` — Standard-Initialisierungsskript -- `progress.md` — Vorlage für Session-Fortschrittslogs - -### Wie dieser Skill gebaut wurde - -`harness-creator` wurde mit der **skill-creator**-Methodik entwickelt, Anthropics offiziellem Meta-Skill zum Erstellen, Testen und Iterieren von Agenten-Skills. skill-creator bietet einen strukturierten Workflow aus Entwurf → Test → Bewertung → Iteration mit Eval-Runnern, Gradern und Benchmark-Viewer. - -- **skill-creator Quelle**: [anthropics/skills — skill-creator](https://github.com/anthropics/skills/tree/main/skills/skill-creator) -- **Claude Code Skills Docs**: [anthropics/claude-code — plugin-dev/skills](https://github.com/anthropics/claude-code/tree/main/plugins/plugin-dev/skills) diff --git a/courses/learn-harness-engineering/docs/en/index.md b/courses/learn-harness-engineering/docs/en/index.md deleted file mode 100644 index af6b0fd4..00000000 --- a/courses/learn-harness-engineering/docs/en/index.md +++ /dev/null @@ -1 +0,0 @@ - diff --git a/courses/learn-harness-engineering/docs/en/lectures/lecture-01-why-capable-agents-still-fail/code/failure-pattern-demo.ts b/courses/learn-harness-engineering/docs/en/lectures/lecture-01-why-capable-agents-still-fail/code/failure-pattern-demo.ts deleted file mode 100644 index 311e665b..00000000 --- a/courses/learn-harness-engineering/docs/en/lectures/lecture-01-why-capable-agents-still-fail/code/failure-pattern-demo.ts +++ /dev/null @@ -1,170 +0,0 @@ -/** - * failure-pattern-demo.ts - * - * Simulates the 4-step failure pattern that capable agents fall into: - * 1. Incomplete context - * 2. Locally reasonable changes - * 3. No global verification - * 4. Premature completion - * - * Run: npx tsx docs/lectures/lecture-01-why-capable-agents-still-fail/code/failure-pattern-demo.ts - */ - -// --------------------------------------------------------------------------- -// Types -// --------------------------------------------------------------------------- - -interface StepState { - step: number; - name: string; - contextAvailable: string[]; - contextMissing: string[]; - actionTaken: string; - localOutcome: string; - globalImpact: string; - completed: boolean; -} - -// --------------------------------------------------------------------------- -// Simulated "model" -- a simple decision function that bases its output -// solely on whatever context it has been given. -// --------------------------------------------------------------------------- - -function modelDecide(context: string[], task: string): string { - const has = (s: string) => context.some((c) => c.includes(s)); - - // The task is to "add a search endpoint to the API". - // Correct answer requires knowing about auth middleware and rate limiting. - - if (!has("auth")) { - return "Created new route handler /search without authentication checks"; - } - if (!has("rate-limit")) { - return "Added search route with auth but forgot rate limiting"; - } - if (!has("test-standards")) { - return "Implemented search with auth and rate-limit, but no tests"; - } - return "Fully implemented search endpoint with auth, rate-limit, and tests"; -} - -// --------------------------------------------------------------------------- -// Failure simulation -// --------------------------------------------------------------------------- - -function simulateFailurePattern(): StepState[] { - const steps: StepState[] = []; - - // ---- Step 1: Incomplete Context ---- - const step1Context = ["project structure", "route definitions"]; - const step1Missing = ["auth middleware", "rate-limiting policy", "test standards"]; - const step1Decision = modelDecide(step1Context, "add search endpoint"); - - steps.push({ - step: 1, - name: "Incomplete Context", - contextAvailable: step1Context, - contextMissing: step1Missing, - actionTaken: step1Decision, - localOutcome: "Looks good -- route compiles, returns data", - globalImpact: "Missing auth means unauthenticated access to search", - completed: false, - }); - - // ---- Step 2: Locally Reasonable Changes ---- - // The agent adds auth after a hint, but still lacks other context. - const step2Context = [...step1Context, "auth middleware"]; - const step2Missing = ["rate-limiting policy", "test standards"]; - const step2Decision = modelDecide(step2Context, "add search endpoint"); - - steps.push({ - step: 2, - name: "Locally Reasonable Changes", - contextAvailable: step2Context, - contextMissing: step2Missing, - actionTaken: step2Decision, - localOutcome: "Route has auth -- looks complete locally", - globalImpact: "No rate limiting means the endpoint can be abused", - completed: false, - }); - - // ---- Step 3: No Global Verification ---- - const step3Context = [...step2Context, "rate-limiting policy"]; - const step3Missing = ["test standards"]; - const step3Decision = modelDecide(step3Context, "add search endpoint"); - - steps.push({ - step: 3, - name: "No Global Verification", - contextAvailable: step3Context, - contextMissing: step3Missing, - actionTaken: step3Decision, - localOutcome: "Feature appears fully implemented", - globalImpact: "No tests -- regression risk, violates project standards", - completed: false, - }); - - // ---- Step 4: Premature Completion ---- - steps.push({ - step: 4, - name: "Premature Completion", - contextAvailable: step3Context, - contextMissing: step3Missing, - actionTaken: 'Agent outputs: "Done. Added search endpoint."', - localOutcome: "Agent is satisfied, task marked complete", - globalImpact: "Task is incomplete -- missing tests, no E2E verification", - completed: true, - }); - - return steps; -} - -// --------------------------------------------------------------------------- -// Comparison table -// --------------------------------------------------------------------------- - -function printComparisonTable(steps: StepState[]): void { - console.log("\n" + "=".repeat(90)); - console.log(" FAILURE PATTERN DEMO -- 4 Steps to a Broken Deliverable"); - console.log("=".repeat(90)); - - for (const s of steps) { - console.log(`\n Step ${s.step}: ${s.name}`); - console.log(" " + "-".repeat(60)); - console.log(` Context available : ${s.contextAvailable.join(", ")}`); - console.log(` Context missing : ${s.contextMissing.join(", ") || "(none)"}`); - console.log(` Action taken : ${s.actionTaken}`); - console.log(` Local outcome : ${s.localOutcome}`); - console.log(` Global impact : ${s.globalImpact}`); - console.log(` Marked complete? : ${s.completed ? "YES (premature)" : "No"}`); - } - - // Summary comparison - console.log("\n" + "=".repeat(90)); - console.log(" COMPARISON: What the agent saw vs. what was actually needed"); - console.log("=".repeat(90)); - - const totalRequired = ["project structure", "route definitions", "auth middleware", "rate-limiting policy", "test standards"]; - const finalAvailable = steps[steps.length - 1].contextAvailable; - - const header = "| Criterion | Available | Missing | Status |"; - const sep = "|------------------------|-----------|---------|----------|"; - console.log("\n" + header); - console.log(sep); - - for (const item of totalRequired) { - const avail = finalAvailable.includes(item); - const row = `| ${item.padEnd(23)}| ${(avail ? "Yes" : "No").padEnd(10)}| ${(!avail ? "Yes" : "").padEnd(8)}| ${(avail ? "OK" : "GAP").padEnd(9)}|`; - console.log(row); - } - - const gapCount = totalRequired.filter((i) => !finalAvailable.includes(i)).length; - console.log("\n Result: Agent completed with " + gapCount + " of " + totalRequired.length + " context items missing."); - console.log(" This is the core failure pattern: each step looked reasonable in isolation.\n"); -} - -// --------------------------------------------------------------------------- -// Run -// --------------------------------------------------------------------------- - -printComparisonTable(simulateFailurePattern()); diff --git a/courses/learn-harness-engineering/docs/en/lectures/lecture-01-why-capable-agents-still-fail/code/failure-signals-checklist.md b/courses/learn-harness-engineering/docs/en/lectures/lecture-01-why-capable-agents-still-fail/code/failure-signals-checklist.md deleted file mode 100644 index fface47f..00000000 --- a/courses/learn-harness-engineering/docs/en/lectures/lecture-01-why-capable-agents-still-fail/code/failure-signals-checklist.md +++ /dev/null @@ -1,10 +0,0 @@ -# Failure Signals Checklist - -Use this checklist when reviewing a weak harness run. - -- Did the agent ask, or infer incorrectly, how to start the app? -- Did it create directories or abstractions that do not match the intended - product? -- Did it stop after making a visible UI shell without a complete workflow? -- Did it leave notes or artifacts that help a future run continue? -- Could a fresh session understand what happened in under five minutes? diff --git a/courses/learn-harness-engineering/docs/en/lectures/lecture-01-why-capable-agents-still-fail/code/index.md b/courses/learn-harness-engineering/docs/en/lectures/lecture-01-why-capable-agents-still-fail/code/index.md deleted file mode 100644 index c9885ed8..00000000 --- a/courses/learn-harness-engineering/docs/en/lectures/lecture-01-why-capable-agents-still-fail/code/index.md +++ /dev/null @@ -1,7 +0,0 @@ -# Code for Lecture 01 - -Use this folder for small examples that show: - -- a strong model failing in a weak environment -- underspecified repo setup -- missing feedback loops diff --git a/courses/learn-harness-engineering/docs/en/lectures/lecture-01-why-capable-agents-still-fail/code/underspecified-task.md b/courses/learn-harness-engineering/docs/en/lectures/lecture-01-why-capable-agents-still-fail/code/underspecified-task.md deleted file mode 100644 index 52fac651..00000000 --- a/courses/learn-harness-engineering/docs/en/lectures/lecture-01-why-capable-agents-still-fail/code/underspecified-task.md +++ /dev/null @@ -1,18 +0,0 @@ -# Underspecified Task Example - -Build a desktop knowledge base app with AI question answering. - -Constraints: - -- None specified -- No startup command given -- No folder structure guidance -- No data model defined -- No explicit completion criteria - -Typical outcomes from this kind of prompt: - -- the agent invents a structure ad hoc -- the app may compile but not start consistently -- the UI may appear before there is any usable ingest/query path -- the agent often stops after cosmetic success diff --git a/courses/learn-harness-engineering/docs/en/lectures/lecture-01-why-capable-agents-still-fail/index.md b/courses/learn-harness-engineering/docs/en/lectures/lecture-01-why-capable-agents-still-fail/index.md deleted file mode 100644 index 81719cfb..00000000 --- a/courses/learn-harness-engineering/docs/en/lectures/lecture-01-why-capable-agents-still-fail/index.md +++ /dev/null @@ -1,117 +0,0 @@ -[中文版本 →](../../../zh/lectures/lecture-01-why-capable-agents-still-fail/) - -> Code examples: [code/](https://github.com/walkinglabs/learn-harness-engineering/blob/main/docs/en/lectures/lecture-01-why-capable-agents-still-fail/code/) -> Practice project: [Project 01. Prompt-only vs. rules-first](./../../projects/project-01-baseline-vs-minimal-harness/index.md) - -# Lecture 01. Strong Models Don't Mean Reliable Execution - -You consider yourself well-traveled in the AI world — Claude Pro subscription, GPT-4o API key, SWE-bench leaderboard numbers memorized. One day you finally hand a real project to an AI agent, brimming with confidence. The result? It adds a feature but breaks the tests, fixes a bug but introduces two more, runs for 20 minutes and proudly declares "done" — and you look at the code and it's not what you asked for at all. - -Your first instinct? "This model isn't good enough. Time to upgrade." Hold on. Before you reach for your wallet, consider that the problem might not be the model at all. - -Let's look at some numbers. As of late 2025, the strongest coding agents on SWE-bench Verified achieve roughly 50-60%. And that's on carefully selected tasks with clear issue descriptions and existing test cases. Move to your daily development environment — vague requirements, no existing tests, implicit business rules scattered everywhere — and that number only goes down. - -But behind these numbers lies a counterintuitive truth. - -## Same Horse, Different Fates - -Anthropic ran a controlled experiment. Same prompt ("build a 2D retro game maker"), same model (Opus 4.5). First run: bare, no support — 20 minutes, $9, the game's core features didn't work at all. Second run: full harness (planner + generator + evaluator three-agent architecture) — 6 hours, $200, the game was playable. - -They didn't change the model. Opus 4.5 was still Opus 4.5. What changed was the tack. - -OpenAI's 2025 harness engineering article puts it plainly: Codex in a well-harnessed repository goes from "unreliable" to "reliable." Note their wording — not "a bit better," but a qualitative shift. Like a thoroughbred: you can ride it without proper tack, but you won't go far, won't go fast, and falling off is no surprise. The harness is that full set of tack — **everything in the engineering infrastructure outside the model weights.** - -## Where Agents Actually Get Stuck - -So what specifically goes wrong? - -The most common: you never clearly defined the task. You say "add a search feature," and the agent's understanding is completely different from yours — search what? Full-text or structured? Pagination? Highlighting? You didn't specify, so the agent guesses. A correct guess is luck; a wrong one costs more to fix than being specific would have cost in the first place. It's like walking into a restaurant and telling the chef "I'll have fish" — whether you get it braised, steamed, or in a hot pot is entirely up to chance. - -Even when you do specify, the project has implicit architectural conventions the agent doesn't know. Your team standardized on SQLAlchemy 2.0 syntax, but the agent writes 1.x code by default. All API endpoints must use OAuth 2.0 authentication, but that rule only exists in your head and a Slack message from three months ago. The agent can't see these — it's not that it doesn't want to comply, it literally doesn't know these rules exist. - -The environment is a trap too. Incomplete dev environment, missing dependencies, wrong tool versions. The agent burns precious context window on `pip install` failures and Node version mismatches instead of solving your actual task. Like hiring a skilled carpenter but forgetting to provide a hammer, nails, or a level workbench — no matter how talented, they can't do the job. - -Even more common: there's simply no way to verify. No tests, no lint, or verification commands never communicated to the agent. The agent writes code, looks at it, decides it's fine, says "done." It's like asking a student to submit homework with no answer key — they think they got it right, but when you grade it there's a pile of errors. Anthropic also observed an interesting phenomenon: when agents sense context is running low, they rush to finish, skip verification, and choose a simple solution over the optimal one. They call it "context anxiety" — the same thing that happens when you realize time is almost up on an exam and start randomly guessing on the remaining multiple-choice questions. - -Long tasks spanning sessions are even worse — all discoveries from the previous session are lost, and every new session has to re-explore the project structure and re-understand the code organization. Agents without persistent state see failure rates spike sharply on tasks exceeding 30 minutes. - -## Key Terminology - -With these scenarios in mind, these concepts are no longer just jargon: - -- **Capability Gap**: The huge gulf between model performance on benchmarks and performance on real tasks. A 50-60% pass rate on SWE-bench Verified means nearly half of real issues can't be resolved. -- **Harness**: Everything outside the model — instructions, tools, environment, state management, verification feedback. If it's not model weights, it's harness. What we've been calling the "tack." -- **Harness-Induced Failure**: The model has enough capability, but the execution environment has structural defects. Anthropic's controlled experiment already proved this. -- **Verification Gap**: The gap between the agent's confidence in its output and actual correctness. The agent says "I'm done" when it's not done — this is the most common failure mode. -- **Diagnostic Loop**: Execute, observe failure, attribute to a specific harness layer, fix that layer, re-execute. This is the core methodology of harness engineering. -- **Definition of Done**: A set of machine-verifiable conditions — tests pass, lint is clean, type checks pass. Without an explicit definition of done, the agent will invent its own. - -## When Things Fail, Fix the Harness First - -Core principle: **When things fail, don't swap the model first — check the harness.** If the same model succeeds on similar, well-structured tasks, assume it's a harness problem. It's like a car breaking down — you don't immediately suspect the engine. You check if it's out of gas first. - -Concrete steps: - -**Attribute every failure to a specific layer.** Don't just say "the model sucks." Ask: was the task unclear? Was context insufficient? Were there no verification methods? Map each failure to one of the five failure layers: task specification, context provision, execution environment, verification feedback, and state management. These are the practical diagnostic layers, not the six glossary terms above. Build this habit, and you'll find "the model isn't good enough" appearing less and less in your logs. - -**Write an explicit Definition of Done for every task.** Don't say "add a search feature." Say: -``` -Completion criteria: -- New endpoint GET /api/search?q=xxx -- Supports pagination, default 20 items -- Results include highlighted snippets -- All new code passes pytest -- Type checking passes (mypy --strict) -``` - -**Create an AGENTS.md file.** Put it in the repo root to tell the agent the project's tech stack, architectural conventions, and verification commands. This is the first step in harness engineering and the highest-ROI step you can take. One `AGENTS.md` file might be more effective than upgrading to a more expensive model — I'm not joking. - -**Build a diagnostic loop.** Don't treat failures as "the model being dumb again." Treat them as signals that your harness has a defect. Each failure, identify the layer, fix it, never fail that way again. After a few rounds, your harness gets stronger and agent performance stabilizes. Like road repair — every pothole you fill makes the next stretch smoother. - -**Quantify improvements.** Keep a simple log: did each task succeed or fail, and which layer caused the failure. After a few rounds you'll see which layer is the bottleneck — focus your energy there. - -## The Million-Line Experiment - -OpenAI ran an aggressive experiment in 2025: use Codex to build a complete internal product from an empty git repository. Five months later, the repo had roughly one million lines of code — application logic, infrastructure, tooling, documentation, internal dev tools — all agent-generated. Three engineers drove Codex, opening and merging about 1,500 PRs. An average of 3.5 PRs per person per day. - -The key constraint: **humans never write code directly.** This wasn't a gimmick — it was designed to force the team to figure out what changes when the engineer's primary job is no longer writing code, but designing environments, expressing intent, and building feedback loops. - -Early progress was slower than expected. Not because Codex wasn't capable, but because the environment wasn't complete enough — the agent lacked necessary tools, abstractions, and internal structures to advance high-level objectives. The engineers' work became: breaking large goals into small building blocks (design, code, review, test), letting the agent assemble them, then using those blocks to unlock more complex tasks. When something failed, the fix was almost never "try harder" — it was "what capability is the agent missing, and how do we make it both understandable and executable?" - -This experiment directly proves this lecture's core thesis: **the same model produces fundamentally different output in a bare environment versus one with a complete harness.** The model didn't change. The environment did. - -> Source: [OpenAI: Harness engineering: leveraging Codex in an agent-first world](https://openai.com/index/harness-engineering/) - -## A More Down-to-Earth Example - -A team used Claude Sonnet to add a new API endpoint to a mid-sized Python web app (FastAPI + PostgreSQL + Redis, ~15,000 lines of code). - -Initially they gave only one sentence: "add user preferences endpoints under `/api/v2/users`." The result? The agent spent 40% of its context window exploring the repo structure, produced code that looked reasonable but didn't follow the project's error handling patterns, used old SQLAlchemy syntax, and declared completion while the endpoint had runtime errors. The next session had to redo all the discovery work. - -Later they added `AGENTS.md` (describing project architecture and tech stack versions), explicit verification commands (`pytest tests/api/v2/ && python -m mypy src/`), and architecture decision records. The same model succeeded in all three independent runs, with ~60% better context efficiency. - -They didn't change the model. They changed the harness. - -## Key Takeaways - -- Model capability and execution reliability are different things. A thoroughbred still needs good tack. -- When things fail, check the harness first, then the model. Swapping models is the most expensive option — and often it's not even a model problem. -- Every failure is a signal: your harness has a structural defect. Find it, fix it. -- Five defense layers: task specification, context provision, execution environment, verification feedback, state management. Check them systematically, like a doctor ruling out the most common causes first. -- One `AGENTS.md` file might be more effective than upgrading to a more expensive model. Seriously. - -## Further Reading - -- [OpenAI: Harness Engineering — Leveraging Codex in an Agent-First World](https://openai.com/index/harness-engineering/) -- [Anthropic: Effective Harnesses for Long-Running Agents](https://www.anthropic.com/engineering/effective-harnesses-for-long-running-agents) -- [HumanLayer: Skill Issue — Harness Engineering for Coding Agents](https://humanlayer.dev/articles/harness-engineering-for-coding-agents/) -- [SWE-bench Leaderboard](https://www.swebench.com/) -- [Thoughtworks Technology Radar: Harness Engineering](https://www.thoughtworks.com/radar) - -## Exercises - -1. **Comparison experiment**: Pick a codebase you know well and a non-trivial modification task. First, run the agent with no harness support and record failures. Then add an `AGENTS.md` with explicit verification commands and run again with the same agent. Compare results, attributing each failure to one of the five defense layers. - -2. **Verification gap measurement**: Pick 5 coding tasks. After each task, record whether the agent claims completion, then verify actual correctness with independent tests. Calculate the proportion of times the agent claims done when it's actually not done — that's your verification gap. Then think: what verification commands would reduce this proportion? - -3. **Diagnostic loop practice**: Find a task where the agent repeatedly fails in your project. Run once, record the failure. Attribute it to one of the five layers. Fix that layer. Run again. Repeat three to five rounds, recording improvements each time. diff --git a/courses/learn-harness-engineering/docs/en/lectures/lecture-02-what-a-harness-actually-is/code/harness-components.md b/courses/learn-harness-engineering/docs/en/lectures/lecture-02-what-a-harness-actually-is/code/harness-components.md deleted file mode 100644 index 8595b9ba..00000000 --- a/courses/learn-harness-engineering/docs/en/lectures/lecture-02-what-a-harness-actually-is/code/harness-components.md +++ /dev/null @@ -1,21 +0,0 @@ -# Harness Components Example - -For a coding agent working in a local repository: - -- Model: - the LLM itself - -- Harness: - - system prompt - - AGENTS.md - - bash tool - - file read/write tools - - git access - - local filesystem - - startup scripts - - test commands - - stop hooks - - lint checks - - evaluator loop - -If you change any of the above harness pieces, you change the effective agent. diff --git a/courses/learn-harness-engineering/docs/en/lectures/lecture-02-what-a-harness-actually-is/code/harness-vs-no-harness.ts b/courses/learn-harness-engineering/docs/en/lectures/lecture-02-what-a-harness-actually-is/code/harness-vs-no-harness.ts deleted file mode 100644 index d856ba36..00000000 --- a/courses/learn-harness-engineering/docs/en/lectures/lecture-02-what-a-harness-actually-is/code/harness-vs-no-harness.ts +++ /dev/null @@ -1,180 +0,0 @@ -/** - * harness-vs-no-harness.ts - * - * Side-by-side comparison of the same task executor running with and without - * a harness. The harness version adds explicit rules, verification steps, - * and stop conditions. - * - * Run: npx tsx docs/lectures/lecture-02-what-a-harness-actually-is/code/harness-vs-no-harness.ts - */ - -// --------------------------------------------------------------------------- -// Types & helpers -// --------------------------------------------------------------------------- - -interface TaskResult { - name: string; - passed: boolean; - durationMs: number; - issues: string[]; -} - -function pad(s: string, len: number): string { - return s.length >= len ? s : s + " ".repeat(len - s.length); -} - -// --------------------------------------------------------------------------- -// Simulated task executor -// --------------------------------------------------------------------------- - -/** A simple task that can succeed or fail depending on rules. */ -type Task = { - name: string; - requiresAuth: boolean; - hasTests: boolean; - withinScope: boolean; -}; - -const tasks: Task[] = [ - { name: "Add search endpoint", requiresAuth: true, hasTests: false, withinScope: true }, - { name: "Add delete endpoint", requiresAuth: true, hasTests: true, withinScope: true }, - { name: "Refactor auth middleware", requiresAuth: true, hasTests: true, withinScope: false }, - { name: "Add health check", requiresAuth: false, hasTests: true, withinScope: true }, - { name: "Add rate limiter", requiresAuth: true, hasTests: false, withinScope: true }, -]; - -// --------------------------------------------------------------------------- -// Run WITHOUT harness -- just execute every task, no checks -// --------------------------------------------------------------------------- - -function runWithoutHarness(taskList: Task[]): TaskResult[] { - const results: TaskResult[] = []; - - for (const t of taskList) { - const start = performance.now(); - const issues: string[] = []; - - // The "agent" does the work and calls it done. - let passed = true; - - // Problems that go undetected without a harness - if (t.requiresAuth && !t.hasTests) { - issues.push("No tests for auth-protected endpoint"); - // Agent doesn't notice -- marks as pass anyway - } - if (!t.withinScope) { - issues.push("Task is outside current scope"); - // Agent doesn't notice - } - - const duration = performance.now() - start; - results.push({ name: t.name, passed, durationMs: duration, issues }); - } - - return results; -} - -// --------------------------------------------------------------------------- -// Run WITH harness -- rules, verification, stop conditions -// --------------------------------------------------------------------------- - -function runWithHarness(taskList: Task[]): TaskResult[] { - const rules = { - requireTestsForAuth: true, - enforceScope: true, - }; - - const results: TaskResult[] = []; - - for (const t of taskList) { - const start = performance.now(); - const issues: string[] = []; - let passed = true; - - // Rule: auth endpoints must have tests - if (rules.requireTestsForAuth && t.requiresAuth && !t.hasTests) { - issues.push("BLOCKED: Auth-protected endpoint missing tests"); - passed = false; - } - - // Rule: stay in scope - if (rules.enforceScope && !t.withinScope) { - issues.push("BLOCKED: Task outside active scope -- skipped"); - passed = false; - } - - // Verification: re-check after execution - if (passed && !t.hasTests) { - issues.push("WARNING: No tests exist for this change"); - } - - const duration = performance.now() - start; - results.push({ name: t.name, passed, durationMs: duration, issues }); - } - - return results; -} - -// --------------------------------------------------------------------------- -// Reporting -// --------------------------------------------------------------------------- - -function printComparison( - noHarness: TaskResult[], - withHarness: TaskResult[] -): void { - console.log("\n" + "=".repeat(80)); - console.log(" HARNESS vs NO-HARNESS COMPARISON"); - console.log("=".repeat(80)); - - const header = `| ${pad("Task", 28)}| ${pad("No-Harness", 12)}| ${pad("Issues(NH)", 35)}| ${pad("Harness", 12)}| ${pad("Issues(H)", 35)}|`; - const sep = `|${"-".repeat(30)}|${"-".repeat(14)}|${"-".repeat(37)}|${"-".repeat(14)}|${"-".repeat(37)}|`; - - console.log("\n" + header); - console.log(sep); - - for (let i = 0; i < noHarness.length; i++) { - const nh = noHarness[i]; - const wh = withHarness[i]; - const nhPass = nh.passed ? "PASS" : "FAIL"; - const whPass = wh.passed ? "PASS" : "FAIL"; - const nhIssue = nh.issues[0] ?? "(none)"; - const whIssue = wh.issues[0] ?? "(none)"; - - console.log( - `| ${pad(nh.name, 28)}| ${pad(nhPass, 12)}| ${pad(nhIssue, 35)}| ${pad(whPass, 12)}| ${pad(whIssue, 35)}|` - ); - } - - // Metrics - const nhPassed = noHarness.filter((r) => r.passed).length; - const whPassed = withHarness.filter((r) => r.passed).length; - const nhIssues = noHarness.reduce((sum, r) => sum + r.issues.length, 0); - const whIssues = withHarness.reduce((sum, r) => sum + r.issues.length, 0); - - console.log("\n" + "=".repeat(80)); - console.log(" SUMMARY METRICS"); - console.log("=".repeat(80)); - - const metricsHeader = `| ${pad("Metric", 30)}| ${pad("No Harness", 15)}| ${pad("With Harness", 15)}|`; - const metricsSep = `|${"-".repeat(32)}|${"-".repeat(17)}|${"-".repeat(17)}|`; - console.log("\n" + metricsHeader); - console.log(metricsSep); - console.log(`| ${pad("Tasks passed", 30)}| ${pad(String(nhPassed) + "/" + noHarness.length, 15)}| ${pad(String(whPassed) + "/" + withHarness.length, 15)}|`); - console.log(`| ${pad("Issues detected", 30)}| ${pad(String(nhIssues), 15)}| ${pad(String(whIssues), 15)}|`); - console.log(`| ${pad("False positives (passed but flawed)", 30)}| ${pad(String(nhPassed - truePassCount(noHarness)), 15)}| ${pad(String(whPassed - truePassCount(withHarness)), 15)}|`); - - console.log("\n The harness catches problems the no-harness run silently ignores."); - console.log(" Without a harness, every task 'passes' even when it shouldn't.\n"); -} - -/** Count tasks that actually pass (have tests and are in scope). */ -function truePassCount(results: TaskResult[]): number { - return results.filter((r) => r.passed && r.issues.length === 0).length; -} - -// --------------------------------------------------------------------------- -// Run -// --------------------------------------------------------------------------- - -printComparison(runWithoutHarness(tasks), runWithHarness(tasks)); diff --git a/courses/learn-harness-engineering/docs/en/lectures/lecture-02-what-a-harness-actually-is/code/index.md b/courses/learn-harness-engineering/docs/en/lectures/lecture-02-what-a-harness-actually-is/code/index.md deleted file mode 100644 index b16cfa3e..00000000 --- a/courses/learn-harness-engineering/docs/en/lectures/lecture-02-what-a-harness-actually-is/code/index.md +++ /dev/null @@ -1,8 +0,0 @@ -# Code for Lecture 02 - -Use this folder for small examples that distinguish: - -- model behavior -- harness behavior -- prompt-only setups -- environment-backed agent setups diff --git a/courses/learn-harness-engineering/docs/en/lectures/lecture-02-what-a-harness-actually-is/code/minimal-harness-loop.ts b/courses/learn-harness-engineering/docs/en/lectures/lecture-02-what-a-harness-actually-is/code/minimal-harness-loop.ts deleted file mode 100644 index e56cddd8..00000000 --- a/courses/learn-harness-engineering/docs/en/lectures/lecture-02-what-a-harness-actually-is/code/minimal-harness-loop.ts +++ /dev/null @@ -1,36 +0,0 @@ -type Message = { - role: "user" | "assistant"; - content: string; -}; - -type ToolResult = { - ok: boolean; - output: string; -}; - -function runTool(name: string, input: string): ToolResult { - if (name === "read_file") { - return { ok: true, output: `contents of ${input}` }; - } - - return { ok: false, output: `unknown tool: ${name}` }; -} - -export function minimalHarness(messages: Message[]) { - const nextAction = { - tool: "read_file", - input: "README.md" - }; - - const result = runTool(nextAction.tool, nextAction.input); - - return { - messages: [ - ...messages, - { - role: "assistant", - content: `Tool ${nextAction.tool} returned: ${result.output}` - } - ] - }; -} diff --git a/courses/learn-harness-engineering/docs/en/lectures/lecture-02-what-a-harness-actually-is/index.md b/courses/learn-harness-engineering/docs/en/lectures/lecture-02-what-a-harness-actually-is/index.md deleted file mode 100644 index 75673245..00000000 --- a/courses/learn-harness-engineering/docs/en/lectures/lecture-02-what-a-harness-actually-is/index.md +++ /dev/null @@ -1,109 +0,0 @@ -[中文版本 →](../../../zh/lectures/lecture-02-what-a-harness-actually-is/) - -> Code examples: [code/](https://github.com/walkinglabs/learn-harness-engineering/blob/main/docs/en/lectures/lecture-02-what-a-harness-actually-is/code/) -> Practice project: [Project 01. Prompt-only vs. rules-first](./../../projects/project-01-baseline-vs-minimal-harness/index.md) - -# Lecture 02. What Harness Actually Means - -The word "harness" gets thrown around a lot in AI coding agent circles, but honestly, most people mean "a prompt file" when they say harness. That's not a harness. It's like opening a restaurant with nothing but ingredients — no stove, no knives, no recipes, no plating workflow. That's not a restaurant. That's a refrigerator. - -This lecture gives you a precise, actionable harness definition. Not an academic abstraction, but a framework you can use today: a harness consists of five subsystems, each with clear responsibilities and evaluation criteria. - -## Start with an Analogy - -Imagine you're a newly hired engineer dropped into a project with zero documentation. No README, no comments in the code, nobody tells you how to run tests, CI config is buried somewhere. Can you write good code? Maybe — if you're smart enough and patient enough. But you'll spend enormous time on "figuring out what this project is about" rather than "solving the problem." - -An AI agent faces the exact same situation. And it's worse — you can at least ask a colleague. The agent can only see files you put in front of it and commands it can execute. It can't tap someone on the shoulder and ask "hey, which version of the ORM does this project use?" - -OpenAI frames the core principle as "the repo IS the spec" — all necessary context should be in the repository, delivered through structured instruction files, explicit verification commands, and clear directory organization. Anthropic's long-running agents documentation emphasizes state persistence, explicit recovery paths, and structured progress tracking. The two companies focus on different aspects, but they're saying the same thing: **everything in the engineering infrastructure outside the model determines how much of the model's capability actually gets realized.** - -Look at some tools you already know: - -**Claude Code** embodies harness thinking. It reads `CLAUDE.md` from your repo (recipe shelf), can run shell commands (knife rack), executes in your local environment (stove), maintains session history (prep station), and can run tests and see results (quality check window). But if you don't tell it how to run tests, the quality check window is broken — nobody knows whether the dish is fully cooked. - -**Cursor** follows similar logic. Its `.cursorrules` file is the recipe shelf, the terminal is the knife rack, it reads your project structure and lint config for the stove. But Cursor's state management is relatively weak — close the IDE and reopen it, and the previous context is gone. - -**Codex** (OpenAI's coding agent) uses git worktrees to isolate each task's runtime environment, paired with a local observability stack (logs, metrics, traces), so every change is verified in an independent environment. In repos with `AGENTS.md` and clear verification commands, it performs far better than in "bare" repos. - -**AutoGPT** is the cautionary tale — lack of structured state management leads to context accumulation in long tasks, and lack of precise feedback mechanisms causes the agent to loop. Many people say AutoGPT "doesn't work," but really it's AutoGPT's harness that doesn't work — give a chef a broken stove and even the best ingredients won't produce a meal. - -## Core Concepts - -- **What is a harness**: Everything in the engineering infrastructure outside the model weights. OpenAI distills the engineer's core job into three things: designing environments, expressing intent, and building feedback loops. Anthropic calls their Claude Agent SDK a "general-purpose agent harness." -- **The repo is the single source of truth**: Anything the agent can't see, for all practical purposes, doesn't exist. OpenAI treats the repo as the "system of record" — all necessary context must live there, through structured files and clear directory organization. -- **Give a map, not a manual**: OpenAI's experience — `AGENTS.md` should be a directory page, not an encyclopedia. Around 100 lines is enough. If it doesn't fit, split it into the `docs/` directory and let the agent read on demand. -- **Constrain, don't micromanage**: A good harness uses executable rules to constrain the agent, rather than enumerating instructions one by one. OpenAI says "enforce invariants, don't micromanage implementation"; Anthropic found that agents confidently praise their own work, and the solution is to separate "the person who does the work" from "the person who checks the work." -- **Remove components one at a time**: To quantify each harness component's marginal contribution, remove them one at a time and see which removal causes the biggest performance drop. Anthropic used this method and found that as models get stronger, some components stop being critical — but new ones always emerge. - -## The Five-Subsystem Harness Model - -Back to the kitchen analogy. A complete kitchen has five functional areas, and a harness has five subsystems: - -```mermaid -flowchart LR - Rules["Project rules
AGENTS.md / CLAUDE.md"] --> Agent["AI Agent"] - State["Progress and git
PROGRESS.md / commits"] --> Agent - Agent --> Tools["Tools
shell / files / tests"] - Tools --> Env["Runtime
deps / services / versions"] - Env --> Checks["Check results
test / lint / build"] - Checks --> Agent -``` - -**Instruction subsystem (recipe shelf)**: Create `AGENTS.md` (or `CLAUDE.md`) containing a project overview and purpose (one sentence), tech stack and versions (Python 3.11, FastAPI 0.100+, PostgreSQL 15), first-run commands (`make setup`, `make test`), non-negotiable hard constraints ("All APIs must use OAuth 2.0"), and links to more detailed documentation. - -**Tool subsystem (knife rack)**: Ensure the agent has sufficient tool access. Don't disable shell for "security" — if the agent can't even run `pip install`, how is it supposed to work? But don't open everything either — follow least-privilege principles. - -**Environment subsystem (stove)**: Make the environment state self-describing. Use `pyproject.toml` or `package.json` to lock dependencies, `.nvmrc` or `.python-version` for runtime versions, Docker or devcontainers for reproducibility. - -**State subsystem (prep station)**: Long tasks need progress tracking. Use a simple `PROGRESS.md` file recording: what's done, what's in progress, what's blocked. Update before each session ends, read when the next session starts. - -**Feedback subsystem (quality check window)**: This is the highest-ROI subsystem. Explicitly list verification commands in `AGENTS.md`: -``` -Verification commands: -- Tests: pytest tests/ -x -- Type check: mypy src/ --strict -- Lint: ruff check src/ -- Full verification: make check (includes all above) -``` - -Missing any subsystem is like missing a functional area in the kitchen — you can still cook, but it's always awkward. - -**Quantifying harness component value**: Use "isometric model control." Keep the model fixed, remove subsystems one at a time, and measure which removal causes the biggest performance drop. The largest drop identifies the component with the highest marginal contribution in that task, not automatically the bottleneck. To locate the bottleneck, combine this experiment with failure logs and attribution: unclear task, insufficient context, unreproducible environment, missing verification feedback, or broken state management. - -## A Team's Real Story - -A team used GPT-4o on a TypeScript + React frontend app (~20,000 lines of code). They went through four stages — essentially adding kitchen equipment one piece at a time: - -**Stage 1 — Empty kitchen**: Only a basic project description in README. 1 out of 5 runs succeeded (20%). Main failures: chose wrong package manager (npm vs yarn), didn't follow component naming conventions, couldn't run tests. - -**Stage 2 — Recipe shelf installed**: Added `AGENTS.md` with tech stack versions, naming conventions, key architecture decisions. Success rate rose to 60%. Remaining failures were mainly environment issues and missing verification. - -**Stage 3 — Quality check window opened**: Listed verification commands in `AGENTS.md`: `yarn test && yarn lint && yarn build`. Success rate rose to 80%. - -**Stage 4 — Prep station ready**: Introduced progress file templates where agents recorded completed and incomplete work each run. Success rate stabilized at 80-100%. - -Four iterations, the model didn't change at all, success rate went from 20% to near 100%. That's the power of harness engineering. You didn't buy more expensive ingredients — you just organized the kitchen properly. - -## Key Takeaways - -- Harness = Instructions + Tools + Environment + State + Feedback. Five subsystems, like a kitchen's five functional areas — all essential. -- If it's not model weights, it's harness. Your harness determines how much model capability gets realized. -- Among the five subsystems, the feedback subsystem usually has the lowest investment and highest return. Get your verification commands right first — the quality check window is the most worthwhile upgrade. -- Use "isometric model control" to quantify each subsystem's marginal contribution — don't go by gut feeling; use failure logs and attribution to locate the real bottleneck. -- Harness rots like code does. Audit regularly, pay down harness debt like you pay down technical debt. - -## Further Reading - -- [OpenAI: Harness Engineering](https://openai.com/index/harness-engineering/) -- [Anthropic: Effective Harnesses for Long-Running Agents](https://www.anthropic.com/engineering/effective-harnesses-for-long-running-agents) -- [HumanLayer: Harness Engineering for Coding Agents](https://humanlayer.dev/articles/harness-engineering-for-coding-agents/) -- [SWE-agent: Agent-Computer Interfaces](https://github.com/princeton-nlp/SWE-agent) -- [Thoughtworks: Harness Engineering on Technology Radar](https://www.thoughtworks.com/radar) - -## Exercises - -1. **Five-tuple harness audit**: Take a project where you use an AI agent and do a complete audit using the five-tuple framework. Score each subsystem 1-5. Find the lowest-scoring subsystem, spend 30 minutes improving it, then observe the change in agent performance. - -2. **Isometric model control experiment**: Pick one model and one challenging task. Sequentially remove instructions (delete AGENTS.md), remove feedback (don't provide verification commands), remove state (no progress files) — remove only one at a time and measure the performance drop. Based on results, rank subsystem importance for your project. - -3. **Affordance analysis**: Find a scenario where the agent in your project "wants to do something but can't" (e.g., knows it should use parameterized queries but doesn't know your project's ORM patterns). Analyze whether this is a Gulf of Execution (doesn't know how) or Gulf of Evaluation (doesn't know if it's right), then design a harness improvement to bridge it. diff --git a/courses/learn-harness-engineering/docs/en/lectures/lecture-03-why-the-repository-must-become-the-system-of-record/code/index.md b/courses/learn-harness-engineering/docs/en/lectures/lecture-03-why-the-repository-must-become-the-system-of-record/code/index.md deleted file mode 100644 index ead6a80d..00000000 --- a/courses/learn-harness-engineering/docs/en/lectures/lecture-03-why-the-repository-must-become-the-system-of-record/code/index.md +++ /dev/null @@ -1,7 +0,0 @@ -# Code for Lecture 03 - -Use this folder for examples of: - -- agent-readable repo structures -- docs as system of record -- bad vs good knowledge placement diff --git a/courses/learn-harness-engineering/docs/en/lectures/lecture-03-why-the-repository-must-become-the-system-of-record/code/repo-knowledge-layout.txt b/courses/learn-harness-engineering/docs/en/lectures/lecture-03-why-the-repository-must-become-the-system-of-record/code/repo-knowledge-layout.txt deleted file mode 100644 index 45315dac..00000000 --- a/courses/learn-harness-engineering/docs/en/lectures/lecture-03-why-the-repository-must-become-the-system-of-record/code/repo-knowledge-layout.txt +++ /dev/null @@ -1,13 +0,0 @@ -AGENTS.md -docs/ - ARCHITECTURE.md - PRODUCT.md - RELIABILITY.md - references/ - electron.md - sqlite.md - plans/ - active/ - completed/ -src/ -scripts/ diff --git a/courses/learn-harness-engineering/docs/en/lectures/lecture-03-why-the-repository-must-become-the-system-of-record/code/repo-reader.ts b/courses/learn-harness-engineering/docs/en/lectures/lecture-03-why-the-repository-must-become-the-system-of-record/code/repo-reader.ts deleted file mode 100644 index 46f6a62a..00000000 --- a/courses/learn-harness-engineering/docs/en/lectures/lecture-03-why-the-repository-must-become-the-system-of-record/code/repo-reader.ts +++ /dev/null @@ -1,189 +0,0 @@ -/** - * repo-reader.ts - * - * Reads a directory structure and scores it on discoverability. - * Checks for: AGENTS.md, docs/, architecture docs, feature tracking, - * handoff files, and other signals of a repository that serves as the - * system of record. - * - * Usage: - * npx tsx docs/lectures/lecture-03.../code/repo-reader.ts [path] - * (defaults to current working directory if no path given) - * - * Run: npx tsx docs/lectures/lecture-03-why-the-repository-must-become-the-system-of-record/code/repo-reader.ts - */ - -import * as fs from "node:fs"; -import * as path from "node:path"; - -// --------------------------------------------------------------------------- -// Scoring criteria -// --------------------------------------------------------------------------- - -interface Check { - name: string; - description: string; - maxPoints: number; - check: (dir: string) => { points: number; found: string[]; missing: string[] }; -} - -const checks: Check[] = [ - { - name: "AGENTS.md / CLAUDE.md", - description: "Agent-readable instruction file at repo root", - maxPoints: 15, - check: (dir) => { - const candidates = ["AGENTS.md", "CLAUDE.md", ".claude/CLAUDE.md"]; - const found = candidates.filter((f) => fs.existsSync(path.join(dir, f))); - return { points: found.length > 0 ? 15 : 0, found, missing: found.length > 0 ? [] : candidates }; - }, - }, - { - name: "Documentation directory", - description: "Dedicated docs/ or documentation/ directory", - maxPoints: 10, - check: (dir) => { - const candidates = ["docs", "documentation", "doc"]; - const found = candidates.filter((f) => { - const p = path.join(dir, f); - return fs.existsSync(p) && fs.statSync(p).isDirectory(); - }); - return { points: found.length > 0 ? 10 : 0, found, missing: found.length > 0 ? [] : ["docs/"] }; - }, - }, - { - name: "Architecture documentation", - description: "Files describing system architecture", - maxPoints: 15, - check: (dir) => { - const patterns = [ - "architecture.md", "ARCHITECTURE.md", "docs/architecture.md", - "docs/architecture/", "design.md", "DESIGN.md", - ]; - const found = patterns.filter((f) => fs.existsSync(path.join(dir, f))); - return { points: found.length > 0 ? 15 : 0, found, missing: found.length > 0 ? [] : ["architecture.md or docs/architecture/"] }; - }, - }, - { - name: "Feature tracking", - description: "Feature list or task tracking file", - maxPoints: 15, - check: (dir) => { - const patterns = [ - "feature_list.json", "features.md", "FEATURES.md", - "docs/features.md", "tasks.json", "TODO.md", - ]; - const found = patterns.filter((f) => fs.existsSync(path.join(dir, f))); - return { points: found.length > 0 ? 15 : 0, found, missing: found.length > 0 ? [] : ["feature_list.json or features.md"] }; - }, - }, - { - name: "Handoff / session continuity", - description: "Files for multi-session continuity", - maxPoints: 15, - check: (dir) => { - const patterns = [ - "HANDOFF.md", "handoff.md", "SESSION_NOTES.md", - "docs/handoff.md", ".handoff", "PROGRESS.md", - ]; - const found = patterns.filter((f) => fs.existsSync(path.join(dir, f))); - return { points: found.length > 0 ? 15 : 0, found, missing: found.length > 0 ? [] : ["HANDOFF.md or PROGRESS.md"] }; - }, - }, - { - name: "Testing structure", - description: "Test directory or test configuration", - maxPoints: 10, - check: (dir) => { - const patterns = ["test", "tests", "__tests__", "spec"]; - const found = patterns.filter((f) => { - const p = path.join(dir, f); - return fs.existsSync(p) && fs.statSync(p).isDirectory(); - }); - return { points: found.length > 0 ? 10 : 0, found, missing: found.length > 0 ? [] : ["test/ or tests/"] }; - }, - }, - { - name: "Configuration files", - description: "Project config (package.json, tsconfig, etc.)", - maxPoints: 10, - check: (dir) => { - const patterns = ["package.json", "tsconfig.json", "pyproject.toml", "Cargo.toml", "go.mod"]; - const found = patterns.filter((f) => fs.existsSync(path.join(dir, f))); - return { points: found.length > 0 ? 10 : 0, found, missing: found.length > 0 ? [] : ["package.json or equivalent"] }; - }, - }, - { - name: "README", - description: "Root README file", - maxPoints: 10, - check: (dir) => { - const patterns = ["README.md", "README.rst", "README.txt", "README"]; - const found = patterns.filter((f) => fs.existsSync(path.join(dir, f))); - return { points: found.length > 0 ? 10 : 0, found, missing: found.length > 0 ? [] : ["README.md"] }; - }, - }, -]; - -// --------------------------------------------------------------------------- -// Report generation -// --------------------------------------------------------------------------- - -function pad(s: string, len: number): string { - return s.length >= len ? s : s + " ".repeat(len - s.length); -} - -function scoreRepo(targetDir: string): void { - const resolved = path.resolve(targetDir); - - if (!fs.existsSync(resolved)) { - console.error(` Error: Directory not found: ${resolved}`); - process.exit(1); - } - - console.log("\n" + "=".repeat(80)); - console.log(" REPOSITORY DISCOVERABILITY SCORE"); - console.log("=".repeat(80)); - console.log(` Target: ${resolved}\n`); - - const header = `| ${pad("Criterion", 30)}| ${pad("Points", 8)}| ${pad("Status", 10)}| Details`; - const sep = `|${"-".repeat(32)}|${"-".repeat(10)}|${"-".repeat(12)}|${"-".repeat(30)}`; - console.log(header); - console.log(sep); - - let totalScore = 0; - let maxScore = 0; - - for (const check of checks) { - const result = check.check(resolved); - totalScore += result.points; - maxScore += check.maxPoints; - const status = result.points > 0 ? "PASS" : "FAIL"; - const detail = result.found.length > 0 ? result.found.join(", ") : result.missing[0]; - - console.log(`| ${pad(check.name, 30)}| ${pad(result.points + "/" + check.maxPoints, 8)}| ${pad(status, 10)}| ${detail}`); - } - - // Final score - console.log("\n" + "-".repeat(80)); - console.log(` TOTAL SCORE: ${totalScore} / ${maxScore} (${Math.round((totalScore / maxScore) * 100)}%)`); - - // Grade - const pct = totalScore / maxScore; - let grade: string; - if (pct >= 0.9) grade = "A -- Repository is a strong system of record"; - else if (pct >= 0.7) grade = "B -- Good foundation, minor gaps"; - else if (pct >= 0.5) grade = "C -- Partial structure, significant gaps"; - else if (pct >= 0.3) grade = "D -- Minimal structure, hard for agents to navigate"; - else grade = "F -- Repository lacks discoverability signals"; - - console.log(` GRADE: ${grade}`); - console.log("=".repeat(80) + "\n"); -} - -// --------------------------------------------------------------------------- -// Main -// --------------------------------------------------------------------------- - -const target = process.argv[2] || process.cwd(); -scoreRepo(target); diff --git a/courses/learn-harness-engineering/docs/en/lectures/lecture-03-why-the-repository-must-become-the-system-of-record/code/system-of-record-checklist.md b/courses/learn-harness-engineering/docs/en/lectures/lecture-03-why-the-repository-must-become-the-system-of-record/code/system-of-record-checklist.md deleted file mode 100644 index e0856c50..00000000 --- a/courses/learn-harness-engineering/docs/en/lectures/lecture-03-why-the-repository-must-become-the-system-of-record/code/system-of-record-checklist.md +++ /dev/null @@ -1,11 +0,0 @@ -# System of Record Checklist - -Can a fresh agent discover the following from the repo alone? - -- What product is being built? -- What the app should do for users? -- How the codebase is organized? -- How the app starts? -- How health is checked? -- What work is currently in progress? -- What quality standards matter? diff --git a/courses/learn-harness-engineering/docs/en/lectures/lecture-03-why-the-repository-must-become-the-system-of-record/index.md b/courses/learn-harness-engineering/docs/en/lectures/lecture-03-why-the-repository-must-become-the-system-of-record/index.md deleted file mode 100644 index b59d885d..00000000 --- a/courses/learn-harness-engineering/docs/en/lectures/lecture-03-why-the-repository-must-become-the-system-of-record/index.md +++ /dev/null @@ -1,137 +0,0 @@ -[中文版本 →](../../../zh/lectures/lecture-03-why-the-repository-must-become-the-system-of-record/) - -> Code examples: [code/](https://github.com/walkinglabs/learn-harness-engineering/blob/main/docs/en/lectures/lecture-03-why-the-repository-must-become-the-system-of-record/code/) -> Practice project: [Project 02. Agent-readable workspace](./../../projects/project-02-agent-readable-workspace/index.md) - -# Lecture 03. Make the Repository Your Single Source of Truth - -Your team's architecture decisions are scattered across Confluence, Slack, Jira, and a few senior engineers' heads. For humans this barely works — you can ask a colleague, search chat history, dig through docs. If all else fails, you can corner someone in the break room. But for an AI agent, information that's not in the repository simply does not exist. - -This isn't an exaggeration. Think about what an agent's inputs actually are: system prompts and task descriptions, file contents from the repository, and tool execution output. That's it. Your Slack history, Jira tickets, Confluence pages, and that architecture decision you discussed with a colleague over coffee on Friday afternoon — the agent can't see any of it. It can't "go ask someone" or "search the chat history." It's an engineer locked inside the repository — everything outside, it knows nothing about. - -So the question becomes: are you going to give this engineer a good map? - -## What Belongs on the Map - -OpenAI states this bluntly: **information that doesn't exist in the repo, doesn't exist for the agent.** They call this the "repo as spec" principle — the repository itself is the highest-authority specification document. - -Anthropic's long-running agents documentation echoes this: persistent state is a necessary condition for long-task continuity. Cross-session knowledge recoverability directly determines task success rates. And this state must exist in the repository — because that's the only stable, accessible storage the agent has. - -You might think: "Our team is small, knowledge is in everyone's heads, and it works fine." Sure, for humans. But if you're using an agent, accept this fact: the agent can't ask people. Everything it needs to know must be written down and placed where it can find it. - -This isn't about "writing more documentation." It's about "putting decision information in the right place." A 50-line `ARCHITECTURE.md` in the `src/api/` directory is ten thousand times more useful than a 500-page design document in Confluence that nobody maintains. It's like a hand-drawn office map taped to your desk versus a beautiful architectural blueprint locked in a filing cabinet — the former is right there when you need it; the latter is technically superior but useless in the moment. - -## Knowledge Visibility - -```mermaid -flowchart LR - Slack["Rules in Slack"] --> Write["Write them into repo files
AGENTS.md / ARCHITECTURE.md / PROGRESS.md"] - Confluence["Rules in Confluence"] --> Write - Heads["Rules in people's heads"] --> Write - Jira["Rules in Jira tickets"] --> Write - Write --> Repo["Repository files"] - Repo --> Agent["New agent session
reads the repo directly"] - Warning["If a rule is not in the repo,
the agent cannot see it"] --> Agent -``` - -How do you test whether your map is good enough? Run a "cold-start test": open a brand new agent session using only repo contents, and see if it can answer five basic questions: - -```mermaid -flowchart TB - Q1["What is this system?"] --> A1["AGENTS.md / README"] - Q2["How is it organized?"] --> A2["ARCHITECTURE.md / module docs"] - Q3["How do I run it?"] --> A3["Makefile / init.sh / package scripts"] - Q4["How do I verify it?"] --> A4["Test, lint, and check commands"] - Q5["Where are we now?"] --> A5["PROGRESS.md / feature list / git history"] - - A1 --> Ready["A new session can start work
without asking a human"] - A2 --> Ready - A3 --> Ready - A4 --> Ready - A5 --> Ready -``` - -If it can't answer, the map has blank spots. Where the map is blank, the agent guesses — wrong guesses become bugs, excessive guessing wastes context. And every new session guesses all over again. The cost of guessing is always higher than the cost of drawing the map properly in the first place. - -## Core Concepts - -- **Knowledge Visibility Gap**: The proportion of total project knowledge that's NOT in the repository. The bigger the gap, the higher the agent's failure rate. How much implicit knowledge about this project lives in your head? Count it all, then see how much made it into the repo — the difference is your visibility gap. -- **System of Record**: The code repository as the authoritative source for project decisions, architecture constraints, execution state, and verification standards. The repo has the final word, nowhere else counts. Like a map that marks "road closed" — you won't go down that road. But if that information only exists in Old Zhang's head, you have to ask Old Zhang every time. -- **Cold-Start Test**: The five questions above. How many it can answer is how complete your map is. -- **Discovery Cost**: How much context budget the agent burns to find a key piece of information in the repo. The more hidden the information, the higher the discovery cost, and the less budget left for the actual task. Hiding critical information in a README ten directory levels deep is like locking the fire extinguisher in a basement safe — it exists, but you can't find it when you need it. -- **Knowledge Decay Rate**: The proportion of knowledge entries that become stale per unit of time. Documentation going out of sync with code is the biggest enemy — worse than no documentation at all. -- **ACID Analogy**: Applying database transaction principles (Atomicity, Consistency, Isolation, Durability) to agent state management. We'll expand on this below. - -## How to Draw a Good Map - -**Principle 1: Knowledge lives next to code.** A rule about API endpoint authentication belongs next to the API code, not buried in a giant global document. Put a short doc in each module directory explaining that module's responsibilities, interfaces, and special constraints. Like library shelf labels — you want history books, go straight to the shelf marked "History." No need to search the entire library. - -**Principle 2: Use a standardized entry file.** `AGENTS.md` (or `CLAUDE.md`) is the agent's "landing page." It doesn't need to contain all information, but it must let the agent quickly answer three questions: "What is this project," "How do I run it," and "How do I verify it." 50-100 lines is enough. - -**Principle 3: Minimal but complete.** Every piece of knowledge should have a clear use case. If removing a rule doesn't affect the agent's decision quality, that rule shouldn't exist. But every question from the cold-start test must have an answer. This is a delicate balance — not too much, not too little, just enough. - -**Principle 4: Update with code.** Bind knowledge updates to code changes. The simplest approach: put architecture docs in the corresponding module directory. When you modify code, you naturally see the doc. After code changes, CI can remind you to check if docs need updating. - -**Concrete repo structure**: - -``` -project/ -├── AGENTS.md # Entry: project overview, run commands, hard constraints -├── src/ -│ ├── api/ -│ │ ├── ARCHITECTURE.md # API layer architecture decisions -│ │ └── ... -│ ├── db/ -│ │ ├── CONSTRAINTS.md # Database operation hard constraints -│ │ └── ... -│ └── ... -├── PROGRESS.md # Current progress: done, in-progress, blocked -└── Makefile # Standardized commands: setup, test, lint, check -``` - -## Managing Agent State with ACID Principles - -This analogy comes from database transaction management — you might think it's overcomplicating things, but it actually gives you a very practical framework: - -- **Atomicity**: Each "logical operation" (e.g., "add new endpoint and update tests") gets one git commit. If it fails midway, `git stash` to roll back. All or nothing — no "half done." -- **Consistency**: Define "consistent state" verification predicates — all tests pass, lint reports zero errors. The agent runs verification after each operation; inconsistent intermediate states don't get committed. Like a bank transfer — you can't debit without crediting. -- **Isolation**: When multiple agents work concurrently, design state files to avoid race conditions. Simple approach: each agent uses its own progress file, or use git branches for isolation. Two chefs can't season the same pot simultaneously — who takes responsibility when it's over-salted? -- **Durability**: Critical project knowledge lives in git-tracked files. Temporary state can stay in session memory, but cross-session knowledge must be persisted to files. What's in your head doesn't count — only what's on paper counts. - -## A Real Transformation Story - -A team maintained an e-commerce platform with ~30 microservices. Architecture decisions (inter-service communication protocols, data consistency strategies, API versioning rules) were scattered across: Confluence (partially outdated), Slack (hard to search), a few senior engineers' heads (not scalable), and sporadic code comments (not systematic). - -After introducing AI agents, 70% of tasks required human intervention. Nearly every failure involved the agent violating some "everyone knows but nobody wrote down" implicit constraint. It's like a new employee whom nobody told "you need to post your lunch order in the group chat" — they guess wrong, get scolded, but after the scolding still nobody tells them the rule. - -The team executed a transformation: -1. Created `AGENTS.md` in the repo root with project overview, tech stack versions, and global hard constraints -2. Added `ARCHITECTURE.md` in each microservice directory describing responsibilities, interfaces, and dependencies -3. Created a centralized `CONSTRAINTS.md` with hard constraints in explicit "MUST/MUST NOT" language -4. Added `PROGRESS.md` in each service directory tracking current work status - -After transformation: the same agent could answer all key project questions on cold start, and task completion quality improved significantly. - -## Key Takeaways - -- Knowledge not in the repo doesn't exist for the agent. Putting critical decisions in the repo is the most basic harness investment — draw a good map so you don't get lost. -- Use the "cold-start test" to evaluate repo quality: can a fresh session answer five basic questions using only repo contents? -- Knowledge should be near code, minimal but complete, and updated with code. It's not about writing more docs — it's about putting information in the right place. -- Use ACID principles for agent state: atomic commits, consistency verification, concurrency isolation, durable critical knowledge. -- Knowledge decay is the biggest enemy. Documentation out of sync with code is more dangerous than no documentation — it sends the agent in the wrong direction while they think they're right. - -## Further Reading - -- [OpenAI: Harness Engineering](https://openai.com/index/harness-engineering/) -- [Anthropic: Effective Harnesses for Long-Running Agents](https://www.anthropic.com/engineering/effective-harnesses-for-long-running-agents) -- [Infrastructure as Code — Martin Fowler](https://martinfowler.com/bliki/InfrastructureAsCode.html) -- [ADR: Architecture Decision Records](https://adr.github.io/) -- [The Twelve-Factor App](https://12factor.net/) - -## Exercises - -1. **Cold-start test**: Open a completely fresh agent session in your project (no verbal context, repo contents only). Ask it five questions: What is this system? How is it organized? How do I run it? How do I verify it? What's the current progress? Record what it can't answer, then improve the repo until it can. - -2. **Knowledge externalization quantification**: List all decisions and constraints important for development work in your project. Mark each as inside or outside the repo. Calculate your knowledge visibility gap (proportion outside repo). Make a plan to get it below 10%. - -3. **ACID assessment**: Evaluate your project's state management using this lecture's ACID analogy. Atomicity — can agent operations be cleanly rolled back? Consistency — is there "consistent state" verification? Isolation — do concurrent agents step on each other? Durability — is all cross-session knowledge persisted? diff --git a/courses/learn-harness-engineering/docs/en/lectures/lecture-04-why-one-giant-instruction-file-fails/code/AGENTS-short.md b/courses/learn-harness-engineering/docs/en/lectures/lecture-04-why-one-giant-instruction-file-fails/code/AGENTS-short.md deleted file mode 100644 index cf0ae2d4..00000000 --- a/courses/learn-harness-engineering/docs/en/lectures/lecture-04-why-one-giant-instruction-file-fails/code/AGENTS-short.md +++ /dev/null @@ -1,15 +0,0 @@ -# AGENTS.md - -## Start Here - -- Read `docs/ARCHITECTURE.md` -- Read `docs/PRODUCT.md` -- Use `npm run dev` to start the app -- Use `npm run check` before marking work complete - -## Hard Rules - -- Do not change Electron main/preload/renderer boundaries without reading - `docs/ARCHITECTURE.md` -- Do not mark a feature complete without verification -- Leave a clean state for the next session diff --git a/courses/learn-harness-engineering/docs/en/lectures/lecture-04-why-one-giant-instruction-file-fails/code/anti-patterns.md b/courses/learn-harness-engineering/docs/en/lectures/lecture-04-why-one-giant-instruction-file-fails/code/anti-patterns.md deleted file mode 100644 index 2fb84eb1..00000000 --- a/courses/learn-harness-engineering/docs/en/lectures/lecture-04-why-one-giant-instruction-file-fails/code/anti-patterns.md +++ /dev/null @@ -1,7 +0,0 @@ -# Instruction File Anti-Patterns - -- Putting all repository knowledge into one file -- Repeating the same rule in multiple places -- Encoding obsolete rules that nobody audits -- Writing conditional instructions so specific that they rarely apply -- Embedding long tool manuals into the startup context diff --git a/courses/learn-harness-engineering/docs/en/lectures/lecture-04-why-one-giant-instruction-file-fails/code/index.md b/courses/learn-harness-engineering/docs/en/lectures/lecture-04-why-one-giant-instruction-file-fails/code/index.md deleted file mode 100644 index 756b5009..00000000 --- a/courses/learn-harness-engineering/docs/en/lectures/lecture-04-why-one-giant-instruction-file-fails/code/index.md +++ /dev/null @@ -1,7 +0,0 @@ -# Code for Lecture 04 - -Use this folder for examples of: - -- monolithic instruction files -- short entrypoints -- progressive disclosure patterns diff --git a/courses/learn-harness-engineering/docs/en/lectures/lecture-04-why-one-giant-instruction-file-fails/code/split-vs-monolithic.ts b/courses/learn-harness-engineering/docs/en/lectures/lecture-04-why-one-giant-instruction-file-fails/code/split-vs-monolithic.ts deleted file mode 100644 index 59a96328..00000000 --- a/courses/learn-harness-engineering/docs/en/lectures/lecture-04-why-one-giant-instruction-file-fails/code/split-vs-monolithic.ts +++ /dev/null @@ -1,209 +0,0 @@ -/** - * split-vs-monolithic.ts - * - * Creates a monolithic instruction file (~200 lines) and then shows how - * splitting into 4 focused files dramatically reduces the context needed - * for any single query. Simulates an "agent" searching for a specific rule - * and measures how many lines it must read in each approach. - * - * Run: npx tsx docs/lectures/lecture-04-why-one-giant-instruction-file-fails/code/split-vs-monolithic.ts - */ - -// --------------------------------------------------------------------------- -// Simulated monolithic instruction file (200 lines of rules) -// --------------------------------------------------------------------------- - -const monolithicInstructions: { lineNumber: number; section: string; content: string }[] = []; - -// Section 1: Project Overview (lines 1-50) -for (let i = 1; i <= 50; i++) { - monolithicInstructions.push({ - lineNumber: i, - section: "Project Overview", - content: i === 25 ? "This project uses React 18 with TypeScript strict mode." : `Overview detail line ${i}`, - }); -} - -// Section 2: Code Style Rules (lines 51-100) -for (let i = 51; i <= 100; i++) { - monolithicInstructions.push({ - lineNumber: i, - section: "Code Style", - content: - i === 72 - ? "RULE: Always use explicit return types on exported functions." - : i === 78 - ? "RULE: Use const assertions for immutable arrays." - : `Style rule detail line ${i}`, - }); -} - -// Section 3: Testing Standards (lines 101-150) -for (let i = 101; i <= 150; i++) { - monolithicInstructions.push({ - lineNumber: i, - section: "Testing", - content: - i === 120 - ? "RULE: Every new endpoint must have integration tests." - : i === 135 - ? "RULE: Test files must mirror the source file structure." - : `Testing detail line ${i}`, - }); -} - -// Section 4: Deployment Rules (lines 151-200) -for (let i = 151; i <= 200; i++) { - monolithicInstructions.push({ - lineNumber: i, - section: "Deployment", - content: - i === 175 - ? "RULE: Never deploy on Fridays. Deploy window is Tue-Thu 10am-3pm." - : `Deployment detail line ${i}`, - }); -} - -// --------------------------------------------------------------------------- -// Split instruction files (4 focused files) -// --------------------------------------------------------------------------- - -const splitInstructions: Record = { - "01-project-overview.md": monolithicInstructions.filter((l) => l.section === "Project Overview"), - "02-code-style.md": monolithicInstructions.filter((l) => l.section === "Code Style"), - "03-testing.md": monolithicInstructions.filter((l) => l.section === "Testing"), - "04-deployment.md": monolithicInstructions.filter((l) => l.section === "Deployment"), -}; - -// --------------------------------------------------------------------------- -// Simulated queries -- the agent needs to find specific rules -// --------------------------------------------------------------------------- - -interface Query { - description: string; - targetRule: string; - relevantSection: string; -} - -const queries: Query[] = [ - { - description: "Find the rule about return types", - targetRule: "explicit return types", - relevantSection: "Code Style", - }, - { - description: "Find the deployment window rule", - targetRule: "deploy on Fridays", - relevantSection: "Deployment", - }, - { - description: "Find the integration test rule", - targetRule: "integration tests", - relevantSection: "Testing", - }, - { - description: "Find the test file structure rule", - targetRule: "mirror the source file", - relevantSection: "Testing", - }, -]; - -// --------------------------------------------------------------------------- -// Search simulation -// --------------------------------------------------------------------------- - -function searchMonolithic(query: Query): { linesRead: number; found: boolean } { - // Agent must scan from the top, reading each line until it finds the rule. - // In the worst case it reads all lines. - let linesRead = 0; - let found = false; - - for (const line of monolithicInstructions) { - linesRead++; - if (line.content.toLowerCase().includes(query.targetRule.toLowerCase())) { - found = true; - break; - } - } - - return { linesRead, found }; -} - -function searchSplit(query: Query): { linesRead: number; found: boolean; fileAccessed: string } { - // Agent knows which file to look in based on the section. - // It only reads lines from that one file. - const fileMap: Record = { - "Project Overview": "01-project-overview.md", - "Code Style": "02-code-style.md", - Testing: "03-testing.md", - Deployment: "04-deployment.md", - }; - - const targetFile = fileMap[query.relevantSection]; - const lines = splitInstructions[targetFile]; - let linesRead = 0; - let found = false; - - for (const line of lines) { - linesRead++; - if (line.content.toLowerCase().includes(query.targetRule.toLowerCase())) { - found = true; - break; - } - } - - return { linesRead, found, fileAccessed: targetFile }; -} - -// --------------------------------------------------------------------------- -// Reporting -// --------------------------------------------------------------------------- - -function pad(s: string, len: number): string { - return s.length >= len ? s : s + " ".repeat(len - s.length); -} - -function run(): void { - console.log("\n" + "=".repeat(90)); - console.log(" MONOLITHIC vs SPLIT INSTRUCTION FILES"); - console.log("=".repeat(90)); - - console.log("\n Monolithic file: 1 file, " + monolithicInstructions.length + " lines total"); - console.log(" Split files: 4 files, ~" + Math.round(monolithicInstructions.length / 4) + " lines each\n"); - - const header = `| ${pad("Query", 42)}| ${pad("Monolithic (lines)", 20)}| ${pad("Split (lines)", 15)}| ${pad("File Accessed", 22)}| Savings`; - const sep = `|${"-".repeat(44)}|${"-".repeat(22)}|${"-".repeat(17)}|${"-".repeat(24)}|${"-".repeat(10)}`; - console.log(header); - console.log(sep); - - let totalMono = 0; - let totalSplit = 0; - - for (const q of queries) { - const mono = searchMonolithic(q); - const split = searchSplit(q); - totalMono += mono.linesRead; - totalSplit += split.linesRead; - - const savings = Math.round(((mono.linesRead - split.linesRead) / mono.linesRead) * 100); - console.log( - `| ${pad(q.description, 42)}| ${pad(String(mono.linesRead), 20)}| ${pad(String(split.linesRead), 15)}| ${pad(split.fileAccessed, 22)}| ${savings}%` - ); - } - - console.log(sep); - const avgMono = Math.round(totalMono / queries.length); - const avgSplit = Math.round(totalSplit / queries.length); - console.log( - `| ${pad("AVERAGE", 42)}| ${pad(String(avgMono), 20)}| ${pad(String(avgSplit), 15)}| ${pad("(targeted file)", 22)}| ${Math.round(((avgMono - avgSplit) / avgMono) * 100)}%` - ); - - console.log("\n" + "=".repeat(90)); - console.log(" KEY INSIGHT"); - console.log("=".repeat(90)); - console.log(" With a monolithic file, the agent must scan up to " + monolithicInstructions.length + " lines for every query."); - console.log(" With split files, it reads only the relevant " + Math.round(monolithicInstructions.length / 4) + "-line file."); - console.log(" This means less context window usage, fewer hallucinations, and faster execution.\n"); -} - -run(); diff --git a/courses/learn-harness-engineering/docs/en/lectures/lecture-04-why-one-giant-instruction-file-fails/index.md b/courses/learn-harness-engineering/docs/en/lectures/lecture-04-why-one-giant-instruction-file-fails/index.md deleted file mode 100644 index 88fa2712..00000000 --- a/courses/learn-harness-engineering/docs/en/lectures/lecture-04-why-one-giant-instruction-file-fails/index.md +++ /dev/null @@ -1,132 +0,0 @@ -[中文版本 →](../../../zh/lectures/lecture-04-why-one-giant-instruction-file-fails/) - -> Code examples: [code/](https://github.com/walkinglabs/learn-harness-engineering/blob/main/docs/en/lectures/lecture-04-why-one-giant-instruction-file-fails/code/) -> Practice project: [Project 02. Agent-readable workspace](./../../projects/project-02-agent-readable-workspace/index.md) - -# Lecture 04. Split Instructions Across Files - -You got serious about harness engineering — good for you. You created an `AGENTS.md` and packed every rule, constraint, and lesson learned you could think of into it. One month later the file bloated to 300 lines, two months 450 lines, three months 600 lines. Then you notice the agent's performance is actually getting worse — on a simple bug fix, the agent burns tons of context processing irrelevant deployment instructions; a critical security constraint buried at line 300 gets ignored outright; three contradictory code style rules mean the agent picks one at random each time. - -This is the "giant instruction file" trap. It's like overpacking a suitcase — everything seems useful, so you cram it all in until the zipper is about to burst. Finding your change of underwear means emptying the entire bag. You carried a full suitcase, but you actually used maybe a third of what's inside. - -## The Vicious Cycle at the Root - -The most common vicious cycle goes like this: agent makes a mistake, you say "add a rule to prevent this," add it to AGENTS.md, it works temporarily, agent makes a different mistake, add another rule, repeat, file bloats out of control. - -This isn't your fault. It's a very natural reaction — "add a rule" each time something goes wrong feels reasonable, like tossing one more thing into your bag every time you leave the house "just in case." But the cumulative effect is disastrous. Let's look at what goes wrong specifically. - -**Context budget gets eaten alive.** The agent's context window is finite. Say your agent has a 200K token window (Claude's standard). A bloated instruction file might eat 10-20K tokens. Seems like there's still plenty of room? But a complex task might need to read dozens of source files, tool execution output also takes context, and conversation history accumulates. By the time the agent needs to understand the code, the budget is already tight — like a suitcase so full of "just in case" items that there's no room for your laptop. - -**Lost in the middle.** The "Lost in the Middle" paper (Liu et al., 2023) clearly demonstrated that LLMs utilize information in the middle of long texts significantly less effectively than at the beginning or end. Your AGENTS.md is 600 lines, and line 300 says "all database queries must use parameterized queries" — that's a security hard constraint. But it's buried in the middle, and the agent will almost certainly ignore it. Like that bottle of sunscreen at the bottom of your overstuffed suitcase — you know it's there, you dig three times, can't find it, end up buying another one. - -**Priority conflicts.** The file mixes non-negotiable hard constraints ("never use eval()"), important design guidelines ("prefer functional style"), and a specific historical lesson ("fixed a WebSocket memory leak last week, watch for similar patterns"). These three rules have completely different importance levels, but they look identical in the file. The agent has no reliable signal to distinguish — like your passport and charging cable jumbled together in the suitcase, no way to tell which is more urgent. - -**Maintenance decay.** Large files are inherently hard to maintain. Outdated instructions rarely get deleted — because the consequences of deletion are uncertain ("maybe something else depends on this rule?"), while adding new instructions feels free. The result: the file only grows, never shrinks, and signal-to-noise ratio continuously declines. This is exactly like technical debt accumulation in software. - -**Contradiction accumulation.** Instructions added at different times start contradicting each other — one says "use TypeScript strict mode," another says "some legacy files allow any types." The agent randomly picks one to follow each time. Like your mom saying "dress warm" and your dad saying "don't wear too much," and you standing at the door not knowing who to listen to. - -## Core Concepts - -- **Instruction Bloat**: When an instruction file occupies more than 10-15% of the context window, it starts crowding out budget for code reading and task reasoning. A 600-line `AGENTS.md` might consume 10,000-20,000 tokens — that's 8-15% of a 128K window eaten before the agent even starts. -- **Lost in the Middle Effect**: Liu et al.'s 2023 research proved that LLMs use information in the middle of long texts significantly less effectively than information at the beginning or end. A critical constraint buried at line 300 of a 600-line file has a very high probability of being effectively ignored. -- **Instruction Signal-to-Noise Ratio (SNR)**: The proportion of instructions in a file that are relevant to the current task. Being forced to read 50 lines of deployment instructions during a bug fix — that's low SNR. -- **Routing File**: A short entry file whose core function is pointing the agent to more detailed docs, not containing everything itself. 50-200 lines is plenty. -- **Progressive Disclosure**: Give overview information first, detailed information when needed. Good harness design is like good UI design — don't dump all options on the user at once. -- **Priority Ambiguity**: When all instructions appear in the same format and location, the agent can't distinguish non-negotiable hard constraints from suggestive soft guidelines. - -## Instruction Architecture - -```mermaid -flowchart LR - Mono["One 600-line AGENTS.md"] --> MonoLoad["Even a small bug fix
must read deploy rules and old notes"] - MonoLoad --> MonoRisk["Important rules buried in the middle
are easy to miss"] - - Router["Short AGENTS.md"] --> Topics["Load API / DB / testing docs
only when this task needs them"] - Topics --> RoutedResult["More context left for code reading
and verification"] -``` - -```mermaid -flowchart TB - File["600-line instruction file"] --> Top["Top section
quick start + hard constraints"] - File --> Mid["Middle section
critical security rule at line 300"] - File --> Bot["Bottom section
explicit end-of-file checklist"] - Top --> Seen["High chance of recall"] - Bot --> Seen - Mid --> Missed["High chance of being diluted or missed"] -``` - -## How to Split - -Core principle: keep frequently-needed information at hand, tuck away occasionally-needed information, and leave behind what you'll never use. - -The entry file `AGENTS.md` stays at 50-200 lines, containing only the most frequently used items — project overview (one or two sentences), first-run commands (`make setup && make test`), global hard constraints (no more than 15 non-negotiable rules), and links to topic documents (one-line description + applicability condition). - -```markdown -# AGENTS.md - -## Project Overview -Python 3.11 FastAPI backend, PostgreSQL 15 database. - -## Quick Start -- Install: `make setup` -- Test: `make test` -- Full verification: `make check` - -## Hard Constraints -- All APIs must use OAuth 2.0 authentication -- All database queries must use SQLAlchemy 2.0 syntax -- All PRs must pass pytest + mypy --strict + ruff check - -## Topic Docs -- [API Design Patterns](docs/api-patterns.md) — Required reading when adding endpoints -- [Database Rules](docs/database-rules.md) — Required when modifying database operations -- [Testing Standards](docs/testing-standards.md) — Reference when writing tests -``` - -Each topic document is 50-150 lines, organized by subject in the `docs/` directory or next to the corresponding module. The agent only reads them when needed. Like packing cubes in a suitcase — underwear in one cube, toiletries in another, chargers in a third. Finding things doesn't require emptying the whole bag. - -Some information is better placed directly in the code — type definitions, interface comments, explanations in config files. The agent naturally sees these when reading code, no need to duplicate in instructions. - -Every instruction should have a source ("why was this rule added?"), an applicability condition ("when is this rule needed?"), and an expiry condition ("under what circumstances can this rule be removed?"). Audit regularly, remove outdated, redundant, and contradictory entries. Manage your instructions like you manage code dependencies — unused dependencies should be deleted, otherwise they just slow the system down. - -If an instruction must be in the entry file, put it at the top or bottom — never the middle. The "lost in the middle" effect tells us that LLMs use information at the extremes significantly better than in the center. But the better approach is to move instructions to topic documents for on-demand loading. - -Both OpenAI and Anthropic implicitly support the splitting approach. OpenAI says entry files should be "short and routing-oriented," Anthropic says long-running agent control information should be "concise and high-priority." Both are saying the same thing: don't stuff everything into one file. A suitcase needs organizing, not just brute-force cramming. - -## Real-World Example - -A SaaS team's `AGENTS.md` ballooned from 50 lines to 600. Contents mixed tech stack versions, coding standards, historical bug fix notes, API usage guides, deployment procedures, and team members' personal preferences — the entire suitcase bursting at the seams. - -Agent performance started declining noticeably: during simple bug fixes the agent spent lots of context processing irrelevant deployment instructions; the security constraint "all database queries must use parameterized queries" was buried at line 300 and frequently ignored; three contradictory code style rules caused random agent behavior. - -The team executed a "suitcase reorganization": -1. `AGENTS.md` trimmed to 80 lines: only project overview, run commands, and 15 global hard constraints -2. Created topic documents: `docs/api-patterns.md` (120 lines), `docs/database-rules.md` (60 lines), `docs/testing-standards.md` (80 lines) -3. Added topic document links in the routing file -4. Historical notes either converted to test cases or deleted - -After refactoring: same task set success rate went from 45% to 72%. Security constraint compliance went from 60% to 95% — because it moved from the file middle to the routing file top, no longer "lost in the middle." - -## Key Takeaways - -- "Add a rule" is short-term pain relief, long-term poison. Before adding a rule, ask: would this be better in a topic document? Don't just keep cramming things into the suitcase. -- The entry file is a router, not an encyclopedia. 50-200 lines with overview, hard constraints, and links only. -- Leverage the "lost in the middle" effect: important info goes at the top or bottom; unimportant info moves to topic documents. -- Manage instruction bloat like technical debt. Regular audits, every instruction needs a source, applicability condition, and expiry condition. -- After splitting, SNR improves and the agent spends more context budget on actual tasks instead of processing irrelevant instructions. - -## Further Reading - -- [OpenAI: Harness Engineering](https://openai.com/index/harness-engineering/) -- [Anthropic: Effective Harnesses for Long-Running Agents](https://www.anthropic.com/engineering/effective-harnesses-for-long-running-agents) -- [Lost in the Middle: How Language Models Use Long Contexts](https://arxiv.org/abs/2307.03172) -- [HumanLayer: Harness Engineering for Coding Agents](https://humanlayer.dev/articles/harness-engineering-for-coding-agents/) -- [Nielsen Norman Group: Progressive Disclosure](https://www.nngroup.com/articles/progressive-disclosure/) - -## Exercises - -1. **SNR audit**: Take your current entry instruction file and list all instruction entries. Pick 5 different common task types and mark whether each instruction is relevant to that task. Calculate SNR for each task type. Instructions that are noise for most tasks should move to topic documents. - -2. **Progressive disclosure refactor**: If you have an instruction file over 300 lines, split it into: (a) a routing file under 100 lines, (b) 3-5 topic documents. Run the same set of tasks (at least 5) before and after, compare success rates. - -3. **Lost in the middle verification**: In a long instruction file, place a critical constraint at the top, middle, and bottom respectively, running the same task set each time (at least 5 runs per position). See if there's a difference in compliance rate. You might be surprised by how strong the position effect is. diff --git a/courses/learn-harness-engineering/docs/en/lectures/lecture-05-why-long-running-tasks-lose-continuity/code/continuity-checklist.md b/courses/learn-harness-engineering/docs/en/lectures/lecture-05-why-long-running-tasks-lose-continuity/code/continuity-checklist.md deleted file mode 100644 index 6040ea60..00000000 --- a/courses/learn-harness-engineering/docs/en/lectures/lecture-05-why-long-running-tasks-lose-continuity/code/continuity-checklist.md +++ /dev/null @@ -1,6 +0,0 @@ -# Continuity Checklist - -- Can a fresh agent identify recent work in under five minutes? -- Is the current stable startup path documented? -- Is unfinished work clearly identified? -- Is the next best task visible without reading old chat logs? diff --git a/courses/learn-harness-engineering/docs/en/lectures/lecture-05-why-long-running-tasks-lose-continuity/code/index.md b/courses/learn-harness-engineering/docs/en/lectures/lecture-05-why-long-running-tasks-lose-continuity/code/index.md deleted file mode 100644 index 636e092b..00000000 --- a/courses/learn-harness-engineering/docs/en/lectures/lecture-05-why-long-running-tasks-lose-continuity/code/index.md +++ /dev/null @@ -1,7 +0,0 @@ -# Code for Lecture 05 - -Use this folder for examples of: - -- broken multi-session tasks -- missing continuity artifacts -- continuity recovery patterns diff --git a/courses/learn-harness-engineering/docs/en/lectures/lecture-05-why-long-running-tasks-lose-continuity/code/session-handoff.md b/courses/learn-harness-engineering/docs/en/lectures/lecture-05-why-long-running-tasks-lose-continuity/code/session-handoff.md deleted file mode 100644 index 09030a3e..00000000 --- a/courses/learn-harness-engineering/docs/en/lectures/lecture-05-why-long-running-tasks-lose-continuity/code/session-handoff.md +++ /dev/null @@ -1,17 +0,0 @@ -# Session Handoff Example - -## Completed - -- Added markdown import support -- Added a basic document list in the renderer - -## Broken or Unverified - -- Import succeeds for `.md` but fails for large `.txt` files -- The app starts, but the detail view has not been wired up - -## Next Best Step - -- Fix `.txt` import path -- Verify import end-to-end -- Then add the document detail panel diff --git a/courses/learn-harness-engineering/docs/en/lectures/lecture-05-why-long-running-tasks-lose-continuity/code/session-simulator.ts b/courses/learn-harness-engineering/docs/en/lectures/lecture-05-why-long-running-tasks-lose-continuity/code/session-simulator.ts deleted file mode 100644 index 69f9c3e2..00000000 --- a/courses/learn-harness-engineering/docs/en/lectures/lecture-05-why-long-running-tasks-lose-continuity/code/session-simulator.ts +++ /dev/null @@ -1,162 +0,0 @@ -/** - * session-simulator.ts - * - * Simulates two sessions working on a multi-step task. - * Run 1: No handoff file -- Session B starts from scratch and duplicates work. - * Run 2: With handoff file -- Session B picks up where Session A left off. - * - * Run: npx tsx docs/lectures/lecture-05-why-long-running-tasks-lose-continuity/code/session-simulator.ts - */ - -// --------------------------------------------------------------------------- -// Types -// --------------------------------------------------------------------------- - -interface TaskStep { - id: number; - name: string; - durationMs: number; -} - -interface SessionResult { - session: string; - stepsCompleted: number; - totalDurationMs: number; - duplicatedSteps: number; - output: string[]; -} - -// --------------------------------------------------------------------------- -// Task definition -// --------------------------------------------------------------------------- - -const taskSteps: TaskStep[] = [ - { id: 1, name: "Read project structure", durationMs: 50 }, - { id: 2, name: "Understand existing auth module", durationMs: 80 }, - { id: 3, name: "Design new search endpoint", durationMs: 60 }, - { id: 4, name: "Implement search endpoint", durationMs: 100 }, - { id: 5, name: "Write integration tests", durationMs: 70 }, - { id: 6, name: "Update documentation", durationMs: 40 }, -]; - -// --------------------------------------------------------------------------- -// Simulated session executor -// --------------------------------------------------------------------------- - -/** Simulate a session doing work. */ -function runSession( - sessionName: string, - startStep: number, - endStep: number -): SessionResult { - const output: string[] = []; - let totalDuration = 0; - - for (let i = startStep; i <= endStep; i++) { - const step = taskSteps.find((s) => s.id === i)!; - totalDuration += step.durationMs; - output.push(` [${sessionName}] Step ${step.id}: ${step.name} (${step.durationMs}ms)`); - } - - return { - session: sessionName, - stepsCompleted: endStep - startStep + 1, - totalDurationMs: totalDuration, - duplicatedSteps: 0, - output, - }; -} - -// --------------------------------------------------------------------------- -// Run 1: No handoff file -// --------------------------------------------------------------------------- - -function simulateNoHandoff(): { sessionA: SessionResult; sessionB: SessionResult } { - // Session A does steps 1-3 before timing out - const sessionA = runSession("Session A", 1, 3); - - // Session B has no context, starts from scratch - const sessionB = runSession("Session B", 1, 6); - sessionB.duplicatedSteps = 3; // Redoes steps 1-3 that A already did - - return { sessionA, sessionB }; -} - -// --------------------------------------------------------------------------- -// Run 2: With handoff file -// --------------------------------------------------------------------------- - -function simulateWithHandoff(): { sessionA: SessionResult; sessionB: SessionResult } { - // Session A does steps 1-3 and writes a handoff file - const sessionA = runSession("Session A", 1, 3); - - // Session B reads handoff, starts from step 4 - const sessionB = runSession("Session B", 4, 6); - sessionB.duplicatedSteps = 0; - - return { sessionA, sessionB }; -} - -// --------------------------------------------------------------------------- -// Reporting -// --------------------------------------------------------------------------- - -function pad(s: string, len: number): string { - return s.length >= len ? s : s + " ".repeat(len - s.length); -} - -function printRun(title: string, result: { sessionA: SessionResult; sessionB: SessionResult }): void { - console.log("\n" + "=".repeat(80)); - console.log(` ${title}`); - console.log("=".repeat(80)); - - console.log("\n Session A output:"); - for (const line of result.sessionA.output) { - console.log(line); - } - - console.log("\n Session B output:"); - for (const line of result.sessionB.output) { - console.log(line); - } - - console.log("\n Session B notes:"); - if (result.sessionB.duplicatedSteps > 0) { - console.log(` - Redid ${result.sessionB.duplicatedSteps} steps that Session A already completed`); - console.log(` - Wasted ${result.sessionB.totalDurationMs - (taskSteps.slice(3).reduce((s, t) => s + t.durationMs, 0))}ms on duplicate work`); - } else { - console.log(" - Read handoff file, continued from step 4"); - console.log(" - No duplicate work performed"); - } -} - -function printComparison(): void { - const noHandoff = simulateNoHandoff(); - const withHandoff = simulateWithHandoff(); - - printRun("RUN 1: NO HANDOFF FILE", noHandoff); - printRun("RUN 2: WITH HANDOFF FILE", withHandoff); - - // Comparison table - console.log("\n" + "=".repeat(80)); - console.log(" COMPARISON TABLE"); - console.log("=".repeat(80) + "\n"); - - const header = `| ${pad("Metric", 35)}| ${pad("No Handoff", 15)}| ${pad("With Handoff", 15)}|`; - const sep = `|${"-".repeat(37)}|${"-".repeat(17)}|${"-".repeat(17)}|`; - console.log(header); - console.log(sep); - - const noTotal = noHandoff.sessionA.totalDurationMs + noHandoff.sessionB.totalDurationMs; - const withTotal = withHandoff.sessionA.totalDurationMs + withHandoff.sessionB.totalDurationMs; - - console.log(`| ${pad("Session A steps completed", 35)}| ${pad(String(noHandoff.sessionA.stepsCompleted), 15)}| ${pad(String(withHandoff.sessionA.stepsCompleted), 15)}|`); - console.log(`| ${pad("Session B steps completed", 35)}| ${pad(String(noHandoff.sessionB.stepsCompleted), 15)}| ${pad(String(withHandoff.sessionB.stepsCompleted), 15)}|`); - console.log(`| ${pad("Duplicated steps", 35)}| ${pad(String(noHandoff.sessionB.duplicatedSteps), 15)}| ${pad(String(withHandoff.sessionB.duplicatedSteps), 15)}|`); - console.log(`| ${pad("Total work (ms)", 35)}| ${pad(String(noTotal), 15)}| ${pad(String(withTotal), 15)}|`); - console.log(`| ${pad("Time saved by handoff", 35)}| ${pad("-", 15)}| ${pad(String(noTotal - withTotal) + "ms", 15)}|`); - - console.log("\n A handoff file eliminates duplicate work and ensures continuity across sessions.\n"); -} - -printComparison(); diff --git a/courses/learn-harness-engineering/docs/en/lectures/lecture-05-why-long-running-tasks-lose-continuity/index.md b/courses/learn-harness-engineering/docs/en/lectures/lecture-05-why-long-running-tasks-lose-continuity/index.md deleted file mode 100644 index 6095d01d..00000000 --- a/courses/learn-harness-engineering/docs/en/lectures/lecture-05-why-long-running-tasks-lose-continuity/index.md +++ /dev/null @@ -1,180 +0,0 @@ -[中文版本 →](../../../zh/lectures/lecture-05-why-long-running-tasks-lose-continuity/) - -> Code examples: [code/](https://github.com/walkinglabs/learn-harness-engineering/blob/main/docs/en/lectures/lecture-05-why-long-running-tasks-lose-continuity/code/) -> Practice project: [Project 03. Multi-session continuity](./../../projects/project-03-multi-session-continuity/index.md) - -# Lecture 05. Keep Context Alive Across Sessions - -You ask Claude Code to implement a complete feature. It runs for 30 minutes, does most of the work, but context is running low. You start a new session to continue — and discover it doesn't remember what decisions were made last time, why option A was chosen over option B, which files were already modified, or what state the tests are in. It spends 15 minutes re-exploring the project, and might be inconsistent with the previous approach. - -Imagine if you were a craftsman who forgot everything each morning upon waking. You'd have to reacquaint yourself with the entire construction site — which wall is half-built, why red bricks were chosen over blue ones, where the plumbing runs got to. Worse, you might tear out a window that was already installed yesterday, simply because you didn't remember it was done. - -This is exactly the predicament AI coding agents face in cross-session tasks. This lecture explains why agents "black out" during long tasks, and how structured state persistence can make them like a craftsman who keeps a reliable daily journal — still amnesiac, but the journal remembers everything. - -## Context Windows: Not Infinite - -Context windows are finite. This isn't solvable by model upgrades — even if window sizes grow to 1M tokens, complex tasks will still exhaust them. Because agents aren't just generating code; they're understanding codebases, tracking their own decision history, processing tool output, and maintaining conversation context. All this information grows faster than window expansion. - -A deeper problem: information the agent produces isn't uniformly important. Intermediate reasoning steps contain the "why" of decisions — why option B was chosen over A, why this library instead of that one, why a particular optimization was skipped. The final output only contains the "what" — the code itself. Compaction strategies usually preserve the latter but lose the former. The next session sees the code but doesn't know why it's written that way, and might "optimize" away a deliberate design decision. - -Anthropic discovered something fascinating in their long-running agent research: when agents sense context is running low, they exhibit "premature convergence" behavior — rushing to finish current work, skipping verification steps, or choosing a simple solution over the optimal one. It's like realizing time is running out on an exam and quickly guessing on the remaining multiple-choice questions. Anthropic calls this "context anxiety." - -## Session Continuity Flow - -Without continuity artifacts, every new session is a disaster: - -```mermaid -flowchart LR - S1["Session 1
feature is half done"] --> End1["Context is nearly full
session ends"] - End1 --> S2["Session 2 starts fresh"] - S2 --> Guess["Re-read folders, rerun tests,
guess why the code was written this way"] - Guess --> Drift["Work gets repeated
and recovery is slow"] -``` - -With continuity artifacts, new sessions can pick up quickly: - -```mermaid -flowchart LR - Work["Session 1 work"] --> Progress["PROGRESS.md
done / in progress / next step"] - Work --> Decisions["DECISIONS.md
why this approach was chosen"] - Work --> Verify["Verification notes
which tests pass and fail"] - Work --> Commit["Git checkpoint
exact repo state"] - - Progress --> Rebuild["Session 2 rebuild"] - Decisions --> Rebuild - Verify --> Rebuild - Commit --> Rebuild - - Rebuild --> Resume["New session picks up quickly"] -``` - -## Core Concepts - -- **Context windows are finite**: No matter what window size is claimed (128K, 200K, 1M), long tasks will eventually exhaust them. After exhaustion, either compaction (losing information) or reset (new session) is required. Both lose something. -- **Continuity artifacts**: Persisted state files that let a new session unambiguously resume where the last one left off. The basic form: progress log + verification record + next actions. That craftsman's journal. -- **Rebuild cost**: The time a new session needs to reach an executable state. Good harnesses can compress rebuild cost from 15 minutes to 3 minutes. -- **Drift**: The gap between the agent's understanding and the actual state of the code repository. Every session boundary introduces drift; without control, it compounds. -- **Context anxiety**: A phenomenon observed by Anthropic — agents exhibit premature convergence behavior when approaching perceived context limits, ending tasks early to avoid information loss. It's an irrational resource anxiety. -- **Compaction vs reset**: Compaction summarizes context within the same session (keeps "what," may lose "why"); reset opens a new session rebuilding from persisted state (clean but depends on artifact completeness). - -## What Happens When Continuity Breaks - -The previous session spent significant context budget analyzing three approaches and choosing option B. This session's agent doesn't know about that analysis and might re-decide based on incomplete information — potentially choosing option A. Like the amnesiac craftsman who doesn't remember why red bricks were chosen, looks at the blue ones today and thinks they're prettier, and tears down yesterday's wall to rebuild. - -Even worse is duplicate work. The agent isn't sure whether certain work was already completed and does it again. Or worse — does half of it, discovers a conflict with the existing implementation, and has to rework. On a construction site, two teams can't build the same wall simultaneously — but without progress records, the new crew has no idea someone is already working on it. - -Over several sessions, the implementation direction may have silently drifted from the original requirements. Each new session has a slightly different understanding of the project goals. Like a game of telephone — after ten people pass the message, "pick me up a coffee" might become "buy me a coffee machine." - -There's also the verification gap. The previous session's verification results (which tests pass, which fail, why they fail) weren't recorded. The new session has to re-run all verification to understand the current state. Every session re-diagnoses from scratch, every time wasting precious context. - -Both OpenAI and Anthropic emphasize structured state persistence in their documentation. OpenAI's harness engineering article treats the repository as an "operational record" — every operation's results should leave traceable evidence in the repo. Anthropic's long-running agents documentation specifically recommends "handoff files" — structured documents containing current state, known issues, and next actions. - -## A Journal for the Amnesiac Craftsman - -Core approach: **Treat the agent like a brilliant engineer with amnesia.** Before it "clocks out," it must write down critical information so the next "shift" agent can pick up quickly. - -**Tool 1: Progress file (PROGRESS.md).** The most basic continuity artifact — the core of the journal: - -```markdown -# Project Progress - -## Current State -- Latest commit: abc1234 (feat: add user preferences endpoint) -- Test status: 42/43 passing (test_pagination_edge_case failing) -- Lint: passing - -## Completed -- [x] User model and database migration -- [x] Basic CRUD endpoints -- [x] Auth middleware integration - -## In Progress -- [ ] Pagination feature (90% - edge case test failing) - -## Known Issues -- test_pagination_edge_case returns 500 on empty result sets -- Need to confirm whether deleted users should appear in listings - -## Next Steps -1. Fix pagination edge case bug -2. Add "include deleted users" query parameter -3. Update API documentation -``` - -**Tool 2: Decision log (DECISIONS.md).** Record important design decisions and reasons. No need for detailed design documents — just "what decision, why, when" — the memos in the journal: - -```markdown -# Design Decisions - -## 2024-01-15: Use Redis for user preferences caching -- Reason: High read frequency (every API call), small data size -- Rejected alternative: PostgreSQL materialized view (high change frequency makes maintenance cost not worthwhile) -- Constraint: Cache TTL of 5 minutes, active invalidation on write -``` - -**Tool 3: Git commits as checkpoints.** Commit after completing each atomic unit of work. Commit messages should explain what was done and why. These are free, automatically versioned state snapshots. - -**Tool 4: init.sh or harness initialization flow.** Specify in `AGENTS.md` the "clock-in" and "clock-out" routines: - -```markdown -## At session start (clock in) -1. Read PROGRESS.md for current state -2. Read DECISIONS.md for important decisions -3. Run make check to confirm repo is in consistent state -4. Continue from PROGRESS.md "Next Steps" section - -## Before session end (clock out) -1. Update PROGRESS.md -2. Run make check to confirm consistent state -3. Commit all completed work -``` - -**Mixed strategy**: Not every task needs a context reset. Short tasks (under 30 minutes) can complete within one session. Long tasks (spanning sessions) must use progress files and decision logs for continuity. Decision criterion: if a task needs more than 60% of the window, start preparing handoff. - -### Deep Dive on Context Anxiety - -Anthropic's March 2026 research further revealed the specific manifestations of context anxiety: on Sonnet 4.5, when context approaches the window limit, the agent shows strong "premature convergence" behavior. It's like realizing time is almost up on an exam and quickly filling in random answers on the multiple choice. - -Two strategies address this: - -**Compaction**: Summarizing early conversation within the same session. Advantage: maintains continuity, the agent can see "what." Disadvantage: "why" is often lost in summaries — why option B was chosen over A, why a particular optimization was skipped. More critically, compaction doesn't eliminate context anxiety — the agent knows context was once large, and psychologically still tends to rush to closure. - -**Context reset**: Completely clearing context, opening a new session, rebuilding from persisted artifacts. Advantage: clean mental state — the new session has no "I'm running out of time" anxiety. Disadvantage: depends on the completeness of handoff artifacts. If the journal is missing critical information, the new session may waste time going in the wrong direction. - -Anthropic's actual data: for Sonnet 4.5, context anxiety is severe enough that compaction alone isn't sufficient — context reset becomes a critical component of harness design. But for Opus 4.5, this behavior is greatly diminished, and compaction can manage context without relying on resets. This means: **harness design needs specific understanding of the target model, not a one-size-fits-all template.** - -> Source: [Anthropic: Harness design for long-running application development](https://www.anthropic.com/engineering/harness-design-long-running-apps) - -## Real-World Example - -An agent was tasked with implementing a blog system with user authentication — 12 feature points, estimated 5 sessions needed. - -**Baseline without the journal**: Session 1 implemented the user model and basic routes. Session 2 started without the agent remembering the auth middleware's interface contract, spending ~15 minutes inferring the previous design intent. By session 3, accumulated drift caused the agent to start reimplementing already-completed features. By session 5, the repo contained lots of redundant code but the core auth feature still hadn't passed end-to-end tests. Only 7 of 12 feature points completed, 3 with hidden correctness issues. Like the craftsman who never writes in his journal — by day five, the construction site is chaos, some walls built twice, some that should have been built never started. - -**With the journal**: Using progress files, decision logs, verification records, and git checkpoints. State report updated automatically at each session end. Session 2's rebuild cost dropped to ~3 minutes. By session 5, all 12 feature points completed and verified. - -Quantitative comparison: rebuild time reduced ~78%, feature completion rate from 58% to 100%, hidden defect rate from 43% down to 8%. The craftsman is still amnesiac, but with the journal, each day starts from where yesterday stopped, not from zero. - -## Key Takeaways - -- Context windows are a finite resource. Long tasks will span sessions, and sessions will lose information — like the craftsman who forgets each day, this is objective reality. -- The solution isn't bigger windows — it's better state persistence. Progress files + decision logs + git checkpoints — give the amnesiac craftsman a reliable journal. -- Treat the agent like an engineer with amnesia: before "clocking out," write down what was done, why, and what's next. -- Rebuild cost is the key metric. Good harnesses should get new sessions to an executable state within 3 minutes. -- Mixed strategy: short tasks within sessions, long tasks with structured artifacts for continuity. - -## Further Reading - -- [Anthropic: Effective Harnesses for Long-Running Agents](https://www.anthropic.com/engineering/effective-harnesses-for-long-running-agents) -- [OpenAI: Harness Engineering](https://openai.com/index/harness-engineering/) -- [Lost in the Middle: How Language Models Use Long Contexts](https://arxiv.org/abs/2307.03172) -- [Claude Code Documentation](https://docs.anthropic.com/en/docs/claude-code) -- [HumanLayer: Harness Engineering for Coding Agents](https://humanlayer.dev/articles/harness-engineering-for-coding-agents/) - -## Exercises - -1. **Continuity loss measurement**: Pick a development task needing at least 3 sessions. Without providing any continuity artifacts, record at each session start how much context the agent spends "figuring out what happened last time." After each session, create a progress file and let the next session start from it. Compare rebuild costs with and without progress files. - -2. **Handoff template design**: Design a minimal handoff template with four fields: repo state (commit hash), runtime state (test pass rate), blockers, next actions. Let a completely fresh agent session restore project state using only this template. Record ambiguities encountered during restoration, iterate to improve the template. - -3. **Mixed strategy experiment**: In a 5-session development task, compare three strategies: (a) always start fresh sessions + progress files, (b) do as much as possible in one session (context compaction), (c) mixed strategy (short tasks in-session, long tasks across sessions + progress files). Compare rebuild time, feature completion rate, and decision consistency. diff --git a/courses/learn-harness-engineering/docs/en/lectures/lecture-06-why-initialization-needs-its-own-phase/code/index.md b/courses/learn-harness-engineering/docs/en/lectures/lecture-06-why-initialization-needs-its-own-phase/code/index.md deleted file mode 100644 index fd65a7fa..00000000 --- a/courses/learn-harness-engineering/docs/en/lectures/lecture-06-why-initialization-needs-its-own-phase/code/index.md +++ /dev/null @@ -1,8 +0,0 @@ -# Code for Lecture 06 - -Use this folder for examples of: - -- initializer outputs -- init scripts -- progress files -- first-run scaffolding diff --git a/courses/learn-harness-engineering/docs/en/lectures/lecture-06-why-initialization-needs-its-own-phase/code/init-check.ts b/courses/learn-harness-engineering/docs/en/lectures/lecture-06-why-initialization-needs-its-own-phase/code/init-check.ts deleted file mode 100644 index 52dcd985..00000000 --- a/courses/learn-harness-engineering/docs/en/lectures/lecture-06-why-initialization-needs-its-own-phase/code/init-check.ts +++ /dev/null @@ -1,296 +0,0 @@ -/** - * init-check.ts - * - * Programmatically checks initialization prerequisites: - * - Node.js version - * - Dependencies installed (node_modules exists) - * - Data directory exists - * - Config files present - * - * Simulates running with and without an explicit init phase, - * showing how missing prerequisites cause silent failures later. - * - * Run: npx tsx docs/lectures/lecture-06-why-initialization-needs-its-own-phase/code/init-check.ts - */ - -import * as fs from "node:fs"; -import * as path from "node:path"; -import * as childProcess from "node:child_process"; - -// --------------------------------------------------------------------------- -// Types -// --------------------------------------------------------------------------- - -interface CheckItem { - name: string; - category: string; - check: () => { pass: boolean; detail: string }; - impactIfMissing: string; -} - -interface CheckResult { - name: string; - category: string; - passed: boolean; - detail: string; - impactIfMissing: string; -} - -// --------------------------------------------------------------------------- -// Checks -// --------------------------------------------------------------------------- - -function createChecks(targetDir: string): CheckItem[] { - return [ - { - name: "Node.js version >= 18", - category: "Runtime", - check: () => { - try { - const version = process.version; // e.g. "v20.11.0" - const major = parseInt(version.replace("v", "").split(".")[0], 10); - return { - pass: major >= 18, - detail: `Detected: ${version} (need >= v18)`, - }; - } catch { - return { pass: false, detail: "Could not detect Node.js version" }; - } - }, - impactIfMissing: "TypeScript features and built-in APIs unavailable", - }, - { - name: "package.json exists", - category: "Config", - check: () => { - const p = path.join(targetDir, "package.json"); - const exists = fs.existsSync(p); - return { - pass: exists, - detail: exists ? `Found: ${p}` : "Not found in target directory", - }; - }, - impactIfMissing: "Cannot install dependencies or run scripts", - }, - { - name: "Dependencies installed (node_modules)", - category: "Dependencies", - check: () => { - const p = path.join(targetDir, "node_modules"); - const exists = fs.existsSync(p); - return { - pass: exists, - detail: exists ? `Found: ${p}` : "node_modules/ not found -- run npm install", - }; - }, - impactIfMissing: "All imports fail at runtime", - }, - { - name: "TypeScript available", - category: "Toolchain", - check: () => { - try { - const result = childProcess.execSync("npx tsc --version", { - encoding: "utf-8", - timeout: 10000, - stdio: ["pipe", "pipe", "pipe"], - }).trim(); - return { pass: true, detail: `Detected: ${result}` }; - } catch { - return { pass: false, detail: "TypeScript not available via npx" }; - } - }, - impactIfMissing: "Cannot compile TypeScript files", - }, - { - name: "tsconfig.json exists", - category: "Config", - check: () => { - const p = path.join(targetDir, "tsconfig.json"); - const exists = fs.existsSync(p); - return { - pass: exists, - detail: exists ? `Found: ${p}` : "Not found", - }; - }, - impactIfMissing: "TypeScript compiler uses defaults, may not match project needs", - }, - { - name: "Source directory exists", - category: "Structure", - check: () => { - const candidates = ["src", "lib", "app"]; - const found = candidates.filter((d) => { - const p = path.join(targetDir, d); - return fs.existsSync(p) && fs.statSync(p).isDirectory(); - }); - return { - pass: found.length > 0, - detail: found.length > 0 ? `Found: ${found.join(", ")}` : "No src/ lib/ or app/ directory found", - }; - }, - impactIfMissing: "Agent cannot locate source files to modify", - }, - { - name: "Test directory exists", - category: "Structure", - check: () => { - const candidates = ["test", "tests", "__tests__", "spec"]; - const found = candidates.filter((d) => { - const p = path.join(targetDir, d); - return fs.existsSync(p) && fs.statSync(p).isDirectory(); - }); - return { - pass: found.length > 0, - detail: found.length > 0 ? `Found: ${found.join(", ")}` : "No test directory found", - }; - }, - impactIfMissing: "Agent cannot find or run existing tests", - }, - { - name: "Git repository initialized", - category: "Version Control", - check: () => { - const gitDir = path.join(targetDir, ".git"); - const exists = fs.existsSync(gitDir); - return { - pass: exists, - detail: exists ? "Git repository detected" : "No .git directory found", - }; - }, - impactIfMissing: "No rollback capability, no change history", - }, - ]; -} - -// --------------------------------------------------------------------------- -// Simulation: with and without init phase -// --------------------------------------------------------------------------- - -interface SimResult { - scenario: string; - checksRun: boolean; - failuresBeforeWork: number; - workAttempted: boolean; - workSucceeded: boolean; - timeWastedMs: number; -} - -function simulateWithoutInit(): SimResult { - // Agent skips init, goes straight to work. - // Encounters failures one at a time as it hits missing prerequisites. - const checks = createChecks(process.cwd()); - let failures = 0; - let timeWasted = 0; - - for (const check of checks) { - const result = check.check(); - if (!result.pass) { - failures++; - timeWasted += 200; // Agent spends time discovering each missing piece - } - } - - return { - scenario: "NO INIT PHASE", - checksRun: false, - failuresBeforeWork: 0, // Didn't check upfront - workAttempted: true, - workSucceeded: failures === 0, - timeWastedMs: timeWasted, - }; -} - -function simulateWithInit(): SimResult { - // Agent runs init phase first, discovers all issues upfront. - const checks = createChecks(process.cwd()); - let failures = 0; - - for (const check of checks) { - if (!check.check().pass) { - failures++; - } - } - - return { - scenario: "WITH INIT PHASE", - checksRun: true, - failuresBeforeWork: failures, - workAttempted: failures === 0, - workSucceeded: failures === 0, - timeWastedMs: 0, - }; -} - -// --------------------------------------------------------------------------- -// Reporting -// --------------------------------------------------------------------------- - -function pad(s: string, len: number): string { - return s.length >= len ? s : s + " ".repeat(len - s.length); -} - -function run(): void { - const targetDir = process.cwd(); - console.log("\n" + "=".repeat(80)); - console.log(" INITIALIZATION PREREQUISITE CHECK"); - console.log("=".repeat(80)); - console.log(` Target: ${targetDir}\n`); - - const checks = createChecks(targetDir); - const results: CheckResult[] = checks.map((c) => { - const r = c.check(); - return { - name: c.name, - category: c.category, - passed: r.pass, - detail: r.detail, - impactIfMissing: c.impactIfMissing, - }; - }); - - // Detailed report - const header = `| ${pad("Check", 35)}| ${pad("Category", 12)}| ${pad("Status", 6)}| Detail`; - const sep = `|${"-".repeat(37)}|${"-".repeat(14)}|${"-".repeat(8)}|${"-".repeat(40)}`; - console.log(header); - console.log(sep); - - for (const r of results) { - const status = r.passed ? "PASS" : "FAIL"; - console.log(`| ${pad(r.name, 35)}| ${pad(r.category, 12)}| ${pad(status, 6)}| ${r.detail}`); - } - - const passCount = results.filter((r) => r.passed).length; - const failCount = results.filter((r) => !r.passed).length; - - console.log("\n" + "-".repeat(80)); - console.log(` Results: ${passCount} passed, ${failCount} failed out of ${results.length} checks`); - - if (failCount > 0) { - console.log("\n FAILED CHECKS -- Impact if not resolved:"); - for (const r of results.filter((r) => !r.passed)) { - console.log(` - ${r.name}: ${r.impactIfMissing}`); - } - } - - // Comparison: with vs without init - const noInit = simulateWithoutInit(); - const withInit = simulateWithInit(); - - console.log("\n" + "=".repeat(80)); - console.log(" INIT PHASE COMPARISON"); - console.log("=".repeat(80) + "\n"); - - const cHeader = `| ${pad("Metric", 35)}| ${pad("No Init Phase", 18)}| ${pad("With Init Phase", 18)}|`; - const cSep = `|${"-".repeat(37)}|${"-".repeat(20)}|${"-".repeat(20)}|`; - console.log(cHeader); - console.log(cSep); - console.log(`| ${pad("Prerequisites checked upfront", 35)}| ${pad("No", 18)}| ${pad("Yes", 18)}|`); - console.log(`| ${pad("Work attempted despite issues", 35)}| ${pad(String(noInit.workAttempted), 18)}| ${pad(String(withInit.workAttempted), 18)}|`); - console.log(`| ${pad("Time wasted discovering issues late", 35)}| ${pad(noInit.timeWastedMs + "ms", 18)}| ${pad(withInit.timeWastedMs + "ms", 18)}|`); - console.log(`| ${pad("Work succeeded", 35)}| ${pad(String(noInit.workSucceeded), 18)}| ${pad(String(withInit.workSucceeded), 18)}|`); - - console.log("\n An explicit init phase catches problems before they waste time.\n"); -} - -run(); diff --git a/courses/learn-harness-engineering/docs/en/lectures/lecture-06-why-initialization-needs-its-own-phase/code/init.sh b/courses/learn-harness-engineering/docs/en/lectures/lecture-06-why-initialization-needs-its-own-phase/code/init.sh deleted file mode 100644 index 2c6e1b0c..00000000 --- a/courses/learn-harness-engineering/docs/en/lectures/lecture-06-why-initialization-needs-its-own-phase/code/init.sh +++ /dev/null @@ -1,10 +0,0 @@ -#!/usr/bin/env bash -set -euo pipefail - -echo "[init] installing dependencies" -npm install - -echo "[init] starting the docs site is optional" -echo "[init] use npm run docs:dev for course docs" - -echo "[init] project-specific startup would go here" diff --git a/courses/learn-harness-engineering/docs/en/lectures/lecture-06-why-initialization-needs-its-own-phase/code/initializer-output-checklist.md b/courses/learn-harness-engineering/docs/en/lectures/lecture-06-why-initialization-needs-its-own-phase/code/initializer-output-checklist.md deleted file mode 100644 index 58484c4d..00000000 --- a/courses/learn-harness-engineering/docs/en/lectures/lecture-06-why-initialization-needs-its-own-phase/code/initializer-output-checklist.md +++ /dev/null @@ -1,7 +0,0 @@ -# Initializer Output Checklist - -- Is there a canonical startup command? -- Is there a canonical verification command? -- Is there a first progress artifact? -- Is there a stable first commit? -- Is there a visible feature surface for later runs? diff --git a/courses/learn-harness-engineering/docs/en/lectures/lecture-06-why-initialization-needs-its-own-phase/index.md b/courses/learn-harness-engineering/docs/en/lectures/lecture-06-why-initialization-needs-its-own-phase/index.md deleted file mode 100644 index afe55587..00000000 --- a/courses/learn-harness-engineering/docs/en/lectures/lecture-06-why-initialization-needs-its-own-phase/index.md +++ /dev/null @@ -1,157 +0,0 @@ -[中文版本 →](../../../zh/lectures/lecture-06-why-initialization-needs-its-own-phase/) - -> Code examples: [code/](https://github.com/walkinglabs/learn-harness-engineering/blob/main/docs/en/lectures/lecture-06-why-initialization-needs-its-own-phase/code/) -> Practice project: [Project 03. Multi-session continuity](./../../projects/project-03-multi-session-continuity/index.md) - -# Lecture 06. Initialize Before Every Agent Session - -You start a new agent session and say "add a search feature." It jumps straight into coding — admirable enthusiasm. After 20 minutes it discovers the test framework isn't configured properly, spends another 10 fixing that, then the database migration script format is wrong, more fiddling. The search feature eventually gets added, but the whole session was inefficient — most time went to "figuring out how this project works" rather than writing the search feature. - -The better approach: before letting the agent start working, use a separate phase to get the base environment ready, verification commands passing, and project structure understood. It's like building a house — you don't pour the foundation and put up walls simultaneously. If you do, the walls go up before the foundation has cured, and the whole building has to be torn down and started over. Pour the foundation first, let it cure, then build the walls — clean and efficient. - -This lecture explains why initialization must be a separate phase, not mixed in with implementation. - -## Foundation and Walls: Two Fundamentally Different Jobs - -Initialization and implementation have completely different optimization targets. The implementation phase optimizes for: maximizing the quantity and quality of verified features. The initialization phase optimizes for: maximizing the reliability and efficiency of all subsequent implementation. - -When you mix initialization and implementation, the agent faces a multi-objective optimization problem — simultaneously building infrastructure and writing feature code. Without explicit priority setting, the agent naturally gravitates toward writing code (because that's directly visible output) while sacrificing infrastructure (because its value only shows in subsequent sessions). It's like telling a construction crew to simultaneously pour the foundation and build the walls — they'll probably rush to build walls because walls are visible and demonstrable. But a house with a bad foundation has systemic problems down the line. - -## Initialization Lifecycle - -```mermaid -flowchart TB - subgraph Wrong["Mixed session (wrong)"] - W1["Start feature work immediately"] --> W2["Discover env and test gaps mid-task"] - W2 --> W3["Accumulate unverified code"] - W3 --> W4["Next session must rediscover project state"] - end - - subgraph Right["Dedicated initialization (right)"] - R1["Session 1: environment runnable"] --> R2["Example test passing"] - R2 --> R3["Bootstrap contract + task list written"] - R3 --> R4["Clean checkpoint committed"] - R4 --> R5["Later sessions start directly on verified tasks"] - end -``` - -## What Happens When You Mix Them - -The most direct problem: the foundation doesn't set properly. The agent spends 80% of its effort on feature code and 20% casually setting up some infrastructure. The test framework is configured but never verified, lint rules are set but too loose, no progress file created. These defects aren't obvious in the first session (because the agent still remembers what it did), but they surface in the second session — the new agent doesn't know how to run, test, or where things stand. Shoddy foundation, shaky building. - -A more hidden cost is "unverified accumulation" — feature code written before the test framework is configured is code without verification. When you finally go back to add tests for that code, you might discover the design was wrong from the start — had you known, you would have implemented it differently. Like tiling over wet concrete — when you discover the floor isn't level, all the tiles have to be pried up and redone. - -Session budget is being wasted too. Initialization work (configuring environments, setting up tests, understanding project structure) consumes significant budget, leaving less for actual feature implementation. Result: the first session only completes half the features, and the second session has to start over understanding the project. Budget spent on the foundation, but the foundation isn't solid either — neither goal achieved. - -The most easily overlooked problem is implicit assumption landmines. Decisions the agent makes during initialization (which test framework, how to organize directories, dependency management) — if not explicitly recorded, subsequent sessions can't understand these choices. Worse, subsequent sessions might make contradictory choices. The first construction crew used a concrete foundation, the second crew doesn't know and drove wooden pilings into it — the foundation cracks. - -Anthropic's long-running application development research explicitly recommends separating initialization from implementation. Their experimental data: projects using a dedicated initialization phase showed 31% higher feature completion rates in multi-session scenarios compared to mixed approaches. The key insight — time invested in the initialization phase is fully recovered in the next 3-4 sessions. The more solid the foundation, the faster the walls go up. - -OpenAI's Codex harness engineering guide also emphasizes the "repository as operational record" principle — establish clear operational structure from the first run, or every new session has to re-infer project conventions. - -## Core Concepts - -- **Initialization Phase**: The first phase in the agent's lifecycle — no feature implementation, only establishing prerequisites for all subsequent implementation phases. The output isn't code, it's infrastructure. -- **Bootstrap Contract**: The conditions under which a project can be unambiguously operated by a fresh agent session — can start, can test, can see progress, can pick up next steps. Four conditions, all required. -- **Cold Start vs Warm Start**: Cold start is from an empty directory where the agent must guess project structure; warm start is from a template or existing project where infrastructure is already in place. Warm start far outperforms cold start — like starting work on a site with running water and electricity versus beginning from a barren wasteland. -- **Handoff Readiness**: The project is in a state at any given moment where a fresh agent can take over. No verbal explanation needed — just repo contents. -- **Time to First Verification**: The time from project start until the first feature point passes verification. This is the core metric for measuring initialization efficiency. -- **Downstream Usability**: The best measure of initialization quality — the proportion of subsequent sessions that can successfully execute tasks without relying on implicit knowledge. - -## How to Do Initialization Right - -**Treat initialization as a dedicated phase.** The first session does only initialization — no business feature code at all. Initialization produces: - -**1. Runnable environment.** The project starts, dependencies are installed, no environment issues. Foundation poured, no cracks. - -**2. Verifiable test framework.** At least one example test passes. This proves the test framework itself is properly configured — like standing a pillar on the foundation to prove it can bear weight. - -**3. Bootstrap contract document.** A clear document telling subsequent sessions: -```markdown -# Initialization Contract - -## Start Commands -- Install dependencies: `make setup` -- Start dev server: `make dev` -- Run tests: `make test` -- Full verification: `make check` - -## Current State -- All dependencies installed and locked -- Test framework configured (Vitest + React Testing Library) -- Example test passing (1/1) -- Lint rules configured (ESLint + Prettier) - -## Project Structure -- src/ — Source code -- src/components/ — React components -- src/api/ — API client -- tests/ — Test files -``` - -**4. Task breakdown.** Split the entire project into an ordered task list, each task with clear acceptance criteria: -```markdown -# Task Breakdown - -## Task 1: User Authentication Basics -- Implement JWT auth middleware -- Add login/register endpoints -- Acceptance: pytest tests/test_auth.py all passing - -## Task 2: User Profile Page -- Implement user profile CRUD -- Add profile edit form -- Acceptance: pytest tests/test_profile.py all passing - -## Task 3: Search Feature -- ... -``` - -**5. Git commit as checkpoint.** After initialization completes, commit a clean checkpoint. All subsequent work starts from this checkpoint. - -**Warm start strategy**: Don't start from an empty directory. Use a project template (create-react-app, fastapi-template, etc.) to preset standard directory structure, dependency configuration, and test framework. Bake common initialization steps into the template, leaving only project-specific initialization work. Like starting work on a site with running water and electricity — ten thousand times better than beginning from a barren wasteland. - -**Initialization completion criteria**: Not "how much code was written," but whether the bootstrap contract's four conditions are met — can start, can test, can see progress, can pick up next steps. Use this checklist to validate initialization: - -```markdown -## Initialization Acceptance Checklist -- [ ] `make setup` succeeds from scratch -- [ ] `make test` has at least one passing test -- [ ] A new agent session can answer "how to run" and "how to test" from repo contents alone -- [ ] Task breakdown file exists with at least 3 tasks -- [ ] Everything committed to git -``` - -## Real-World Example - -Two initialization approaches for a React frontend project: - -**Mixed approach (pouring foundation and building walls simultaneously)**: The agent simultaneously created project scaffolding and implemented the first feature in session 1. At session end, the repo had runnable code but: no explicit start/test command documentation, no progress tracking file, no task breakdown. Session 2 spent ~20 minutes inferring project structure, test framework, and build process — like a new construction crew arriving at a site, not knowing how far the foundation got or where the plumbing runs are, having to dig holes one by one to find out. - -**Dedicated initialization (foundation first)**: Session 1 did only initialization — created directory structure from a template, configured the test framework (Vitest + React Testing Library), wrote and verified one example test, created the bootstrap contract document and task breakdown file, committed the initial checkpoint. Session 2's rebuild time was under 3 minutes, and it started working directly from the task list — the crew arrives, glances at the blueprint, and knows exactly where to pick up. - -Full project cycle comparison: the mixed approach's total rebuild time (across all sessions) was ~60% more than the dedicated initialization approach. The extra 20 minutes spent on initialization was recovered many times over in subsequent sessions. Like a solid foundation making the walls go up faster — slow is fast. - -## Key Takeaways - -- Initialization and implementation have different optimization targets — mixing them just drags both down. Pour the foundation first, then build the walls. -- Initialization's output isn't code, it's infrastructure: runnable environment, verifiable tests, bootstrap contract, task breakdown. -- Validate initialization with the bootstrap contract's four conditions: can start, can test, can see progress, can pick up next steps. -- Warm start beats cold start. Use project templates to preset standardized infrastructure. -- Time invested in initialization is fully recovered in the next 3-4 sessions. This isn't extra cost — it's upfront investment. The more solid the foundation, the faster the building goes up. - -## Further Reading - -- [Anthropic: Effective Harnesses for Long-Running Agents](https://www.anthropic.com/engineering/effective-harnesses-for-long-running-agents) -- [OpenAI: Harness Engineering](https://openai.com/index/harness-engineering/) -- [HumanLayer: Harness Engineering for Coding Agents](https://humanlayer.dev/articles/harness-engineering-for-coding-agents/) -- [Infrastructure as Code — Martin Fowler](https://martinfowler.com/bliki/InfrastructureAsCode.html) -- [SWE-agent: Agent-Computer Interfaces](https://github.com/princeton-nlp/SWE-agent) - -## Exercises - -1. **Bootstrap contract design**: Write a complete bootstrap contract for a project you're developing. Then open a completely fresh agent session, show it only repo contents (no verbal context), and have it try to start the project, run tests, and understand current progress. Record every problem it encounters — each one corresponds to a missing clause in your bootstrap contract. - -2. **Comparison experiment**: Pick a moderately complex new project. Approach A: let the agent initialize and do first implementation simultaneously. Approach B: spend one session on dedicated initialization, start implementation in session 2. After 4 sessions, compare: time to first verification, rebuild cost, feature completion rate. - -3. **Initialization acceptance checklist**: Design an initialization acceptance checklist for your project. Have a fresh agent session execute each checklist item and record which pass and which fail. The failing items are where your harness needs strengthening. diff --git a/courses/learn-harness-engineering/docs/en/lectures/lecture-07-why-agents-overreach-and-under-finish/code/index.md b/courses/learn-harness-engineering/docs/en/lectures/lecture-07-why-agents-overreach-and-under-finish/code/index.md deleted file mode 100644 index cfa201b5..00000000 --- a/courses/learn-harness-engineering/docs/en/lectures/lecture-07-why-agents-overreach-and-under-finish/code/index.md +++ /dev/null @@ -1,8 +0,0 @@ -# Code for Lecture 07 - -Use this folder for examples of: - -- one-shot failures -- oversized task prompts -- incremental task shaping -- structured feature surfaces diff --git a/courses/learn-harness-engineering/docs/en/lectures/lecture-07-why-agents-overreach-and-under-finish/code/next-task-template.md b/courses/learn-harness-engineering/docs/en/lectures/lecture-07-why-agents-overreach-and-under-finish/code/next-task-template.md deleted file mode 100644 index b97cab0a..00000000 --- a/courses/learn-harness-engineering/docs/en/lectures/lecture-07-why-agents-overreach-and-under-finish/code/next-task-template.md +++ /dev/null @@ -1,6 +0,0 @@ -# Next Task Template - -- Current highest-priority feature: -- Why this feature is next: -- What counts as passing: -- What must not be changed during this step: diff --git a/courses/learn-harness-engineering/docs/en/lectures/lecture-07-why-agents-overreach-and-under-finish/code/scope-surface-example.md b/courses/learn-harness-engineering/docs/en/lectures/lecture-07-why-agents-overreach-and-under-finish/code/scope-surface-example.md deleted file mode 100644 index 1f524847..00000000 --- a/courses/learn-harness-engineering/docs/en/lectures/lecture-07-why-agents-overreach-and-under-finish/code/scope-surface-example.md +++ /dev/null @@ -1,17 +0,0 @@ -# Scope Surface Example - -Task: - -- Add indexing to the Electron knowledge app - -Bad scope shape: - -- “Implement indexing” - -Better scope shape: - -- Parse imported documents -- Split documents into chunks -- Persist chunk metadata -- Expose indexing status in the UI -- Add a reindex action diff --git a/courses/learn-harness-engineering/docs/en/lectures/lecture-07-why-agents-overreach-and-under-finish/code/scope-tracker.ts b/courses/learn-harness-engineering/docs/en/lectures/lecture-07-why-agents-overreach-and-under-finish/code/scope-tracker.ts deleted file mode 100644 index c587a1dc..00000000 --- a/courses/learn-harness-engineering/docs/en/lectures/lecture-07-why-agents-overreach-and-under-finish/code/scope-tracker.ts +++ /dev/null @@ -1,151 +0,0 @@ -/** - * scope-tracker.ts - * - * Reads a feature list and a change log. Enforces single-active-feature - * policy. Given a log of changes, flags any changes outside the active - * feature scope. Demonstrates how scope drift happens and how the tracker - * catches it. - * - * Run: npx tsx docs/lectures/lecture-07-why-agents-overreach-and-under-finish/code/scope-tracker.ts - */ - -// --------------------------------------------------------------------------- -// Types -// --------------------------------------------------------------------------- - -interface Feature { - id: string; - name: string; - status: "active" | "pending" | "done"; -} - -interface ChangeLogEntry { - step: number; - file: string; - description: string; - featureId: string; // The feature this change claims to belong to -} - -// --------------------------------------------------------------------------- -// Sample data -// --------------------------------------------------------------------------- - -const features: Feature[] = [ - { id: "F-001", name: "Search endpoint", status: "active" }, - { id: "F-002", name: "Delete endpoint", status: "pending" }, - { id: "F-003", name: "Rate limiting", status: "pending" }, - { id: "F-004", name: "User dashboard", status: "pending" }, -]; - -// A realistic change log where the agent gradually drifts from the active feature -const changeLog: ChangeLogEntry[] = [ - { step: 1, file: "src/routes/search.ts", description: "Add search route handler", featureId: "F-001" }, - { step: 2, file: "src/routes/search.ts", description: "Add query parameter validation", featureId: "F-001" }, - { step: 3, file: "src/routes/search.ts", description: "Add search results pagination", featureId: "F-001" }, - { step: 4, file: "src/routes/delete.ts", description: "Add delete route handler", featureId: "F-002" }, // DRIFT - { step: 5, file: "src/middleware/rate-limit.ts", description: "Add rate limiter middleware", featureId: "F-003" }, // DRIFT - { step: 6, file: "src/routes/search.ts", description: "Integrate rate limiter into search", featureId: "F-001" }, - { step: 7, file: "src/dashboard/ui.tsx", description: "Create dashboard layout component", featureId: "F-004" }, // DRIFT - { step: 8, file: "src/routes/search.ts", description: "Add search response formatting", featureId: "F-001" }, - { step: 9, file: "src/routes/delete.ts", description: "Add delete confirmation logic", featureId: "F-002" }, // DRIFT - { step: 10, file: "src/routes/search.ts", description: "Add search tests", featureId: "F-001" }, -]; - -// --------------------------------------------------------------------------- -// Scope tracker -// --------------------------------------------------------------------------- - -interface ScopeCheckResult { - step: number; - file: string; - description: string; - featureId: string; - inScope: boolean; - activeFeature: string; -} - -function trackScope( - featureList: Feature[], - changes: ChangeLogEntry[] -): ScopeCheckResult[] { - const activeFeatures = featureList.filter((f) => f.status === "active"); - const activeIds = new Set(activeFeatures.map((f) => f.id)); - const activeNames = activeFeatures.map((f) => f.name).join(", "); - - return changes.map((change) => ({ - step: change.step, - file: change.file, - description: change.description, - featureId: change.featureId, - inScope: activeIds.has(change.featureId), - activeFeature: activeNames, - })); -} - -// --------------------------------------------------------------------------- -// Reporting -// --------------------------------------------------------------------------- - -function pad(s: string, len: number): string { - return s.length >= len ? s : s + " ".repeat(len - s.length); -} - -function run(): void { - const results = trackScope(features, changeLog); - - console.log("\n" + "=".repeat(100)); - console.log(" SCOPE TRACKER -- Single Active Feature Enforcement"); - console.log("=".repeat(100)); - - console.log("\n Active feature: " + features.filter((f) => f.status === "active").map((f) => `${f.id} (${f.name})`).join(", ")); - console.log(" Pending features: " + features.filter((f) => f.status === "pending").map((f) => `${f.id} (${f.name})`).join(", ")); - - // Detailed change log - console.log("\n" + "-".repeat(100)); - const header = `| ${pad("Step", 5)}| ${pad("File", 35)}| ${pad("Description", 40)}| ${pad("Feature", 8)}| ${pad("In Scope", 10)}|`; - const sep = `|${"-".repeat(7)}|${"-".repeat(37)}|${"-".repeat(42)}|${"-".repeat(10)}|${"-".repeat(12)}|`; - console.log(header); - console.log(sep); - - let inScopeCount = 0; - let driftCount = 0; - - for (const r of results) { - const scopeLabel = r.inScope ? "OK" : "DRIFT"; - if (r.inScope) inScopeCount++; - else driftCount++; - - const marker = r.inScope ? " " : ">>"; - console.log(`${marker}| ${pad(String(r.step), 5)}| ${pad(r.file, 35)}| ${pad(r.description, 40)}| ${pad(r.featureId, 8)}| ${pad(scopeLabel, 10)}|`); - } - - // Summary - console.log("\n" + "=".repeat(100)); - console.log(" SCOPE DRIFT SUMMARY"); - console.log("=".repeat(100) + "\n"); - - const sHeader = `| ${pad("Metric", 40)}| ${pad("Value", 15)}|`; - const sSep = `|${"-".repeat(42)}|${"-".repeat(17)}|`; - console.log(sHeader); - console.log(sSep); - console.log(`| ${pad("Total changes", 40)}| ${pad(String(results.length), 15)}|`); - console.log(`| ${pad("Changes within active scope (F-001)", 40)}| ${pad(String(inScopeCount), 15)}|`); - console.log(`| ${pad("Changes outside active scope (DRIFT)", 40)}| ${pad(String(driftCount), 15)}|`); - console.log(`| ${pad("Features touched (total)", 40)}| ${pad(String(new Set(results.map((r) => r.featureId)).size), 15)}|`); - - // Drift detail - const driftFeatures = [...new Set(results.filter((r) => !r.inScope).map((r) => r.featureId))]; - if (driftFeatures.length > 0) { - console.log("\n DRIFTED FEATURES:"); - for (const fid of driftFeatures) { - const feat = features.find((f) => f.id === fid); - const driftChanges = results.filter((r) => r.featureId === fid); - console.log(` ${fid} (${feat?.name}): ${driftChanges.length} unauthorized changes`); - } - } - - console.log("\n Without a scope tracker, the agent silently worked on " + driftFeatures.length + " unrelated features."); - console.log(" The tracker catches this drift and enforces the single-active-feature policy.\n"); -} - -run(); diff --git a/courses/learn-harness-engineering/docs/en/lectures/lecture-07-why-agents-overreach-and-under-finish/index.md b/courses/learn-harness-engineering/docs/en/lectures/lecture-07-why-agents-overreach-and-under-finish/index.md deleted file mode 100644 index 5fd9c47b..00000000 --- a/courses/learn-harness-engineering/docs/en/lectures/lecture-07-why-agents-overreach-and-under-finish/index.md +++ /dev/null @@ -1,134 +0,0 @@ -[中文版本 →](../../../zh/lectures/lecture-07-why-agents-overreach-and-under-finish/) - -> Code examples: [code/](https://github.com/walkinglabs/learn-harness-engineering/blob/main/docs/en/lectures/lecture-07-why-agents-overreach-and-under-finish/code/) -> Practice project: [Project 04. Runtime feedback and scope control](./../../projects/project-04-incremental-indexing/index.md) - -# Lecture 07. Draw Clear Task Boundaries for Agents - -You tell Claude Code to "add user authentication to this project," and it starts modifying the database schema, writing routes, changing frontend components, and — while it's at it — refactoring the error-handling middleware. Two hours later you check: 12 files modified, 800 lines of new code, and not a single feature works end-to-end. - -Biting off more than you can chew — this saying applies to AI agents especially well. Agents are born with an impulse to "do a little extra" — they see related things and just handle them along the way, like someone who goes to the supermarket for a bottle of soy sauce and comes out pushing a full cart. The problem is, humans who buy too much just waste money; agents doing too many things simultaneously means none of them get done properly. - -Anthropic's "Effective harnesses for long-running agents" engineering blog states clearly: when prompts are too broad, agents tend to "start multiple things at once" rather than "finish one thing first." OpenAI's Codex engineering practices found the same — tasks without explicit scope controls see completion rates plummet. This is not a model problem — it's a harness problem. You didn't draw the boundary. - -## Attention Is a Finite Resource - -This isn't a metaphor — it's math. Assume the agent's context capacity is C and it activates k tasks simultaneously. Each task gets an average of C/k reasoning resources. When C/k drops below the minimum threshold needed to complete a single task, none of them get finished. Your stomach is only so big — stuff ten dumplings in at once and you won't digest them all, you'll just get ten cases of indigestion. - -Claude Code's real behavior is telling. Ask it to "add user registration" and it might: - -1. Create a User model -2. Write the registration route -3. Notice it needs email verification, so add a mail service -4. See that passwords need hashing, so bring in bcrypt -5. Notice the error handling is inconsistent, so refactor the global error middleware -6. See the test file structure is messy, so reorganize the directory - -Six steps later, every one is half-done. No end-to-end verification, complex coupling between the half-baked code, and the next session to pick up the pieces will be completely lost. Like someone cooking six dishes simultaneously — every dish is in the pan but none has been plated. They all burn. - -Anthropic's experimental data directly supports this: agents using a "small next step" strategy (equivalent to WIP=1) show a 37% higher task completion rate than agents using broad prompts. More interestingly, the number of lines of code generated by agents is weakly negatively correlated with actual feature completion — more code written, fewer features completed. Biting off more than you can chew, proven by data. - -## WIP=1 Workflow - -```mermaid -flowchart LR - Queue["Feature queue"] --> Pick["Pick exactly one task"] - Pick --> Active["Only one active item"] - Active --> Verify["Run end-to-end verification"] - Verify -->|pass| Commit["Commit and unlock next task"] - Verify -->|fail| Active - Commit --> Queue -``` - -```mermaid -flowchart TB - Budget["Available reasoning budget = C"] --> One["WIP = 1
C / 1 per task"] - Budget --> Many["WIP = 5
C / 5 per task"] - - One --> Finish["One feature reaches passing state"] - Many --> Partial["Five partial implementations"] - Partial --> VCR["Low verified completion rate
high rework next session"] -``` - -## Core Concepts - -- **Overreach**: The agent activates more tasks in a single session than optimal. It's quantifiable — doing 5 features with 0 passing end-to-end is overreach. -- **Under-finish**: The ratio of tasks that pass end-to-end verification, out of all activated tasks, falls below threshold. Code written but tests not passing is under-finish. -- **WIP Limit (Work-in-Progress Limit)**: From Kanban methodology. Core idea: limit how many tasks are in-flight at once. For agents, WIP=1 is the safest default — finish one before starting the next. Like a buffet — don't pile your plate, finish one plate then go back for the next. -- **Completion Evidence**: The verifiable condition a task must satisfy to move from "in progress" to "done." Without this, agents substitute "the code looks fine" for "the behavior passes tests." -- **Scope Surface**: A DAG structure where each node is a work unit and edges are dependencies. States are limited to four: not_started, active, blocked, passing. -- **Completion Pressure**: The constraining force the harness exerts through WIP limits and completion evidence requirements, forcing the agent to finish the current task before starting a new one. - -## Overreach and Under-finish Are Symbiotic - -These two problems aren't independent — they amplify each other. Overreach dilutes attention, diluted attention causes under-finish, and the half-finished code left behind increases system complexity, which further drives overreach in the next task. A vicious cycle. - -In Kanban terms: Little's Law tells us L = lambda * W. If work-in-progress L is too high (doing too many things at once), the lead time W for each task inevitably increases. For agents, this means each feature takes longer from start to verified completion, and the probability of failure grows. - -This is an old problem in the human world too — Steve McConnell documented in *Rapid Development* that scope creep is the leading cause of project failure. But humans at least have the intuition of "I've done enough." Agents have none. Generating the next idea costs the model almost nothing in extra tokens — writing "let me fix this too while I'm here" barely registers — but every additional modification dilutes the agent's attention. Like a buffet where each extra plate has near-zero marginal cost, but your stomach only has so much capacity. - -## How to Do It Right - -### 1. Enforce WIP=1 - -This is the most direct and effective method. In your harness, tell the agent explicitly: **only one task is allowed in "active" status at any time.** In Claude Code's CLAUDE.md or Codex's AGENTS.md, write: - -``` -## Work Rules -- Work on one feature at a time -- Only start the next feature after the current one passes end-to-end verification -- Don't "also refactor" feature B while implementing feature A -``` - -Like eating at a buffet — one plate at a time, finish it before going back for more. - -### 2. Define Explicit Completion Evidence for Every Task - -Done is not "code is written" — it's "behavior verification passes." In your feature list, every entry needs a verification command: - -``` -F01: User Registration - Verification: curl -X POST /api/register -d '{"email":"test@example.com","password":"123456"}' | jq .status == 201 - State: passing -``` - -### 3. Externalize the Scope Surface - -Use a machine-readable file (JSON or Markdown) to record all task states. Any new session can read this file and immediately know: which task is active? What behavior counts as done? What verifications have passed? - -### 4. Monitor Verified Completion Rate - -The harness should continuously track VCR (Verified Completion Rate) = verified tasks / activated tasks. Block new task activations when VCR < 1.0. - -## Real-World Case - -A REST API project with 8 features, two strategies compared: - -**Buffet mode (unconstrained)**: Agent activates 5 features simultaneously in session 1. Produces ~800 lines across 12 files. End-to-end test pass rate: 20% — only user registration works. The other 4 features: database schema created but missing validation logic, routes defined but returning wrong response formats. Like someone cooking six dishes at once, only one is barely edible. By end of session 3, only 3 of 8 features complete. - -**Single-plate mode (WIP=1)**: Agent works on user registration only in session 1. Produces ~200 lines across 4 files. End-to-end tests: 100% passing. Commits a clean, verified implementation. By end of session 4, 7 of 8 features complete (the 8th blocked by an external dependency). - -Result: less total code (800 vs 1200 lines) but more effective code. Completion rate: 87.5% vs 37.5%. Take one bite at a time, and you actually eat more. - -## Key Takeaways - -- **WIP=1 is the default safe setting for agent harnesses** — finish one, then start the next; don't try to parallelize. You can't become fat in one bite. -- **Completion evidence must be executable** — "the code looks fine" doesn't count; "curl returns 201" does. -- **The scope surface must be externalized as a file** — not just mentioned in conversation, but recorded in a machine-readable format in the repo. -- **Overreach and under-finish are symbiotic** — solving one solves the other. -- **"Do less but finish" always beats "do more but leave half-done"** — agent code lines and feature completion rate are negatively correlated. Quality always beats quantity. - -## Further Reading - -- [Effective harnesses for long-running agents - Anthropic](https://www.anthropic.com/engineering/effective-harnesses-for-long-running-agents) — Anthropic's engineering blog, detailed discussion of the "small next step" strategy -- [Harness Engineering - OpenAI](https://openai.com/index/harness-engineering/) — OpenAI's complete treatment of harness engineering -- [Kanban: Successful Evolutionary Change - David Anderson](https://www.goodreads.com/book/show/1070822.Kanban) — The classic source on WIP limits -- [Rapid Development - Steve McConnell](https://www.goodreads.com/book/show/125171.Rapid_Development) — Empirical data on scope creep as the leading cause of project failure - -## Exercises - -1. **Task Atomization**: Pick a broad requirement (e.g., "implement a user management system") and break it into at least 5 atomic work units. For each unit, specify: (a) a single behavior description, (b) an executable verification command, (c) dependencies. Check whether the decomposition satisfies the WIP=1 constraint. - -2. **Comparison Experiment**: Run the same project twice — once without constraints, once with enforced WIP=1. Compare: verified completion rate, total lines of code, effective code ratio. - -3. **Completion Evidence Audit**: Review a recent agent run's output, classifying each code change as "completed behavior," "incomplete behavior," or "scaffolding." Add missing verification commands for each incomplete behavior. diff --git a/courses/learn-harness-engineering/docs/en/lectures/lecture-08-why-feature-lists-are-harness-primitives/code/feature-list-validator.ts b/courses/learn-harness-engineering/docs/en/lectures/lecture-08-why-feature-lists-are-harness-primitives/code/feature-list-validator.ts deleted file mode 100644 index 52d6a2b9..00000000 --- a/courses/learn-harness-engineering/docs/en/lectures/lecture-08-why-feature-lists-are-harness-primitives/code/feature-list-validator.ts +++ /dev/null @@ -1,218 +0,0 @@ -/** - * feature-list-validator.ts - * - * Reads a feature_list.json, validates its schema, and checks for features - * marked "pass" without verification evidence. Outputs a structured report. - * Can run against any project directory that has a feature_list.json. - * - * Usage: - * npx tsx docs/lectures/lecture-08.../code/feature-list-validator.ts [path-to-dir] - * (defaults to the directory containing this script) - * - * Run: npx tsx docs/lectures/lecture-08-why-feature-lists-are-harness-primitives/code/feature-list-validator.ts - */ - -import * as fs from "node:fs"; -import * as path from "node:path"; - -// --------------------------------------------------------------------------- -// Types -// --------------------------------------------------------------------------- - -interface FeatureEntry { - id?: string; - category?: string; - description?: string; - verification?: string[]; - passes?: boolean; - // Allow unknown fields for flexibility - [key: string]: unknown; -} - -interface ValidationResult { - featureId: string; - schemaValid: boolean; - schemaErrors: string[]; - hasVerification: boolean; - markedPassWithoutEvidence: boolean; - passes: boolean; - verificationCount: number; -} - -// --------------------------------------------------------------------------- -// Schema validation -// --------------------------------------------------------------------------- - -function validateSchema(entry: FeatureEntry, index: number): string[] { - const errors: string[] = []; - const label = entry.id ?? `entry ${index + 1}`; - - if (!entry.id || typeof entry.id !== "string") { - errors.push(`[${label}] Missing or invalid 'id' field`); - } - if (!entry.category || typeof entry.category !== "string") { - errors.push(`[${label}] Missing or invalid 'category' field`); - } - if (!entry.description || typeof entry.description !== "string") { - errors.push(`[${label}] Missing or invalid 'description' field`); - } - if (entry.verification !== undefined && !Array.isArray(entry.verification)) { - errors.push(`[${label}] 'verification' must be an array if present`); - } - if (entry.passes !== undefined && typeof entry.passes !== "boolean") { - errors.push(`[${label}] 'passes' must be a boolean if present`); - } - - return errors; -} - -// --------------------------------------------------------------------------- -// Evidence validation -// --------------------------------------------------------------------------- - -function checkEvidence(entry: FeatureEntry): { - hasVerification: boolean; - markedPassWithoutEvidence: boolean; -} { - const hasVerification = Array.isArray(entry.verification) && entry.verification.length > 0; - const markedPassWithoutEvidence = entry.passes === true && !hasVerification; - - return { hasVerification, markedPassWithoutEvidence }; -} - -// --------------------------------------------------------------------------- -// Process feature list -// --------------------------------------------------------------------------- - -function processFeatureList(entries: FeatureEntry[]): ValidationResult[] { - return entries.map((entry, index) => { - const schemaErrors = validateSchema(entry, index); - const evidence = checkEvidence(entry); - - return { - featureId: (entry.id as string) ?? `entry-${index + 1}`, - schemaValid: schemaErrors.length === 0, - schemaErrors, - hasVerification: evidence.hasVerification, - markedPassWithoutEvidence: evidence.markedPassWithoutEvidence, - passes: entry.passes === true, - verificationCount: Array.isArray(entry.verification) ? entry.verification.length : 0, - }; - }); -} - -// --------------------------------------------------------------------------- -// Reporting -// --------------------------------------------------------------------------- - -function pad(s: string, len: number): string { - return s.length >= len ? s : s + " ".repeat(len - s.length); -} - -function run(): void { - // Resolve target directory - const scriptDir = path.dirname(new URL(import.meta.url).pathname); - const targetDir = process.argv[2] - ? path.resolve(process.argv[2]) - : scriptDir; - - const filePath = path.join(targetDir, "feature_list.json"); - - console.log("\n" + "=".repeat(90)); - console.log(" FEATURE LIST VALIDATOR"); - console.log("=".repeat(90)); - console.log(` Reading: ${filePath}\n`); - - if (!fs.existsSync(filePath)) { - console.error(` ERROR: feature_list.json not found at ${filePath}`); - console.error(" Usage: npx tsx feature-list-validator.ts [path-to-directory-containing-feature_list.json]\n"); - process.exit(1); - } - - let entries: FeatureEntry[]; - try { - const raw = fs.readFileSync(filePath, "utf-8"); - entries = JSON.parse(raw); - } catch (err) { - console.error(` ERROR: Could not parse feature_list.json: ${err}`); - process.exit(1); - } - - if (!Array.isArray(entries)) { - console.error(" ERROR: feature_list.json must contain a JSON array at the top level."); - process.exit(1); - } - - // For demo purposes, also validate an extended test set - const demoEntries: FeatureEntry[] = [ - ...entries, - { - id: "qna-002", - category: "import", - description: "User can import a PDF document.", - verification: ["Upload a PDF file", "Verify it appears in the document list"], - passes: true, - }, - { - id: "qna-003", - category: "grounded_qa", - description: "System hallucination rate is below 5%.", - verification: [], // Empty -- no evidence - passes: true, // Marked as pass WITHOUT evidence - }, - { - id: "missing-fields", - // Missing 'category' and 'description' - passes: true, - } as FeatureEntry, - ]; - - const results = processFeatureList(demoEntries); - - // Print report - const header = `| ${pad("Feature ID", 14)}| ${pad("Schema", 8)}| ${pad("Passes", 7)}| ${pad("Verifications", 14)}| ${pad("Evidence?", 12)}| Notes`; - const sep = `|${"-".repeat(16)}|${"-".repeat(10)}|${"-".repeat(9)}|${"-".repeat(16)}|${"-".repeat(14)}|${"-".repeat(30)}`; - console.log(header); - console.log(sep); - - for (const r of results) { - const schemaLabel = r.schemaValid ? "OK" : "INVALID"; - const passesLabel = r.passes ? "PASS" : "FAIL"; - const evidenceLabel = r.hasVerification ? "Present" : "MISSING"; - const notes = r.markedPassWithoutEvidence - ? "FLAGGED: passes without evidence!" - : r.schemaErrors.length > 0 - ? r.schemaErrors[0] - : ""; - - const marker = r.markedPassWithoutEvidence ? ">>" : r.schemaValid ? " " : "!!"; - console.log( - `${marker}| ${pad(r.featureId, 14)}| ${pad(schemaLabel, 8)}| ${pad(passesLabel, 7)}| ${pad(String(r.verificationCount), 14)}| ${pad(evidenceLabel, 12)}| ${notes}` - ); - } - - // Summary - const total = results.length; - const schemaOk = results.filter((r) => r.schemaValid).length; - const passing = results.filter((r) => r.passes).length; - const flagged = results.filter((r) => r.markedPassWithoutEvidence).length; - const withEvidence = results.filter((r) => r.hasVerification).length; - - console.log("\n" + "-".repeat(90)); - console.log(" SUMMARY"); - console.log("-".repeat(90)); - console.log(` Total features: ${total}`); - console.log(` Schema valid: ${schemaOk}/${total}`); - console.log(` Marked as passing: ${passing}/${total}`); - console.log(` With verification evidence: ${withEvidence}/${total}`); - console.log(` Flagged (pass without evidence): ${flagged}`); - - if (flagged > 0) { - console.log(`\n WARNING: ${flagged} feature(s) marked as "pass" without any verification evidence.`); - console.log(" These features need verification before they can be trusted.\n"); - } else { - console.log("\n All passing features have verification evidence. Feature list is healthy.\n"); - } -} - -run(); diff --git a/courses/learn-harness-engineering/docs/en/lectures/lecture-08-why-feature-lists-are-harness-primitives/code/feature_list.json b/courses/learn-harness-engineering/docs/en/lectures/lecture-08-why-feature-lists-are-harness-primitives/code/feature_list.json deleted file mode 100644 index 8b973e31..00000000 --- a/courses/learn-harness-engineering/docs/en/lectures/lecture-08-why-feature-lists-are-harness-primitives/code/feature_list.json +++ /dev/null @@ -1,15 +0,0 @@ -[ - { - "id": "qna-001", - "category": "grounded_qa", - "description": "User can ask a question about an imported document and receive an answer with visible citations.", - "verification": [ - "Import a markdown document", - "Open the Q&A panel", - "Ask a question about known content", - "Verify the answer is returned", - "Verify one or more citations are displayed" - ], - "passes": false - } -] diff --git a/courses/learn-harness-engineering/docs/en/lectures/lecture-08-why-feature-lists-are-harness-primitives/code/index.md b/courses/learn-harness-engineering/docs/en/lectures/lecture-08-why-feature-lists-are-harness-primitives/code/index.md deleted file mode 100644 index ac4215fb..00000000 --- a/courses/learn-harness-engineering/docs/en/lectures/lecture-08-why-feature-lists-are-harness-primitives/code/index.md +++ /dev/null @@ -1,8 +0,0 @@ -# Code for Lecture 08 - -Use this folder for examples of: - -- pass-state gating -- end-to-end verification -- weak vs strong completion criteria -- evaluator-loop examples diff --git a/courses/learn-harness-engineering/docs/en/lectures/lecture-08-why-feature-lists-are-harness-primitives/code/pass-gate-policy.md b/courses/learn-harness-engineering/docs/en/lectures/lecture-08-why-feature-lists-are-harness-primitives/code/pass-gate-policy.md deleted file mode 100644 index c922f64b..00000000 --- a/courses/learn-harness-engineering/docs/en/lectures/lecture-08-why-feature-lists-are-harness-primitives/code/pass-gate-policy.md +++ /dev/null @@ -1,8 +0,0 @@ -# Pass Gate Policy - -A feature may only move from `passes: false` to `passes: true` when: - -- the expected workflow has been exercised -- the evidence of success is recorded -- no blocking error is present in the tested path -- the implementation does not leave the app in a broken or ambiguous state diff --git a/courses/learn-harness-engineering/docs/en/lectures/lecture-08-why-feature-lists-are-harness-primitives/index.md b/courses/learn-harness-engineering/docs/en/lectures/lecture-08-why-feature-lists-are-harness-primitives/index.md deleted file mode 100644 index e58b5f7d..00000000 --- a/courses/learn-harness-engineering/docs/en/lectures/lecture-08-why-feature-lists-are-harness-primitives/index.md +++ /dev/null @@ -1,137 +0,0 @@ -[中文版本 →](../../../zh/lectures/lecture-08-why-feature-lists-are-harness-primitives/) - -> Code examples: [code/](https://github.com/walkinglabs/learn-harness-engineering/blob/main/docs/en/lectures/lecture-08-why-feature-lists-are-harness-primitives/code/) -> Practice project: [Project 04. Runtime feedback and scope control](./../../projects/project-04-incremental-indexing/index.md) - -# Lecture 08. Use Feature Lists to Constrain What the Agent Does - -You ask an agent to build an e-commerce site. After it finishes, it tells you "done." You look at the code — user authentication works, but the checkout button in the shopping cart does nothing, and the payment flow isn't connected at all. The problem: you never told it what "done" means, so it used its own standard — "I wrote a lot of code and it looks fairly complete." - - Feature lists, in many people's eyes, are just a memo — write things down so you don't forget, then toss it aside. But in the harness world, a feature list isn't a memo for humans — it's the backbone of the entire harness. The scheduler relies on it to pick tasks, the verifier relies on it to judge completion, the handoff reporter relies on it to generate summaries. Break the backbone and the whole body is paralyzed. - -Both Anthropic and OpenAI emphasize: **artifacts must be externalized.** Feature state must live in a machine-readable file in the repo, not in unstructured conversation text. - -## Agents Don't Know What "Done" Means - -Neither Claude Code nor Codex automatically knows what you mean by "done." You say "add a shopping cart feature," and the model's interpretation might be "write a Cart component and an addToCart method." But you meant "the user can browse products, add to cart, and complete checkout end-to-end." This understanding gap persists without a feature list. The agent uses its own implicit standard — usually "the code has no obvious syntax errors." What you need is end-to-end behavioral verification. Like asking a friend to buy you fruit — you say "get some fruit" and they come back with lemons. Their fruit and your fruit are not the same fruit. - -Look at this common progress note: - -``` -Did user auth, shopping cart mostly done, still need payments -``` -Can a new agent session answer these questions from this note? What does "mostly done" mean? Which tests did the cart pass? What's blocking payments? The answer to all is "nobody knows." Like telling your doctor "my stomach hurts, been okay lately" — what medicine can they prescribe? - -The result: the new session spends 20 minutes inferring project state, and may re-implement completed features. Anthropic's engineering data shows that good progress records reduce session startup diagnostic time by 60-80%. - -## Feature State Machine - -```mermaid -flowchart LR - Feature["One feature row"] --> Behavior["Behavior
for example: POST /cart/items returns 201"] - Feature --> Check["Verification command
the exact check to run"] - Feature --> State["State
not_started / active / blocked / passing"] - - Behavior --> Complete["Only with all three fields
is the feature row usable"] - Check --> Complete - State --> Complete -``` - -```mermaid -flowchart LR - List["feature_list.json / features.md"] --> Scheduler["Pick the next not_started item"] - Scheduler --> Agent["Agent works on that one item"] - Agent --> Verifier["Run that item's verification command"] - Verifier -->|pass| Passing["Mark it passing
and write the evidence"] - Verifier -->|fail| Active["Keep it active"] - Verifier -->|dependency issue| Blocked["Mark it blocked"] - Passing --> Handoff["Update handoff note
and current progress"] - Active --> Agent -``` - -## Core Concepts - -- **Feature lists are harness primitives**: Not "optional planning tools," but foundational data structures that all other harness components depend on. Like database table structures — you can't say "let's skip primary keys." -- **Triple structure**: Each feature item is a `(behavior description, verification command, current state)` triple. Missing any element makes the item incomplete. -- **State machine model**: Each feature item has four states — `not_started`, `active`, `blocked`, `passing`. State transitions are controlled by the harness, not freely changed by the agent. -- **Pass-state gating**: The only way a feature moves from `active` to `passing` is by verification command executing successfully. This is irreversible — once `passing`, it can't go back. Like passing an exam means you passed, you can't retroactively change the score. -- **Single source of truth**: All information about "what needs to be done" must derive from one feature list. No contradictions between the feature list and conversation history. -- **Back-pressure**: The number of features that haven't passed yet is the pressure the harness exerts on the agent. Zero pressure = project complete. - -## Why Feature Lists Must Be "Primitives" - -Documents are for humans to read; primitives are for systems to execute. Documents can be ignored; primitives can't be bypassed. - -Think of it like database trigger constraints vs. application-layer checks: the former is enforced by the database engine, no SQL can skip it; the latter depends on application code correctness and can be accidentally bypassed. Feature lists as harness primitives are Specifically, the feature list serves four harness components: - -1. **Scheduler**: Reads states, picks the next `not_started` feature. Like a factory production planning system. -2. **Verifier**: Executes verification commands, decides whether to allow state transitions. Like quality inspection. -3. **Handoff reporter**: Automatically generates session handoff summaries from the feature list. Like an automatic shift-change report. -4. **Progress tracker**: Tallies state distribution, provides project health metrics. Like a dashboard. - -## How to Do It Right - -### 1. Define a Minimal Feature List Format - -You don't need a complex system — a structured Markdown or JSON file works. The key is every entry must have the triple: - -```json -{ - "id": "F03", - "behavior": "POST /cart/items with {product_id, quantity} returns 201", - "verification": "curl -X POST http://localhost:3000/api/cart/items -H 'Content-Type: application/json' -d '{\"product_id\":1,\"quantity\":2}' | jq .status == 201", - "state": "passing", - "evidence": "commit abc123, test output log" -} -``` - -### 2. Let the Harness Control State Transitions - -The agent can't directly change a feature's state to `passing`. It can only submit a verification request; the harness executes the verification command and decides whether to allow the transition. This is "pass-state gating." - -### 3. Write the Rules in CLAUDE.md - -``` -## Feature List Rules -- Feature list file: /docs/features.md -- Only one feature active at a time -- Verification command must pass before marking as passing -- Don't modify feature list states yourself — the verification script updates them automatically -``` - -### 4. Calibrate Granularity - -Each feature item should be scoped to "completable in one session." Too broad and it won't finish; too narrow and the management overhead grows. "User can add items to cart" is good granularity. "Implement the shopping cart" is too broad. "Create the name field on the Cart model" is too narrow. Like cutting a steak — not the whole piece, and not ground meat. - -## Real-World Case - -An e-commerce platform with 10 features. Two tracking approaches compared: - -**Memo mode**: Agent uses unstructured notes. After 3 sessions, notes become "did user auth and product list, shopping cart mostly done but has bugs, payments not started." New session needs 20 minutes to infer state, ultimately re-implements completed features. Like your shopping list saying "milk, bread, and that thing" — at the store, you still don't know what to buy. - -**Backbone mode**: Every feature has a clear state and verification command. New session reads the feature list and in 3 minutes knows: F01-F05 are `passing`, F06 is `active`, F07-F10 are `not_started`. Picks up from F06 directly, zero rework. - -Quantified result: projects using structured feature lists show 45% higher feature completion rate than free-form tracking, with zero duplicate implementations. - -## Key Takeaways - -- **Feature lists are the harness's backbone**, not memos for humans. Scheduler, verifier, and handoff reporter all depend on them. -- **Every feature item must have the triple**: behavior description + verification command + current state. Missing one element makes it incomplete — like a three-legged stool missing a leg. -- **State transitions are controlled by the harness** — the agent can't change states on its own. Passing verification = the only upgrade path. -- **The feature list is the project's single source of truth** — all "what to do" information derives from one list. -- **Calibrate granularity to "completable in one session."** - -## Further Reading - -- [Building Effective Agents - Anthropic](https://www.anthropic.com/research/building-effective-agents) — Explicitly identifies feature list as the "core data structure" for controlling agent scope -- [Harness Engineering - OpenAI](https://openai.com/index/harness-engineering/) — Emphasizes the principle of "externalizing artifacts" -- [Design by Contract - Bertrand Meyer](https://www.goodreads.com/book/show/130439.Object_Oriented_Software_Construction) — Contract design principles, the theoretical foundation of feature lists -- [How Google Tests Software](https://www.goodreads.com/book/show/13563030-how-google-tests-software) — Test pyramid and behavioral specification engineering practices - -## Exercises - -1. **Feature List Design**: Define a minimal feature list JSON schema. Include: id, behavior description, verification command, current state, evidence reference. Use it to describe a real project with 5 features. - -2. **Verification Strictness Comparison**: Pick 3 features and design both a "loose" verification (e.g., "code has no syntax errors") and a "strict" verification (e.g., "end-to-end test passes"). Compare false positive rate under each approach. - -3. **Single Source Principle Audit**: Review an existing agent project and check for scope information that contradicts the feature list (implicit requirements in conversations, TODO comments in code, etc.). Design a plan to unify all information into the feature list. diff --git a/courses/learn-harness-engineering/docs/en/lectures/lecture-09-why-agents-declare-victory-too-early/code/clean-state-checklist.md b/courses/learn-harness-engineering/docs/en/lectures/lecture-09-why-agents-declare-victory-too-early/code/clean-state-checklist.md deleted file mode 100644 index 5a592e34..00000000 --- a/courses/learn-harness-engineering/docs/en/lectures/lecture-09-why-agents-declare-victory-too-early/code/clean-state-checklist.md +++ /dev/null @@ -1,6 +0,0 @@ -# Clean State Checklist - -- App starts without manual repair -- Current progress is recorded -- No half-finished import or indexing step remains undocumented -- The next agent can run the standard startup and verification path immediately diff --git a/courses/learn-harness-engineering/docs/en/lectures/lecture-09-why-agents-declare-victory-too-early/code/index.md b/courses/learn-harness-engineering/docs/en/lectures/lecture-09-why-agents-declare-victory-too-early/code/index.md deleted file mode 100644 index e6ea6fca..00000000 --- a/courses/learn-harness-engineering/docs/en/lectures/lecture-09-why-agents-declare-victory-too-early/code/index.md +++ /dev/null @@ -1,8 +0,0 @@ -# Code for Lecture 09 - -Use this folder for examples of: - -- logs as feedback -- runtime-state visibility -- clean-state checks -- recovery examples diff --git a/courses/learn-harness-engineering/docs/en/lectures/lecture-09-why-agents-declare-victory-too-early/code/victory-detector.ts b/courses/learn-harness-engineering/docs/en/lectures/lecture-09-why-agents-declare-victory-too-early/code/victory-detector.ts deleted file mode 100644 index fad1a3ba..00000000 --- a/courses/learn-harness-engineering/docs/en/lectures/lecture-09-why-agents-declare-victory-too-early/code/victory-detector.ts +++ /dev/null @@ -1,241 +0,0 @@ -/** - * victory-detector.ts - * - * Simulates an agent that claims task completion, then checks the claimed - * state against actual verification. Outputs: claimed vs actual, highlighting - * gaps between what the agent said and what's really true. - * - * Run: npx tsx docs/lectures/lecture-09-why-agents-declare-victory-too-early/code/victory-detector.ts - */ - -// --------------------------------------------------------------------------- -// Types -// --------------------------------------------------------------------------- - -interface Task { - name: string; - claimedComplete: boolean; - checks: VerificationCheck[]; -} - -interface VerificationCheck { - description: string; - claimed: boolean; // What the agent says - actual: boolean; // What is actually true - severity: "critical" | "warning"; -} - -// --------------------------------------------------------------------------- -// Simulated tasks with claimed vs actual states -// --------------------------------------------------------------------------- - -const tasks: Task[] = [ - { - name: "Add search endpoint", - claimedComplete: true, - checks: [ - { - description: "Route handler exists", - claimed: true, - actual: true, - severity: "critical", - }, - { - description: "Authentication middleware applied", - claimed: true, - actual: false, // Agent forgot auth - severity: "critical", - }, - { - description: "Input validation added", - claimed: true, - actual: true, - severity: "critical", - }, - { - description: "Unit tests written", - claimed: true, - actual: false, // Agent skipped tests - severity: "critical", - }, - { - description: "Integration tests pass", - claimed: true, - actual: false, // No tests to run - severity: "critical", - }, - { - description: "Documentation updated", - claimed: true, - actual: false, // Agent skipped docs - severity: "warning", - }, - ], - }, - { - name: "Fix pagination bug", - claimedComplete: true, - checks: [ - { - description: "Bug reproduction case confirmed", - claimed: true, - actual: true, - severity: "critical", - }, - { - description: "Root cause identified", - claimed: true, - actual: true, - severity: "critical", - }, - { - description: "Fix applied", - claimed: true, - actual: true, - severity: "critical", - }, - { - description: "Edge cases tested", - claimed: true, - actual: false, // Only happy path tested - severity: "warning", - }, - { - description: "Regression tests added", - claimed: true, - actual: false, - severity: "critical", - }, - ], - }, - { - name: "Refactor auth module", - claimedComplete: true, - checks: [ - { - description: "Old code removed", - claimed: true, - actual: false, // Old code still present - severity: "warning", - }, - { - description: "All existing tests still pass", - claimed: true, - actual: false, // Two tests broke - severity: "critical", - }, - { - description: "New auth flow works", - claimed: true, - actual: true, - severity: "critical", - }, - { - description: "Migration guide written", - claimed: true, - actual: false, - severity: "warning", - }, - ], - }, -]; - -// --------------------------------------------------------------------------- -// Verification -// --------------------------------------------------------------------------- - -interface TaskVerification { - taskName: string; - claimedComplete: boolean; - actuallyComplete: boolean; - totalChecks: number; - claimedPassing: number; - actualPassing: number; - gaps: { description: string; severity: string }[]; -} - -function verifyTask(task: Task): TaskVerification { - const claimedPassing = task.checks.filter((c) => c.claimed).length; - const actualPassing = task.checks.filter((c) => c.actual).length; - const gaps = task.checks.filter((c) => c.claimed && !c.actual); - - return { - taskName: task.name, - claimedComplete: task.claimedComplete, - actuallyComplete: gaps.length === 0, - totalChecks: task.checks.length, - claimedPassing, - actualPassing, - gaps: gaps.map((g) => ({ description: g.description, severity: g.severity })), - }; -} - -// --------------------------------------------------------------------------- -// Reporting -// --------------------------------------------------------------------------- - -function pad(s: string, len: number): string { - return s.length >= len ? s : s + " ".repeat(len - s.length); -} - -function run(): void { - console.log("\n" + "=".repeat(90)); - console.log(" VICTORY DETECTOR -- Claimed vs Actual Verification"); - console.log("=".repeat(90)); - - const verifications = tasks.map(verifyTask); - - for (const v of verifications) { - console.log("\n Task: " + v.taskName); - console.log(" " + "-".repeat(70)); - console.log(` Agent claimed: ${v.claimedComplete ? "COMPLETE" : "NOT COMPLETE"}`); - console.log(` Actually is: ${v.actuallyComplete ? "COMPLETE" : "INCOMPLETE"}`); - - if (!v.actuallyComplete) { - console.log(` MISMATCH: Agent declared victory but ${v.gaps.length} check(s) failed!`); - } - - // Detailed checks for this task - const task = tasks.find((t) => t.name === v.taskName)!; - console.log("\n Check detail:"); - const header = ` | ${pad("Check", 40)}| ${pad("Claimed", 9)}| ${pad("Actual", 9)}| ${pad("Gap?", 6)}|`; - const sep = ` |${"-".repeat(42)}|${"-".repeat(11)}|${"-".repeat(11)}|${"-".repeat(8)}|`; - console.log(header); - console.log(sep); - - for (const check of task.checks) { - const claimedLabel = check.claimed ? "PASS" : "FAIL"; - const actualLabel = check.actual ? "PASS" : "FAIL"; - const gapLabel = check.claimed && !check.actual ? "GAP" : ""; - const marker = gapLabel ? ">>" : " "; - console.log(`${marker}| ${pad(check.description, 40)}| ${pad(claimedLabel, 9)}| ${pad(actualLabel, 9)}| ${pad(gapLabel, 6)}|`); - } - } - - // Overall summary - console.log("\n" + "=".repeat(90)); - console.log(" OVERALL SUMMARY"); - console.log("=".repeat(90) + "\n"); - - const sHeader = `| ${pad("Task", 28)}| ${pad("Claimed", 10)}| ${pad("Actual", 10)}| ${pad("Gaps", 6)}| ${pad("Accuracy", 10)}|`; - const sSep = `|${"-".repeat(30)}|${"-".repeat(12)}|${"-".repeat(12)}|${"-".repeat(8)}|${"-".repeat(12)}|`; - console.log(sHeader); - console.log(sSep); - - for (const v of verifications) { - const accuracy = Math.round((v.actualPassing / v.totalChecks) * 100); - const cLabel = v.claimedComplete ? "Done" : "Pending"; - const aLabel = v.actuallyComplete ? "Done" : "Incomplete"; - console.log(`| ${pad(v.taskName, 28)}| ${pad(cLabel, 10)}| ${pad(aLabel, 10)}| ${pad(String(v.gaps.length), 6)}| ${pad(accuracy + "%", 10)}|`); - } - - const totalGaps = verifications.reduce((s, v) => s + v.gaps.length, 0); - const falseVictories = verifications.filter((v) => v.claimedComplete && !v.actuallyComplete).length; - - console.log("\n False victories: " + falseVictories + " of " + verifications.length + " tasks"); - console.log(" Total undetected gaps: " + totalGaps); - console.log("\n The agent declared 'done' on every task, but verification reveals " + totalGaps + " unmet criteria."); - console.log(" Without explicit verification, premature victory declarations go unchecked.\n"); -} - -run(); diff --git a/courses/learn-harness-engineering/docs/en/lectures/lecture-09-why-agents-declare-victory-too-early/index.md b/courses/learn-harness-engineering/docs/en/lectures/lecture-09-why-agents-declare-victory-too-early/index.md deleted file mode 100644 index 8cbb6672..00000000 --- a/courses/learn-harness-engineering/docs/en/lectures/lecture-09-why-agents-declare-victory-too-early/index.md +++ /dev/null @@ -1,145 +0,0 @@ -[中文版本 →](../../../zh/lectures/lecture-09-why-agents-declare-victory-too-early/) - -> Code examples for this lecture: [code/](https://github.com/walkinglabs/learn-harness-engineering/blob/main/docs/en/lectures/lecture-09-why-agents-declare-victory-too-early/code/) -> Hands-on practice: [Project 05. Let the agent verify its own work](./../../projects/project-05-grounded-qa-verification/index.md) - -# Lecture 9. Preventing Agents from Declaring Victory Too Early - -You ask an agent to implement a "password reset" feature. It modifies the database schema, writes the API endpoint, adds the email template, runs unit tests (all pass), and then confidently tells you "it's done." When you actually try to run it—the password reset link can't be sent (missing email service config), the database migration fails halfway through (schema inconsistency), and the end-to-end flow hasn't been executed even once. - -This feeling shouldn't be unfamiliar—it's like filling up the entire exam paper, confidently being the first to hand it in, only to fail when the grades come out. Just because the paper is full doesn't mean the answers are right. - -This isn't an isolated incident. The classic 2017 ICML paper by Guo et al. proved: **modern neural networks are systematically overconfident**—the confidence reported by models is significantly higher than their actual accuracy. The same applies to AI coding agents: they "feel" they're done, but in reality, they're far from it. Your harness must replace the agent's "feelings" with externalized, execution-based verification. - -## The Slippery Slope - -Premature completion declarations almost always follow the same pattern: the code looks okay—syntax is correct, logic seems reasonable, and static analysis shows no obvious errors. But the harness doesn't enforce comprehensive execution verification, so the agent skips actually running it or only runs partial tests. It runs unit tests but skips integration tests; it runs tests but doesn't check coverage. Ultimately, "the code looks fine" is taken as evidence that "the feature is complete." And the exam paper is handed in. - -Information is lost at every step. From task specifications to code implementation to runtime behavior, every transformation can introduce bias, and every skipped verification exacerbates the information asymmetry. - -## Three-Layer Termination Check - -```mermaid -flowchart LR - Claim["Agent says: done"] --> L1["First run
lint / typecheck"] - L1 --> L2["Then run
tests and startup checks"] - L2 --> L3["Finally run
complete user flow"] - L3 --> Done["Pass all three to be done"] -``` - -```mermaid -flowchart LR - A["Code is written
unit tests are green"] --> B["But app didn't really start
full flow never ran"] - B --> C["Config, DB, external service issues
all remain hidden"] - C --> D["So agent declares victory too early"] -``` - -## Core Concepts - -- **Premature Completion Declaration**: The agent asserts the task is complete, but unmet correctness specifications still exist. The core issue: the agent judges based on local confidence at the code level, while system-level correctness requires global verification. -- **Confidence Calibration Bias**: The systematic gap between the agent's self-reported confidence in completion and the actual completion quality. For complex multi-file tasks, this bias is significantly positive—the agent is always more confident than it actually performs. Just like a student who always overestimates their score after an exam. -- **Termination Criteria**: A clear, executable set of judgment conditions defined in the harness. The agent must satisfy all conditions before declaring completion. "Done" shifts from a subjective judgment to an objective determination. -- **Verification-Validation Dual Gate**: The first verification layer checks "did the code correctly implement the specified behavior"; the second validation layer checks "does the system-level behavior meet the end-to-end requirements". Both must pass to be considered complete. -- **Runtime Feedback Signals**: Logs, process states, and health checks from program execution. This is the objective basis for the harness to judge completion quality. -- **Completion Priority Constraint**: First verify functional correctness, then handle performance, and finally address style. Refactoring is forbidden until core functionality is verified. - -## Passing Unit Tests ≠ Task Complete - -This is the most common trap, and the most dangerous one. The agent wrote the code, ran the unit tests, got all greens, and said "done." But the design philosophy of unit tests—isolating the tested unit and mocking dependencies—is exactly what makes them incapable of detecting cross-component issues: - -**Interface Mismatch**: The file path passed by the render process to the preload script is a relative path, but the preload script expects an absolute path. Their respective unit tests both used mocks and passed. The issue is only discovered during end-to-end testing. Just like every musician in a band practicing perfectly on their own, only to realize they are in different keys when playing together. - -**State Propagation Errors**: A database migration changes the table schema, but the ORM caching layer still holds cache entries for the old schema. Unit tests provide a fresh mock environment every time, which won't expose this cross-layer state inconsistency. - -**Environment Dependency**: The code behaves correctly in the test environment (where everything is mocked) but fails in the real environment due to configuration differences, network latency, or service unavailability. Like singing perfectly in the rehearsal room, but encountering audio equipment issues on stage. - -### "Refactoring While We're at It" is Poison to Completion Judgment - -Claude Code has a common behavioral pattern: it starts refactoring code, optimizing performance, and improving style before the core functionality has passed verification. Knuth's quote, "Premature optimization is the root of all evil," takes on new meaning in the agent scenario—refactoring alters the boundary between verified and unverified code, potentially breaking previously implicitly correct code paths. It's like re-copying your multiple-choice answers for better formatting before you've finished the math essay questions—not only does it waste time, but you might copy them wrong. - -### Systematic Bias in Self-Evaluation - -Anthropic discovered a deeper failure pattern in their 2026 research: **when an agent is asked to evaluate its own work, it systematically provides overly positive evaluations—even when a human observer would consider the quality clearly substandard.** This is like asking a student to grade their own exam—they will always be particularly lenient with their own answers. - -This issue is especially severe in subjective tasks (such as design aesthetics)—whether a "layout is exquisite" is a judgment call, and the agent reliably skews positive. Even on tasks with verifiable results, the agent's performance can be hindered by poor judgment. - -The solution isn't to make the agent "more objective"—the same model generating and evaluating inherently favors being generous to itself. **The solution is to separate the "worker" from the "checker".** Just like a student shouldn't grade their own exam—you need an independent grader. - -An independent evaluating agent, specifically tuned to be "picky", is far more effective than having the generating agent evaluate itself. Experimental data from Anthropic: - -| Architecture | Runtime | Cost | Core Features Working? | -|--------------|---------|------|------------------------| -| Single Agent (bare run) | 20 mins | $9 | No (game entities unresponsive to input) | -| Three Agents (planner + generator + evaluator) | 6 hours | $200 | Yes (game is fully playable) | - -This is the exact same model (Opus 4.5) with the exact same prompt ("build a 2D retro game editor"). The only difference is the harness—from "running bare" to "planner expands requirements → generator implements feature by feature → evaluator performs actual click testing using Playwright". - -> Source: [Anthropic: Harness design for long-running application development](https://www.anthropic.com/engineering/harness-design-long-running-apps) - -## How to Prevent Premature Hand-ins - -### 1. Externalize Termination Judgment - -The completion judgment shouldn't be made by the agent itself. The harness must independently execute termination validation, using runtime signals as input, not the agent's confidence. Write this clearly in `CLAUDE.md`: - -``` -## Definition of Done -- Feature complete = end-to-end verification passed, not "code is written" -- Required verification levels: - 1. Unit tests pass - 2. Integration tests pass - 3. End-to-end flow verification passes -- Do not proceed to level 2 if level 1 fails -- Do not proceed to level 3 if level 2 fails -``` - -### 2. Build a Three-Layer Termination Validation - -- **Layer 1: Syntax and Static Analysis**. Lowest cost, least information, but must pass. This is the bare minimum check—you must spell the words right before we look at anything else. -- **Layer 2: Runtime Behavior Verification**. Test execution, app startup checks, critical path validation. This is the core evidence of completion. It's not enough to just write it; it must run. -- **Layer 3: System-Level Confirmation**. End-to-end testing, integration validation, user scenario simulation. The final line of defense against premature declarations. It's not enough to run; it must run correctly. - -### 3. Design Good "Red Pen Markups" for Agents - -OpenAI introduced a particularly effective pattern during their Codex practice: **error messages for agents should include fix instructions**. Don't just draw a big red cross like a lazy grader; be like a good teacher and write "here's how you should change this" in the margins. Don't use `"Test failed"`, but use `"Test failed: POST /api/reset-password returned 500. Check that the email service config exists in environment variables. The template file should be at templates/reset-email.html."` This specific, actionable feedback allows the agent to self-correct without human intervention. - -### 4. Capture Runtime Signals - -Effective runtime signals include: -- Did the application successfully start and reach a ready state? -- Did the critical feature paths execute successfully at runtime? -- Were database writes, file operations, and other side effects correct? -- Were temporary resources cleaned up? - -## Real-World Case - -**Task**: Implement user password reset functionality. Involves database operations, email sending, and API endpoint modifications. - -**Premature hand-in path**: Agent modifies database schema, writes API endpoint, adds email template, runs unit tests (passes), and declares completion. The exam paper is completely filled out. - -**Actual point deductions**: (1) End-to-end flow untested—the actual sending and verification of the reset link was never confirmed. (2) Database migration failed after partial execution, causing schema inconsistency. (3) Email service config was missing in the target environment. - -**Harness intervention**: Termination validation enforced—(1) Start the full app to verify reset endpoint accessibility; (2) Execute the full reset flow; (3) Verify database state consistency. All defects were found within the session, saving 5-10x the cost of subsequent fixes. The independent grader found the real issues. - -## Key Takeaways - -- **Agents are systematically overconfident**—confidence calibration bias is an objective reality. Filling out the exam paper doesn't mean you got it right. -- **Completion judgment must be externalized**—the harness verifies independently; don't trust the agent's "feelings". Students cannot grade their own exams. -- **All three layers of validation are essential**—syntax passing, behavior passing, system passing, progressing layer by layer. -- **Error messages should be like a good teacher's red pen markup**—include specific fix steps so the agent can self-correct. -- **No refactoring until core functionality is verified**—the completion priority constraint is the key to preventing premature optimization. - -## Further Reading - -- [On Calibration of Modern Neural Networks - Guo et al.](https://arxiv.org/abs/1706.04599) — Proves modern deep networks are systematically overconfident -- [Building Effective Agents - Anthropic](https://www.anthropic.com/research/building-effective-agents) — The critical role of runtime evidence in completion judgment -- [Harness Engineering - OpenAI](https://openai.com/index/harness-engineering/) — Premature completion declaration is one of the main failure modes of agents -- [The Art of Software Testing - Myers](https://www.goodreads.com/book/show/137543.The_Art_of_Software_Testing) — Classic reference on testing method hierarchies and effectiveness - -## Exercises - -1. **Termination Validation Function Design**: Design a complete termination validation for a task involving a database migration and API modification. List the required runtime signals and the pass/fail criteria for each signal. Run it on a real task and record what hidden issues it finds. - -2. **Calibration Bias Measurement**: Choose 10 different types of coding tasks, and record the agent's self-reported completion confidence vs. the actual completion quality. Calculate the bias value and analyze its relationship with task complexity. - -3. **Multi-Layer Defense Experiment**: Run three configurations on the same set of tasks—(a) static analysis only, (b) add unit testing, (c) full three-layer validation. Compare the proportion of premature completion declarations and the number of uncaught defects. \ No newline at end of file diff --git a/courses/learn-harness-engineering/docs/en/lectures/lecture-10-why-end-to-end-testing-changes-results/code/architecture-rules.md b/courses/learn-harness-engineering/docs/en/lectures/lecture-10-why-end-to-end-testing-changes-results/code/architecture-rules.md deleted file mode 100644 index 6a6179bd..00000000 --- a/courses/learn-harness-engineering/docs/en/lectures/lecture-10-why-end-to-end-testing-changes-results/code/architecture-rules.md +++ /dev/null @@ -1,6 +0,0 @@ -# Electron Architecture Rules - -- Renderer code may not directly access the filesystem. -- Preload is the only bridge between renderer and Electron main. -- Retrieval and indexing logic live in service modules, not UI components. -- Logging should be structured and emitted from service boundaries. diff --git a/courses/learn-harness-engineering/docs/en/lectures/lecture-10-why-end-to-end-testing-changes-results/code/e2e-runner.ts b/courses/learn-harness-engineering/docs/en/lectures/lecture-10-why-end-to-end-testing-changes-results/code/e2e-runner.ts deleted file mode 100644 index 24284165..00000000 --- a/courses/learn-harness-engineering/docs/en/lectures/lecture-10-why-end-to-end-testing-changes-results/code/e2e-runner.ts +++ /dev/null @@ -1,253 +0,0 @@ -/** - * e2e-runner.ts - * - * A minimal E2E test harness. Defines test cases as user action sequences - * (import doc -> index -> ask question -> verify citation). Simulates - * running them and shows the difference between "unit tests pass" and - * "full pipeline works". - * - * Run: npx tsx docs/lectures/lecture-10-why-end-to-end-testing-changes-results/code/e2e-runner.ts - */ - -// --------------------------------------------------------------------------- -// Types -// --------------------------------------------------------------------------- - -interface PipelineStep { - name: string; - unitTestPasses: boolean; - // Simulated actual behavior in the pipeline - actualBehavior: "works" | "fails" | "partial"; - failureReason?: string; -} - -interface TestCase { - name: string; - steps: PipelineStep[]; -} - -interface TestResult { - testCase: string; - unitTestsPassed: number; - unitTestsTotal: number; - unitTestResult: "PASS" | "FAIL"; - e2eResult: "PASS" | "FAIL"; - e2eFailureStep?: string; - e2eFailureReason?: string; -} - -// --------------------------------------------------------------------------- -// Test cases -- realistic scenarios where unit tests pass but E2E fails -// --------------------------------------------------------------------------- - -const testCases: TestCase[] = [ - { - name: "Import document and ask question", - steps: [ - { - name: "Parse uploaded document", - unitTestPasses: true, - actualBehavior: "works", - }, - { - name: "Store document chunks", - unitTestPasses: true, - actualBehavior: "works", - }, - { - name: "Index chunks for retrieval", - unitTestPasses: true, - actualBehavior: "partial", // Indexes but with wrong embedding dimensions - failureReason: "Embedding dimension mismatch between indexer and retriever", - }, - { - name: "Retrieve relevant chunks", - unitTestPasses: true, // Unit test uses mock data with correct dimensions - actualBehavior: "fails", - failureReason: "Empty results due to dimension mismatch from previous step", - }, - { - name: "Generate answer with citations", - unitTestPasses: true, // Unit test provides pre-retrieved chunks - actualBehavior: "fails", - failureReason: "No chunks retrieved, so answer has no citations", - }, - ], - }, - { - name: "Delete document and verify removal", - steps: [ - { - name: "Find document by ID", - unitTestPasses: true, - actualBehavior: "works", - }, - { - name: "Delete document record", - unitTestPasses: true, - actualBehavior: "works", - }, - { - name: "Remove indexed chunks", - unitTestPasses: true, - actualBehavior: "fails", // Orphaned chunks remain in the index - failureReason: "Index cleanup query timed out, chunks remain orphaned", - }, - { - name: "Verify document not in search results", - unitTestPasses: true, // Unit test mocks the search - actualBehavior: "fails", - failureReason: "Orphaned chunks from previous step still appear in results", - }, - ], - }, - { - name: "Multi-user concurrent access", - steps: [ - { - name: "User A imports document", - unitTestPasses: true, - actualBehavior: "works", - }, - { - name: "User B imports document", - unitTestPasses: true, - actualBehavior: "works", - }, - { - name: "User A queries their document", - unitTestPasses: true, - actualBehavior: "partial", // Cross-contamination of results - failureReason: "No user-scoping on retrieval, returns chunks from User B's doc", - }, - { - name: "Verify only User A's results returned", - unitTestPasses: true, - actualBehavior: "fails", - failureReason: "Results include documents from other users", - }, - ], - }, -]; - -// --------------------------------------------------------------------------- -// Run tests -// --------------------------------------------------------------------------- - -function runUnitTests(tc: TestCase): { passed: number; total: number } { - const passed = tc.steps.filter((s) => s.unitTestPasses).length; - return { passed, total: tc.steps.length }; -} - -function runE2ETest(tc: TestCase): { pass: boolean; failureStep?: string; failureReason?: string } { - // Pipeline: if any step actually fails, the whole E2E fails - for (const step of tc.steps) { - if (step.actualBehavior === "fails") { - return { - pass: false, - failureStep: step.name, - failureReason: step.failureReason ?? "Unknown failure", - }; - } - // "partial" means the step technically completes but creates problems downstream - // We let it continue but track it - } - // Check if any step was "partial" (which may cause downstream issues) - const partialSteps = tc.steps.filter((s) => s.actualBehavior === "partial"); - if (partialSteps.length > 0) { - // The partial steps may or may not cause overall failure - // In our simulation, partial steps always lead to failure downstream - // unless there's an explicit "fails" step that already caught it - // This case means all steps were either "works" or "partial" - return { - pass: false, - failureStep: partialSteps[partialSteps.length - 1].name, - failureReason: partialSteps[partialSteps.length - 1].failureReason ?? "Partial completion", - }; - } - - return { pass: true }; -} - -// --------------------------------------------------------------------------- -// Reporting -// --------------------------------------------------------------------------- - -function pad(s: string, len: number): string { - return s.length >= len ? s : s + " ".repeat(len - s.length); -} - -function run(): void { - console.log("\n" + "=".repeat(95)); - console.log(" E2E TEST RUNNER -- Unit Tests vs Full Pipeline"); - console.log("=".repeat(95)); - - const results: TestResult[] = testCases.map((tc) => { - const unit = runUnitTests(tc); - const e2e = runE2ETest(tc); - - return { - testCase: tc.name, - unitTestsPassed: unit.passed, - unitTestsTotal: unit.total, - unitTestResult: unit.passed === unit.total ? "PASS" : "FAIL", - e2eResult: e2e.pass ? "PASS" : "FAIL", - e2eFailureStep: e2e.failureStep, - e2eFailureReason: e2e.failureReason, - }; - }); - - // Per-test-case detail - for (let i = 0; i < testCases.length; i++) { - const tc = testCases[i]; - const r = results[i]; - - console.log("\n Test Case: " + tc.name); - console.log(" " + "-".repeat(70)); - - const stepHeader = ` | ${pad("Step", 40)}| ${pad("Unit Test", 11)}| ${pad("E2E Actual", 12)}|`; - const stepSep = ` |${"-".repeat(42)}|${"-".repeat(13)}|${"-".repeat(14)}|`; - console.log(stepHeader); - console.log(stepSep); - - for (const step of tc.steps) { - const utLabel = step.unitTestPasses ? "PASS" : "FAIL"; - let e2eLabel: string; - if (step.actualBehavior === "works") e2eLabel = "PASS"; - else if (step.actualBehavior === "partial") e2eLabel = "PARTIAL*"; - else e2eLabel = "FAIL"; - - const marker = step.actualBehavior !== "works" ? ">>" : " "; - console.log(`${marker}| ${pad(step.name, 40)}| ${pad(utLabel, 11)}| ${pad(e2eLabel, 12)}|`); - } - - if (r.e2eFailureStep) { - console.log(`\n E2E Failure at: ${r.e2eFailureStep}`); - console.log(` Reason: ${r.e2eFailureReason}`); - } - } - - // Summary comparison - console.log("\n" + "=".repeat(95)); - console.log(" COMPARISON: Unit Tests vs E2E Tests"); - console.log("=".repeat(95) + "\n"); - - const header = `| ${pad("Test Case", 35)}| ${pad("Unit Tests", 15)}| ${pad("E2E Result", 12)}| Discrepancy`; - const sep = `|${"-".repeat(37)}|${"-".repeat(17)}|${"-".repeat(14)}|${"-".repeat(30)}`; - console.log(header); - console.log(sep); - - for (const r of results) { - const utLabel = `${r.unitTestsPassed}/${r.unitTestsTotal} ${r.unitTestResult}`; - const discrepancy = r.unitTestResult === "PASS" && r.e2eResult === "FAIL"; - const discLabel = discrepancy ? "UNIT PASS BUT E2E FAIL" : "Consistent"; - - console.log(`| ${pad(r.testCase, 35)}| ${pad(utLabel, 15)}| ${pad(r.e2eResult, 12)}| ${discLabel}`); - } - - const falseConfidence = results.filter((r) => r.unitTestResult === "PASS" && r.e2eResult === "FAIL").length; - console.log("\n False confidence count: " + falseConfidence + " of " + results.length); - console.log(" Unit tests pass but E2E reveals integration failures that units cannot catch.\n"); -} - -run(); diff --git a/courses/learn-harness-engineering/docs/en/lectures/lecture-10-why-end-to-end-testing-changes-results/code/index.md b/courses/learn-harness-engineering/docs/en/lectures/lecture-10-why-end-to-end-testing-changes-results/code/index.md deleted file mode 100644 index 8825e4e6..00000000 --- a/courses/learn-harness-engineering/docs/en/lectures/lecture-10-why-end-to-end-testing-changes-results/code/index.md +++ /dev/null @@ -1,8 +0,0 @@ -# Code for Lecture 10 - -Use this folder for examples of: - -- architecture constraints -- structural tests -- taste invariants -- remediation-oriented lint messages diff --git a/courses/learn-harness-engineering/docs/en/lectures/lecture-10-why-end-to-end-testing-changes-results/code/review-feedback-to-rule.md b/courses/learn-harness-engineering/docs/en/lectures/lecture-10-why-end-to-end-testing-changes-results/code/review-feedback-to-rule.md deleted file mode 100644 index f3cfd404..00000000 --- a/courses/learn-harness-engineering/docs/en/lectures/lecture-10-why-end-to-end-testing-changes-results/code/review-feedback-to-rule.md +++ /dev/null @@ -1,10 +0,0 @@ -# Example: Turning Review Feedback into a Rule - -Repeated review comment: - -> Do not call filesystem utilities from the renderer. Use the preload bridge. - -Promoted harness rule: - -- add a lint or import rule preventing `fs` usage in renderer code -- add remediation text explaining the preload boundary diff --git a/courses/learn-harness-engineering/docs/en/lectures/lecture-10-why-end-to-end-testing-changes-results/index.md b/courses/learn-harness-engineering/docs/en/lectures/lecture-10-why-end-to-end-testing-changes-results/index.md deleted file mode 100644 index c7c75ac2..00000000 --- a/courses/learn-harness-engineering/docs/en/lectures/lecture-10-why-end-to-end-testing-changes-results/index.md +++ /dev/null @@ -1,158 +0,0 @@ -[中文版本 →](../../../zh/lectures/lecture-10-why-end-to-end-testing-changes-results/) - -> Code examples for this lecture: [code/](https://github.com/walkinglabs/learn-harness-engineering/blob/main/docs/en/lectures/lecture-10-why-end-to-end-testing-changes-results/code/) -> Hands-on practice: [Project 05. Let the agent verify its own work](./../../projects/project-05-grounded-qa-verification/index.md) - -# Lecture 10. Only End-to-End Testing is True Verification - -You ask the agent to add a file export feature to an Electron app. It writes the render process component, the preload script, and the service layer logic. The unit tests for each component pass perfectly. The agent says, "It's done." When you actually click the export button—the file path format is wrong, the progress bar doesn't update, and exporting large files causes a memory leak. Five component boundary defects, and the unit tests didn't catch a single one. - -It's like a choir rehearsal—each voice part sounds perfect when sung individually, but when they sing together, the sopranos are half a beat faster than the basses, and the accompaniment is a semitone off from the main melody. Each part is "correct" on its own, but the whole thing is out of tune. - -Google's Testing Pyramid tells us: a large number of unit tests are the foundation, but if you stop there, you will systematically miss component interaction issues. For AI coding agents, this problem is even more severe—agents tend to run only the fastest tests and then declare completion. **Only end-to-end testing can prove that system-level defects don't exist.** - -## The Blind Spots of Unit Testing - -The design philosophy of unit testing is isolation—mocking dependencies and focusing solely on the unit under test. This philosophy makes unit testing fast and precise, but it also creates systematic blind spots. It's like having each voice part practice with headphones on during a choir rehearsal—it sounds fine to them, but the issues only emerge when they come together: - -**Interface Mismatch**: The file path passed by the render process to the preload script is a relative path, but the preload script expects an absolute path. Their respective unit tests both used mocks and passed. The issue is only discovered when the end-to-end flow is executed—just like two voice parts practicing independently and feeling fine, only to realize during the ensemble that one is singing in 4/4 time and the other in 3/4 time. - -**State Propagation Errors**: A database migration changes the table schema, but the ORM caching layer still holds cache entries for the old schema. Unit tests provide a completely new mock environment every time, which won't expose this cross-layer state inconsistency. It's like changing the lyrics of a song, but someone is still singing the old version. - -**Resource Lifecycle Issues**: The acquisition and release of file handles, database connections, and network sockets span multiple components. Unit tests create and destroy independent resources for each test, failing to expose resource contention or leaks. It's like each voice part taking turns using the microphones during rehearsal, but when everyone goes on stage together, there aren't enough mics. - -**Environment Dependency**: The code behaves correctly in the test environment (where everything is mocked) but fails in the real environment due to configuration differences, network latency, or service unavailability. Like singing perfectly in the rehearsal room, but encountering audio feedback and wind interference at an outdoor festival. - -## End-to-End Testing Not Only Changes Results, It Changes Behavior - -This is something many people fail to realize: when an agent knows its work will be subjected to end-to-end testing, its coding behavior changes. - -1. **Considering Component Interactions**: While writing code, it will think about "how this interface connects with upstream," rather than just focusing on a single function. Just like knowing you'll eventually sing together, you'll pay attention to other voice parts during practice. -2. **Respecting Architectural Boundaries**: In systems with architectural constraints, end-to-end testing forces the agent to adhere to boundary rules. Like sheet music marked with "crescendo here," you have to follow it. -3. **Handling Error Paths**: End-to-end tests usually include failure scenarios, forcing the agent to consider exception handling. It's like simulating "what if the mic suddenly dies" during rehearsal, so you know what to do. - -## Testing Pyramid and Review Feedback Promotion - -```mermaid -flowchart TB - subgraph Unit["Unit tests only check isolated parts"] - U1["Renderer tests"] - U2["Preload tests"] - U3["Service tests"] - end - - subgraph E2E["E2E runs through the real system"] - R["Click renderer button"] --> P["Preload bridge"] - P --> S["Service layer"] - S --> F["File System / OS"] - F --> Result["Actual exported file"] - end -``` - -```mermaid -flowchart LR - Review["Review feedback:
renderer cannot import fs directly"] --> Rule["Add a direct fs import check"] - Rule --> Message["Tell agent in error message
to move file access to preload"] - Message --> Harness["Add this check to harness"] - Harness --> Stronger["Will fail immediately next time"] -``` - -In Codex engineering practices, OpenAI emphasizes: **error messages written for agents must include fix instructions.** Don't just write `"Direct filesystem access in renderer"`; write `"Direct filesystem access in renderer. All file operations must go through the preload bridge. Move this call to preload/file-ops.ts and invoke it via window.api."` This turns architectural rules into an auto-correction loop. Like a choir conductor who doesn't just say "you sang that wrong," but instead says "you were half a beat fast here, listen to the altos' rhythm, and come in at measure 32." - -## Core Concepts - -- **Component Boundary Defects**: Component A and B both pass their unit tests, but their interaction produces incorrect behavior. This is the type of issue end-to-end testing is best at catching—like choir parts that are individually correct but out of tune together. -- **Testing Adequacy Gradient**: Defects caught by unit tests <= defects caught by integration tests <= defects caught by end-to-end tests. Each layer up increases detection capability. -- **Architectural Boundary Enforcement Rules**: Turning rules from architecture documents (like "render process cannot access the file system directly") into executable, automated checks. From "written on paper" to "running in CI." -- **Review Feedback Promotion**: Converting repeated code review comments into automated tests. Every time a recurring issue is found, add a rule, and the harness automatically grows stronger. Like a conductor turning common rehearsal mistakes into warm-up exercises—the next time the same mistake is made, the exercise itself exposes it without the conductor needing to say a word. -- **Agent-Oriented Error Messages**: Failure messages shouldn't just state "what went wrong," but also tell the agent exactly how to fix it. This turns test failures into self-correcting feedback loops. - -## How to Do It - -### 0. Define Architectural Boundaries First, Then Write E2E Tests - -The prerequisite for end-to-end testing is clear system boundaries. If the architecture is a plate of spaghetti, end-to-end testing will only prove "this plate of spaghetti runs," it won't tell you where design intents were violated. It's like a choir that hasn't even divided into voice parts—no amount of rehearsal will make it sound good. - -OpenAI's experience: **for codebases generated by agents, architectural constraints must be early prerequisites established on day one, not something to consider when the team grows larger.** The reason is simple—agents will copy existing patterns in the repository, even if those patterns are uneven or suboptimal. Without architectural constraints, the agent will introduce more deviations in every session. - -OpenAI adopted a "Layered Domain Architecture"—each business domain is divided into fixed layers: Types → Config → Repo → Service → Runtime → UI. Dependencies flow strictly forward, and cross-domain concerns enter through explicit Providers interfaces. Any other dependencies are forbidden and mechanically enforced via custom linting. - -Key principle: **Enforce invariants, don't micromanage implementation.** For example, require "data is parsed at the boundary," but don't dictate which library to use. Error messages must include fix instructions—not just saying "violation," but telling the agent exactly how to change it. - -> Source: [OpenAI: Harness engineering: leveraging Codex in an agent-first world](https://openai.com/index/harness-engineering/) - -### 1. Harness Must Include an End-to-End Layer - -Make it explicit in your validation flow: for tasks involving cross-component changes, passing end-to-end tests is a prerequisite for completion: - -``` -## Validation Hierarchy -- Level 1: Unit tests (Must pass) -- Level 2: Integration tests (Must pass) -- Level 3: End-to-end tests (Must pass when cross-component changes are involved) -- Skipping any required level = Not Complete -``` - -### 2. Turn Architectural Rules into Executable Checks - -Every architectural constraint should have a corresponding test or lint rule: - -```bash -# Check if the render process directly calls Node.js APIs -grep -r "require('fs')" src/renderer/ && exit 1 || echo "OK: no direct fs access in renderer" -``` - -### 3. Design Agent-Oriented Error Messages - -Failure messages should contain three elements: what went wrong, why, and how to fix it: - -``` -ERROR: Found direct import of 'fs' in src/renderer/App.tsx:12 -WHY: Renderer process has no access to Node.js APIs for security -FIX: Move file operations to src/preload/file-ops.ts and call via window.api.readFile() -``` - -### 4. Establish a Review Feedback Promotion Process - -Every time a new type of agent error is found during code review, turn it into an automated check. A month later, your harness will be significantly stronger than at the start of the month. It's like rehearsal notes for a choir—recording issues found in every rehearsal so they can be checked before the next one. Over time, common errors decrease, and the music becomes more harmonious. - -## Real-World Case - -**Task**: Implement a file export feature in an Electron app. Involves render process UI, preload script filesystem proxy, and service layer data transformation. - -**Singing parts individually (Unit tests passed)**: Render component tests (passed, file operations mocked), preload script tests (passed, filesystem mocked), service layer tests (passed, data source mocked). Agent declares completion. - -**Singing together (Defects revealed by End-to-End tests)**: - -| Defect | Description | Unit Test | E2E | -|--------|-------------|-----------|-----| -| Interface Mismatch | Inconsistent file path format | Missed | Caught | -| State Propagation | Export progress not sent back to UI via IPC | Missed | Caught | -| Resource Leak | Large file export handles not released | Missed | Caught | -| Permission Issue | Different permissions in packaged environment | Missed | Caught | -| Error Propagation | Service layer exceptions didn't reach UI layer | Missed | Caught | - -All 5 defects were caught by end-to-end tests, while unit tests caught none. The cost was an increase in test time from 2 seconds to 15 seconds—completely acceptable in an agent workflow. No matter how well each part sings individually, it can't beat a full ensemble rehearsal. - -## Key Takeaways - -- **Unit tests are systematically blind to component boundary defects**—their isolation design is exactly what prevents them from detecting interaction issues. Everyone singing correctly doesn't mean the choir isn't out of tune. -- **End-to-end testing not only detects defects, it changes agent coding behavior**—making it focus more on integration and boundaries. -- **Architectural rules must be executable**—not written in a document waiting to be read, but automatically checked on every commit. -- **Error messages must be designed for agents**—including specific steps on "how to fix it" to form a self-correcting loop. -- **Review feedback promotion makes the harness automatically stronger**—every category of captured defect becomes a permanent line of defense. - -## Further Reading - -- [How Google Tests Software - Whittaker et al.](https://www.goodreads.com/book/show/13563030-how-google-tests-software) — The classic source of the Testing Pyramid model -- [Harness Engineering - OpenAI](https://openai.com/index/harness-engineering/) — Engineering practices for automated execution of architectural constraints -- [Chaos Engineering - Netflix (Basiri et al.)](https://ieeexplore.ieee.org/document/7466237) — Proactively injecting failures to verify system resilience -- [QuickCheck - Claessen & Hughes](https://www.cs.tufts.edu/~nr/cs257/archive/john-hughes/quick.pdf) — Property testing methodology, sitting between example testing and formal verification - -## Exercises - -1. **Cross-Component Defect Detection**: Pick a modification task involving at least three components. First, run only unit tests and record the results, then run end-to-end tests. Analyze which type of cross-layer interaction issue each additionally discovered defect belongs to. - -2. **Architectural Rule Automation**: Pick an architectural constraint from your project and turn it into an executable check (with an agent-oriented error message). Integrate it into the harness and verify its effectiveness with a baseline task. - -3. **Review Feedback Promotion**: Find a recurring comment type from your code review history and convert it into an automated check using the five-step process. Compare the frequency of the issue before and after the promotion. \ No newline at end of file diff --git a/courses/learn-harness-engineering/docs/en/lectures/lecture-11-why-observability-belongs-inside-the-harness/code/evaluator-rubric.md b/courses/learn-harness-engineering/docs/en/lectures/lecture-11-why-observability-belongs-inside-the-harness/code/evaluator-rubric.md deleted file mode 100644 index 2c932d8e..00000000 --- a/courses/learn-harness-engineering/docs/en/lectures/lecture-11-why-observability-belongs-inside-the-harness/code/evaluator-rubric.md +++ /dev/null @@ -1,8 +0,0 @@ -# Evaluator Rubric Example - -Use 1-5 scoring for each dimension: - -- Grounding: are answers clearly tied to imported sources? -- Citation quality: are the source references visible and specific? -- Functionality: can the user complete the question-answer flow? -- Product coherence: does the workflow feel integrated? diff --git a/courses/learn-harness-engineering/docs/en/lectures/lecture-11-why-observability-belongs-inside-the-harness/code/index.md b/courses/learn-harness-engineering/docs/en/lectures/lecture-11-why-observability-belongs-inside-the-harness/code/index.md deleted file mode 100644 index 5f094b95..00000000 --- a/courses/learn-harness-engineering/docs/en/lectures/lecture-11-why-observability-belongs-inside-the-harness/code/index.md +++ /dev/null @@ -1,8 +0,0 @@ -# Code for Lecture 11 - -Use this folder for examples of: - -- planner outputs -- evaluator rubrics -- generator/evaluator loops -- single-agent vs multi-role comparisons diff --git a/courses/learn-harness-engineering/docs/en/lectures/lecture-11-why-observability-belongs-inside-the-harness/code/runtime-logger.ts b/courses/learn-harness-engineering/docs/en/lectures/lecture-11-why-observability-belongs-inside-the-harness/code/runtime-logger.ts deleted file mode 100644 index 213dc198..00000000 --- a/courses/learn-harness-engineering/docs/en/lectures/lecture-11-why-observability-belongs-inside-the-harness/code/runtime-logger.ts +++ /dev/null @@ -1,260 +0,0 @@ -/** - * runtime-logger.ts - * - * A structured logging module demo. Shows ad-hoc console.log output vs - * structured JSON log output when diagnosing a failure. Includes a seeded - * failure scenario and demonstrates how structured logs pinpoint the issue - * faster. - * - * Run: npx tsx docs/lectures/lecture-11-why-observability-belongs-inside-the-harness/code/runtime-logger.ts - */ - -// --------------------------------------------------------------------------- -// Types -// --------------------------------------------------------------------------- - -interface StructuredLogEntry { - timestamp: string; - level: "info" | "warn" | "error" | "debug"; - component: string; - action: string; - durationMs?: number; - input?: unknown; - output?: unknown; - error?: string; - correlationId: string; -} - -// --------------------------------------------------------------------------- -// Simulated pipeline with a seeded failure -// --------------------------------------------------------------------------- - -const CORRELATION_ID = "req-" + Math.random().toString(36).slice(2, 8); - -// Simulated stages of a document Q&A pipeline -interface PipelineStage { - component: string; - action: string; - durationMs: number; - success: boolean; - errorMessage?: string; - input?: unknown; - output?: unknown; -} - -function runPipeline(): PipelineStage[] { - return [ - { - component: "DocumentLoader", - action: "parse_upload", - durationMs: 45, - success: true, - input: { filename: "report.pdf", size: "2.3MB" }, - output: { chunks: 47 }, - }, - { - component: "ChunkIndexer", - action: "embed_and_store", - durationMs: 230, - success: true, - input: { chunks: 47 }, - output: { indexed: 47, embeddingDim: 1536 }, - }, - { - component: "QueryRouter", - action: "route_query", - durationMs: 12, - success: true, - input: { query: "What is the revenue target?" }, - output: { routedTo: "RetrievalEngine" }, - }, - { - // SEEDED FAILURE: Retrieval returns 0 results due to dimension mismatch - component: "RetrievalEngine", - action: "semantic_search", - durationMs: 180, - success: false, - errorMessage: "Vector dimension mismatch: query embedding dim=768, index embedding dim=1536", - input: { query: "What is the revenue target?", topK: 5 }, - output: { results: 0 }, - }, - { - component: "AnswerGenerator", - action: "generate_with_citations", - durationMs: 1500, - success: true, // Doesn't crash, but produces a bad answer - input: { context: [], query: "What is the revenue target?" }, - output: { answer: "I could not find relevant information.", citations: 0 }, - }, - ]; -} - -// --------------------------------------------------------------------------- -// Ad-hoc logging (console.log style) -// --------------------------------------------------------------------------- - -function printAdHocLog(stages: PipelineStage[]): void { - console.log("Starting document Q&A pipeline..."); - console.log("User uploaded report.pdf"); - - for (const stage of stages) { - if (stage.success) { - console.log(`${stage.component}: ${stage.action} done (${stage.durationMs}ms)`); - } else { - console.log(`${stage.component}: something went wrong`); - } - } - - console.log("Pipeline finished. Answer: I could not find relevant information."); -} - -// --------------------------------------------------------------------------- -// Structured logging (JSON) -// --------------------------------------------------------------------------- - -function printStructuredLog(stages: PipelineStage[]): StructuredLogEntry[] { - const entries: StructuredLogEntry[] = []; - - for (const stage of stages) { - entries.push({ - timestamp: new Date().toISOString(), - level: stage.success ? "info" : "error", - component: stage.component, - action: stage.action, - durationMs: stage.durationMs, - input: stage.input, - output: stage.output, - error: stage.errorMessage, - correlationId: CORRELATION_ID, - }); - } - - return entries; -} - -// --------------------------------------------------------------------------- -// Diagnose failure from structured logs -// --------------------------------------------------------------------------- - -function diagnoseFromStructured(entries: StructuredLogEntry[]): string[] { - const diagnosis: string[] = []; - - // Find errors - const errors = entries.filter((e) => e.level === "error"); - if (errors.length > 0) { - diagnosis.push("ERRORS FOUND:"); - for (const err of errors) { - diagnosis.push(` - ${err.component}.${err.action}: ${err.error}`); - } - } - - // Check for latency spikes - const slowStages = entries.filter((e) => (e.durationMs ?? 0) > 1000); - if (slowStages.length > 0) { - diagnosis.push("LATENCY SPIKES:"); - for (const s of slowStages) { - diagnosis.push(` - ${s.component}.${s.action}: ${s.durationMs}ms`); - } - } - - // Check for cascading failures - const emptyOutputs = entries.filter((e) => { - const out = e.output as Record | undefined; - return out && typeof out === "object" && "results" in out && (out.results as number) === 0; - }); - if (emptyOutputs.length > 0) { - diagnosis.push("EMPTY OUTPUTS (possible cascade):"); - for (const e of emptyOutputs) { - diagnosis.push(` - ${e.component}.${e.action}: returned 0 results`); - } - } - - return diagnosis; -} - -// --------------------------------------------------------------------------- -// Reporting -// --------------------------------------------------------------------------- - -function pad(s: string, len: number): string { - return s.length >= len ? s : s + " ".repeat(len - s.length); -} - -function run(): void { - const pipeline = runPipeline(); - - console.log("\n" + "=".repeat(90)); - console.log(" OBSERVABILITY DEMO -- Ad-hoc vs Structured Logging"); - console.log("=".repeat(90)); - - // --- Ad-hoc output --- - console.log("\n" + "-".repeat(90)); - console.log(" SCENARIO A: Ad-hoc console.log output"); - console.log("-".repeat(90) + "\n"); - printAdHocLog(pipeline); - console.log("\n Diagnosis from ad-hoc logs: ??? Hard to tell what went wrong."); - console.log(" The failure message is vague: 'something went wrong'."); - console.log(" No dimensions, no input/output data, no correlation ID."); - - // --- Structured output --- - console.log("\n" + "-".repeat(90)); - console.log(" SCENARIO B: Structured JSON log output"); - console.log("-".repeat(90) + "\n"); - - const structuredEntries = printStructuredLog(pipeline); - - const header = `| ${pad("Timestamp", 26)}| ${pad("Level", 6)}| ${pad("Component", 20)}| ${pad("Action", 25)}| ${pad("Duration", 9)}| Error?`; - const sep = `|${"-".repeat(28)}|${"-".repeat(8)}|${"-".repeat(22)}|${"-".repeat(27)}|${"-".repeat(11)}|${"-".repeat(30)}`; - console.log(header); - console.log(sep); - - for (const entry of structuredEntries) { - const hasError = entry.error ? entry.error.slice(0, 30) : ""; - const marker = entry.level === "error" ? ">>" : " "; - console.log( - `${marker}| ${pad(entry.timestamp, 26)}| ${pad(entry.level, 6)}| ${pad(entry.component, 20)}| ${pad(entry.action, 25)}| ${pad(String(entry.durationMs) + "ms", 9)}| ${hasError}` - ); - } - - // --- Diagnosis --- - console.log("\n" + "-".repeat(90)); - console.log(" AUTOMATED DIAGNOSIS FROM STRUCTURED LOGS"); - console.log("-".repeat(90) + "\n"); - - const errors = structuredEntries.filter((e) => e.level === "error"); - for (const err of errors) { - console.log(` ROOT CAUSE: ${err.component}.${err.action}`); - console.log(` Error: ${err.error}`); - console.log(` Input: ${JSON.stringify(err.input)}`); - console.log(` Output: ${JSON.stringify(err.output)}`); - console.log(` Correlation ID: ${err.correlationId}`); - } - - // Downstream impact - console.log("\n DOWNSTREAM IMPACT:"); - const answerGen = structuredEntries.find((e) => e.component === "AnswerGenerator"); - if (answerGen) { - const out = answerGen.output as Record; - console.log(` AnswerGenerator received empty context (${JSON.stringify(answerGen.input)})`); - console.log(` Produced answer: "${out.answer}" with ${out.citations} citations`); - } - - // Comparison summary - console.log("\n" + "=".repeat(90)); - console.log(" COMPARISON"); - console.log("=".repeat(90) + "\n"); - - const cHeader = `| ${pad("Metric", 35)}| ${pad("Ad-hoc Logs", 18)}| ${pad("Structured Logs", 18)}|`; - const cSep = `|${"-".repeat(37)}|${"-".repeat(20)}|${"-".repeat(20)}|`; - console.log(cHeader); - console.log(cSep); - console.log(`| ${pad("Root cause identifiable", 35)}| ${pad("No", 18)}| ${pad("Yes", 18)}|`); - console.log(`| ${pad("Input/output traceable", 35)}| ${pad("No", 18)}| ${pad("Yes", 18)}|`); - console.log(`| ${pad("Correlation across steps", 35)}| ${pad("No", 18)}| ${pad("Yes", 18)}|`); - console.log(`| ${pad("Machine-parseable", 35)}| ${pad("No", 18)}| ${pad("Yes", 18)}|`); - console.log(`| ${pad("Time to diagnose", 35)}| ${pad("Minutes (manual)", 18)}| ${pad("Seconds (auto)", 18)}|`); - - console.log("\n Structured logging transforms debugging from guesswork into a deterministic lookup.\n"); -} - -run(); diff --git a/courses/learn-harness-engineering/docs/en/lectures/lecture-11-why-observability-belongs-inside-the-harness/code/sprint-contract.md b/courses/learn-harness-engineering/docs/en/lectures/lecture-11-why-observability-belongs-inside-the-harness/code/sprint-contract.md deleted file mode 100644 index bae2450e..00000000 --- a/courses/learn-harness-engineering/docs/en/lectures/lecture-11-why-observability-belongs-inside-the-harness/code/sprint-contract.md +++ /dev/null @@ -1,12 +0,0 @@ -# Sprint Contract Example - -Sprint goal: - -- Add visible citations to grounded Q&A results - -Done means: - -- User asks a question -- App returns an answer -- At least one citation is shown -- Clicking a citation opens the source location in the document view diff --git a/courses/learn-harness-engineering/docs/en/lectures/lecture-11-why-observability-belongs-inside-the-harness/index.md b/courses/learn-harness-engineering/docs/en/lectures/lecture-11-why-observability-belongs-inside-the-harness/index.md deleted file mode 100644 index 6593df80..00000000 --- a/courses/learn-harness-engineering/docs/en/lectures/lecture-11-why-observability-belongs-inside-the-harness/index.md +++ /dev/null @@ -1,154 +0,0 @@ -[中文版本 →](../../../zh/lectures/lecture-11-why-observability-belongs-inside-the-harness/) - -> Code examples: [code/](https://github.com/walkinglabs/learn-harness-engineering/blob/main/docs/en/lectures/lecture-11-why-observability-belongs-inside-the-harness/code/) -> Practice project: [Project 06. Complete harness (Capstone)](./../../projects/project-06-runtime-observability-and-debugging/index.md) - -# Lecture 11. Make the Agent's Runtime Observable - -## What Problem Does This Lecture Solve? - -You ask an agent to implement a feature. It runs for 20 minutes, modifies a bunch of files, then tells you "done, but two tests are failing." You ask why they're failing — "not sure, might be a timing issue." You ask which critical paths it changed — "let me look at the code..." - -This isn't about the agent lacking capability. It's about your harness not providing enough observability. **Without observability, agents make decisions under uncertainty, evaluations become subjective judgments, and retries become blind wandering.** Both OpenAI and Anthropic define reliability as an evidence problem — the harness must expose runtime behavior and evaluation signals in a form that can guide the next decision. - -## Core Concepts - -- **Runtime observability**: System-level signals — logs, traces, process events, health checks. Answers "what did the system do." -- **Process observability**: Visibility into harness decision artifacts — plans, scoring rubrics, acceptance criteria. Answers "why should this change be accepted." -- **Task trace**: A complete decision-path record from task start to completion, analogous to request tracing in distributed systems. Every step the agent takes, with context, is recorded. -- **Sprint contract**: A short-term agreement negotiated before coding begins — specifying task scope, verification standards, and exclusions. The core tool for process observability. -- **Evaluator rubric**: Transforms quality evaluation from subjective judgment into evidence-based structured scoring. Makes different evaluators produce similar results for the same output. -- **Layered observability**: System-layer and process-layer observability designed simultaneously and reinforcing each other. Runtime signals explain behavior; process artifacts explain intent. - -## Layered Observability - -```mermaid -flowchart LR - Contract["Write down the task first
what to change / what not to change / pass criteria"] --> Generator["Generator"] - Generator --> Signals["Collect app logs, traces,
and health checks while it runs"] - Contract --> Review["Check the result item by item
behavior / tests / boundaries"] - Signals --> Review - Review --> Verdict["Point to the failed check
and where to fix it"] - Verdict --> Generator -``` - -## Why This Happens - -### The Real Cost of Missing Observability - -When a harness lacks observability, four types of problems systematically appear: - -**Cannot distinguish "correct" from "looks correct"**: A function looks perfectly right during code review — correct syntax, sound logic. But at runtime, an edge case handling error produces incorrect results under specific inputs. Only runtime traces can reveal that the actual execution path deviated from expectations. - -**Evaluation becomes mysticism**: Without scoring rubrics and acceptance criteria, evaluators (human or agent) rely on implicit assumptions. The same output might get wildly different evaluations from different assessors. Quality assessment becomes non-reproducible. - -**Retries become blind guesses**: When the agent doesn't know why something failed, retry direction is random. It might try repeatedly in the wrong direction — fixing unrelated code paths while ignoring the actual failure root cause. Every blind retry costs tokens and time. - -**Session handoff information cliff**: When incomplete work is handed to the next session, missing observability means the new session must diagnose system state from scratch. Anthropic's long-running agent observations show this redundant diagnosis can consume 30-50% of total session time. - -### A Real Claude Code Scenario - -Imagine a harness using a "planner-generator-evaluator" three-role workflow, executing an "add dark mode to the app" task. - -**Without observability**: The planner outputs a vague description. The generator implements dark mode based on that vagueness, but it doesn't match the planner's implicit expectations. The evaluator rejects based on their own implicit standards but can't articulate what's specifically wrong. The generator retries blindly based on vague rejection reasons. The cycle repeats 3-4 times, taking about 45 minutes, producing a barely acceptable output. - -**With full observability**: The planner outputs a sprint contract — listing which components to modify, verification standards for each, and exclusions (no print styles). The generator implements according to the contract. Runtime observability records each component's style loading and application process. The evaluator uses a scoring rubric to evaluate dimension by dimension, with specific evidence citations. One iteration produces a high-quality result, in about 15 minutes. - -3x efficiency difference. The only change is observability. - -### Why Agents Can't Solve This Themselves - -You might be thinking: "Can't the agent just print its own logs?" The problems are: - -1. The agent doesn't know what it doesn't know — it won't proactively record signals it doesn't realize it needs. -2. Log formats are inconsistent — different sessions use different log formats, making systematic analysis impossible. -3. Process observability can't be solved by logs — sprint contracts and scoring rubrics are structured artifacts that need harness-level support. - -## How to Do It Right - -### 1. Build Runtime Signal Collection into the Harness - -Don't rely on the agent to print its own logs. The harness should automatically collect these signals: - -- **Application lifecycle**: Startup, ready, running, shutdown phase states -- **Feature path execution**: Records of critical path execution, including entry points, checkpoints, and exits -- **Data flow**: Records of data flowing between components -- **Resource utilization**: Abnormal resource usage patterns (e.g., continuously growing memory) -- **Errors and exceptions**: Full error context, not just error messages - -### 2. Implement Sprint Contracts - -Before each task starts, the generator and evaluator (which may be different invocations of the same agent) negotiate a contract: - -```markdown -# Sprint Contract: Dark Mode Support - -## Scope -- Modify the theme toggle component -- Update global CSS variables -- Add dark mode tests - -## Verification Standards -- Visual regression tests pass for each component -- Main flow end-to-end tests pass -- No flash of unstyled content (FOUC) - -## Exclusions -- Not handling print styles -- Not handling third-party component dark mode -``` - -### 3. Establish an Evaluator Rubric - -Turn "is it good or not" into quantifiable scoring: - -```markdown -# Scoring Rubric - -| Dimension | A | B | C | D | -|-----------|---|---|---|---| -| Code correctness | All tests pass | Main flow passes | Partial pass | Build fails | -| Architecture compliance | Fully compliant | Minor deviations | Obvious deviations | Serious violations | -| Test coverage | Main + edge cases | Main flow only | Only skeleton | No tests | -``` - -### 4. Standardize with OpenTelemetry - -Create a trace for each harness session, a span for each task, and sub-spans for each verification step. Use standard attributes to annotate key information. This way observability data integrates with standard toolchains (Jaeger, Zipkin). - -## Real-World Case - -A harness using a planner-generator-evaluator workflow, executing "add dark mode support": - -**Unobservable version**: 3-4 rounds of blind retries, 45 minutes, barely acceptable output. Evaluator says "it doesn't feel right" but can't say what specifically. Generator wastes significant time in wrong directions. - -**Fully observable version**: -- Sprint contract clarifies scope, standards, and exclusions -- Runtime traces record each component's style loading process -- Scoring rubric provides dimension-by-dimension structured evaluation -- One iteration produces high-quality results, 15 minutes - -3x efficiency improvement, more stable quality, reproducible evaluations. - -## Key Takeaways - -- **Observability is a harness architecture property** — not a feature added after the fact, but a core capability that must be considered during design. -- **Both observability layers are essential** — runtime signals explain "what happened," process artifacts explain "why it was done this way." -- **Sprint contracts front-load alignment** — preventing "the generator built something the evaluator immediately rejects for foreseeable reasons." -- **Scoring rubrics make evaluation reproducible** — different evaluators produce similar scores for the same output. -- **Missing observability wastes 30-50% of session time on redundant diagnosis.** - -## Further Reading - -- [Observability Engineering - Charity Majors](https://www.honeycomb.io/blog/observability-engineering-book) — Theory and practice framework for modern observability engineering -- [Dapper - Google (Sigelman et al.)](https://research.google/pubs/pub36356/) — Groundbreaking practice in large-scale distributed tracing -- [Harness Design - Anthropic](https://www.anthropic.com/engineering/harness-design-long-running-apps) — Introducing sprint contracts and evaluator rubrics -- [Site Reliability Engineering - Google](https://sre.google/sre-book/table-of-contents/) — Systematic application of observability in production systems - -## Exercises - -1. **Observability Gap Analysis**: Audit your current harness for system-layer and process-layer observability. Find system states that can't be distinguished from existing signals, and propose additions. - -2. **Sprint Contract Practice**: Write a sprint contract for a real task. Have the agent execute according to the contract, and compare efficiency and quality with and without the contract. - -3. **Task Trace Construction**: Record every step of an agent's operations during a complete coding task. Annotate with OpenTelemetry semantic conventions. Analyze information bottlenecks in the trace — which steps lack sufficient signal support for decisions. \ No newline at end of file diff --git a/courses/learn-harness-engineering/docs/en/lectures/lecture-12-why-every-session-must-leave-a-clean-state/code/benchmark-comparison-template.md b/courses/learn-harness-engineering/docs/en/lectures/lecture-12-why-every-session-must-leave-a-clean-state/code/benchmark-comparison-template.md deleted file mode 100644 index 45d8a3da..00000000 --- a/courses/learn-harness-engineering/docs/en/lectures/lecture-12-why-every-session-must-leave-a-clean-state/code/benchmark-comparison-template.md +++ /dev/null @@ -1,18 +0,0 @@ -# Benchmark Comparison Template - -Harness A: - -- completion rate -- average retries -- bugs caught before human review - -Harness B: - -- completion rate -- average retries -- bugs caught before human review - -Interpretation: - -- Which harness changed the result? -- Which harness changed the cost of getting the result? diff --git a/courses/learn-harness-engineering/docs/en/lectures/lecture-12-why-every-session-must-leave-a-clean-state/code/benchmark-runner.ts b/courses/learn-harness-engineering/docs/en/lectures/lecture-12-why-every-session-must-leave-a-clean-state/code/benchmark-runner.ts deleted file mode 100644 index 0388cad7..00000000 --- a/courses/learn-harness-engineering/docs/en/lectures/lecture-12-why-every-session-must-leave-a-clean-state/code/benchmark-runner.ts +++ /dev/null @@ -1,216 +0,0 @@ -/** - * benchmark-runner.ts - * - * Reads a benchmark task definition (JSON array of tasks with pass criteria), - * "executes" each task, records timing and pass/fail, outputs a comparison - * report showing which tasks pass and which fail. - * - * Run: npx tsx docs/lectures/lecture-12-why-every-session-must-leave-a-clean-state/code/benchmark-runner.ts - */ - -// --------------------------------------------------------------------------- -// Types -// --------------------------------------------------------------------------- - -interface BenchmarkTask { - id: string; - name: string; - category: string; - passCriteria: string[]; - expectedDurationMs: number; - // Simulated actual results - actualDurationMs: number; - actualPass: boolean; - failureReason?: string; -} - -interface BenchmarkResult { - id: string; - name: string; - category: string; - criteriaTotal: number; - criteriaPassed: number; - pass: boolean; - expectedMs: number; - actualMs: number; - durationDelta: number; - failureReason?: string; -} - -// --------------------------------------------------------------------------- -// Benchmark task definitions -// --------------------------------------------------------------------------- - -const benchmarkTasks: BenchmarkTask[] = [ - { - id: "bench-001", - name: "Import markdown document", - category: "Document Pipeline", - passCriteria: ["File accepted", "Chunks created", "Metadata stored"], - expectedDurationMs: 100, - actualDurationMs: 95, - actualPass: true, - }, - { - id: "bench-002", - name: "Import PDF document", - category: "Document Pipeline", - passCriteria: ["File accepted", "Text extracted", "Chunks created", "Metadata stored"], - expectedDurationMs: 200, - actualDurationMs: 310, - actualPass: false, - failureReason: "PDF text extraction failed on page 3 -- encoding issue", - }, - { - id: "bench-003", - name: "Ask grounded question", - category: "Q&A Pipeline", - passCriteria: ["Query received", "Relevant chunks retrieved", "Answer generated", "Citations present"], - expectedDurationMs: 500, - actualDurationMs: 480, - actualPass: true, - }, - { - id: "bench-004", - name: "Ask question with no relevant docs", - category: "Q&A Pipeline", - passCriteria: ["Query received", "Graceful 'no results' message", "No hallucinated citations"], - expectedDurationMs: 400, - actualDurationMs: 420, - actualPass: false, - failureReason: "Model hallucinated a citation instead of saying 'no results'", - }, - { - id: "bench-005", - name: "Delete imported document", - category: "Document Pipeline", - passCriteria: ["Document removed from list", "Chunks removed from index", "No orphan data"], - expectedDurationMs: 150, - actualDurationMs: 145, - actualPass: true, - }, - { - id: "bench-006", - name: "Concurrent user access", - category: "Security", - passCriteria: ["Users isolated", "No cross-user data leak", "Performance within SLA"], - expectedDurationMs: 300, - actualDurationMs: 850, - actualPass: false, - failureReason: "Cross-user data leak detected + response time exceeded SLA (850ms > 500ms)", - }, - { - id: "bench-007", - name: "Session continuity after restart", - category: "Reliability", - passCriteria: ["State persisted", "Session recovers", "No data loss"], - expectedDurationMs: 200, - actualDurationMs: 180, - actualPass: true, - }, - { - id: "bench-008", - name: "API rate limiting", - category: "Security", - passCriteria: ["Rate limit enforced", "429 response after limit", "Legitimate traffic unaffected"], - expectedDurationMs: 100, - actualDurationMs: 100, - actualPass: true, - }, -]; - -// --------------------------------------------------------------------------- -// Execution simulation -// --------------------------------------------------------------------------- - -function executeBenchmark(tasks: BenchmarkTask[]): BenchmarkResult[] { - return tasks.map((task) => ({ - id: task.id, - name: task.name, - category: task.category, - criteriaTotal: task.passCriteria.length, - criteriaPassed: task.actualPass ? task.passCriteria.length : Math.floor(task.passCriteria.length * 0.5), - pass: task.actualPass, - expectedMs: task.expectedDurationMs, - actualMs: task.actualDurationMs, - durationDelta: task.actualDurationMs - task.expectedDurationMs, - failureReason: task.failureReason, - })); -} - -// --------------------------------------------------------------------------- -// Reporting -// --------------------------------------------------------------------------- - -function pad(s: string, len: number): string { - return s.length >= len ? s : s + " ".repeat(len - s.length); -} - -function run(): void { - console.log("\n" + "=".repeat(100)); - console.log(" BENCHMARK RUNNER -- Task Execution Report"); - console.log("=".repeat(100)); - - const results = executeBenchmark(benchmarkTasks); - - // Detailed results - const header = `| ${pad("ID", 10)}| ${pad("Task", 35)}| ${pad("Category", 18)}| ${pad("Pass?", 6)}| ${pad("Criteria", 10)}| ${pad("Expected", 10)}| ${pad("Actual", 10)}| ${pad("Delta", 8)}|`; - const sep = `|${"-".repeat(12)}|${"-".repeat(37)}|${"-".repeat(20)}|${"-".repeat(8)}|${"-".repeat(12)}|${"-".repeat(12)}|${"-".repeat(12)}|${"-".repeat(10)}|`; - console.log("\n" + header); - console.log(sep); - - for (const r of results) { - const passLabel = r.pass ? "PASS" : "FAIL"; - const criteriaLabel = `${r.criteriaPassed}/${r.criteriaTotal}`; - const deltaLabel = r.durationDelta >= 0 ? `+${r.durationDelta}ms` : `${r.durationDelta}ms`; - const marker = r.pass ? " " : ">>"; - - console.log( - `${marker}| ${pad(r.id, 10)}| ${pad(r.name, 35)}| ${pad(r.category, 18)}| ${pad(passLabel, 6)}| ${pad(criteriaLabel, 10)}| ${pad(r.expectedMs + "ms", 10)}| ${pad(r.actualMs + "ms", 10)}| ${pad(deltaLabel, 8)}|` - ); - } - - // Failure details - const failures = results.filter((r) => !r.pass); - if (failures.length > 0) { - console.log("\n" + "-".repeat(100)); - console.log(" FAILURE DETAILS"); - console.log("-".repeat(100)); - for (const f of failures) { - console.log(`\n [${f.id}] ${f.name}`); - console.log(` Category: ${f.category}`); - console.log(` Criteria: ${f.criteriaPassed}/${f.criteriaTotal} passed`); - console.log(` Reason: ${f.failureReason ?? "Unknown"}`); - console.log(` Timing: Expected ${f.expectedMs}ms, actual ${f.actualMs}ms (delta: ${f.durationDelta >= 0 ? "+" : ""}${f.durationDelta}ms)`); - } - } - - // Summary - console.log("\n" + "=".repeat(100)); - console.log(" SUMMARY BY CATEGORY"); - console.log("=".repeat(100) + "\n"); - - const categories = [...new Set(results.map((r) => r.category))]; - const catHeader = `| ${pad("Category", 20)}| ${pad("Total", 8)}| ${pad("Passed", 8)}| ${pad("Failed", 8)}| ${pad("Pass Rate", 12)}|`; - const catSep = `|${"-".repeat(22)}|${"-".repeat(10)}|${"-".repeat(10)}|${"-".repeat(10)}|${"-".repeat(14)}|`; - console.log(catHeader); - console.log(catSep); - - for (const cat of categories) { - const catResults = results.filter((r) => r.category === cat); - const catPassed = catResults.filter((r) => r.pass).length; - const catFailed = catResults.filter((r) => !r.pass).length; - const rate = Math.round((catPassed / catResults.length) * 100); - - console.log(`| ${pad(cat, 20)}| ${pad(String(catResults.length), 8)}| ${pad(String(catPassed), 8)}| ${pad(String(catFailed), 8)}| ${pad(rate + "%", 12)}|`); - } - - // Overall - const totalPassed = results.filter((r) => r.pass).length; - const totalFailed = results.filter((r) => !r.pass).length; - console.log("\n" + "-".repeat(100)); - console.log(` OVERALL: ${totalPassed}/${results.length} passed (${Math.round((totalPassed / results.length) * 100)}%), ${totalFailed} failures`); - console.log("=".repeat(100) + "\n"); -} - -run(); diff --git a/courses/learn-harness-engineering/docs/en/lectures/lecture-12-why-every-session-must-leave-a-clean-state/code/cleanup-loop.md b/courses/learn-harness-engineering/docs/en/lectures/lecture-12-why-every-session-must-leave-a-clean-state/code/cleanup-loop.md deleted file mode 100644 index effd751d..00000000 --- a/courses/learn-harness-engineering/docs/en/lectures/lecture-12-why-every-session-must-leave-a-clean-state/code/cleanup-loop.md +++ /dev/null @@ -1,9 +0,0 @@ -# Cleanup Loop Example - -Recurring cleanup tasks: - -- scan for stale docs -- scan for structural violations -- update quality grades -- open targeted cleanup PRs -- rerun a fixed benchmark slice after cleanup diff --git a/courses/learn-harness-engineering/docs/en/lectures/lecture-12-why-every-session-must-leave-a-clean-state/code/cleanup-scanner.ts b/courses/learn-harness-engineering/docs/en/lectures/lecture-12-why-every-session-must-leave-a-clean-state/code/cleanup-scanner.ts deleted file mode 100644 index 6b35905c..00000000 --- a/courses/learn-harness-engineering/docs/en/lectures/lecture-12-why-every-session-must-leave-a-clean-state/code/cleanup-scanner.ts +++ /dev/null @@ -1,336 +0,0 @@ -/** - * cleanup-scanner.ts - * - * Scans a project directory for stale artifacts, dead code, structural - * violations, and outputs a cleanup report. Helps enforce the "clean state - * at end of every session" principle. - * - * Usage: - * npx tsx docs/lectures/lecture-12.../code/cleanup-scanner.ts [path] - * (defaults to current working directory) - * - * Run: npx tsx docs/lectures/lecture-12-why-every-session-must-leave-a-clean-state/code/cleanup-scanner.ts - */ - -import * as fs from "node:fs"; -import * as path from "node:path"; - -// --------------------------------------------------------------------------- -// Types -// --------------------------------------------------------------------------- - -interface ScanResult { - category: string; - check: string; - severity: "critical" | "warning" | "info"; - found: string[]; - description: string; -} - -// --------------------------------------------------------------------------- -// Scanner checks -// --------------------------------------------------------------------------- - -interface ScannerCheck { - category: string; - name: string; - severity: "critical" | "warning" | "info"; - description: string; - scan: (dir: string) => string[]; -} - -function createChecks(): ScannerCheck[] { - return [ - { - category: "Stale Artifacts", - name: "Temporary files (*.tmp, *.bak, *.swp)", - severity: "warning", - description: "Leftover temp files from editing or processing", - scan: (dir) => findFiles(dir, [".tmp", ".bak", ".swp", "~"]), - }, - { - category: "Stale Artifacts", - name: "Debug/log files in source", - severity: "warning", - description: "Log files that should not be in the source tree", - scan: (dir) => { - const found: string[] = []; - const logPatterns = [".log"]; - for (const p of findFiles(dir, logPatterns)) { - // Only flag log files in source directories - if (p.includes("/src/") || p.includes("/lib/") || p.includes("/app/")) { - found.push(p); - } - } - return found; - }, - }, - { - category: "Dead Code", - name: "Unused imports (placeholder detection)", - severity: "info", - description: "Files that may contain unused code (heuristic scan)", - scan: (dir) => { - const found: string[] = []; - const srcDirs = ["src", "lib", "app"]; - for (const srcDir of srcDirs) { - const srcPath = path.join(dir, srcDir); - if (fs.existsSync(srcPath)) { - scanForPatterns(srcPath, ["TODO:", "FIXME:", "HACK:", "XXX:"], found, dir); - } - } - return found; - }, - }, - { - category: "Structural Violations", - name: "Missing .gitignore", - severity: "warning", - description: "No .gitignore file found -- risk of committing build artifacts", - scan: (dir) => fs.existsSync(path.join(dir, ".gitignore")) ? [] : [".gitignore (MISSING)"], - }, - { - category: "Structural Violations", - name: "Build artifacts in source", - severity: "critical", - description: "Compiled output (dist/, build/) mixed with source", - scan: (dir) => { - const found: string[] = []; - // Check if dist/ or build/ exists inside src/ - const dirs = ["src/dist", "src/build", "lib/dist", "lib/build"]; - for (const d of dirs) { - if (fs.existsSync(path.join(dir, d))) { - found.push(d + "/"); - } - } - return found; - }, - }, - { - category: "Structural Violations", - name: "node_modules in source tree", - severity: "critical", - description: "node_modules directory found outside root (nested dependency)", - scan: (dir) => { - const found: string[] = []; - const srcDirs = ["src", "lib", "app", "test", "tests"]; - for (const srcDir of srcDirs) { - const nmPath = path.join(dir, srcDir, "node_modules"); - if (fs.existsSync(nmPath)) { - found.push(srcDir + "/node_modules/"); - } - } - return found; - }, - }, - { - category: "Session Cleanliness", - name: "Uncommitted changes indicator", - severity: "info", - description: "Checks for common artifacts of incomplete sessions", - scan: (dir) => { - const found: string[] = []; - const indicators = [ - "WIP.md", "IN_PROGRESS.md", "scratch.ts", "scratch.js", - "debug.ts", "test-manual.ts", "temp.ts", - ]; - for (const f of indicators) { - if (fs.existsSync(path.join(dir, f))) { - found.push(f); - } - } - return found; - }, - }, - { - category: "Session Cleanliness", - name: "Empty directories", - severity: "info", - description: "Empty directories that may be leftover from incomplete work", - scan: (dir) => { - const found: string[] = []; - const commonDirs = ["src", "lib", "app", "test", "tests", "docs"]; - for (const d of commonDirs) { - const dp = path.join(dir, d); - if (fs.existsSync(dp) && fs.statSync(dp).isDirectory()) { - try { - const contents = fs.readdirSync(dp); - if (contents.length === 0) { - found.push(d + "/"); - } - } catch { - // Skip directories we can't read - } - } - } - return found; - }, - }, - { - category: "Configuration", - name: "Environment files in source", - severity: "critical", - description: ".env files that may contain secrets", - scan: (dir) => { - const found: string[] = []; - const envFiles = [".env", ".env.local", ".env.production", ".env.staging"]; - for (const f of envFiles) { - if (fs.existsSync(path.join(dir, f))) { - found.push(f); - } - } - return found; - }, - }, - ]; -} - -// --------------------------------------------------------------------------- -// Helper functions -// --------------------------------------------------------------------------- - -function findFiles(dir: string, extensions: string[]): string[] { - const found: string[] = []; - - function walk(current: string, depth: number): void { - if (depth > 4) return; // Limit recursion depth - try { - const entries = fs.readdirSync(current, { withFileTypes: true }); - for (const entry of entries) { - // Skip common non-source directories - if (entry.isDirectory() && ["node_modules", ".git", "dist", "build", ".next", "coverage"].includes(entry.name)) { - continue; - } - - const fullPath = path.join(current, entry.name); - const relativePath = path.relative(dir, fullPath); - - if (entry.isDirectory()) { - walk(fullPath, depth + 1); - } else if (entry.isFile()) { - if (extensions.some((ext) => entry.name.endsWith(ext))) { - found.push(relativePath); - } - } - } - } catch { - // Skip directories we can't read - } - } - - walk(dir, 0); - return found; -} - -function scanForPatterns(dir: string, patterns: string[], results: string[], baseDir: string): void { - try { - const entries = fs.readdirSync(dir, { withFileTypes: true }); - for (const entry of entries) { - if (entry.isDirectory()) { - if (!["node_modules", ".git", "dist", "build"].includes(entry.name)) { - scanForPatterns(path.join(dir, entry.name), patterns, results, baseDir); - } - continue; - } - - const fullPath = path.join(dir, entry.name); - if (!entry.name.endsWith(".ts") && !entry.name.endsWith(".tsx") && !entry.name.endsWith(".js")) { - continue; - } - - try { - const content = fs.readFileSync(fullPath, "utf-8"); - const lines = content.split("\n"); - for (let i = 0; i < Math.min(lines.length, 50); i++) { - for (const pattern of patterns) { - if (lines[i].includes(pattern)) { - const relative = path.relative(baseDir, fullPath); - results.push(`${relative}:${i + 1} contains ${pattern}`); - } - } - } - } catch { - // Skip files we can't read - } - } - } catch { - // Skip directories we can't read - } -} - -// --------------------------------------------------------------------------- -// Reporting -// --------------------------------------------------------------------------- - -function pad(s: string, len: number): string { - return s.length >= len ? s : s + " ".repeat(len - s.length); -} - -function run(): void { - const targetDir = process.argv[2] ? path.resolve(process.argv[2]) : process.cwd(); - - console.log("\n" + "=".repeat(90)); - console.log(" CLEANUP SCANNER -- Stale Artifact & Violation Report"); - console.log("=".repeat(90)); - console.log(` Scanning: ${targetDir}\n`); - - const checks = createChecks(); - const results: ScanResult[] = []; - - for (const check of checks) { - const found = check.scan(targetDir); - results.push({ - category: check.category, - check: check.name, - severity: check.severity, - found, - description: check.description, - }); - } - - // Report - const header = `| ${pad("Category", 20)}| ${pad("Check", 40)}| ${pad("Severity", 10)}| ${pad("Count", 6)}| Details`; - const sep = `|${"-".repeat(22)}|${"-".repeat(42)}|${"-".repeat(12)}|${"-".repeat(8)}|${"-".repeat(30)}`; - console.log(header); - console.log(sep); - - for (const r of results) { - const count = r.found.length; - const detail = count > 0 ? r.found.slice(0, 2).join(", ") : "(clean)"; - const marker = count > 0 && r.severity === "critical" ? ">>" : count > 0 ? " * " : " "; - - console.log(`${marker}| ${pad(r.category, 20)}| ${pad(r.check, 40)}| ${pad(r.severity, 10)}| ${pad(String(count), 6)}| ${detail}`); - } - - // Summary - const criticals = results.filter((r) => r.severity === "critical" && r.found.length > 0); - const warnings = results.filter((r) => r.severity === "warning" && r.found.length > 0); - const infos = results.filter((r) => r.severity === "info" && r.found.length > 0); - const clean = results.filter((r) => r.found.length === 0); - - console.log("\n" + "=".repeat(90)); - console.log(" CLEANUP SUMMARY"); - console.log("=".repeat(90) + "\n"); - - console.log(` Critical issues: ${criticals.length}`); - console.log(` Warnings: ${warnings.length}`); - console.log(` Info items: ${infos.length}`); - console.log(` Clean checks: ${clean.length}/${results.length}`); - - if (criticals.length > 0) { - console.log("\n ACTION REQUIRED (Critical):"); - for (const c of criticals) { - console.log(` - ${c.check}: ${c.found.length} item(s) found`); - console.log(` ${c.description}`); - } - } - - const totalIssues = results.reduce((s, r) => s + r.found.length, 0); - if (totalIssues === 0) { - console.log("\n Project is in a clean state. No issues found.\n"); - } else { - console.log(`\n Total issues found: ${totalIssues}. Run this scanner at the end of every session to maintain clean state.\n`); - } -} - -run(); diff --git a/courses/learn-harness-engineering/docs/en/lectures/lecture-12-why-every-session-must-leave-a-clean-state/code/index.md b/courses/learn-harness-engineering/docs/en/lectures/lecture-12-why-every-session-must-leave-a-clean-state/code/index.md deleted file mode 100644 index 29cf9f5f..00000000 --- a/courses/learn-harness-engineering/docs/en/lectures/lecture-12-why-every-session-must-leave-a-clean-state/code/index.md +++ /dev/null @@ -1,8 +0,0 @@ -# Code for Lecture 12 - -Use this folder for examples of: - -- benchmark slices -- cleanup tasks -- entropy reduction examples -- repeatable harness runs diff --git a/courses/learn-harness-engineering/docs/en/lectures/lecture-12-why-every-session-must-leave-a-clean-state/index.md b/courses/learn-harness-engineering/docs/en/lectures/lecture-12-why-every-session-must-leave-a-clean-state/index.md deleted file mode 100644 index 0e006065..00000000 --- a/courses/learn-harness-engineering/docs/en/lectures/lecture-12-why-every-session-must-leave-a-clean-state/index.md +++ /dev/null @@ -1,186 +0,0 @@ -[中文版本 →](../../../zh/lectures/lecture-12-why-every-session-must-leave-a-clean-state/) - -> Code examples: [code/](https://github.com/walkinglabs/learn-harness-engineering/blob/main/docs/en/lectures/lecture-12-why-every-session-must-leave-a-clean-state/code/) -> Practice project: [Project 06. Complete harness (Capstone)](./../../projects/project-06-runtime-observability-and-debugging/index.md) - -# Lecture 12. Clean Handoff at the End of Every Session - -## What Problem Does This Lecture Solve? - -Your agent runs all afternoon, modifies 20 files, commits the code, session ends. The next agent session starts and immediately discovers: build is broken, tests are red, temporary debug files are everywhere, the feature list wasn't updated, and progress is completely unclear. The new session spends its first 30 minutes just figuring out "what did the last session actually do." - -Both OpenAI and Anthropic state clearly: **long-term reliability depends on operational discipline, not just single-run success.** The quality of state at session exit directly determines the next session's efficiency. Think of it like Git best practices — every commit should be an atomic, compilable change, not a pile of half-finished code. - -## Core Concepts - -- **Clean state**: The system satisfies five conditions at session end — build passes, tests pass, progress recorded, no stale artifacts, startup path available. Missing any one means the session isn't "done." -- **Session integrity**: Analogous to database transactions — either fully commit and leave a clean state, or roll back to the last consistent state. No middle ground. -- **Quality document**: An active artifact that continuously records quality ratings for each module. Not a one-time assessment, but a tracker showing whether the codebase is getting stronger or weaker over time. -- **Cleanup loop**: A regular maintenance session aimed at systematically reducing entropy in the codebase. Not an emergency fix, but routine operations. -- **Harness simplification**: As model capabilities improve, periodically remove harness components that are no longer necessary. A constraint essential today may be unnecessary overhead in three months. -- **Idempotent cleanup**: Cleanup operations produce the same result regardless of how many times they run. Ensures cleanup remains safe even in failure-retry scenarios. - -## Five Dimensions of Clean State - -```mermaid -flowchart LR - Work["Feature work complete"] --> Build{"Build passes?"} - Build -->|yes| Test{"Tests pass?"} - Build -->|no| Fix["Fix before exit"] - Test -->|yes| Record["Update feature list + progress"] - Test -->|no| Fix - Record --> Cleanup["Remove temp artifacts / debug code"] - Cleanup --> Startup{"Standard startup path works?"} - Startup -->|yes| Clean["Clean handoff"] - Startup -->|no| Fix - Fix --> Build -``` - -```mermaid -flowchart LR - Dirty["Session ends with
red tests / temp files / no progress update"] --> Diagnose["Next session first has to
figure out what happened"] - Diagnose --> Fragile["New work starts on a messy repo"] - Fragile --> More["More debug files, more broken checks,
more unclear progress"] - More --> Dirty - - Clean["Session ends with
green tests / updated progress / temp files removed"] --> Fast["Next session can start coding immediately"] - Fast --> Stable["No need to rescue the repo first"] - Stable --> Clean -``` - -## Why This Happens - -### Entropy Growth Is the Default State - -Lehman's laws of software evolution tell us: systems undergoing continuous change will inevitably increase in complexity unless actively managed. This is especially true for AI coding agents — every session introduces changes, and without cleanup at exit, technical debt accumulates exponentially. - -Real data is telling. A project developed with agents for 12 weeks, without cleanup strategy: - -- Week 1: Build pass rate 100%, test pass rate 100%, new session startup 5 min -- Week 4: Build 95%, tests 92%, startup 15 min -- Week 8: Build 82%, tests 78%, startup 35 min -- Week 12: Build 68%, tests 61%, startup 60+ min - -Same project with a cleanup strategy: - -- Week 1: 100%, 100%, 5 min -- Week 12: 97%, 95%, 9 min - -After 12 weeks: build pass rate differs by 29 percentage points, new session startup time differs by 85%. This is not theoretical — it's an observed difference. - -### Five Dimensions of Clean State - -Clean state isn't just "the code compiles." It's five dimensions evaluated together: - -**Build dimension**: Does the code build without errors? This is the most basic — the next session shouldn't have to fix build errors first. - -**Test dimension**: Do all tests pass? Including tests that existed before the session — the session is responsible for not breaking existing functionality. And it should be verified in CI, not just "works on my machine." - -**Progress dimension**: Is current progress recorded in a machine-readable artifact? Completed subtasks with their passing criteria, in-progress but incomplete subtasks with current state, not-yet-started subtasks. Good progress records reduce 60-80% of session startup diagnostic time. - -**Artifact dimension**: Are there stale or ambiguous temporary artifacts? Debug logs, temporary files, commented-out code, TODO markers — all of these increase cognitive load for the next session. - -**Startup dimension**: Is the standard startup path available? Can the next session start working without manual intervention? Environment initialization, codebase loading, context acquisition, task selection — these paths must not be broken. - -### "Clean Up Later" Means Never Clean Up - -The most common mental trap is "no time to clean up this session, I'll do it next time." But the next agent session doesn't know what you left behind — it sees a mess of code and uncertain state. It'll spend significant time inferring "which parts of this code are intentional and which are temporary." - -Worse, every session has its own task objectives. The new session is there to do new work, not clean up the previous session's mess. It'll ignore the chaos and start new work on top of it, introducing more chaos on top of chaos. This is entropy's positive feedback loop. - -## How to Do It Right - -### 1. Clean State as a Completion Requirement - -Define explicitly in the harness: **session completion = task passes verification AND clean state check passes.** Missing either one means the session isn't complete. Write in CLAUDE.md: - -``` -## Session Exit Checklist -- [ ] Build passes (npm run build) -- [ ] All tests pass (npm test) -- [ ] Feature list updated -- [ ] No debug code remaining (console.log, debugger, TODO) -- [ ] Standard startup path available (npm run dev) -``` - -### 2. Dual-Mode Cleanup Strategy - -Combine two cleanup modes: - -**Immediate cleanup (at end of every session)**: Clean up temporary artifacts created during the session, update feature list state, ensure build and tests pass. This is "reference counting" cleanup. - -**Periodic cleanup (weekly)**: Full-system scan — handle accumulated structural issues, update quality documents, run benchmark tests to detect drift. This is "tracing" cleanup. - -### 3. Maintain a Quality Document - -A quality document is an active artifact that continuously scores each module: - -```markdown -# Quality Document - -## User Authentication Module (Quality: A) -- Verification passing: Yes -- Agent understandable: Yes -- Test stability: Stable -- Architecture boundaries: Compliant -- Code conventions: Followed - -## Payment Module (Quality: C) -- Verification passing: Partial (payment callback untested) -- Agent understandable: Difficult (logic spread across 3 files) -- Test stability: Unstable (2 flaky tests) -- Architecture boundaries: Violations present -- Code conventions: Partially followed -``` - -New sessions read this document and immediately know where to prioritize. Fix the lowest-scoring module first. - -### 4. Periodically Simplify the Harness - -An important insight from Anthropic: **every harness component exists because the model can't reliably do something on its own. But as models improve, these assumptions become outdated.** A constraint essential three months ago may be unnecessary overhead today. - -Recommended practice: Every month, pick one harness component, temporarily disable it, and run benchmark tasks. If results don't degrade, remove it permanently. If they do, restore it or replace with a lighter alternative. - -### 5. Cleanup Operations Must Be Idempotent - -Cleanup scripts should be safe to run repeatedly: - -```bash -# Idempotent cleanup operations -rm -f /tmp/debug-*.log # -f ensures no error when files don't exist -git checkout -- .env.local # Restore to known state -npm run test # Verify cleanup didn't break anything -``` - -## Real-World Case - -An Electron app developed with agents over 12 weeks, comparing two approaches: - -**Without cleanup strategy** (control group): Week 12, build pass rate 68%, test pass rate 61%, new session startup 60+ min, stale artifacts 103. - -**With cleanup strategy** (experimental group): Full clean-state check at every session end + weekly cleanup loop. Week 12, build pass rate 97%, test pass rate 95%, new session startup 9 min, stale artifacts 11. - -By week 12, the experimental group's build pass rate is 29 percentage points higher, test pass rate 34 points higher, and new session startup time 85% lower. - -## Key Takeaways - -- **Clean state is a necessary condition for session completion** — not optional housekeeping, but part of the "definition of done." -- **All five dimensions are required** — build, tests, progress, artifacts, startup — each must be explicitly checked. -- **Quality documents make codebase health trackable** — you can only fix what you know is degrading. -- **Periodically simplify the harness** — as model capabilities improve, remove constraints that are no longer needed. -- **"Clean up later" equals never cleaning up** — entropy growth is the default; only active cleanup counteracts it. - -## Further Reading - -- [Clean Code - Robert C. Martin](https://www.goodreads.com/book/show/3735293-clean-code) — Systematic principles of code cleanliness -- [Harness Engineering - OpenAI](https://openai.com/index/harness-engineering/) — Reproducibility as a core harness design requirement -- [Effective Harnesses - Anthropic](https://www.anthropic.com/engineering/effective-harnesses-for-long-running-agents) — The critical role of clean session exits for long-term reliability -- [Programs, Life Cycles, and Laws of Software Evolution - Lehman](https://ieeexplore.ieee.org/document/1702314) — Software evolution laws proving system complexity inevitably grows without active maintenance - -## Exercises - -1. **Clean State Checklist**: Design a session exit checklist for your codebase covering all five dimensions. Apply it across 5 consecutive sessions and record violations per dimension. - -2. **Benchmark Comparison**: Use a fixed task set with two harness variants (with/without clean state requirements). Compare completion rate, retry count, and defect escape rate. - -3. **Harness Simplification Practice**: Pick one harness component, temporarily disable it, and run benchmark tasks. Compare results with and without it. Decide whether to keep, remove, or replace. \ No newline at end of file diff --git a/courses/learn-harness-engineering/docs/en/projects/index.md b/courses/learn-harness-engineering/docs/en/projects/index.md deleted file mode 100644 index ef9e07d4..00000000 --- a/courses/learn-harness-engineering/docs/en/projects/index.md +++ /dev/null @@ -1,23 +0,0 @@ -# Welcome to Projects - -This is the hands-on section of Learn Harness Engineering. Reading the lectures isn't enough—you need to build the environments yourself and observe how Codex, Claude Code, or other AI agents behave under different rules. - -## Project Overview - -This course features 6 progressive, hands-on projects that teach you how to build a reliable agentic working environment from scratch: - -1. **Prompt-Only vs. Rules-First**: Compare how an agent performs with just a prompt versus a basic harness. -2. **Agent-Readable Workspace**: Learn how to structure your repository to make it AI-friendly and establish handoff mechanisms. -3. **Multi-Session Continuity**: Design state files and initialization scripts so your agent can resume work seamlessly across sessions. -4. **Runtime Feedback and Scope Control**: Introduce tools that allow the agent to test its own code and correct errors during execution. -5. **Self-Verification and Role Separation**: Build an independent review mechanism to prevent hallucinations and early declarations of victory. -6. **Complete Harness (Capstone)**: Assemble a final, observable, end-to-end agent working environment. - -## How to Proceed - -Each project folder typically contains: -- `starter/`: Your starting workspace. -- `solution/`: A reference implementation (if you get stuck). -- Task instructions detailing your background and specific goals. - -Use your preferred AI Coding Agent (e.g., Claude Code, Cursor, Trae) to complete the tasks inside the `starter/` directory. diff --git a/courses/learn-harness-engineering/docs/en/projects/project-01-baseline-vs-minimal-harness/index.md b/courses/learn-harness-engineering/docs/en/projects/project-01-baseline-vs-minimal-harness/index.md deleted file mode 100644 index 80aba982..00000000 --- a/courses/learn-harness-engineering/docs/en/projects/project-01-baseline-vs-minimal-harness/index.md +++ /dev/null @@ -1,25 +0,0 @@ -[中文版本 →](../../../zh/projects/project-01-baseline-vs-minimal-harness/) - -> Related lectures: [Lecture 01. Strong models don't mean reliable execution](./../../lectures/lecture-01-why-capable-agents-still-fail/index.md) · [Lecture 02. What harness actually means](./../../lectures/lecture-02-what-a-harness-actually-is/index.md) -> Template files: [templates/](https://github.com/walkinglabs/learn-harness-engineering/blob/main/docs/en/resources/templates/) - -# Project 01. Prompt-Only vs. Rules-First: How Much Difference Does It Make - -## What You Do - -Build a minimal Electron knowledge-base app shell — a window with a document list on the left, a Q&A panel on the right, and a local data directory. The task itself is not complex. What's complex is how you get the agent to complete it. - -You run it twice. First time: just a prompt, no preparation. Second time: `AGENTS.md`, `init.sh`, `feature_list.json` pre-placed in the repo. Then compare. - -The core of this project is not writing code — it's figuring out how big the gap is between "spend 15 minutes preparing rules first" and "just let the agent go." - -## Tools - -- Claude Code or Codex (pick one, use it for both runs) -- Git (manage branches and compare) -- Node.js + Electron (project stack) -- A timer (record each run's duration) - -## Harness Mechanism - -Minimal harness: `AGENTS.md` + `init.sh` + `feature_list.json` diff --git a/courses/learn-harness-engineering/docs/en/projects/project-02-agent-readable-workspace/index.md b/courses/learn-harness-engineering/docs/en/projects/project-02-agent-readable-workspace/index.md deleted file mode 100644 index 70b4985d..00000000 --- a/courses/learn-harness-engineering/docs/en/projects/project-02-agent-readable-workspace/index.md +++ /dev/null @@ -1,22 +0,0 @@ -[中文版本 →](../../../zh/projects/project-02-agent-readable-workspace/) - -> Related lectures: [Lecture 03. Make the repository your single source of truth](./../../lectures/lecture-03-why-the-repository-must-become-the-system-of-record/index.md) · [Lecture 04. Split instructions across files](./../../lectures/lecture-04-why-one-giant-instruction-file-fails/index.md) -> Template files: [templates/](https://github.com/walkinglabs/learn-harness-engineering/blob/main/docs/en/resources/templates/) - -# Project 02. Make the Project Readable and Pick Up Where You Left Off - -## What You Do - -Add "readability" to the repo so a new agent can quickly understand the project structure, know the current progress, and pick up work. Specifically: implement document import, document detail view, and local persistence, completed across two sessions. - -You run it twice: first without any help, second with `ARCHITECTURE.md`, `PRODUCT.md`, and `session-handoff.md` pre-placed in the repo. - -## Tools - -- Claude Code or Codex -- Git -- Node.js + Electron - -## Harness Mechanism - -Agent-readable workspace + persistent state files diff --git a/courses/learn-harness-engineering/docs/en/projects/project-03-multi-session-continuity/index.md b/courses/learn-harness-engineering/docs/en/projects/project-03-multi-session-continuity/index.md deleted file mode 100644 index 856e5cfb..00000000 --- a/courses/learn-harness-engineering/docs/en/projects/project-03-multi-session-continuity/index.md +++ /dev/null @@ -1,22 +0,0 @@ -[中文版本 →](../../../zh/projects/project-03-multi-session-continuity/) - -> Related lectures: [Lecture 05. Keep context alive across sessions](./../../lectures/lecture-05-why-long-running-tasks-lose-continuity/index.md) · [Lecture 06. Initialize before every agent session](./../../lectures/lecture-06-why-initialization-needs-its-own-phase/index.md) -> Template files: [templates/](https://github.com/walkinglabs/learn-harness-engineering/blob/main/docs/en/resources/templates/) - -# Project 03. Keep the Agent Working Across Session Restarts - -## What You Do - -Add scope control and verification gates to the agent. Implement document chunking, metadata extraction, indexing progress display, and citation-based Q&A flow. Use `feature_list.json` to track feature status — one feature at a time, no marking as "pass" without verification evidence. - -You run it twice: first without constraints, second with strict enforcement. - -## Tools - -- Claude Code or Codex -- Git -- Node.js + Electron - -## Harness Mechanism - -Progress log + session handoff + multi-session continuity diff --git a/courses/learn-harness-engineering/docs/en/projects/project-04-incremental-indexing/index.md b/courses/learn-harness-engineering/docs/en/projects/project-04-incremental-indexing/index.md deleted file mode 100644 index 38fe4624..00000000 --- a/courses/learn-harness-engineering/docs/en/projects/project-04-incremental-indexing/index.md +++ /dev/null @@ -1,22 +0,0 @@ -[中文版本 →](../../../zh/projects/project-04-incremental-indexing/) - -> Related lectures: [Lecture 07. Draw clear task boundaries for agents](./../../lectures/lecture-07-why-agents-overreach-and-under-finish/index.md) · [Lecture 08. Use feature lists to constrain what the agent does](./../../lectures/lecture-08-why-feature-lists-are-harness-primitives/index.md) -> Template files: [templates/](https://github.com/walkinglabs/learn-harness-engineering/blob/main/docs/en/resources/templates/) - -# Project 04. Use Runtime Feedback to Correct Agent Behavior - -## What You Do - -Add runtime observability (startup logs, import/indexing logs, error states) and architecture constraints to prevent cross-layer violations. Plant a runtime bug for the agent to fix. - -You run it twice: first without logs or constraints, second with proper tools and rules. - -## Tools - -- Claude Code or Codex -- Git -- Node.js + Electron - -## Harness Mechanism - -Runtime feedback + scope control + incremental indexing diff --git a/courses/learn-harness-engineering/docs/en/projects/project-05-grounded-qa-verification/index.md b/courses/learn-harness-engineering/docs/en/projects/project-05-grounded-qa-verification/index.md deleted file mode 100644 index 23f79db6..00000000 --- a/courses/learn-harness-engineering/docs/en/projects/project-05-grounded-qa-verification/index.md +++ /dev/null @@ -1,22 +0,0 @@ -[中文版本 →](../../../zh/projects/project-05-grounded-qa-verification/) - -> Related lectures: [Lecture 09. Stop agents from declaring victory early](./../../lectures/lecture-09-why-agents-declare-victory-too-early/index.md) · [Lecture 10. Only a full-pipeline run counts as real verification](./../../lectures/lecture-10-why-end-to-end-testing-changes-results/index.md) -> Template files: [templates/](https://github.com/walkinglabs/learn-harness-engineering/blob/main/docs/en/resources/templates/) - -# Project 05. Make the Agent Verify Its Own Work - -## What You Do - -Implement role separation — a generator that implements, an evaluator that reviews, and optionally a planner. Run three times to measure the effect of each added role. - -Choose a substantive feature upgrade (multi-turn conversation, citation panel redesign, or document filtering) and keep it consistent across all runs. - -## Tools - -- Claude Code or Codex -- Git -- Node.js + Electron - -## Harness Mechanism - -Self-verification + grounded Q&A + evidence-based completion diff --git a/courses/learn-harness-engineering/docs/en/projects/project-06-runtime-observability-and-debugging/index.md b/courses/learn-harness-engineering/docs/en/projects/project-06-runtime-observability-and-debugging/index.md deleted file mode 100644 index 34072cf7..00000000 --- a/courses/learn-harness-engineering/docs/en/projects/project-06-runtime-observability-and-debugging/index.md +++ /dev/null @@ -1,25 +0,0 @@ -[中文版本 →](../../../zh/projects/project-06-runtime-observability-and-debugging/) - -> Related lectures: [Lecture 11. Make the agent's runtime observable](./../../lectures/lecture-11-why-observability-belongs-inside-the-harness/index.md) · [Lecture 12. Clean handoff at the end of every session](./../../lectures/lecture-12-why-every-session-must-leave-a-clean-state/index.md) -> Template files: [templates/](https://github.com/walkinglabs/learn-harness-engineering/blob/main/docs/en/resources/templates/) - -# Project 06. Build a Complete Agent Harness (Capstone) - -## What You Do - -This is the capstone project. Assemble everything learned in the first five projects, run a full benchmark, then do a cleanup pass to verify quality is maintainable. - -Use a fixed multi-feature task set covering the complete product slice: document import, indexing, citation-based Q&A, runtime observability, and readable restartable repo state. First run with weak harness baseline, then with your strongest harness, then a cleanup and re-run. Finally, do a harness ablation experiment — remove one component at a time and see which ones actually matter. - -## Tools - -- Claude Code or Codex -- Git -- Node.js + Electron -- Quality document template -- Evaluator rubric -- All harness components accumulated from the first five projects - -## Harness Mechanism - -Complete harness: all mechanisms + observability + ablation study diff --git a/courses/learn-harness-engineering/docs/en/resources/index.md b/courses/learn-harness-engineering/docs/en/resources/index.md deleted file mode 100644 index c078e8bc..00000000 --- a/courses/learn-harness-engineering/docs/en/resources/index.md +++ /dev/null @@ -1,58 +0,0 @@ -# English Resource Library - -This folder turns the course methods into copy-ready templates and compact -references you can use in a real repository. - -## When To Use It - -Start here when you want Codex, Claude Code, or another coding agent to work -across multiple sessions without constantly re-deriving setup, status, and -scope. - -It is especially useful when: - -- work spans multiple sessions -- features are numerous and easy to leave half-finished -- agents tend to declare victory too early -- startup steps are rediscovered every time - -## Start Here - -For a minimal setup, begin with: - -- root instructions: [`templates/AGENTS.md`](./templates/AGENTS.md) or [`templates/CLAUDE.md`](./templates/CLAUDE.md) -- feature state: [`templates/feature_list.json`](./templates/feature_list.json) -- progress log: [`templates/claude-progress.md`](./templates/claude-progress.md) -- bootstrap script reference: `docs/en/resources/templates/init.sh` - -Then add: - -- session handoff: [`templates/session-handoff.md`](./templates/session-handoff.md) -- clean-exit checklist: [`templates/clean-state-checklist.md`](./templates/clean-state-checklist.md) -- evaluator rubric: [`templates/evaluator-rubric.md`](./templates/evaluator-rubric.md) - -If you want the fuller OpenAI-style repository structure from the -"Harness engineering" post, use the advanced pack: - -- [`openai-advanced/index.md`](./openai-advanced/index.md) - -## Library Structure - -- [`templates/`](./templates/index.md): templates to copy into a real repo -- [`reference/`](./reference/index.md): method notes, startup flow, and failure-mode maps -- [`openai-advanced/`](./openai-advanced/index.md): advanced repo skeleton, - system-of-record docs, and agent-first governance templates - -## Recommended Minimal Pack - -- `AGENTS.md` or `CLAUDE.md` -- `feature_list.json` -- `claude-progress.md` -- `init.sh` - -Those four files are enough to make most agent workflows noticeably more stable. - -When the repo grows into a longer-running system with multiple domains, active -plans, quality scoring, and reliability policies, move up to the -[`openai-advanced/`](./openai-advanced/index.md) pack instead of stretching the -minimal pack too far. diff --git a/courses/learn-harness-engineering/docs/en/resources/openai-advanced/index.md b/courses/learn-harness-engineering/docs/en/resources/openai-advanced/index.md deleted file mode 100644 index c89b786a..00000000 --- a/courses/learn-harness-engineering/docs/en/resources/openai-advanced/index.md +++ /dev/null @@ -1,83 +0,0 @@ -# OpenAI Advanced Pack - -This folder packages the more opinionated repository shape described in -OpenAI's "Harness engineering: leveraging Codex in an agent-first world" -article into copy-ready starter files. - -Use this pack when the minimal harness is no longer enough and your repository -now needs: - -- a short routing-style `AGENTS.md` -- durable system-of-record docs inside the repo -- active and completed execution plans -- explicit product, reliability, security, and frontend policy files -- quality scoring by product domain and architectural layer -- model-friendly reference material folders -- standard operating procedures for architecture, knowledge capture, and runtime validation - -## Included Starter Layout - -The starter pack under [`repo-template/`](./repo-template/index.md) mirrors the -structure below: - -```text -AGENTS.md -ARCHITECTURE.md -docs/ -├── design-docs/ -│ ├── index.md -│ └── core-beliefs.md -├── exec-plans/ -│ ├── active/ -│ ├── completed/ -│ └── tech-debt-tracker.md -├── generated/ -│ └── db-schema.md -├── product-specs/ -│ ├── index.md -│ └── new-user-onboarding.md -├── references/ -│ ├── design-system-reference-llms.txt -│ ├── nixpacks-llms.txt -│ └── uv-llms.txt -├── DESIGN.md -├── FRONTEND.md -├── PLANS.md -├── PRODUCT_SENSE.md -├── QUALITY_SCORE.md -├── RELIABILITY.md -└── SECURITY.md -``` - -## How To Adopt It - -1. Start from the minimal pack if your repo is still small. -2. Copy the files in [`repo-template/`](./repo-template/index.md) into your - own repository once you need stronger structure. -3. Keep `AGENTS.md` short. Treat it as a router into the deeper docs, not as an - encyclopedia. -4. Update the quality, reliability, and plan docs as part of normal work, not - as a separate cleanup day. -5. Keep generated artifacts and external references explicit so agents can find - them without relying on chat history. - -## SOP Library - -The [`sops/`](./sops/index.md) folder turns the article's diagrams into -step-by-step operating procedures: - -- layered domain architecture setup -- encode unseen knowledge into the repository -- local observability stack and feedback-loop workflow -- Chrome DevTools validation loop for UI work - -## Design Principles - -- Short entrypoint, deeper linked docs -- Repository as system of record -- Mechanical checks beat remembered rules -- Plans and quality history live beside the code -- Cleanup and simplification are first-class responsibilities - -This pack is intentionally opinionated, but it should still be adapted to your -project rather than copied blindly. diff --git a/courses/learn-harness-engineering/docs/en/resources/openai-advanced/repo-template/AGENTS.md b/courses/learn-harness-engineering/docs/en/resources/openai-advanced/repo-template/AGENTS.md deleted file mode 100644 index 83900528..00000000 --- a/courses/learn-harness-engineering/docs/en/resources/openai-advanced/repo-template/AGENTS.md +++ /dev/null @@ -1,61 +0,0 @@ -# AGENTS.md - -This repository is optimized for long-running coding-agent work. Keep this file -short. Use it as the routing layer into the system-of-record docs, not as a -giant instruction dump. - -## Startup Workflow - -Before changing code: - -1. Confirm the repo root with `pwd`. -2. Read `ARCHITECTURE.md` for the current system map and hard dependency rules. -3. Read `docs/QUALITY_SCORE.md` to see which domains or layers are weakest. -4. Read `docs/PLANS.md`, then open the active plan you are working from. -5. Read the relevant product spec in `docs/product-specs/`. -6. Run the standard bootstrap and verification path for this repo. -7. If baseline verification is failing, repair the baseline before adding scope. - -## Routing Map - -- `ARCHITECTURE.md`: domain map, layer model, dependency rules -- `docs/design-docs/index.md`: design decisions and core beliefs -- `docs/product-specs/index.md`: current product behaviors and acceptance targets -- `docs/PLANS.md`: plan lifecycle and execution-plan policy -- `docs/QUALITY_SCORE.md`: product-domain and layer health -- `docs/RELIABILITY.md`: runtime signals, benchmarks, and restart expectations -- `docs/SECURITY.md`: secrets, sandbox, data, and external-action rules -- `docs/FRONTEND.md`: UI constraints, design system rules, accessibility checks - -## Working Contract - -- Work from one bounded plan or feature slice at a time. -- Do not mark work done from code inspection alone; runnable evidence is - required. -- If you change behavior, update the matching product, plan, or reliability - docs in the same session. -- If you see repeated review feedback, promote it into a mechanical rule, check, - or linter instead of re-explaining it in chat. -- Keep generated material in `docs/generated/` and source references in - `docs/references/`. -- Prefer adding small, current docs over growing this file. - -## Definition Of Done - -A change is done only when all of the following are true: - -- target behavior is implemented -- required verification actually ran -- evidence is linked from the relevant plan or quality document -- affected docs remain current -- the repository can restart cleanly from the standard startup path - -## End Of Session - -Before ending a session: - -1. Update the active execution plan. -2. Update `docs/QUALITY_SCORE.md` if any domain or layer meaningfully changed. -3. Record new debt in `docs/exec-plans/tech-debt-tracker.md` if you deferred it. -4. Move finished plans to `docs/exec-plans/completed/` when appropriate. -5. Leave the repo in a restartable state with a clear next action. diff --git a/courses/learn-harness-engineering/docs/en/resources/openai-advanced/repo-template/ARCHITECTURE.md b/courses/learn-harness-engineering/docs/en/resources/openai-advanced/repo-template/ARCHITECTURE.md deleted file mode 100644 index 574920dd..00000000 --- a/courses/learn-harness-engineering/docs/en/resources/openai-advanced/repo-template/ARCHITECTURE.md +++ /dev/null @@ -1,57 +0,0 @@ -# ARCHITECTURE.md - -This file is the top-level map of the system. It should stay concise and point -to deeper documents when needed. - -## System Shape - -- Product: `[replace with product name]` -- Primary user workflow: `[replace with main workflow]` -- Runtime surfaces: `[desktop / web / cli / services / workers]` -- Source of truth for product behavior: `docs/product-specs/` - -## Domain Map - -| Domain | Purpose | Primary Entry Points | Related Spec | -|--------|---------|----------------------|--------------| -| `[domain-a]` | `[what it owns]` | `[modules / routes / commands]` | `[spec path]` | -| `[domain-b]` | `[what it owns]` | `[modules / routes / commands]` | `[spec path]` | - -## Layer Model - -Use a fixed directional model so agents do not invent ad hoc architecture: - -`Types -> Config -> Repo -> Service -> Runtime -> UI` - -Cross-cutting concerns should enter through explicit provider or adapter -boundaries instead of reaching across layers directly. - -## Hard Dependency Rules - -- Lower layers must not depend on higher layers. -- UI must not bypass runtime or service contracts. -- Data access must enter through repositories or equivalent adapters. -- Shared utilities must remain generic and must not accumulate domain logic. -- New dependencies should be justified in the matching plan or design doc. - -## Cross-Cutting Interfaces - -| Concern | Approved Boundary | Notes | -|--------|-------------------|-------| -| Logging and tracing | `[provider / utility path]` | `[structured only, no ad hoc console use]` | -| Auth | `[provider path]` | `[token/session rules]` | -| External APIs | `[client or provider path]` | `[rate limit / retry guidance]` | -| Feature flags | `[flag boundary]` | `[ownership]` | - -## Current Hot Spots - -- `[area that is hardest for agents to change safely]` -- `[area with weak boundaries or fragile tests]` - -## Change Checklist - -When you touch architecture-relevant code: - -1. Update this file if the domain map or allowed boundaries changed. -2. Update the related design doc in `docs/design-docs/` if the reasoning changed. -3. Add or update an executable check if the rule should be enforced mechanically. diff --git a/courses/learn-harness-engineering/docs/en/resources/openai-advanced/repo-template/docs/DESIGN.md b/courses/learn-harness-engineering/docs/en/resources/openai-advanced/repo-template/docs/DESIGN.md deleted file mode 100644 index 46bf163d..00000000 --- a/courses/learn-harness-engineering/docs/en/resources/openai-advanced/repo-template/docs/DESIGN.md +++ /dev/null @@ -1,28 +0,0 @@ -# DESIGN.md - -This file is the design entrypoint. Keep it brief and use it to route into the -more detailed files under `docs/design-docs/`. - -## Purpose - -Record durable product and system design decisions that should survive beyond a -single chat, sprint, or reviewer memory. - -## Read This When - -- you need the current design philosophy -- you are about to introduce a new pattern -- you need to know which design decisions are settled versus still open - -## Canonical Design Docs - -- `docs/design-docs/index.md`: index of accepted, proposed, and deprecated docs -- `docs/design-docs/core-beliefs.md`: project-wide agent-first beliefs - -## Design Rules - -- Keep design docs small and current. -- Prefer one doc per decision area. -- Link design docs from plans and specs when a change depends on them. -- If a design rule becomes operationally critical, promote it into an automated - check or update `ARCHITECTURE.md`. diff --git a/courses/learn-harness-engineering/docs/en/resources/openai-advanced/repo-template/docs/FRONTEND.md b/courses/learn-harness-engineering/docs/en/resources/openai-advanced/repo-template/docs/FRONTEND.md deleted file mode 100644 index 05cae78d..00000000 --- a/courses/learn-harness-engineering/docs/en/resources/openai-advanced/repo-template/docs/FRONTEND.md +++ /dev/null @@ -1,24 +0,0 @@ -# FRONTEND.md - -This file defines stable frontend expectations so agents do not invent UI -patterns unpredictably. - -## UI Principles - -- Optimize for clarity before novelty. -- Keep interaction flows discoverable and restartable. -- Prefer a small number of reusable components over one-off variants. -- Accessibility checks are part of normal verification, not polish work. - -## Guardrails - -- Document the design system or component library in `docs/references/`. -- Record key user-facing states: empty, loading, success, error, retry. -- Keep copy, keyboard behavior, and visual hierarchy consistent across flows. -- When a UI bug is fixed, add or update the matching validation step. - -## Verification Expectations - -- Capture evidence for critical user journeys. -- Record browser or runtime validation steps in the relevant plan. -- If visual regressions are common, standardize screenshot or DOM checks. diff --git a/courses/learn-harness-engineering/docs/en/resources/openai-advanced/repo-template/docs/PLANS.md b/courses/learn-harness-engineering/docs/en/resources/openai-advanced/repo-template/docs/PLANS.md deleted file mode 100644 index dd9729e0..00000000 --- a/courses/learn-harness-engineering/docs/en/resources/openai-advanced/repo-template/docs/PLANS.md +++ /dev/null @@ -1,35 +0,0 @@ -# PLANS.md - -This file defines how execution plans are created, updated, completed, and -archived. - -## When A Plan Is Required - -Create an execution plan when work: - -- spans more than one session -- changes more than one subsystem -- has non-trivial verification or rollout risk -- depends on open decisions that should be logged - -## Plan Locations - -- `docs/exec-plans/active/`: plans currently driving work -- `docs/exec-plans/completed/`: finished plans kept for future agent context -- `docs/exec-plans/tech-debt-tracker.md`: deferred work and follow-ups - -## Minimum Plan Sections - -- objective -- scope and out-of-scope -- verification path -- risks and blockers -- progress log -- open decisions - -## Operating Rules - -- One active plan should have one clearly owned current step. -- Update the plan as work progresses; do not treat it as static prose. -- If a decision changes implementation direction, record it in the plan. -- Move finished plans to `completed/` so agents can still discover prior context. diff --git a/courses/learn-harness-engineering/docs/en/resources/openai-advanced/repo-template/docs/PRODUCT_SENSE.md b/courses/learn-harness-engineering/docs/en/resources/openai-advanced/repo-template/docs/PRODUCT_SENSE.md deleted file mode 100644 index cb5c6645..00000000 --- a/courses/learn-harness-engineering/docs/en/resources/openai-advanced/repo-template/docs/PRODUCT_SENSE.md +++ /dev/null @@ -1,26 +0,0 @@ -# PRODUCT_SENSE.md - -This file captures durable product judgment that agents cannot infer reliably -from code alone. - -## Product Core - -- Primary user: `[replace]` -- Job to be done: `[replace]` -- Main frustration to remove: `[replace]` -- Quality bar for acceptance: `[replace]` - -## Product Rules - -- Favor user-visible reliability over feature count. -- Treat ambiguous behavior as a spec gap, not as permission to guess. -- If implementation changes what users see or trust, update the matching spec. -- Use product specs for concrete flows, and use this file for cross-cutting - product priorities. - -## No-Go Patterns - -- Hidden destructive actions -- Silent failure without user feedback -- Unclear source of truth for visible state -- Features that cannot be explained in one sentence diff --git a/courses/learn-harness-engineering/docs/en/resources/openai-advanced/repo-template/docs/QUALITY_SCORE.md b/courses/learn-harness-engineering/docs/en/resources/openai-advanced/repo-template/docs/QUALITY_SCORE.md deleted file mode 100644 index 02674df4..00000000 --- a/courses/learn-harness-engineering/docs/en/resources/openai-advanced/repo-template/docs/QUALITY_SCORE.md +++ /dev/null @@ -1,40 +0,0 @@ -# QUALITY_SCORE.md - -This document tracks whether the repository is getting stronger or weaker over -time. - -## Grading Scale - -- `A`: verified, legible, stable, boundaries enforced -- `B`: working with minor gaps -- `C`: partially working, notable confusion or instability -- `D`: broken, unsafe, or structurally unclear - -## Product Domains - -| Domain | Grade | Verification | Agent Legibility | Test Stability | Key Gaps | Last Updated | -|--------|-------|-------------|-----------------|---------------|----------|-------------| -| `[domain-a]` | - | - | - | - | - | - | -| `[domain-b]` | - | - | - | - | - | - | -| `[domain-c]` | - | - | - | - | - | - | - -## Architectural Layers - -| Layer | Grade | Boundary Enforcement | Agent Legibility | Key Gaps | Last Updated | -|-------|-------|---------------------|-----------------|----------|-------------| -| Types | - | - | - | - | - | -| Services | - | - | - | - | - | -| Runtime | - | - | - | - | - | -| UI | - | - | - | - | - | - -## Benchmark Snapshots - -| Date | Harness Variant | Completion Rate | Retries | Defects Before Review | Notes | -|------|-----------------|----------------|--------|-----------------------|------| -| YYYY-MM-DD | `[baseline / improved / simplified]` | - | - | - | - | - -## Simplification Log - -| Date | Component Removed | Outcome | Decision | -|------|-------------------|---------|----------| -| YYYY-MM-DD | `[component]` | `[degraded / unchanged]` | `[restore / keep removed]` | diff --git a/courses/learn-harness-engineering/docs/en/resources/openai-advanced/repo-template/docs/RELIABILITY.md b/courses/learn-harness-engineering/docs/en/resources/openai-advanced/repo-template/docs/RELIABILITY.md deleted file mode 100644 index df90e357..00000000 --- a/courses/learn-harness-engineering/docs/en/resources/openai-advanced/repo-template/docs/RELIABILITY.md +++ /dev/null @@ -1,33 +0,0 @@ -# RELIABILITY.md - -This file defines how the system proves it is healthy and restartable. - -## Standard Paths - -- Bootstrap: `[command]` -- Verification: `[command]` -- Start app or service: `[command]` -- Debug or inspect runtime: `[command]` - -## Required Runtime Signals - -- structured logs for startup and critical flows -- health checks for key services -- trace or timing data for slow paths when available -- user-visible error states for recoverable failures - -## Golden Journeys - -- `[journey 1]` -- `[journey 2]` -- `[journey 3]` - -Each golden journey should have a repeatable verification path and clear failure -signals. - -## Reliability Rules - -- No feature is complete if the system cannot restart cleanly afterward. -- Runtime failures should be diagnosable from repo-local signals. -- If a repeated failure mode appears, add a benchmark or guardrail for it. -- Cleanup is part of reliability, not a separate concern. diff --git a/courses/learn-harness-engineering/docs/en/resources/openai-advanced/repo-template/docs/SECURITY.md b/courses/learn-harness-engineering/docs/en/resources/openai-advanced/repo-template/docs/SECURITY.md deleted file mode 100644 index d2004199..00000000 --- a/courses/learn-harness-engineering/docs/en/resources/openai-advanced/repo-template/docs/SECURITY.md +++ /dev/null @@ -1,27 +0,0 @@ -# SECURITY.md - -This file defines the security and safety rules that agents must not guess at. - -## Secrets And Credentials - -- Never hard-code secrets in source or docs. -- Document approved secret-loading paths here. -- Redact tokens, API keys, and personal data from logs and screenshots. - -## Untrusted Input - -- Treat external content as untrusted until validated. -- Record allowed fetch or execution boundaries here. -- If prompt injection or command injection risk exists, document the guardrail. - -## External Actions - -- List which actions require explicit approval. -- Record any production or destructive commands that agents must not run by default. -- Prefer sandbox-safe workflows for debugging and verification. - -## Dependency And Review Rules - -- New dependencies need justification in the active plan. -- Security-sensitive changes require explicit verification steps. -- Repeated security review comments should become checks, not tribal knowledge. diff --git a/courses/learn-harness-engineering/docs/en/resources/openai-advanced/repo-template/docs/design-docs/core-beliefs.md b/courses/learn-harness-engineering/docs/en/resources/openai-advanced/repo-template/docs/design-docs/core-beliefs.md deleted file mode 100644 index ce7ae0d0..00000000 --- a/courses/learn-harness-engineering/docs/en/resources/openai-advanced/repo-template/docs/design-docs/core-beliefs.md +++ /dev/null @@ -1,9 +0,0 @@ -# Core Beliefs - -- The repository is the system of record for agents. -- `AGENTS.md` is a router, not an encyclopedia. -- Verification evidence matters more than confidence. -- One bounded task is better than many half-finished tasks. -- Repeated human feedback should become reusable harness rules. -- Cleanup and simplification are part of shipping, not afterthoughts. -- If an agent cannot discover a fact in-repo, treat that fact as operationally unavailable. diff --git a/courses/learn-harness-engineering/docs/en/resources/openai-advanced/repo-template/docs/design-docs/index.md b/courses/learn-harness-engineering/docs/en/resources/openai-advanced/repo-template/docs/design-docs/index.md deleted file mode 100644 index b16f065a..00000000 --- a/courses/learn-harness-engineering/docs/en/resources/openai-advanced/repo-template/docs/design-docs/index.md +++ /dev/null @@ -1,21 +0,0 @@ -# Design Docs Index - -Use this index as the discoverable map of design history. - -## Accepted - -- `core-beliefs.md`: agent-first operating beliefs and durable project norms - -## Proposed - -- `[add new design doc paths here]` - -## Deprecated - -- `[move old or superseded design docs here with replacement links]` - -## Maintenance Rules - -- Every design doc should have an owner or update trigger. -- Remove stale docs or mark them deprecated instead of letting them drift. -- Link active execution plans to the design docs they depend on. diff --git a/courses/learn-harness-engineering/docs/en/resources/openai-advanced/repo-template/docs/exec-plans/active/index.md b/courses/learn-harness-engineering/docs/en/resources/openai-advanced/repo-template/docs/exec-plans/active/index.md deleted file mode 100644 index 4f60638a..00000000 --- a/courses/learn-harness-engineering/docs/en/resources/openai-advanced/repo-template/docs/exec-plans/active/index.md +++ /dev/null @@ -1,10 +0,0 @@ -# Active Plans - -Keep one markdown file per active execution plan in this folder. - -Suggested filename pattern: - -- `YYYY-MM-DD-short-topic.md` - -Each active plan should be current enough that a fresh agent session can resume -work from the repository alone. diff --git a/courses/learn-harness-engineering/docs/en/resources/openai-advanced/repo-template/docs/exec-plans/completed/index.md b/courses/learn-harness-engineering/docs/en/resources/openai-advanced/repo-template/docs/exec-plans/completed/index.md deleted file mode 100644 index 256f03f3..00000000 --- a/courses/learn-harness-engineering/docs/en/resources/openai-advanced/repo-template/docs/exec-plans/completed/index.md +++ /dev/null @@ -1,5 +0,0 @@ -# Completed Plans - -Move finished plans here instead of deleting them. Completed plans are part of -the repository memory surface and help later agent runs understand why the code -looks the way it does. diff --git a/courses/learn-harness-engineering/docs/en/resources/openai-advanced/repo-template/docs/exec-plans/tech-debt-tracker.md b/courses/learn-harness-engineering/docs/en/resources/openai-advanced/repo-template/docs/exec-plans/tech-debt-tracker.md deleted file mode 100644 index e92b6bc4..00000000 --- a/courses/learn-harness-engineering/docs/en/resources/openai-advanced/repo-template/docs/exec-plans/tech-debt-tracker.md +++ /dev/null @@ -1,7 +0,0 @@ -# Tech Debt Tracker - -Use this file for debt that is real, acknowledged, and intentionally deferred. - -| Date | Area | Debt | Why Deferred | Risk | Next Trigger | -|------|------|------|--------------|------|--------------| -| YYYY-MM-DD | `[area]` | `[debt]` | `[reason]` | `[risk]` | `[when to revisit]` | diff --git a/courses/learn-harness-engineering/docs/en/resources/openai-advanced/repo-template/docs/generated/db-schema.md b/courses/learn-harness-engineering/docs/en/resources/openai-advanced/repo-template/docs/generated/db-schema.md deleted file mode 100644 index 06141597..00000000 --- a/courses/learn-harness-engineering/docs/en/resources/openai-advanced/repo-template/docs/generated/db-schema.md +++ /dev/null @@ -1,14 +0,0 @@ -# Database Schema - -Use this folder for generated or derived artifacts that agents should be able -to inspect without reverse-engineering them from code. - -## Source - -- Generated from: `[command or source path]` -- Last refreshed: `YYYY-MM-DD` - -## Notes - -- Do not hand-edit generated sections. -- Regenerate this file when the underlying schema changes. diff --git a/courses/learn-harness-engineering/docs/en/resources/openai-advanced/repo-template/docs/product-specs/index.md b/courses/learn-harness-engineering/docs/en/resources/openai-advanced/repo-template/docs/product-specs/index.md deleted file mode 100644 index ad6e90cc..00000000 --- a/courses/learn-harness-engineering/docs/en/resources/openai-advanced/repo-template/docs/product-specs/index.md +++ /dev/null @@ -1,14 +0,0 @@ -# Product Specs Index - -Use this folder for current user-facing behavior specs. - -## Active Specs - -- `new-user-onboarding.md` - -## Rules - -- Specs should describe user-visible behavior and acceptance criteria. -- If implementation diverges from the spec, update one of them in the same - session. -- Keep this index current so a fresh agent can discover product scope quickly. diff --git a/courses/learn-harness-engineering/docs/en/resources/openai-advanced/repo-template/docs/product-specs/new-user-onboarding.md b/courses/learn-harness-engineering/docs/en/resources/openai-advanced/repo-template/docs/product-specs/new-user-onboarding.md deleted file mode 100644 index b985494f..00000000 --- a/courses/learn-harness-engineering/docs/en/resources/openai-advanced/repo-template/docs/product-specs/new-user-onboarding.md +++ /dev/null @@ -1,26 +0,0 @@ -# New User Onboarding - -## Goal - -Describe the first-run experience a new user should have. - -## Entry Conditions - -- `[state before the flow starts]` - -## User Flow - -1. `[step one]` -2. `[step two]` -3. `[step three]` - -## Acceptance Criteria - -- `[observable outcome]` -- `[observable outcome]` -- `[observable outcome]` - -## Failure States - -- `[recoverable error and user feedback]` -- `[blocked state and fallback]` diff --git a/courses/learn-harness-engineering/docs/en/resources/openai-advanced/repo-template/docs/references/design-system-reference-llms.txt b/courses/learn-harness-engineering/docs/en/resources/openai-advanced/repo-template/docs/references/design-system-reference-llms.txt deleted file mode 100644 index 6f1c612c..00000000 --- a/courses/learn-harness-engineering/docs/en/resources/openai-advanced/repo-template/docs/references/design-system-reference-llms.txt +++ /dev/null @@ -1,9 +0,0 @@ -Purpose: store model-friendly reference material for the design system. - -Suggested contents: -- component naming rules -- spacing and typography tokens -- state variants -- accessibility expectations - -Keep this file concise and refresh it when the upstream design system changes. diff --git a/courses/learn-harness-engineering/docs/en/resources/openai-advanced/repo-template/docs/references/nixpacks-llms.txt b/courses/learn-harness-engineering/docs/en/resources/openai-advanced/repo-template/docs/references/nixpacks-llms.txt deleted file mode 100644 index c30cc9c2..00000000 --- a/courses/learn-harness-engineering/docs/en/resources/openai-advanced/repo-template/docs/references/nixpacks-llms.txt +++ /dev/null @@ -1,8 +0,0 @@ -Purpose: store a clean agent-readable extract of the deployment or packaging -rules your repository depends on. - -Suggested contents: -- build entrypoints -- runtime assumptions -- environment variable expectations -- common failure signatures diff --git a/courses/learn-harness-engineering/docs/en/resources/openai-advanced/repo-template/docs/references/uv-llms.txt b/courses/learn-harness-engineering/docs/en/resources/openai-advanced/repo-template/docs/references/uv-llms.txt deleted file mode 100644 index f2ceba9a..00000000 --- a/courses/learn-harness-engineering/docs/en/resources/openai-advanced/repo-template/docs/references/uv-llms.txt +++ /dev/null @@ -1,8 +0,0 @@ -Purpose: store a compact reference for your Python package and environment -workflow when `uv` or similar tooling matters to the repo. - -Suggested contents: -- install and sync commands -- lockfile policy -- virtualenv expectations -- verification commands diff --git a/courses/learn-harness-engineering/docs/en/resources/openai-advanced/repo-template/index.md b/courses/learn-harness-engineering/docs/en/resources/openai-advanced/repo-template/index.md deleted file mode 100644 index 28ba73f9..00000000 --- a/courses/learn-harness-engineering/docs/en/resources/openai-advanced/repo-template/index.md +++ /dev/null @@ -1,24 +0,0 @@ -# Advanced Repo Template - -Copy this starter into a real repository when you want an OpenAI-style -agent-first documentation surface instead of only a minimal harness. - -## Copy Order - -1. Copy `AGENTS.md` and `ARCHITECTURE.md` to the repo root. -2. Copy the whole `docs/` tree. -3. Fill in `docs/PRODUCT_SENSE.md`, `docs/QUALITY_SCORE.md`, and - `docs/RELIABILITY.md` first. -4. Add your first active plan under `docs/exec-plans/active/`. -5. Keep the entrypoint files short and route detail into the linked docs. - -## What This Template Optimizes For - -- durable repo-local context -- progressive disclosure instead of one giant instruction file -- explicit plan lifecycle -- quality tracking over time -- readable boundaries for agents and humans - -Treat every file here as a starter. Replace placeholders, examples, and sample -commands with your real project specifics before relying on it. diff --git a/courses/learn-harness-engineering/docs/en/resources/openai-advanced/sops/chrome-devtools-validation-loop.md b/courses/learn-harness-engineering/docs/en/resources/openai-advanced/sops/chrome-devtools-validation-loop.md deleted file mode 100644 index 409c9316..00000000 --- a/courses/learn-harness-engineering/docs/en/resources/openai-advanced/sops/chrome-devtools-validation-loop.md +++ /dev/null @@ -1,51 +0,0 @@ -# SOP: Chrome DevTools Validation Loop - -Use this SOP when UI work depends on actual runtime interaction and screenshots, -DOM state, and console output matter more than code inspection alone. - -## Goal - -Turn UI validation into a repeatable interaction loop the agent can run until -the journey is clean. - -## Core Loop - -1. Select the target page or app instance. -2. Clear stale console noise. -3. Capture the BEFORE state. -4. Trigger the UI path. -5. Observe runtime events during interaction. -6. Capture the AFTER state. -7. Apply the fix and restart the app if needed. -8. Re-run validation until the journey is clean. - -## Required Inputs - -- a stable startup command -- a reproducible UI journey -- a way to snapshot DOM, console, or screenshots -- a rule for what counts as "clean" - -## Execution SOP - -1. Write the target journey in the active plan. -2. Define success in observable terms: text present, button enabled, error gone, - console clean, request succeeded. -3. Snapshot the initial state before interaction. -4. Trigger exactly one path at a time. -5. Record runtime events, DOM changes, and visible output. -6. If the journey fails, fix the smallest responsible layer and restart. -7. Re-run the same path and compare BEFORE/AFTER evidence. - -## Clean Criteria - -- intended visible state is present -- unexpected errors are absent -- console noise is understood or cleared -- rerunning the same path gives the same result - -## Repo Artifacts To Update - -- active execution plan -- `docs/RELIABILITY.md` if the journey becomes a golden path -- product spec if the visible behavior changed diff --git a/courses/learn-harness-engineering/docs/en/resources/openai-advanced/sops/encode-knowledge-into-repo.md b/courses/learn-harness-engineering/docs/en/resources/openai-advanced/sops/encode-knowledge-into-repo.md deleted file mode 100644 index 70e7c188..00000000 --- a/courses/learn-harness-engineering/docs/en/resources/openai-advanced/sops/encode-knowledge-into-repo.md +++ /dev/null @@ -1,45 +0,0 @@ -# SOP: Encode Unseen Knowledge Into The Repo - -Use this SOP when important context still lives in Google Docs, chat threads, -tickets, or people's heads. - -## Goal - -Make agent-invisible knowledge discoverable in the codebase so a fresh session -can act on it without relying on prior conversation. - -## Trigger Signals - -- The agent keeps asking how the system works. -- Humans say "we decided this in Slack" or "follow what X said last week." -- Reviews reference product or security rules that are not written in-repo. -- New sessions repeat discovery work that should already be settled. - -## Execution SOP - -1. List the invisible knowledge sources: docs, chats, tacit team rules, verbal decisions. -2. For each source, ask: is this architecture, product behavior, security policy, - reliability expectation, plan context, or reference material? -3. Encode it into the matching repo artifact: - - architecture -> `ARCHITECTURE.md` - - product behavior -> `docs/product-specs/` - - design rationale -> `docs/design-docs/` - - execution state -> `docs/exec-plans/` - - repeated external references -> `docs/references/` - - quality or reliability expectations -> `docs/QUALITY_SCORE.md` or `docs/RELIABILITY.md` -4. Replace vague statements with operationally useful wording. -5. Remove or deprecate stale copies so the repo keeps one discoverable truth. - -## Good Encoding Rules - -- Write for discoverability, not for literary completeness. -- Prefer short documents with clear filenames. -- Link related artifacts together. -- Store durable rules, not meeting transcripts. -- Update the repo in the same session that the decision is made. - -## Definition Of Done - -- A fresh agent can discover the relevant rule without asking a human. -- The same fact is not scattered across multiple contradictory files. -- The new artifact lives close to the code or workflow it governs. diff --git a/courses/learn-harness-engineering/docs/en/resources/openai-advanced/sops/index.md b/courses/learn-harness-engineering/docs/en/resources/openai-advanced/sops/index.md deleted file mode 100644 index 23854bcc..00000000 --- a/courses/learn-harness-engineering/docs/en/resources/openai-advanced/sops/index.md +++ /dev/null @@ -1,25 +0,0 @@ -# OpenAI Advanced SOPs - -These SOPs translate the article's operating patterns into concrete execution -playbooks you can follow or adapt. - -## Included SOPs - -- [`layered-domain-architecture.md`](./layered-domain-architecture.md): - establish explicit layers and cross-cutting boundaries -- [`encode-knowledge-into-repo.md`](./encode-knowledge-into-repo.md): - move invisible knowledge from chat, docs, and memory into repo-local files -- [`observability-feedback-loop.md`](./observability-feedback-loop.md): - give agents logs, metrics, traces, and a repeatable debug loop -- [`chrome-devtools-validation-loop.md`](./chrome-devtools-validation-loop.md): - use browser automation and snapshots to validate UI behavior until clean - -## How To Use Them - -1. Pick the SOP that matches your current bottleneck. -2. Use the checklist to set up the missing artifacts or tooling. -3. Encode the resulting rules into your copied `repo-template/` docs. -4. Convert repeated review comments into checks, scripts, or guardrails. - -These are not meant to be followed blindly. They are meant to make the harness -more legible, enforceable, and repeatable. diff --git a/courses/learn-harness-engineering/docs/en/resources/openai-advanced/sops/layered-domain-architecture.md b/courses/learn-harness-engineering/docs/en/resources/openai-advanced/sops/layered-domain-architecture.md deleted file mode 100644 index 40198375..00000000 --- a/courses/learn-harness-engineering/docs/en/resources/openai-advanced/sops/layered-domain-architecture.md +++ /dev/null @@ -1,50 +0,0 @@ -# SOP: Layered Domain Architecture - -Use this SOP when the agent keeps violating boundaries, duplicating logic across -layers, or producing code that becomes hard to review after a few sessions. - -## Goal - -Make domain boundaries explicit enough that agents can move quickly without -silently degrading structure. - -## Target Model - -Within a business domain, prefer this directional flow: - -`Types -> Config -> Repo -> Service -> Runtime -> UI` - -Cross-cutting concerns should enter through explicit providers or adapters. -Shared utils stay outside the domain and should not accumulate domain logic. - -## Setup Checklist - -- Define the current domains in `ARCHITECTURE.md`. -- Write allowed dependency directions in `ARCHITECTURE.md`. -- Record cross-cutting interfaces such as auth, telemetry, and external APIs. -- Add one short note for the hardest current boundary violation. -- Decide what should be enforced mechanically by lint, tests, or scripts. - -## Execution SOP - -1. Map the codebase into domains before touching implementation style. -2. For each domain, identify the allowed layer sequence. -3. Identify all cross-cutting concerns and route them through providers or adapters. -4. Move ambiguous shared logic either into the owning domain or into truly generic utils. -5. Document the rules in `ARCHITECTURE.md`. -6. Add one executable guardrail for the highest-cost violation. -7. Update quality scoring after the change. - -## Definition Of Done - -- A fresh agent can tell which layer owns a change. -- UI code no longer reaches into repo or external side effects directly. -- Cross-cutting concerns have named entry points. -- At least one important boundary is enforced mechanically. - -## Repo Artifacts To Update - -- `ARCHITECTURE.md` -- `docs/QUALITY_SCORE.md` -- `docs/design-docs/` when the rationale changed -- `docs/PLANS.md` or the active execution plan diff --git a/courses/learn-harness-engineering/docs/en/resources/openai-advanced/sops/observability-feedback-loop.md b/courses/learn-harness-engineering/docs/en/resources/openai-advanced/sops/observability-feedback-loop.md deleted file mode 100644 index 478ce883..00000000 --- a/courses/learn-harness-engineering/docs/en/resources/openai-advanced/sops/observability-feedback-loop.md +++ /dev/null @@ -1,44 +0,0 @@ -# SOP: Observability Feedback Loop - -Use this SOP when debugging is slow, agents keep claiming success without -evidence, or runtime behavior is harder to inspect than the code itself. - -## Goal - -Give the agent a local feedback loop over logs, metrics, traces, and runnable -workloads so it can reason from execution, not only from code inspection. - -## Minimum Stack - -- application emits structured logs -- application emits metrics and traces when feasible -- local fan-out or collection layer -- query interfaces for logs, metrics, and traces -- repeatable workload or user journey to rerun after each change - -## Execution SOP - -1. Define the golden runtime journeys that matter most. -2. Add structured logs to startup and the critical path. -3. Add metrics for latency, failure counts, or queue depth where useful. -4. Add traces or timing markers for slow or multi-step flows. -5. Make the signals queryable from the local dev environment. -6. Give the agent one repeatable workload or scenario to rerun. -7. Require the loop: query -> correlate -> reason -> implement -> restart -> - rerun -> verify. - -## Debug Session Checklist - -- What failed? -- Which signal proves the failure? -- Which layer owns the failure? -- What changed after the fix? -- Did the app restart cleanly? -- Did the same workload pass after rerun? - -## Definition Of Done - -- The agent can explain a failure mode from runtime evidence. -- The same workload can be rerun after each change. -- Restart and rerun are part of the normal task loop. -- Reliability signals are documented in `docs/RELIABILITY.md`. diff --git a/courses/learn-harness-engineering/docs/en/resources/reference/coding-agent-startup-flow.md b/courses/learn-harness-engineering/docs/en/resources/reference/coding-agent-startup-flow.md deleted file mode 100644 index b4d87819..00000000 --- a/courses/learn-harness-engineering/docs/en/resources/reference/coding-agent-startup-flow.md +++ /dev/null @@ -1,34 +0,0 @@ -# Coding Agent Startup Flow - -Use this at the beginning of every session after initialization is complete. - -## Fixed Startup Template - -1. Run `pwd` and confirm the repository root. -2. Read `claude-progress.md`. -3. Read `feature_list.json`. -4. Review recent commits with `git log --oneline -5`. -5. Run `./init.sh`. -6. Run a baseline smoke or end-to-end path. -7. If the baseline is broken, fix that first. -8. Select the highest-priority unfinished feature. -9. Work only on that feature until it is verified or explicitly blocked. - -## Why This Order Matters - -- `pwd` prevents accidental work in the wrong directory. -- progress and feature files recover durable state before new edits begin. -- recent commits explain what changed most recently. -- `init.sh` standardizes startup instead of relying on memory. -- baseline verification catches broken starting states before new work hides - them. - -## End-Of-Session Mirror - -The same session should end by: - -1. recording progress -2. updating feature state -3. writing a handoff if needed -4. committing safe work -5. leaving a clean restart path diff --git a/courses/learn-harness-engineering/docs/en/resources/reference/index.md b/courses/learn-harness-engineering/docs/en/resources/reference/index.md deleted file mode 100644 index 4c6c8ac1..00000000 --- a/courses/learn-harness-engineering/docs/en/resources/reference/index.md +++ /dev/null @@ -1,86 +0,0 @@ -# English Reference - -These notes explain how to use the templates as a working harness instead of a -loose pile of files. - -## Internal Reference Notes - -- [`method-map.md`](./method-map.md): map common long-running failure modes to - the artifact or policy that addresses them first -- [`initializer-agent-playbook.md`](./initializer-agent-playbook.md): what the - initializer should leave behind before feature work starts -- [`coding-agent-startup-flow.md`](./coding-agent-startup-flow.md): fixed - session-start flow for later coding runs -- [`prompt-calibration.md`](./prompt-calibration.md): how to keep root - instructions sharp without making them bloated and brittle - -## Primary Articles - -This list is intentionally narrow. A harness means the execution system around -the model: the agent loop, tool execution, sandboxing, state, context, -verification, termination, orchestration, and observability. General prompt -engineering or broad agent-framework articles do not belong in the primary -list. - -The original three articles remain the backbone of the course: - -- [OpenAI: Harness engineering: leveraging Codex in an agent-first world](https://openai.com/index/harness-engineering/) (2026-02-11): agent-first repositories, repo-local context, custom linting, and structural guardrails. -- [Anthropic: Effective harnesses for long-running agents](https://www.anthropic.com/engineering/effective-harnesses-for-long-running-agents) (2025-11-26): initializer agent, coding agent, feature list, progress log, and handoff across context windows. -- [Anthropic: Harness design for long-running application development](https://www.anthropic.com/engineering/harness-design-long-running-apps) (2026-03-24): planner / generator / evaluator roles, context resets, harness simplification, and stale assumptions. - -Only a few highly relevant 2026 articles are added: - -- [OpenAI: Unrolling the Codex agent loop](https://openai.com/index/unrolling-the-codex-agent-loop/) (2026-01-23): the Codex runtime harness, tool calls, context growth, and loop termination. -- [Anthropic: Demystifying evals for AI agents](https://www.anthropic.com/engineering/demystifying-evals-for-ai-agents) (2026-01-09): evaluating the model and harness together, and distinguishing evaluation harnesses from agent harnesses. -- [LangChain: Improving Deep Agents with harness engineering](https://www.langchain.com/blog/improving-deep-agents-with-harness-engineering) (2026-02-17): holding the model fixed while improving system prompts, tools, middleware, tracing, and self-verification to move a coding agent from Top 30 to Top 5 on Terminal Bench 2.0. -- [Thoughtworks / Martin Fowler: Harness engineering for coding agent users](https://martinfowler.com/articles/harness-engineering.html) (2026-04-02): coding-agent user harnesses as feedforward guides and feedback sensors, with deterministic and inferential controls. -- [Cursor: Continually improving our agent harness](https://cursor.com/blog/continually-improving-agent-harness) (2026-04-30): treating the harness as a continuously improved product system with offline evals, online metrics, tool-error taxonomy, model-specific tuning, and mid-chat model switching. - -## 2026 Extended References - -These are not core course sources, but they are useful when designing specific -harness modules. This section only keeps sources whose body directly covers the -agent loop, tool execution, context management, verification, sandboxing, -control layers, or regression governance. Pure agent products, platform -announcements, team case studies, and benchmarks are excluded. - -- [OpenAI: Unlocking the Codex harness: how we built the App Server](https://openai.com/index/unlocking-the-codex-harness/) (2026-02-04): the harness as a reusable App Server protocol with thread lifecycle, resume, fork, diffs, and client integrations. -- [OpenAI Developers: Run long horizon tasks with Codex](https://developers.openai.com/blog/run-long-horizon-tasks-with-codex) (2026-02-23): durable project memory, milestone validation, and done-when examples for long-running tasks. -- [OpenAI: The next evolution of the Agents SDK](https://openai.com/index/the-next-evolution-of-the-agents-sdk/) (2026-04-15): model-native harnesses, sandbox execution, and file/command execution. -- [OpenAI: An open-source spec for Codex orchestration: Symphony](https://openai.com/index/open-source-codex-orchestration-symphony/) (2026-04-27): turning an issue tracker or Linear board into a multi-agent control plane. -- [Anthropic: Building a C compiler with a team of parallel Claudes](https://www.anthropic.com/engineering/building-c-compiler) (2026-02-05): parallel agent teams, task locks, git synchronization, container isolation, and autonomous loops. -- [Anthropic: Scaling Managed Agents: Decoupling the brain from the hands](https://www.anthropic.com/engineering/managed-agents) (2026-04-08): a meta-harness view that separates session, harness, and sandbox as swappable interfaces. -- [Anthropic: An update on recent Claude Code quality reports](https://www.anthropic.com/engineering/april-23-postmortem) (2026-04-23): reasoning effort, context pruning, and system prompts as harness changes that need regression governance. -- [LangChain: Context Management for Deep Agents](https://www.langchain.com/blog/context-management-for-deepagents) (2026-01-28): filesystem offloading, tool-call truncation, summarization, and targeted evals for context-management harnesses. -- [LangChain: Tuning Deep Agents to Work Well with Different Models](https://www.langchain.com/blog/tuning-deep-agents-different-models) (2026-04-29): model-specific harness profiles for prompts, tool names, middleware, and subagent configuration. -- [LangChain: Continual learning for AI agents](https://www.langchain.com/blog/continual-learning-for-ai-agents) (2026-04-05): splitting agent improvement into model, harness, and context layers, powered by traces. -- [Microsoft: Agent Harness in Agent Framework](https://devblogs.microsoft.com/agent-framework/agent-harness-in-agent-framework/) (2026-03-12): shell/filesystem harnesses, approval flow, hosted shell execution, and context compaction. -- [Google: Announcing ADK for Java 1.0.0](https://developers.googleblog.com/announcing-adk-for-java-100-building-the-future-of-ai-agents-in-java/) (2026-03-30): plugins, event compaction, HITL, session/memory services, and A2A as reusable harness primitives. -- [GitHub: Automate repository tasks with GitHub Agentic Workflows](https://github.blog/ai-and-ml/automate-repository-tasks-with-github-agentic-workflows/) (2026-02-13): GitHub Actions as an agentic workflow runner with safe outputs, sandboxing, permissions, and review. -- [AWS: AI agents in enterprises: Best practices with Amazon Bedrock AgentCore](https://aws.amazon.com/blogs/machine-learning/ai-agents-in-enterprises-best-practices-with-amazon-bedrock-agentcore/) (2026-02-03): enterprise harness layers across Runtime, Memory, Gateway, Identity/Policy, Observability, and Evaluations. -- [Stripe: Minions: Stripe's one-shot, end-to-end coding agents](https://stripe.dev/blog/minions-stripes-one-shot-end-to-end-coding-agents) (2026-02-09) and [Part 2](https://stripe.dev/blog/minions-stripes-one-shot-end-to-end-coding-agents-part-2) (2026-02-19): devbox isolation, custom agent harnesses, blueprint state machines, rule files, MCP tool curation, security controls, and pre-push/CI feedback loops. -- [Cognition: What We Learned Building Cloud Agents](https://cognition.ai/blog/what-we-learned-building-cloud-agents) (2026-04-23): VM isolation, session snapshot/resume, orchestration, governance, audit logging, and integrations for cloud-agent runtimes. -- [Cognition: Multi-Agents: What's Actually Working](https://cognition.ai/blog/multi-agents-working) (2026-04-22): generator-verifier loops, clean-context reviewers, smart-friend routing, manager-child coordination, and cross-agent communication boundaries. -- [Replit: Decision-Time Guidance: Keeping Replit Agent Reliable](https://blog.replit.com/decision-time-guidance) (2026-01-20, updated 2026-01-23): a lightweight classifier injects short situational guidance at the decision point instead of stuffing all rules into the system prompt. -- [Vercel: How we made v0 an effective coding agent](https://vercel.com/blog/how-we-made-v0-an-effective-coding-agent) (2026-01-07): dynamic system prompts, a streaming rewrite layer, and deterministic/model-driven autofixers. -- [Vercel: Introducing deepsec](https://vercel.com/blog/introducing-deepsec-find-and-fix-vulnerabilities-in-your-code-base) (2026-05-04): a security-focused coding-agent harness with scan, investigate, revalidate, enrich, export, plugin, and refusal-checker steps. -- [Sourcegraph: CodeScaleBench](https://sourcegraph.com/blog/codescalebench-testing-coding-agents-on-large-codebases-and-multi-repo-software-engineering-tasks) (2026-03-03): an eval/tooling harness reference covering MCP tool adoption, tool-use transcripts, benchmark QA, verifier/reproducibility gates, and prompt/preamble iteration. - -Strictly 2025-only general references are excluded from the primary list. The -original 2025 Anthropic harness article remains because it is a foundation -source for the course. - -## Suggested Reading Order - -1. `method-map.md` -2. `initializer-agent-playbook.md` -3. `coding-agent-startup-flow.md` -4. `prompt-calibration.md` -5. OpenAI Harness engineering -6. Anthropic Effective harnesses -7. Anthropic Harness design for long-running application development -8. OpenAI Codex agent loop -9. Anthropic agent evals -10. LangChain Improving Deep Agents -11. Thoughtworks / Martin Fowler Harness engineering for coding agent users -12. Cursor Continually improving our agent harness diff --git a/courses/learn-harness-engineering/docs/en/resources/reference/initializer-agent-playbook.md b/courses/learn-harness-engineering/docs/en/resources/reference/initializer-agent-playbook.md deleted file mode 100644 index 622cf9b2..00000000 --- a/courses/learn-harness-engineering/docs/en/resources/reference/initializer-agent-playbook.md +++ /dev/null @@ -1,37 +0,0 @@ -# Initializer Agent Playbook - -Use this playbook for the first serious session in a repository, before -incremental feature work begins. - -## Goal - -Create a stable operating surface so later sessions can implement behavior -without re-deriving startup commands, current status, or task boundaries. - -## Required Outputs - -The initializer should leave behind at least these artifacts: - -- a root instruction file such as `AGENTS.md` or `CLAUDE.md` -- a machine-readable feature surface such as `feature_list.json` -- a durable progress artifact such as `claude-progress.md` -- a standard startup helper such as `init.sh` -- an initial safe commit that captures the baseline scaffold - -## Checklist - -1. Define the standard startup path. -2. Define the standard verification path. -3. Create the progress log and record the starting state. -4. Decompose the work into explicit features with statuses. -5. Create the first clean baseline commit. - -## Success Test - -A fresh session with no prior chat context should be able to answer: - -- what this repository does -- how to start it -- how to verify it -- what is unfinished -- what the next best step is diff --git a/courses/learn-harness-engineering/docs/en/resources/reference/method-map.md b/courses/learn-harness-engineering/docs/en/resources/reference/method-map.md deleted file mode 100644 index 405e4ca0..00000000 --- a/courses/learn-harness-engineering/docs/en/resources/reference/method-map.md +++ /dev/null @@ -1,19 +0,0 @@ -# Method Map - -This table maps the most common long-running coding-agent failure modes to the -artifact or operating rule that usually fixes them first. - -| Failure mode | What it looks like in practice | Primary fix | Supporting artifact | -| --- | --- | --- | --- | -| Cold-start confusion | A new session spends most of its time rediscovering setup and status | Make the repository the system of record | `claude-progress.md` | -| Scope sprawl | The agent starts several features and finishes none of them cleanly | Restrict active scope | `feature_list.json` | -| Premature completion | The agent claims done after code edits but before runnable proof | Bind completion to evidence | `clean-state-checklist.md` | -| Fragile startup | Every session re-learns how to boot the project | Standardize setup and verification | `init.sh` | -| Weak handoff | The next session cannot tell what is verified, broken, or next | End with an explicit handoff | `session-handoff.md` | -| Subjective review | Review quality depends on taste or memory | Score output with fixed categories | `evaluator-rubric.md` | - -## Operating Principle - -Add the smallest artifact that directly addresses the observed failure mode. -Avoid solving every reliability problem by dumping more text into one global -instruction file. diff --git a/courses/learn-harness-engineering/docs/en/resources/reference/prompt-calibration.md b/courses/learn-harness-engineering/docs/en/resources/reference/prompt-calibration.md deleted file mode 100644 index 0a694033..00000000 --- a/courses/learn-harness-engineering/docs/en/resources/reference/prompt-calibration.md +++ /dev/null @@ -1,25 +0,0 @@ -# Prompt Calibration - -Root instructions should define the operating frame, not every possible move. - -## Keep In The Root File - -- repository purpose and scope -- startup path -- verification path -- non-negotiable constraints -- required state artifacts -- end-of-session rules - -## Move Out Of The Root File - -- long historical edge cases -- topic-specific implementation details -- local architecture notes that belong near the code -- examples that only apply to one subsystem - -## Working Rule - -The root file should help a fresh session orient itself quickly. If the file is -becoming a dumping ground for every past failure, split the detail into smaller -documents and link to them instead. diff --git a/courses/learn-harness-engineering/docs/en/resources/templates/AGENTS.md b/courses/learn-harness-engineering/docs/en/resources/templates/AGENTS.md deleted file mode 100644 index 30526392..00000000 --- a/courses/learn-harness-engineering/docs/en/resources/templates/AGENTS.md +++ /dev/null @@ -1,55 +0,0 @@ -# AGENTS.md - -This repository is designed for long-running coding-agent work. The goal is not -to maximize raw code output. The goal is to leave the repo in a state where the -next session can continue without guessing. - -## Startup Workflow - -Before writing code: - -1. Confirm the working directory with `pwd`. -2. Read `claude-progress.md` for the latest verified state and next step. -3. Read `feature_list.json` and choose the highest-priority unfinished feature. -4. Review recent commits with `git log --oneline -5`. -5. Run `./init.sh`. -6. Run the required smoke or end-to-end verification before starting new work. - -If baseline verification is already failing, fix that first. Do not stack new -feature work on top of a broken starting state. - -## Working Rules - -- Work on one feature at a time. -- Do not mark a feature complete just because code was added. -- Keep changes within the selected feature scope unless a blocker forces a - narrow supporting fix. -- Do not silently change verification rules during implementation. -- Prefer durable repo artifacts over chat summaries. - -## Required Artifacts - -- `feature_list.json`: source of truth for feature state -- `claude-progress.md`: session log and current verified status -- `init.sh`: standard startup and verification path -- `session-handoff.md`: optional compact handoff for larger sessions - -## Definition Of Done - -A feature is done only when all of the following are true: - -- the target behavior is implemented -- the required verification actually ran -- evidence is recorded in `feature_list.json` or `claude-progress.md` -- the repository remains restartable from the standard startup path - -## End Of Session - -Before ending a session: - -1. Update `claude-progress.md`. -2. Update `feature_list.json`. -3. Record any unresolved risk or blocker. -4. Commit with a descriptive message once the work is in a safe state. -5. Leave the repo clean enough for the next session to run `./init.sh` - immediately. diff --git a/courses/learn-harness-engineering/docs/en/resources/templates/CLAUDE.md b/courses/learn-harness-engineering/docs/en/resources/templates/CLAUDE.md deleted file mode 100644 index 040b4ff3..00000000 --- a/courses/learn-harness-engineering/docs/en/resources/templates/CLAUDE.md +++ /dev/null @@ -1,47 +0,0 @@ -# CLAUDE.md - -You are working in a repository designed for long-running implementation work. -Prioritize reliable completion, continuity across sessions, and explicit -verification over speed. - -## Operating Loop - -At the start of every session: - -1. Run `pwd` and confirm you are in the expected repository root. -2. Read `claude-progress.md`. -3. Read `feature_list.json`. -4. Review recent commits with `git log --oneline -5`. -5. Run `./init.sh`. -6. Check whether the baseline smoke or end-to-end path is already broken. - -Then select exactly one unfinished feature and work only on that feature until -you either verify it or document why it is blocked. - -## Rules - -- One active feature at a time. -- Do not claim completion without runnable evidence. -- Do not rewrite the feature list to hide unfinished work. -- Do not remove or weaken tests just to make the task look complete. -- Use repository artifacts as the system of record. - -## Required Files - -- `feature_list.json` -- `claude-progress.md` -- `init.sh` -- `session-handoff.md` when a compact handoff is useful - -## Completion Gate - -A feature can move to `passing` only after the required verification succeeds -and the result is recorded. - -## Before You Stop - -1. Update the progress log. -2. Update the feature state. -3. Record what is still broken or unverified. -4. Commit once the repository is safe to resume. -5. Leave a clean restart path for the next session. diff --git a/courses/learn-harness-engineering/docs/en/resources/templates/claude-progress.md b/courses/learn-harness-engineering/docs/en/resources/templates/claude-progress.md deleted file mode 100644 index c6a0a7b6..00000000 --- a/courses/learn-harness-engineering/docs/en/resources/templates/claude-progress.md +++ /dev/null @@ -1,35 +0,0 @@ -# Progress Log - -## Current Verified State - -- Repository root: -- Standard startup path: -- Standard verification path: -- Current highest-priority unfinished feature: -- Current blocker: - -## Session Log - -### Session 001 - -- Date: -- Goal: -- Completed: -- Verification run: -- Evidence captured: -- Commits: -- Files or artifacts updated: -- Known risk or unresolved issue: -- Next best step: - -### Session 002 - -- Date: -- Goal: -- Completed: -- Verification run: -- Evidence captured: -- Commits: -- Files or artifacts updated: -- Known risk or unresolved issue: -- Next best step: diff --git a/courses/learn-harness-engineering/docs/en/resources/templates/clean-state-checklist.md b/courses/learn-harness-engineering/docs/en/resources/templates/clean-state-checklist.md deleted file mode 100644 index 5aa8d514..00000000 --- a/courses/learn-harness-engineering/docs/en/resources/templates/clean-state-checklist.md +++ /dev/null @@ -1,8 +0,0 @@ -# Clean State Checklist - -- [ ] The standard startup path still works. -- [ ] The standard verification path still runs. -- [ ] Current progress is recorded in the progress log. -- [ ] Feature state reflects what is actually passing versus unverified. -- [ ] No half-finished step is left undocumented. -- [ ] The next session can continue without manual repair. diff --git a/courses/learn-harness-engineering/docs/en/resources/templates/evaluator-rubric.md b/courses/learn-harness-engineering/docs/en/resources/templates/evaluator-rubric.md deleted file mode 100644 index b1eb57e1..00000000 --- a/courses/learn-harness-engineering/docs/en/resources/templates/evaluator-rubric.md +++ /dev/null @@ -1,24 +0,0 @@ -# Evaluator Rubric - -Use this rubric after implementation and before final acceptance. - -| Category | Question | Score (0-2) | Notes | -| --- | --- | --- | --- | -| Correctness | Does the implemented behavior match the requested feature? | | | -| Verification | Did the required checks actually run, with evidence? | | | -| Scope discipline | Did the session stay inside the chosen feature scope? | | | -| Reliability | Does the result survive restart or rerun without repair? | | | -| Maintainability | Is the code and documentation clear enough for the next session? | | | -| Handoff readiness | Can a fresh session continue work from repo artifacts only? | | | - -## Verdict - -- Accept -- Revise -- Block - -## Required Follow-Up - -- Missing evidence: -- Required fixes: -- Next review trigger: diff --git a/courses/learn-harness-engineering/docs/en/resources/templates/feature_list.json b/courses/learn-harness-engineering/docs/en/resources/templates/feature_list.json deleted file mode 100644 index 138adc6a..00000000 --- a/courses/learn-harness-engineering/docs/en/resources/templates/feature_list.json +++ /dev/null @@ -1,64 +0,0 @@ -{ - "project": "replace-with-project-name", - "last_updated": "YYYY-MM-DD", - "rules": { - "single_active_feature": true, - "passing_requires_evidence": true, - "do_not_skip_verification": true - }, - "status_legend": { - "not_started": "Work has not begun.", - "in_progress": "The feature is the current active task.", - "blocked": "Work cannot continue until a documented blocker is resolved.", - "passing": "Required verification has passed and evidence is recorded." - }, - "features": [ - { - "id": "chat-001", - "priority": 1, - "area": "chat", - "title": "Create a new conversation", - "user_visible_behavior": "A user can click New Chat and see a fresh empty conversation.", - "status": "not_started", - "verification": [ - "Open the app.", - "Click New Chat.", - "Verify a new conversation appears in the sidebar.", - "Verify the main panel shows an empty conversation state." - ], - "evidence": [], - "notes": "" - }, - { - "id": "chat-002", - "priority": 2, - "area": "chat", - "title": "Send a message in the current conversation", - "user_visible_behavior": "A user can submit a message and see it appear in the active thread.", - "status": "not_started", - "verification": [ - "Open an existing conversation.", - "Type a message into the input.", - "Submit the message.", - "Verify the new message appears in the thread." - ], - "evidence": [], - "notes": "" - }, - { - "id": "chat-003", - "priority": 3, - "area": "chat", - "title": "Persist the active conversation list", - "user_visible_behavior": "A user sees previously created conversations after restarting the app.", - "status": "not_started", - "verification": [ - "Create two conversations.", - "Restart the app.", - "Verify both conversations still appear in the sidebar." - ], - "evidence": [], - "notes": "" - } - ] -} diff --git a/courses/learn-harness-engineering/docs/en/resources/templates/index.md b/courses/learn-harness-engineering/docs/en/resources/templates/index.md deleted file mode 100644 index 797fa3c8..00000000 --- a/courses/learn-harness-engineering/docs/en/resources/templates/index.md +++ /dev/null @@ -1,226 +0,0 @@ -# Template Guide - -These templates are ready to copy into your own project. Each one serves a specific purpose in the agent's workflow. Edit the contents to match your project's commands, paths, feature names, and verification steps. - -## How to Get Started - -Copy these four files into your project root first: - -1. `AGENTS.md` or `CLAUDE.md` -2. `init.sh` -3. `claude-progress.md` -4. `feature_list.json` - -Add the remaining files as your project grows. - ---- - -## AGENTS.md - -The root instruction file. This is the first thing the agent reads when it starts a session. It defines the operating rules: what to do before writing code, how to work, and how to wrap up. - -**How to use it:** - -- Copy to your project root directory -- Replace the startup workflow steps with your actual project paths and commands -- Adjust the working rules to match your team's conventions -- Keep the definition of done section — it's the most important part - -**What it does for the agent:** - -- Tells it to read progress and feature state before starting work -- Forces it to work on one feature at a time -- Requires evidence before marking anything as done -- Defines what a clean end-of-session looks like - -Use `AGENTS.md` for Codex or other agents. Use `CLAUDE.md` if you're working with Claude Code — the structure is the same, just formatted for Claude's instruction style. - -## init.sh - -The startup script. Runs dependency installation, verification, and prints the start command — all in one shot. - -**How to use it:** - -- Copy to your project root -- Edit these three variables at the top: - - `INSTALL_CMD` — your dependency install command (e.g. `npm install`, `pip install -r requirements.txt`) - - `VERIFY_CMD` — your basic verification command (e.g. `npm test`, `pytest`) - - `START_CMD` — your dev server start command (e.g. `npm run dev`) -- Make it executable: `chmod +x init.sh` - -**What it does:** - -1. Prints the current directory (so you can confirm it's running in the right place) -2. Installs dependencies -3. Runs the verification command -4. Prints the start command (or runs it if `RUN_START_COMMAND=1` is set) - -If verification fails, the agent should stop and fix the baseline before doing anything else. - -## claude-progress.md - -The progress log. Every session writes to this file, and every new session reads it first. - -**How to use it:** - -- Copy to your project root -- Fill in the "Current Verified State" section with your project's info -- After each session, update the session record - -**What each field means:** - -- **Current Verified State** — the single source of truth for where the project stands - - `Repository root directory` — where the project lives - - `Standard startup path` — the command to get the project running - - `Standard verification path` — the command to run tests - - `Highest priority unfinished feature` — what the next session should work on - - `Current blocker` — anything that's stuck -- **Session Record** — one entry per session - - `Goal` — what you planned to do - - `Completed` — what actually got done - - `Verification run` — what tests were executed - - `Evidence recorded` — what proof was captured - - `Commits` — what was committed - - `Known risks` — what might be broken - - `Next best action` — where the next session should start - -## feature_list.json - -The feature tracker. A machine-readable list of every feature the agent needs to implement, along with its status, verification steps, and evidence. - -**How to use it:** - -- Copy to your project root -- Replace the example features with your own -- Each feature needs: - - `id` — a short unique identifier - - `priority` — integer, lower = higher priority - - `area` — which part of the app (e.g. "chat", "import", "search") - - `title` — short description - - `user_visible_behavior` — what the user should see when it works - - `status` — one of `not_started`, `in_progress`, `blocked`, `passing` - - `verification` — step-by-step instructions to confirm it works - - `evidence` — recorded proof that verification passed (filled in by the agent) - - `notes` — any extra context - -**Status rules:** - -- `not_started` — hasn't been touched -- `in_progress` — the one feature currently being worked on (only one at a time) -- `blocked` — can't proceed due to a documented issue -- `passing` — verification passed and evidence is recorded - -The agent should only have one feature in `in_progress` at any time. - -## session-handoff.md - -A compact handoff note between sessions. Use this when a session ends and you want the next one to pick up quickly. - -**How to use it:** - -- Copy to your project root -- Fill it out at the end of each session (or have the agent fill it out) - -**What each section covers:** - -- **Currently verified** — what's confirmed working and what verification was run -- **Changes this session** — what code or infrastructure changed -- **Still broken or unverified** — known issues and risky areas -- **Next best action** — what the next session should do, and what not to touch -- **Commands** — startup, verification, and debug commands for quick reference - -This file is optional for small sessions. It becomes important when sessions are long or when the project has multiple active areas. - -## clean-state-checklist.md - -A checklist to run through before ending each session. Makes sure the repo is in a good state for the next session to start cleanly. - -**How to use it:** - -- Copy to your project root -- Run through it before you close a session -- The agent should also check these items as part of its end-of-session routine - -**What it checks:** - -- Standard startup still works -- Standard verification still runs -- Progress log is updated -- Feature list reflects actual state (no false `passing` entries) -- No half-finished work left unrecorded -- Next session can continue without manual fixes - -## evaluator-rubric.md - -A scorecard for reviewing agent output quality. Use this after a session or at project milestones to evaluate whether the work meets the bar. - -**How to use it:** - -- Copy to your project root -- After a session (or a set of sessions), score the agent's work across six dimensions -- Each dimension is scored 0-2 - -**The six dimensions:** - -1. **Correctness** — does the implementation match the target behavior? -2. **Verification** — were the required checks actually run, with evidence? -3. **Scope discipline** — did the agent stay within the selected feature? -4. **Reliability** — does the result survive a restart or re-run? -5. **Maintainability** — is the code and documentation clear enough for the next session? -6. **Handoff readiness** — can a new session continue using only repo artifacts? - -**Conclusion options:** - -- Accept — meets the bar -- Revise — needs fixes before accepting -- Block — fundamental issues that need to be resolved first - -**Important: the evaluator needs tuning.** Out of the box, agents are poor self-judges — they identify issues then talk themselves into approving. You will need to iterate: - -1. Run the evaluator on a completed sprint. -2. Compare its scores against your own human judgment. -3. Where they diverge, make the rubric more specific about pass/fail criteria. -4. Re-run and check alignment. -5. Repeat until the evaluator consistently matches human review. - -Plan for 3-5 tuning rounds. Record each change so you can track what improved alignment. - -## quality-document.md - -A quality snapshot that grades each product domain and architectural layer in your project. Tracks codebase health over time, not just individual session output. - -**How to use it:** - -- Copy to your project root -- Before starting a session: read it to understand where the codebase is weakest -- After a session: update grades based on what changed -- Over time: compare snapshots to see which harness changes actually improved codebase health - -**What it grades:** - -- **Product domains** (e.g., document import, Q&A flow, indexing): each domain gets a grade (A-D) across verification status, agent legibility, test stability, and key gaps -- **Architectural layers** (e.g., main process, preload, renderer, services): each layer gets a grade for boundary enforcement and agent legibility - -**Why it matters:** - -The evaluator rubric scores individual agent outputs. The quality document scores the codebase itself. They answer different questions: - -- Evaluator rubric: "Did the agent do good work this session?" -- Quality document: "Is the project getting stronger or weaker over time?" - -**When to update:** - -- After each significant session -- Before benchmark comparisons -- After cleanup or simplification passes -- When onboarding a new agent or model to the project - -**Harness simplification tie-in:** - -The quality document also supports harness simplification. Every harness component encodes an assumption about what the model cannot do. As models improve, these assumptions go stale. To check whether a component is still needed: - -1. Take a quality document snapshot. -2. Remove one harness component. -3. Run the benchmark task suite. -4. Take another snapshot. -5. Compare — if grades didn't drop, the component was overhead. If they did, restore it. diff --git a/courses/learn-harness-engineering/docs/en/resources/templates/init.sh b/courses/learn-harness-engineering/docs/en/resources/templates/init.sh deleted file mode 100755 index d7522d66..00000000 --- a/courses/learn-harness-engineering/docs/en/resources/templates/init.sh +++ /dev/null @@ -1,29 +0,0 @@ -#!/usr/bin/env bash - -set -euo pipefail - -ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" -cd "$ROOT_DIR" - -# Replace these commands with the correct commands for your repository. -INSTALL_CMD=(npm install) -VERIFY_CMD=(npm test) -START_CMD=(npm run dev) - -echo "==> Working directory: $PWD" -echo "==> Syncing dependencies" -"${INSTALL_CMD[@]}" - -echo "==> Running baseline verification" -"${VERIFY_CMD[@]}" - -echo "==> Startup command" -printf ' %q' "${START_CMD[@]}" -printf '\n' - -if [ "${RUN_START_COMMAND:-0}" = "1" ]; then - echo "==> Starting the app" - exec "${START_CMD[@]}" -fi - -echo "Set RUN_START_COMMAND=1 if you want init.sh to launch the app directly." diff --git a/courses/learn-harness-engineering/docs/en/resources/templates/quality-document.md b/courses/learn-harness-engineering/docs/en/resources/templates/quality-document.md deleted file mode 100644 index fb8699bb..00000000 --- a/courses/learn-harness-engineering/docs/en/resources/templates/quality-document.md +++ /dev/null @@ -1,43 +0,0 @@ -# Quality Document - -A quality snapshot for each product domain and architectural layer. Both agents and humans can use this document to quickly understand where the codebase is strong and where it needs work. - -**Update cadence:** After each significant session, or before starting a new phase of work. - -**Grading scale:** - -- **A**: All verification passing, clean architecture, agent-legible, stable tests -- **B**: Verification passing, mostly clean, minor gaps in legibility or test coverage -- **C**: Partially working, known gaps, some code areas hard for agents to understand -- **D**: Not working, or major structural issues - ---- - -## Product Domains - -| Domain | Grade | Verification | Agent Legibility | Test Stability | Key Gaps | Last Updated | -|--------|-------|-------------|-----------------|---------------|----------|-------------| -| Document Import | - | - | - | - | - | - | -| Document Management | - | - | - | - | - | - | -| Document Indexing | - | - | - | - | - | - | -| Q&A Flow | - | - | - | - | - | - | -| Grounded Answers | - | - | - | - | - | - | - -## Architectural Layers - -| Layer | Grade | Boundary Enforcement | Agent Legibility | Key Gaps | Last Updated | -|-------|-------|---------------------|-----------------|----------|-------------| -| Main Process | - | - | - | - | - | -| Preload | - | - | - | - | - | -| Renderer | - | - | - | - | - | -| Services | - | - | - | - | - | - -## Change History - -### YYYY-MM-DD - -- Changes: -- Domains promoted: -- Domoted: -- New gaps identified: -- Gaps closed: diff --git a/courses/learn-harness-engineering/docs/en/resources/templates/session-handoff.md b/courses/learn-harness-engineering/docs/en/resources/templates/session-handoff.md deleted file mode 100644 index c37cf463..00000000 --- a/courses/learn-harness-engineering/docs/en/resources/templates/session-handoff.md +++ /dev/null @@ -1,30 +0,0 @@ -# Session Handoff - -## Verified Now - -- What is currently working: -- What verification actually ran: - -## Changed This Session - -- Code or behavior added: -- Infrastructure or harness changes: - -## Broken Or Unverified - -- Known defect: -- Unverified path: -- Risk for the next session: - -## Next Best Step - -- Highest-priority unfinished feature: -- Why it is next: -- What counts as passing: -- What must not change during that step: - -## Commands - -- Startup: -- Verification: -- Focused debug command: diff --git a/courses/learn-harness-engineering/docs/en/skills/index.md b/courses/learn-harness-engineering/docs/en/skills/index.md deleted file mode 100644 index 6fb1cfce..00000000 --- a/courses/learn-harness-engineering/docs/en/skills/index.md +++ /dev/null @@ -1,49 +0,0 @@ -# Skills - -This directory contains the bundled AI agent skills that ship with this course. Skills are self-contained prompt templates that can be loaded by AI coding agents (Claude Code, Codex, Cursor, Windsurf, etc.) to perform specialized tasks. - -## harness-creator - -A production-grade harness engineering skill for AI coding agents. It helps you create, assess, and improve the five core harness subsystems: instructions, state, verification, scope, and session lifecycle. - -### What It Does - -- **Create harnesses from scratch** — AGENTS.md, feature lists, verification workflows -- **Improve existing harnesses** — Five-subsystem assessment with prioritized improvements -- **Design session continuity** — Memory persistence, progress tracking, handoff procedures -- **Apply production patterns** — Memory, context engineering, tool safety, multi-agent coordination - -### Quick Start - -The skill files live in the repository at [`skills/harness-creator/`](https://github.com/walkinglabs/learn-harness-engineering/tree/main/skills/harness-creator). - -To use it with Claude Code, copy the `harness-creator/` directory into your project's skill path, or point your agent at the SKILL.md file. - -### Reference Patterns - -The skill includes 6 deep-dive reference documents: - -| Pattern | When to Use | -|---------|-------------| -| Memory Persistence | Agent forgets between sessions | -| Context Engineering | Context budget management, JIT loading | -| Tool Registry | Tool safety, concurrency control | -| Multi-Agent Coordination | Parallelism, specialization workflows | -| Lifecycle & Bootstrap | Hooks, background tasks, initialization | -| Gotchas | 15 non-obvious failure modes with fixes | - -### Templates - -The skill bundles ready-to-use templates: - -- `agents.md` — AGENTS.md scaffold with working rules -- `feature-list.json` — JSON Schema + example feature list -- `init.sh` — Standard initialization script -- `progress.md` — Session progress log template - -### How This Skill Was Built - -`harness-creator` was developed using the **skill-creator** methodology — Anthropic's official meta-skill for creating, testing, and iterating on agent skills. The skill-creator provides a structured workflow (draft → test → evaluate → iterate) with built-in eval runners, graders, and a benchmark viewer. - -- **skill-creator source**: [anthropics/skills — skill-creator](https://github.com/anthropics/skills/tree/main/skills/skill-creator) -- **Claude Code skills docs**: [anthropics/claude-code — plugin-dev/skills](https://github.com/anthropics/claude-code/tree/main/plugins/plugin-dev/skills) diff --git a/courses/learn-harness-engineering/docs/es/index.md b/courses/learn-harness-engineering/docs/es/index.md deleted file mode 100644 index 73566750..00000000 --- a/courses/learn-harness-engineering/docs/es/index.md +++ /dev/null @@ -1,73 +0,0 @@ -# Bienvenido a Learn Harness Engineering - -Learn Harness Engineering es un curso dedicado a la ingeniería de agentes de programación con IA. Hemos estudiado y sintetizado algunas de las teorías y prácticas más avanzadas de Harness Engineering en la industria. Nuestras referencias principales incluyen: -- [OpenAI: Harness engineering: leveraging Codex in an agent-first world](https://openai.com/index/harness-engineering/) -- [Anthropic: Effective harnesses for long-running agents](https://www.anthropic.com/engineering/effective-harnesses-for-long-running-agents) -- [Anthropic: Harness design for long-running application development](https://www.anthropic.com/engineering/harness-design-long-running-apps) -- [Awesome Harness Engineering](https://github.com/walkinglabs/awesome-harness-engineering) - -Mediante diseño sistemático del entorno, gestión de estado, verificación y sistemas de control, este curso enseña cómo hacer que herramientas agentic de programación como Codex y Claude Code sean realmente fiables. Aprenderás a construir funciones, corregir errores y automatizar tareas de desarrollo limitando a tu asistente de programación con reglas y límites explícitos. - -## Empezar - -Elige tu ruta de aprendizaje. El curso se divide en clases teóricas, proyectos prácticos y una biblioteca de recursos lista para copiar. - - - -## El mecanismo central de un harness - -Un harness no "vuelve más inteligente al modelo"; establece un **sistema de trabajo** de ciclo cerrado para el modelo. Puedes entender el flujo central con este diagrama: - -```mermaid -graph TD - A["Clear Objective
AGENTS.md"] --> B("Initialization
init.sh") - B --> C{"Run Tasks
AI Agent"} - C -->|Encounter Issues| D["Runtime Feedback
CLI / Logs"] - D -->|Auto-fix| C - C -->|Code Completed| E{"Verify & QA
Test suite"} - E -->|Failed| D - E -->|Passed| F["Cleanup & Handoff
claude-progress.md"] - - classDef primary fill:#D95C41,stroke:#C14E36,color:#fff,font-weight:bold; - classDef process fill:#F4F3EE,stroke:#D1D1D1,color:#1A1A1A; - classDef check fill:#EAE8E1,stroke:#B3B3B3,color:#1A1A1A; - - class A,F primary; - class B,D process; - class C,E check; -``` - -## Qué aprenderás - -Estos son algunos de los conceptos clave que dominarás: - -
    -
  • Restringir el comportamiento del agente con reglas y límites explícitos.
  • -
  • Mantener el contexto en tareas largas y de varias sesiones.
  • -
  • Evitar que los agentes declaren victoria demasiado pronto.
  • -
  • Verificar el trabajo con pruebas de pipeline completo y autorreflexión.
  • -
  • Hacer observable el runtime y facilitar su depuración.
  • -
- -## Siguientes pasos - -Cuando entiendas los conceptos básicos, estas guías te ayudarán a profundizar: - - diff --git a/courses/learn-harness-engineering/docs/es/lectures/lecture-01-why-capable-agents-still-fail/code/failure-pattern-demo.ts b/courses/learn-harness-engineering/docs/es/lectures/lecture-01-why-capable-agents-still-fail/code/failure-pattern-demo.ts deleted file mode 100644 index 311e665b..00000000 --- a/courses/learn-harness-engineering/docs/es/lectures/lecture-01-why-capable-agents-still-fail/code/failure-pattern-demo.ts +++ /dev/null @@ -1,170 +0,0 @@ -/** - * failure-pattern-demo.ts - * - * Simulates the 4-step failure pattern that capable agents fall into: - * 1. Incomplete context - * 2. Locally reasonable changes - * 3. No global verification - * 4. Premature completion - * - * Run: npx tsx docs/lectures/lecture-01-why-capable-agents-still-fail/code/failure-pattern-demo.ts - */ - -// --------------------------------------------------------------------------- -// Types -// --------------------------------------------------------------------------- - -interface StepState { - step: number; - name: string; - contextAvailable: string[]; - contextMissing: string[]; - actionTaken: string; - localOutcome: string; - globalImpact: string; - completed: boolean; -} - -// --------------------------------------------------------------------------- -// Simulated "model" -- a simple decision function that bases its output -// solely on whatever context it has been given. -// --------------------------------------------------------------------------- - -function modelDecide(context: string[], task: string): string { - const has = (s: string) => context.some((c) => c.includes(s)); - - // The task is to "add a search endpoint to the API". - // Correct answer requires knowing about auth middleware and rate limiting. - - if (!has("auth")) { - return "Created new route handler /search without authentication checks"; - } - if (!has("rate-limit")) { - return "Added search route with auth but forgot rate limiting"; - } - if (!has("test-standards")) { - return "Implemented search with auth and rate-limit, but no tests"; - } - return "Fully implemented search endpoint with auth, rate-limit, and tests"; -} - -// --------------------------------------------------------------------------- -// Failure simulation -// --------------------------------------------------------------------------- - -function simulateFailurePattern(): StepState[] { - const steps: StepState[] = []; - - // ---- Step 1: Incomplete Context ---- - const step1Context = ["project structure", "route definitions"]; - const step1Missing = ["auth middleware", "rate-limiting policy", "test standards"]; - const step1Decision = modelDecide(step1Context, "add search endpoint"); - - steps.push({ - step: 1, - name: "Incomplete Context", - contextAvailable: step1Context, - contextMissing: step1Missing, - actionTaken: step1Decision, - localOutcome: "Looks good -- route compiles, returns data", - globalImpact: "Missing auth means unauthenticated access to search", - completed: false, - }); - - // ---- Step 2: Locally Reasonable Changes ---- - // The agent adds auth after a hint, but still lacks other context. - const step2Context = [...step1Context, "auth middleware"]; - const step2Missing = ["rate-limiting policy", "test standards"]; - const step2Decision = modelDecide(step2Context, "add search endpoint"); - - steps.push({ - step: 2, - name: "Locally Reasonable Changes", - contextAvailable: step2Context, - contextMissing: step2Missing, - actionTaken: step2Decision, - localOutcome: "Route has auth -- looks complete locally", - globalImpact: "No rate limiting means the endpoint can be abused", - completed: false, - }); - - // ---- Step 3: No Global Verification ---- - const step3Context = [...step2Context, "rate-limiting policy"]; - const step3Missing = ["test standards"]; - const step3Decision = modelDecide(step3Context, "add search endpoint"); - - steps.push({ - step: 3, - name: "No Global Verification", - contextAvailable: step3Context, - contextMissing: step3Missing, - actionTaken: step3Decision, - localOutcome: "Feature appears fully implemented", - globalImpact: "No tests -- regression risk, violates project standards", - completed: false, - }); - - // ---- Step 4: Premature Completion ---- - steps.push({ - step: 4, - name: "Premature Completion", - contextAvailable: step3Context, - contextMissing: step3Missing, - actionTaken: 'Agent outputs: "Done. Added search endpoint."', - localOutcome: "Agent is satisfied, task marked complete", - globalImpact: "Task is incomplete -- missing tests, no E2E verification", - completed: true, - }); - - return steps; -} - -// --------------------------------------------------------------------------- -// Comparison table -// --------------------------------------------------------------------------- - -function printComparisonTable(steps: StepState[]): void { - console.log("\n" + "=".repeat(90)); - console.log(" FAILURE PATTERN DEMO -- 4 Steps to a Broken Deliverable"); - console.log("=".repeat(90)); - - for (const s of steps) { - console.log(`\n Step ${s.step}: ${s.name}`); - console.log(" " + "-".repeat(60)); - console.log(` Context available : ${s.contextAvailable.join(", ")}`); - console.log(` Context missing : ${s.contextMissing.join(", ") || "(none)"}`); - console.log(` Action taken : ${s.actionTaken}`); - console.log(` Local outcome : ${s.localOutcome}`); - console.log(` Global impact : ${s.globalImpact}`); - console.log(` Marked complete? : ${s.completed ? "YES (premature)" : "No"}`); - } - - // Summary comparison - console.log("\n" + "=".repeat(90)); - console.log(" COMPARISON: What the agent saw vs. what was actually needed"); - console.log("=".repeat(90)); - - const totalRequired = ["project structure", "route definitions", "auth middleware", "rate-limiting policy", "test standards"]; - const finalAvailable = steps[steps.length - 1].contextAvailable; - - const header = "| Criterion | Available | Missing | Status |"; - const sep = "|------------------------|-----------|---------|----------|"; - console.log("\n" + header); - console.log(sep); - - for (const item of totalRequired) { - const avail = finalAvailable.includes(item); - const row = `| ${item.padEnd(23)}| ${(avail ? "Yes" : "No").padEnd(10)}| ${(!avail ? "Yes" : "").padEnd(8)}| ${(avail ? "OK" : "GAP").padEnd(9)}|`; - console.log(row); - } - - const gapCount = totalRequired.filter((i) => !finalAvailable.includes(i)).length; - console.log("\n Result: Agent completed with " + gapCount + " of " + totalRequired.length + " context items missing."); - console.log(" This is the core failure pattern: each step looked reasonable in isolation.\n"); -} - -// --------------------------------------------------------------------------- -// Run -// --------------------------------------------------------------------------- - -printComparisonTable(simulateFailurePattern()); diff --git a/courses/learn-harness-engineering/docs/es/lectures/lecture-01-why-capable-agents-still-fail/code/failure-signals-checklist.md b/courses/learn-harness-engineering/docs/es/lectures/lecture-01-why-capable-agents-still-fail/code/failure-signals-checklist.md deleted file mode 100644 index edf5f65b..00000000 --- a/courses/learn-harness-engineering/docs/es/lectures/lecture-01-why-capable-agents-still-fail/code/failure-signals-checklist.md +++ /dev/null @@ -1,9 +0,0 @@ -# Lista de Verificación de Señales de Fallo - -Usa esta lista al revisar una ejecución de harness débil. - -- ¿El agente preguntó o infirió incorrectamente cómo iniciar la app? -- ¿Creó directorios o abstracciones que no coinciden con el producto previsto? -- ¿Se detuvo tras construir una shell de UI visible sin un flujo completo? -- ¿Dejó notas o artefactos que ayuden a una ejecución futura a continuar? -- ¿Podría una sesión nueva entender qué ocurrió en menos de cinco minutos? diff --git a/courses/learn-harness-engineering/docs/es/lectures/lecture-01-why-capable-agents-still-fail/code/index.md b/courses/learn-harness-engineering/docs/es/lectures/lecture-01-why-capable-agents-still-fail/code/index.md deleted file mode 100644 index 0ba5c967..00000000 --- a/courses/learn-harness-engineering/docs/es/lectures/lecture-01-why-capable-agents-still-fail/code/index.md +++ /dev/null @@ -1,7 +0,0 @@ -# Código de la Clase 01 - -Usa esta carpeta para pequeños ejemplos que muestran: - -- un modelo potente fallando en un entorno débil -- configuración de repositorio insuficientemente especificada -- bucles de retroalimentación ausentes diff --git a/courses/learn-harness-engineering/docs/es/lectures/lecture-01-why-capable-agents-still-fail/code/underspecified-task.md b/courses/learn-harness-engineering/docs/es/lectures/lecture-01-why-capable-agents-still-fail/code/underspecified-task.md deleted file mode 100644 index 21c6a7cc..00000000 --- a/courses/learn-harness-engineering/docs/es/lectures/lecture-01-why-capable-agents-still-fail/code/underspecified-task.md +++ /dev/null @@ -1,18 +0,0 @@ -# Ejemplo de Tarea Subespecificada - -Construye una aplicación de escritorio de base de conocimientos con respuesta a preguntas por IA. - -Restricciones: - -- Ninguna especificada -- No se dio un comando de inicio -- No se proporcionó guía de estructura de carpetas -- No se definió un modelo de datos -- No hay criterios de completitud explícitos - -Resultados típicos de este tipo de prompt: - -- el agente inventa una estructura ad hoc -- la app puede compilar pero no iniciar consistentemente -- la UI puede aparecer antes de que haya una ruta de importación/consulta utilizable -- el agente a menudo se detiene tras un éxito cosmético diff --git a/courses/learn-harness-engineering/docs/es/lectures/lecture-01-why-capable-agents-still-fail/index.md b/courses/learn-harness-engineering/docs/es/lectures/lecture-01-why-capable-agents-still-fail/index.md deleted file mode 100644 index 54e32a96..00000000 --- a/courses/learn-harness-engineering/docs/es/lectures/lecture-01-why-capable-agents-still-fail/index.md +++ /dev/null @@ -1,117 +0,0 @@ -[中文版本 →](../../../zh/lectures/lecture-01-why-capable-agents-still-fail/) - -> Ejemplos de código: [code/](https://github.com/walkinglabs/learn-harness-engineering/blob/main/docs/es/lectures/lecture-01-why-capable-agents-still-fail/code/) -> Proyecto práctico: [Proyecto 01. Solo prompt vs reglas primero](./../../projects/project-01-baseline-vs-minimal-harness/index.md) - -# Lección 01. Los modelos fuertes no significan ejecución fiable - -Te consideras una persona con experiencia en el mundo de la IA: suscripción a Claude Pro, clave de API de GPT-4o, números del leaderboard de SWE-bench memorizados. Un día por fin entregas un proyecto real a un agente de IA, con mucha confianza. ¿El resultado? Añade una función pero rompe los tests, corrige un bug pero introduce dos más, corre durante 20 minutos y declara con orgullo "terminado"; miras el código y no es lo que pediste. - -Tu primer instinto puede ser: "este modelo no es suficientemente bueno, toca actualizar". Espera. Antes de sacar la tarjeta, considera que quizá el problema no sea el modelo. - -Veamos algunos números. A finales de 2025, los agentes de programación más fuertes en SWE-bench Verified logran aproximadamente 50-60%. Y eso en tareas cuidadosamente seleccionadas, con descripciones claras y tests existentes. Si lo llevas a tu entorno diario de desarrollo — requisitos vagos, sin tests existentes, reglas de negocio implícitas por todas partes — ese número solo baja. - -Pero detrás de estos números hay una verdad contraintuitiva. - -## El mismo caballo, destinos distintos - -Anthropic realizó un experimento controlado. Mismo prompt ("construye un creador de juegos retro 2D"), mismo modelo (Opus 4.5). Primera ejecución: sin apoyo, sin harness — 20 minutos, 9 dólares, las funciones centrales del juego no funcionaban. Segunda ejecución: harness completo, con arquitectura de tres agentes planner + generator + evaluator — 6 horas, 200 dólares, el juego era jugable. - -No cambiaron el modelo. Opus 4.5 seguía siendo Opus 4.5. Lo que cambió fue el equipo de monta. - -El artículo de OpenAI sobre harness engineering lo dice con claridad: Codex en un repositorio bien harnessed pasa de "poco fiable" a "fiable". Fíjate en el lenguaje: no es "un poco mejor", sino un cambio cualitativo. Como un caballo pura sangre: puedes montarlo sin equipo de monta adecuado, pero no llegarás lejos, no irás rápido y caerte no será una sorpresa. El harness es ese conjunto completo de equipo: **todo lo que existe en la infraestructura de ingeniería fuera de los pesos del modelo.** - -## Dónde se atascan realmente los agentes - -¿Qué sale mal, exactamente? - -Lo más común: nunca definiste la tarea con claridad. Dices "añade una función de búsqueda" y la interpretación del agente puede ser completamente distinta de la tuya: ¿buscar qué?, ¿texto completo o datos estructurados?, ¿paginación?, ¿resaltado? Si no lo especificas, el agente adivina. Si acierta, es suerte; si falla, corregirlo cuesta más que haber sido específico desde el principio. Es como entrar en un restaurante y decirle al chef "quiero pescado": si llega hervido, al vapor o en una olla picante queda al azar. - -Incluso cuando especificas la tarea, el proyecto tiene convenciones arquitectónicas implícitas que el agente no conoce. Tu equipo estandarizó SQLAlchemy 2.0, pero el agente escribe código 1.x por defecto. Todos los endpoints deben usar OAuth 2.0, pero esa regla solo vive en tu cabeza y en un mensaje de Slack de hace tres meses. El agente no puede verla. No es que no quiera cumplirla; literalmente no sabe que existe. - -El entorno también es una trampa. Entorno de desarrollo incompleto, dependencias faltantes, versiones incorrectas de herramientas. El agente quema una ventana de contexto valiosa en fallos de `pip install` y discrepancias de versión de Node en vez de resolver la tarea real. Es como contratar a un carpintero experto pero olvidar darle martillo, clavos y una mesa nivelada: por muy capaz que sea, no puede trabajar. - -Todavía más común: no hay forma de verificar. No hay tests, no hay lint, o los comandos de verificación nunca se comunican al agente. El agente escribe código, lo mira, decide que está bien y dice "terminado". Es como pedir a un estudiante que entregue deberes sin clave de respuestas: cree que acertó, pero al corregir aparecen muchos errores. Anthropic también observó que cuando los agentes sienten que el contexto se agota, se apresuran a terminar, saltan verificación y eligen soluciones simples en vez de óptimas. Lo llaman "context anxiety": lo mismo que ocurre cuando notas que se acaba el tiempo en un examen y empiezas a marcar respuestas al azar. - -Las tareas largas que cruzan sesiones son aún peores: los descubrimientos de la sesión anterior se pierden, y cada sesión nueva debe volver a explorar la estructura del proyecto y entender la organización del código. Los agentes sin estado persistente ven cómo la tasa de fallos sube bruscamente en tareas de más de 30 minutos. - -## Terminología clave - -Con estos escenarios en mente, estos conceptos ya no son jerga: - -- **Brecha de capacidad**: la distancia enorme entre el rendimiento del modelo en benchmarks y su rendimiento en tareas reales. Un 50-60% en SWE-bench Verified significa que casi la mitad de los issues reales no se resuelven. -- **Harness**: todo lo que está fuera del modelo: instrucciones, herramientas, entorno, gestión de estado y feedback de verificación. Si no son pesos del modelo, es harness. Es el "equipo de monta" del que hablamos. -- **Fallo inducido por harness**: el modelo tiene capacidad suficiente, pero el entorno de ejecución tiene defectos estructurales. El experimento controlado de Anthropic ya lo demostró. -- **Brecha de verificación**: la diferencia entre la confianza del agente en su salida y la corrección real. El agente dice "terminé" cuando no terminó. Es el modo de fallo más común. -- **Bucle diagnóstico**: ejecutar, observar el fallo, atribuirlo a una capa concreta del harness, corregir esa capa y volver a ejecutar. Es la metodología central del harness engineering. -- **Definition of Done**: conjunto de condiciones verificables por máquina: tests pasan, lint limpio, type checks pasan. Sin una definición explícita, el agente inventará la suya. - -## Cuando algo falla, arregla primero el harness - -Principio central: **cuando algo falla, no cambies primero el modelo; revisa el harness.** Si el mismo modelo funciona en tareas similares y bien estructuradas, asume que es un problema de harness. Es como un coche que se detiene: no sospechas inmediatamente del motor; primero miras si tiene gasolina. - -Pasos concretos: - -**Atribuye cada fallo a una capa específica.** No digas simplemente "el modelo es malo". Pregunta: ¿la tarea era poco clara?, ¿faltaba contexto?, ¿no había métodos de verificación? Mapea cada fallo a una de las cinco capas: especificación de tarea, provisión de contexto, entorno de ejecución, feedback de verificación y gestión de estado. Estas son capas diagnósticas prácticas, no los seis términos del glosario anterior. Si construyes este hábito, verás cada vez menos "el modelo no es suficiente" en tus registros. - -**Escribe una Definition of Done explícita para cada tarea.** No digas "añade búsqueda". Di: -``` -Completion criteria: -- New endpoint GET /api/search?q=xxx -- Supports pagination, default 20 items -- Results include highlighted snippets -- All new code passes pytest -- Type checking passes (mypy --strict) -``` - -**Crea un archivo AGENTS.md.** Ponlo en la raíz del repositorio para decirle al agente el stack técnico, las convenciones arquitectónicas y los comandos de verificación. Es el primer paso del harness engineering y uno de los de mayor retorno. Un solo `AGENTS.md` puede ser más efectivo que actualizar a un modelo más caro; no es broma. - -**Construye un bucle diagnóstico.** No trates los fallos como "el modelo volvió a equivocarse". Trátalos como señales de que tu harness tiene un defecto. En cada fallo, identifica la capa, corrígela y evita repetir ese modo de fallo. Tras unas rondas, tu harness se fortalece y el rendimiento del agente se estabiliza. Como reparar carreteras: cada bache que tapas hace más suave el siguiente tramo. - -**Cuantifica las mejoras.** Mantén un registro sencillo: si cada tarea tuvo éxito o falló, y qué capa causó el fallo. Después de unas rondas verás cuál capa es el cuello de botella; enfoca ahí tu energía. - -## El experimento del millón de líneas - -OpenAI realizó en 2025 un experimento agresivo: usar Codex para construir un producto interno completo desde un repositorio git vacío. Cinco meses después, el repositorio tenía alrededor de un millón de líneas de código: lógica de aplicación, infraestructura, tooling, documentación y herramientas internas de desarrollo, todo generado por agentes. Tres ingenieros dirigieron Codex, abriendo y fusionando unas 1.500 PRs, con una media de 3,5 PRs por persona al día. - -La restricción clave: **los humanos nunca escriben código directamente.** No era un truco; estaba diseñado para obligar al equipo a descubrir qué cambia cuando el trabajo principal del ingeniero deja de ser escribir código y pasa a ser diseñar entornos, expresar intención y construir bucles de feedback. - -El progreso inicial fue más lento de lo esperado. No porque Codex no pudiera, sino porque el entorno no estaba completo: al agente le faltaban herramientas, abstracciones y estructuras internas para avanzar hacia objetivos de alto nivel. El trabajo de los ingenieros se convirtió en descomponer metas grandes en bloques pequeños — diseño, código, revisión, test — dejar que el agente los ensamblara y luego usar esos bloques para desbloquear tareas más complejas. Cuando algo fallaba, la solución casi nunca era "intenta más fuerte"; era "qué capacidad le falta al agente, y cómo la hacemos comprensible y ejecutable". - -Este experimento demuestra directamente la tesis central de la lección: **el mismo modelo produce resultados fundamentalmente distintos en un entorno desnudo y en uno con harness completo.** El modelo no cambió. El entorno sí. - -> Fuente: [OpenAI: Harness engineering: leveraging Codex in an agent-first world](https://openai.com/index/harness-engineering/) - -## Un ejemplo más cotidiano - -Un equipo usó Claude Sonnet para añadir un nuevo endpoint a una aplicación web Python mediana (FastAPI + PostgreSQL + Redis, unas 15.000 líneas). - -Al principio dieron solo una frase: "añade endpoints de preferencias de usuario bajo `/api/v2/users`". ¿El resultado? El agente gastó 40% de su ventana de contexto explorando la estructura del repo, produjo código que parecía razonable pero no seguía los patrones de manejo de errores del proyecto, usó sintaxis antigua de SQLAlchemy y declaró finalización aunque el endpoint tenía errores en runtime. La siguiente sesión tuvo que repetir todo el descubrimiento. - -Más tarde añadieron `AGENTS.md`, con arquitectura del proyecto y versiones del stack, comandos de verificación explícitos (`pytest tests/api/v2/ && python -m mypy src/`) y registros de decisiones arquitectónicas. El mismo modelo tuvo éxito en tres ejecuciones independientes, con alrededor de 60% más eficiencia de contexto. - -No cambiaron el modelo. Cambiaron el harness. - -## Ideas clave - -- Capacidad del modelo y fiabilidad de ejecución son cosas distintas. Un pura sangre también necesita buen equipo de monta. -- Cuando algo falla, revisa primero el harness y luego el modelo. Cambiar de modelo es la opción más cara, y muchas veces ni siquiera es un problema del modelo. -- Cada fallo es una señal: tu harness tiene un defecto estructural. Encuéntralo y arréglalo. -- Cinco capas defensivas: especificación de tarea, provisión de contexto, entorno de ejecución, feedback de verificación y gestión de estado. Revísalas de forma sistemática. -- Un solo `AGENTS.md` puede ser más efectivo que actualizar a un modelo más caro. En serio. - -## Lecturas adicionales - -- [OpenAI: Harness Engineering — Leveraging Codex in an Agent-First World](https://openai.com/index/harness-engineering/) -- [Anthropic: Effective Harnesses for Long-Running Agents](https://www.anthropic.com/engineering/effective-harnesses-for-long-running-agents) -- [HumanLayer: Skill Issue — Harness Engineering for Coding Agents](https://humanlayer.dev/articles/harness-engineering-for-coding-agents/) -- [SWE-bench Leaderboard](https://www.swebench.com/) -- [Thoughtworks Technology Radar: Harness Engineering](https://www.thoughtworks.com/radar) - -## Ejercicios - -1. **Experimento comparativo**: elige un codebase que conozcas bien y una modificación no trivial. Primero ejecuta el agente sin soporte de harness y registra fallos. Luego añade un `AGENTS.md` con comandos de verificación explícitos y ejecuta de nuevo con el mismo agente. Compara resultados atribuyendo cada fallo a una de las cinco capas. - -2. **Medición de la brecha de verificación**: elige 5 tareas de programación. Después de cada una, registra si el agente afirma haber terminado y verifica la corrección real con tests independientes. Calcula la proporción de veces que el agente dice que terminó cuando en realidad no terminó. Esa es tu brecha de verificación. - -3. **Práctica de bucle diagnóstico**: encuentra una tarea donde el agente falle repetidamente en tu proyecto. Ejecuta una vez y registra el fallo. Atribúyelo a una de las cinco capas. Corrige esa capa y ejecuta de nuevo. Repite de tres a cinco rondas, registrando las mejoras. diff --git a/courses/learn-harness-engineering/docs/es/lectures/lecture-02-what-a-harness-actually-is/code/harness-components.md b/courses/learn-harness-engineering/docs/es/lectures/lecture-02-what-a-harness-actually-is/code/harness-components.md deleted file mode 100644 index e3545a99..00000000 --- a/courses/learn-harness-engineering/docs/es/lectures/lecture-02-what-a-harness-actually-is/code/harness-components.md +++ /dev/null @@ -1,21 +0,0 @@ -# Ejemplo de Componentes del Harness - -Para un agente de programación trabajando en un repositorio local: - -- Modelo: - el LLM en sí - -- Harness: - - system prompt - - AGENTS.md - - herramienta bash - - herramientas de lectura/escritura de archivos - - acceso a git - - sistema de archivos local - - scripts de inicio - - comandos de prueba - - stop hooks - - verificaciones lint - - bucle de evaluación (evaluator loop) - -Si cambias cualquiera de las piezas del harness anteriores, cambias el agente efectivo. diff --git a/courses/learn-harness-engineering/docs/es/lectures/lecture-02-what-a-harness-actually-is/code/harness-vs-no-harness.ts b/courses/learn-harness-engineering/docs/es/lectures/lecture-02-what-a-harness-actually-is/code/harness-vs-no-harness.ts deleted file mode 100644 index d856ba36..00000000 --- a/courses/learn-harness-engineering/docs/es/lectures/lecture-02-what-a-harness-actually-is/code/harness-vs-no-harness.ts +++ /dev/null @@ -1,180 +0,0 @@ -/** - * harness-vs-no-harness.ts - * - * Side-by-side comparison of the same task executor running with and without - * a harness. The harness version adds explicit rules, verification steps, - * and stop conditions. - * - * Run: npx tsx docs/lectures/lecture-02-what-a-harness-actually-is/code/harness-vs-no-harness.ts - */ - -// --------------------------------------------------------------------------- -// Types & helpers -// --------------------------------------------------------------------------- - -interface TaskResult { - name: string; - passed: boolean; - durationMs: number; - issues: string[]; -} - -function pad(s: string, len: number): string { - return s.length >= len ? s : s + " ".repeat(len - s.length); -} - -// --------------------------------------------------------------------------- -// Simulated task executor -// --------------------------------------------------------------------------- - -/** A simple task that can succeed or fail depending on rules. */ -type Task = { - name: string; - requiresAuth: boolean; - hasTests: boolean; - withinScope: boolean; -}; - -const tasks: Task[] = [ - { name: "Add search endpoint", requiresAuth: true, hasTests: false, withinScope: true }, - { name: "Add delete endpoint", requiresAuth: true, hasTests: true, withinScope: true }, - { name: "Refactor auth middleware", requiresAuth: true, hasTests: true, withinScope: false }, - { name: "Add health check", requiresAuth: false, hasTests: true, withinScope: true }, - { name: "Add rate limiter", requiresAuth: true, hasTests: false, withinScope: true }, -]; - -// --------------------------------------------------------------------------- -// Run WITHOUT harness -- just execute every task, no checks -// --------------------------------------------------------------------------- - -function runWithoutHarness(taskList: Task[]): TaskResult[] { - const results: TaskResult[] = []; - - for (const t of taskList) { - const start = performance.now(); - const issues: string[] = []; - - // The "agent" does the work and calls it done. - let passed = true; - - // Problems that go undetected without a harness - if (t.requiresAuth && !t.hasTests) { - issues.push("No tests for auth-protected endpoint"); - // Agent doesn't notice -- marks as pass anyway - } - if (!t.withinScope) { - issues.push("Task is outside current scope"); - // Agent doesn't notice - } - - const duration = performance.now() - start; - results.push({ name: t.name, passed, durationMs: duration, issues }); - } - - return results; -} - -// --------------------------------------------------------------------------- -// Run WITH harness -- rules, verification, stop conditions -// --------------------------------------------------------------------------- - -function runWithHarness(taskList: Task[]): TaskResult[] { - const rules = { - requireTestsForAuth: true, - enforceScope: true, - }; - - const results: TaskResult[] = []; - - for (const t of taskList) { - const start = performance.now(); - const issues: string[] = []; - let passed = true; - - // Rule: auth endpoints must have tests - if (rules.requireTestsForAuth && t.requiresAuth && !t.hasTests) { - issues.push("BLOCKED: Auth-protected endpoint missing tests"); - passed = false; - } - - // Rule: stay in scope - if (rules.enforceScope && !t.withinScope) { - issues.push("BLOCKED: Task outside active scope -- skipped"); - passed = false; - } - - // Verification: re-check after execution - if (passed && !t.hasTests) { - issues.push("WARNING: No tests exist for this change"); - } - - const duration = performance.now() - start; - results.push({ name: t.name, passed, durationMs: duration, issues }); - } - - return results; -} - -// --------------------------------------------------------------------------- -// Reporting -// --------------------------------------------------------------------------- - -function printComparison( - noHarness: TaskResult[], - withHarness: TaskResult[] -): void { - console.log("\n" + "=".repeat(80)); - console.log(" HARNESS vs NO-HARNESS COMPARISON"); - console.log("=".repeat(80)); - - const header = `| ${pad("Task", 28)}| ${pad("No-Harness", 12)}| ${pad("Issues(NH)", 35)}| ${pad("Harness", 12)}| ${pad("Issues(H)", 35)}|`; - const sep = `|${"-".repeat(30)}|${"-".repeat(14)}|${"-".repeat(37)}|${"-".repeat(14)}|${"-".repeat(37)}|`; - - console.log("\n" + header); - console.log(sep); - - for (let i = 0; i < noHarness.length; i++) { - const nh = noHarness[i]; - const wh = withHarness[i]; - const nhPass = nh.passed ? "PASS" : "FAIL"; - const whPass = wh.passed ? "PASS" : "FAIL"; - const nhIssue = nh.issues[0] ?? "(none)"; - const whIssue = wh.issues[0] ?? "(none)"; - - console.log( - `| ${pad(nh.name, 28)}| ${pad(nhPass, 12)}| ${pad(nhIssue, 35)}| ${pad(whPass, 12)}| ${pad(whIssue, 35)}|` - ); - } - - // Metrics - const nhPassed = noHarness.filter((r) => r.passed).length; - const whPassed = withHarness.filter((r) => r.passed).length; - const nhIssues = noHarness.reduce((sum, r) => sum + r.issues.length, 0); - const whIssues = withHarness.reduce((sum, r) => sum + r.issues.length, 0); - - console.log("\n" + "=".repeat(80)); - console.log(" SUMMARY METRICS"); - console.log("=".repeat(80)); - - const metricsHeader = `| ${pad("Metric", 30)}| ${pad("No Harness", 15)}| ${pad("With Harness", 15)}|`; - const metricsSep = `|${"-".repeat(32)}|${"-".repeat(17)}|${"-".repeat(17)}|`; - console.log("\n" + metricsHeader); - console.log(metricsSep); - console.log(`| ${pad("Tasks passed", 30)}| ${pad(String(nhPassed) + "/" + noHarness.length, 15)}| ${pad(String(whPassed) + "/" + withHarness.length, 15)}|`); - console.log(`| ${pad("Issues detected", 30)}| ${pad(String(nhIssues), 15)}| ${pad(String(whIssues), 15)}|`); - console.log(`| ${pad("False positives (passed but flawed)", 30)}| ${pad(String(nhPassed - truePassCount(noHarness)), 15)}| ${pad(String(whPassed - truePassCount(withHarness)), 15)}|`); - - console.log("\n The harness catches problems the no-harness run silently ignores."); - console.log(" Without a harness, every task 'passes' even when it shouldn't.\n"); -} - -/** Count tasks that actually pass (have tests and are in scope). */ -function truePassCount(results: TaskResult[]): number { - return results.filter((r) => r.passed && r.issues.length === 0).length; -} - -// --------------------------------------------------------------------------- -// Run -// --------------------------------------------------------------------------- - -printComparison(runWithoutHarness(tasks), runWithHarness(tasks)); diff --git a/courses/learn-harness-engineering/docs/es/lectures/lecture-02-what-a-harness-actually-is/code/index.md b/courses/learn-harness-engineering/docs/es/lectures/lecture-02-what-a-harness-actually-is/code/index.md deleted file mode 100644 index 484185b9..00000000 --- a/courses/learn-harness-engineering/docs/es/lectures/lecture-02-what-a-harness-actually-is/code/index.md +++ /dev/null @@ -1,8 +0,0 @@ -# Código de la Clase 02 - -Usa esta carpeta para pequeños ejemplos que distinguen: - -- comportamiento del modelo -- comportamiento del harness -- configuraciones basadas solo en prompts -- configuraciones de agentes respaldadas por el entorno diff --git a/courses/learn-harness-engineering/docs/es/lectures/lecture-02-what-a-harness-actually-is/code/minimal-harness-loop.ts b/courses/learn-harness-engineering/docs/es/lectures/lecture-02-what-a-harness-actually-is/code/minimal-harness-loop.ts deleted file mode 100644 index e56cddd8..00000000 --- a/courses/learn-harness-engineering/docs/es/lectures/lecture-02-what-a-harness-actually-is/code/minimal-harness-loop.ts +++ /dev/null @@ -1,36 +0,0 @@ -type Message = { - role: "user" | "assistant"; - content: string; -}; - -type ToolResult = { - ok: boolean; - output: string; -}; - -function runTool(name: string, input: string): ToolResult { - if (name === "read_file") { - return { ok: true, output: `contents of ${input}` }; - } - - return { ok: false, output: `unknown tool: ${name}` }; -} - -export function minimalHarness(messages: Message[]) { - const nextAction = { - tool: "read_file", - input: "README.md" - }; - - const result = runTool(nextAction.tool, nextAction.input); - - return { - messages: [ - ...messages, - { - role: "assistant", - content: `Tool ${nextAction.tool} returned: ${result.output}` - } - ] - }; -} diff --git a/courses/learn-harness-engineering/docs/es/lectures/lecture-02-what-a-harness-actually-is/index.md b/courses/learn-harness-engineering/docs/es/lectures/lecture-02-what-a-harness-actually-is/index.md deleted file mode 100644 index 6ccff106..00000000 --- a/courses/learn-harness-engineering/docs/es/lectures/lecture-02-what-a-harness-actually-is/index.md +++ /dev/null @@ -1,109 +0,0 @@ -[中文版本 →](../../../zh/lectures/lecture-02-what-a-harness-actually-is/) - -> Ejemplos de código: [code/](https://github.com/walkinglabs/learn-harness-engineering/blob/main/docs/es/lectures/lecture-02-what-a-harness-actually-is/code/) -> Proyecto práctico: [Project 01. Prompt-only vs. rules-first](./../../projects/project-01-baseline-vs-minimal-harness/index.md) - -# Lección 02. Qué significa realmente harness - -La palabra "harness" se usa mucho en los círculos de agentes de codificación con IA, pero honestamente, la mayoría de la gente se refiere a "un archivo de prompt" cuando dice harness. Eso no es un harness. Es como abrir un restaurante con nada más que ingredientes — sin estufa, sin cuchillos, sin recetas, sin flujo de emplatado. Eso no es un restaurante. Es un refrigerador. - -Esta lección te da una definición precisa y práctica de harness. No una abstracción académica, sino un marco que puedes usar hoy: un harness consiste en cinco subsistemas, cada uno con responsabilidades claras y criterios de evaluación. - -## Comienza con una analogía - -Imagina que eres un ingeniero recién contratado que llega a un proyecto sin documentación. Sin README, sin comentarios en el código, nadie te dice cómo ejecutar las pruebas, la configuración de CI está enterrada en algún lugar. ¿Puedes escribir buen código? Tal vez — si eres lo suficientemente inteligente y paciente. Pero pasarás mucho tiempo en "averiguar de qué trata este proyecto" en lugar de "resolver el problema." - -Un agente de IA enfrenta exactamente la misma situación. Y es peor — tú al menos puedes preguntarle a un colega. El agente solo puede ver los archivos que le pongas delante y los comandos que pueda ejecutar. No puede tocar a alguien en el hombro y preguntar "oye, ¿qué versión del ORM usa este proyecto?" - -OpenAI enmarca el principio central como "el repositorio ES la especificación" — todo el contexto necesario debería estar en el repositorio, entregado a través de archivos de instrucciones estructurados, comandos de verificación explícitos y una organización clara de directorios. La documentación de Anthropic sobre agentes de larga duración enfatiza la persistencia del estado, las rutas de recuperación explícitas y el seguimiento estructurado del progreso. Las dos empresas se enfocan en aspectos diferentes, pero están diciendo lo mismo: **todo en la infraestructura de ingeniería fuera del modelo determina cuánta de la capacidad del modelo se realiza realmente.** - -Mira algunas herramientas que ya conoces: - -**Claude Code** encarna el pensamiento de harness. Lee `CLAUDE.md` de tu repositorio (estantería de recetas), puede ejecutar comandos de shell (estante de cuchillos), se ejecuta en tu entorno local (estufa), mantiene el historial de sesión (estación de preparación) y puede ejecutar pruebas y ver resultados (ventana de control de calidad). Pero si no le dices cómo ejecutar las pruebas, la ventana de control de calidad está rota — nadie sabe si el plato está completamente cocinado. - -**Cursor** sigue una lógica similar. Su archivo `.cursorrules` es la estantería de recetas, la terminal es el estante de cuchillos, lee la estructura de tu proyecto y la configuración de lint como estufa. Pero la gestión de estado de Cursor es relativamente débil — cierra el IDE y vuelve a abrirlo, y el contexto anterior desaparece. - -**Codex** (el agente de codificación de OpenAI) usa git worktrees para aislar el entorno de ejecución de cada tarea, junto con una pila de observabilidad local (logs, métricas, trazas), para que cada cambio se verifique en un entorno independiente. En repositorios con `AGENTS.md` y comandos de verificación claros, se desempeña mucho mejor que en repositorios "desnudos". - -**AutoGPT** es el cuento de advertencia — la falta de gestión estructurada del estado lleva a la acumulación de contexto en tareas largas, y la falta de mecanismos precisos de retroalimentación hace que el agente entre en bucle. Mucha gente dice que AutoGPT "no funciona," pero en realidad es el harness de AutoGPT el que no funciona — dale a un chef una estufa rota y ni los mejores ingredientes producirán una comida. - -## Conceptos clave - -- **Qué es un harness**: Todo en la infraestructura de ingeniería fuera de los pesos del modelo. OpenAI destila el trabajo central del ingeniero en tres cosas: diseñar entornos, expresar intenciones y construir ciclos de retroalimentación. Anthropic llama a su Claude Agent SDK un "agent harness de propósito general." -- **El repositorio es la única fuente de verdad**: Cualquier cosa que el agente no pueda ver, para todos los efectos prácticos, no existe. OpenAI trata el repositorio como el "sistema de registro" — todo el contexto necesario debe vivir ahí, a través de archivos estructurados y una organización clara de directorios. -- **Da un mapa, no un manual**: La experiencia de OpenAI — `AGENTS.md` debería ser una página de directorio, no una enciclopedia. Alrededor de 100 líneas es suficiente. Si no cabe, divídelo en el directorio `docs/` y deja que el agente lea bajo demanda. -- **Restringe, no microgestiones**: Un buen harness usa reglas ejecutables para restringir al agente, en lugar de enumerar instrucciones una por una. OpenAI dice "haz cumplir invariantes, no microgestiones la implementación"; Anthropic descubrió que los agentes elogian confiadamente su propio trabajo, y la solución es separar "la persona que hace el trabajo" de "la persona que revisa el trabajo." -- **Elimina componentes uno a la vez**: Para cuantificar la contribución marginal de cada componente del harness, elimínalos uno a la vez y observa cuál eliminación causa la mayor caída de rendimiento. Anthropic usó este método y descubrió que a medida que los modelos se vuelven más fuertes, algunos componentes dejan de ser críticos — pero siempre surgen nuevos. - -## El modelo de harness de cinco subsistemas - -Volviendo a la analogía de la cocina. Una cocina completa tiene cinco áreas funcionales, y un harness tiene cinco subsistemas: - -```mermaid -flowchart LR - Rules["Project rules
AGENTS.md / CLAUDE.md"] --> Agent["AI Agent"] - State["Progress and git
PROGRESS.md / commits"] --> Agent - Agent --> Tools["Tools
shell / files / tests"] - Tools --> Env["Runtime
deps / services / versions"] - Env --> Checks["Check results
test / lint / build"] - Checks --> Agent -``` - -**Subsistema de instrucciones (estantería de recetas)**: Crea `AGENTS.md` (o `CLAUDE.md`) que contenga un resumen y propósito del proyecto (una frase), el stack tecnológico y versiones (Python 3.11, FastAPI 0.100+, PostgreSQL 15), comandos de primera ejecución (`make setup`, `make test`), restricciones fijas innegociables ("Todas las APIs deben usar OAuth 2.0") y enlaces a documentación más detallada. - -**Subsistema de herramientas (estante de cuchillos)**: Asegúrate de que el agente tenga acceso suficiente a herramientas. No desactives el shell por "seguridad" — si el agente ni siquiera puede ejecutar `pip install`, ¿cómo se supone que trabaje? Pero tampoco abras todo — sigue principios de mínimo privilegio. - -**Subsistema de entorno (estufa)**: Haz que el estado del entorno se describa a sí mismo. Usa `pyproject.toml` o `package.json` para bloquear dependencias, `.nvmrc` o `.python-version` para versiones de runtime, Docker o devcontainers para reproducibilidad. - -**Subsistema de estado (estación de preparación)**: Las tareas largas necesitan seguimiento del progreso. Usa un archivo simple `PROGRESS.md` que registre: qué está hecho, qué está en progreso, qué está bloqueado. Actualízalo antes de que termine cada sesión, y léelo cuando comience la siguiente. - -**Subsistema de retroalimentación (ventana de control de calidad)**: Este es el subsistema de mayor ROI. Lista explícitamente los comandos de verificación en `AGENTS.md`: -``` -Verification commands: -- Tests: pytest tests/ -x -- Type check: mypy src/ --strict -- Lint: ruff check src/ -- Full verification: make check (includes all above) -``` - -Que falte cualquier subsistema es como que falte un área funcional en la cocina — todavía puedes cocinar, pero siempre será incómodo. - -**Cuantificar el valor de los componentes del harness**: Usa "control isométrico del modelo." Mantén el modelo fijo, elimina subsistemas uno a la vez y mide cuál eliminación causa la mayor caída de rendimiento. La mayor caída identifica el componente con mayor contribución marginal en esa tarea, no automáticamente el cuello de botella. Para localizar el cuello de botella, combina el experimento con registros de fallos y atribución: tarea poco clara, contexto insuficiente, entorno no reproducible, falta de feedback de verificación o gestión de estado rota. - -## La historia real de un equipo - -Un equipo usó GPT-4o en una aplicación frontend de TypeScript + React (~20,000 líneas de código). Pasaron por cuatro etapas — esencialmente añadiendo equipamiento de cocina pieza por pieza: - -**Etapa 1 — Cocina vacía**: Solo una descripción básica del proyecto en README. 1 de 5 ejecuciones tuvo éxito (20%). Fallos principales: eligió el gestor de paquetes equivocado (npm vs yarn), no siguió las convenciones de nombres de componentes, no pudo ejecutar las pruebas. - -**Etapa 2 — Estantería de recetas instalada**: Añadió `AGENTS.md` con versiones del stack tecnológico, convenciones de nombres, decisiones clave de arquitectura. La tasa de éxito subió al 60%. Los fallos restantes fueron principalmente problemas de entorno y verificación faltante. - -**Etapa 3 — Ventana de control de calidad abierta**: Listó los comandos de verificación en `AGENTS.md`: `yarn test && yarn lint && yarn build`. La tasa de éxito subió al 80%. - -**Etapa 4 — Estación de preparación lista**: Introdujo plantillas de archivos de progreso donde los agentes registraban el trabajo completado e incompleto en cada ejecución. La tasa de éxito se estabilizó en 80-100%. - -Cuatro iteraciones, el modelo no cambió en absoluto, la tasa de éxito pasó del 20% a casi el 100%. Ese es el poder de la ingeniería de harness. No compraste ingredientes más caros — simplemente organizaste la cocina correctamente. - -## Ideas clave - -- Harness = Instrucciones + Herramientas + Entorno + Estado + Retroalimentación. Cinco subsistemas, como las cinco áreas funcionales de una cocina — todos esenciales. -- Si no son pesos del modelo, es harness. Tu harness determina cuánta capacidad del modelo se realiza. -- Entre los cinco subsistemas, el subsistema de retroalimentación suele tener la menor inversión y el mayor retorno. Configura bien tus comandos de verificación primero — la ventana de control de calidad es la mejora más valiosa. -- Usa "control isométrico del modelo" para cuantificar la contribución marginal de cada subsistema; usa registros de fallos y atribución para localizar el cuello de botella real. -- El harness se degrada como el código. Audita regularmente, paga la deuda de harness como pagas la deuda técnica. - -## Lecturas adicionales - -- [OpenAI: Harness Engineering](https://openai.com/index/harness-engineering/) -- [Anthropic: Effective Harnesses for Long-Running Agents](https://www.anthropic.com/engineering/effective-harnesses-for-long-running-agents) -- [HumanLayer: Harness Engineering for Coding Agents](https://humanlayer.dev/articles/harness-engineering-for-coding-agents/) -- [SWE-agent: Agent-Computer Interfaces](https://github.com/princeton-nlp/SWE-agent) -- [Thoughtworks: Harness Engineering on Technology Radar](https://www.thoughtworks.com/radar) - -## Ejercicios - -1. **Auditoría de harness de cinco componentes**: Toma un proyecto donde uses un agente de IA y haz una auditoría completa usando el marco de cinco componentes. Califica cada subsistema del 1 al 5. Encuentra el subsistema con la calificación más baja, dedica 30 minutos a mejorarlo y luego observa el cambio en el rendimiento del agente. - -2. **Experimento de control isométrico del modelo**: Elige un modelo y una tarea desafiante. Secuencialmente elimina las instrucciones (borra AGENTS.md), elimina la retroalimentación (no proporciones comandos de verificación), elimina el estado (sin archivos de progreso) — elimina solo uno a la vez y mide la caída de rendimiento. Basándote en los resultados, clasifica la importancia de los subsistemas para tu proyecto. - -3. **Análisis de affordances**: Encuentra un escenario donde el agente en tu proyecto "quiere hacer algo pero no puede" (por ejemplo, sabe que debería usar consultas parametrizadas pero no conoce los patrones ORM de tu proyecto). Analiza si esto es un Gulf of Execution (no sabe cómo) o un Gulf of Evaluation (no sabe si está bien), y luego diseña una mejora del harness para cerrar esa brecha. diff --git a/courses/learn-harness-engineering/docs/es/lectures/lecture-03-why-the-repository-must-become-the-system-of-record/code/index.md b/courses/learn-harness-engineering/docs/es/lectures/lecture-03-why-the-repository-must-become-the-system-of-record/code/index.md deleted file mode 100644 index c16eec7c..00000000 --- a/courses/learn-harness-engineering/docs/es/lectures/lecture-03-why-the-repository-must-become-the-system-of-record/code/index.md +++ /dev/null @@ -1,7 +0,0 @@ -# Código de la Clase 03 - -Usa esta carpeta para ejemplos de: - -- estructuras de repositorio legibles por agentes -- documentación como sistema de registro (system of record) -- ubicación incorrecta vs. correcta del conocimiento diff --git a/courses/learn-harness-engineering/docs/es/lectures/lecture-03-why-the-repository-must-become-the-system-of-record/code/repo-knowledge-layout.txt b/courses/learn-harness-engineering/docs/es/lectures/lecture-03-why-the-repository-must-become-the-system-of-record/code/repo-knowledge-layout.txt deleted file mode 100644 index 45315dac..00000000 --- a/courses/learn-harness-engineering/docs/es/lectures/lecture-03-why-the-repository-must-become-the-system-of-record/code/repo-knowledge-layout.txt +++ /dev/null @@ -1,13 +0,0 @@ -AGENTS.md -docs/ - ARCHITECTURE.md - PRODUCT.md - RELIABILITY.md - references/ - electron.md - sqlite.md - plans/ - active/ - completed/ -src/ -scripts/ diff --git a/courses/learn-harness-engineering/docs/es/lectures/lecture-03-why-the-repository-must-become-the-system-of-record/code/repo-reader.ts b/courses/learn-harness-engineering/docs/es/lectures/lecture-03-why-the-repository-must-become-the-system-of-record/code/repo-reader.ts deleted file mode 100644 index 46f6a62a..00000000 --- a/courses/learn-harness-engineering/docs/es/lectures/lecture-03-why-the-repository-must-become-the-system-of-record/code/repo-reader.ts +++ /dev/null @@ -1,189 +0,0 @@ -/** - * repo-reader.ts - * - * Reads a directory structure and scores it on discoverability. - * Checks for: AGENTS.md, docs/, architecture docs, feature tracking, - * handoff files, and other signals of a repository that serves as the - * system of record. - * - * Usage: - * npx tsx docs/lectures/lecture-03.../code/repo-reader.ts [path] - * (defaults to current working directory if no path given) - * - * Run: npx tsx docs/lectures/lecture-03-why-the-repository-must-become-the-system-of-record/code/repo-reader.ts - */ - -import * as fs from "node:fs"; -import * as path from "node:path"; - -// --------------------------------------------------------------------------- -// Scoring criteria -// --------------------------------------------------------------------------- - -interface Check { - name: string; - description: string; - maxPoints: number; - check: (dir: string) => { points: number; found: string[]; missing: string[] }; -} - -const checks: Check[] = [ - { - name: "AGENTS.md / CLAUDE.md", - description: "Agent-readable instruction file at repo root", - maxPoints: 15, - check: (dir) => { - const candidates = ["AGENTS.md", "CLAUDE.md", ".claude/CLAUDE.md"]; - const found = candidates.filter((f) => fs.existsSync(path.join(dir, f))); - return { points: found.length > 0 ? 15 : 0, found, missing: found.length > 0 ? [] : candidates }; - }, - }, - { - name: "Documentation directory", - description: "Dedicated docs/ or documentation/ directory", - maxPoints: 10, - check: (dir) => { - const candidates = ["docs", "documentation", "doc"]; - const found = candidates.filter((f) => { - const p = path.join(dir, f); - return fs.existsSync(p) && fs.statSync(p).isDirectory(); - }); - return { points: found.length > 0 ? 10 : 0, found, missing: found.length > 0 ? [] : ["docs/"] }; - }, - }, - { - name: "Architecture documentation", - description: "Files describing system architecture", - maxPoints: 15, - check: (dir) => { - const patterns = [ - "architecture.md", "ARCHITECTURE.md", "docs/architecture.md", - "docs/architecture/", "design.md", "DESIGN.md", - ]; - const found = patterns.filter((f) => fs.existsSync(path.join(dir, f))); - return { points: found.length > 0 ? 15 : 0, found, missing: found.length > 0 ? [] : ["architecture.md or docs/architecture/"] }; - }, - }, - { - name: "Feature tracking", - description: "Feature list or task tracking file", - maxPoints: 15, - check: (dir) => { - const patterns = [ - "feature_list.json", "features.md", "FEATURES.md", - "docs/features.md", "tasks.json", "TODO.md", - ]; - const found = patterns.filter((f) => fs.existsSync(path.join(dir, f))); - return { points: found.length > 0 ? 15 : 0, found, missing: found.length > 0 ? [] : ["feature_list.json or features.md"] }; - }, - }, - { - name: "Handoff / session continuity", - description: "Files for multi-session continuity", - maxPoints: 15, - check: (dir) => { - const patterns = [ - "HANDOFF.md", "handoff.md", "SESSION_NOTES.md", - "docs/handoff.md", ".handoff", "PROGRESS.md", - ]; - const found = patterns.filter((f) => fs.existsSync(path.join(dir, f))); - return { points: found.length > 0 ? 15 : 0, found, missing: found.length > 0 ? [] : ["HANDOFF.md or PROGRESS.md"] }; - }, - }, - { - name: "Testing structure", - description: "Test directory or test configuration", - maxPoints: 10, - check: (dir) => { - const patterns = ["test", "tests", "__tests__", "spec"]; - const found = patterns.filter((f) => { - const p = path.join(dir, f); - return fs.existsSync(p) && fs.statSync(p).isDirectory(); - }); - return { points: found.length > 0 ? 10 : 0, found, missing: found.length > 0 ? [] : ["test/ or tests/"] }; - }, - }, - { - name: "Configuration files", - description: "Project config (package.json, tsconfig, etc.)", - maxPoints: 10, - check: (dir) => { - const patterns = ["package.json", "tsconfig.json", "pyproject.toml", "Cargo.toml", "go.mod"]; - const found = patterns.filter((f) => fs.existsSync(path.join(dir, f))); - return { points: found.length > 0 ? 10 : 0, found, missing: found.length > 0 ? [] : ["package.json or equivalent"] }; - }, - }, - { - name: "README", - description: "Root README file", - maxPoints: 10, - check: (dir) => { - const patterns = ["README.md", "README.rst", "README.txt", "README"]; - const found = patterns.filter((f) => fs.existsSync(path.join(dir, f))); - return { points: found.length > 0 ? 10 : 0, found, missing: found.length > 0 ? [] : ["README.md"] }; - }, - }, -]; - -// --------------------------------------------------------------------------- -// Report generation -// --------------------------------------------------------------------------- - -function pad(s: string, len: number): string { - return s.length >= len ? s : s + " ".repeat(len - s.length); -} - -function scoreRepo(targetDir: string): void { - const resolved = path.resolve(targetDir); - - if (!fs.existsSync(resolved)) { - console.error(` Error: Directory not found: ${resolved}`); - process.exit(1); - } - - console.log("\n" + "=".repeat(80)); - console.log(" REPOSITORY DISCOVERABILITY SCORE"); - console.log("=".repeat(80)); - console.log(` Target: ${resolved}\n`); - - const header = `| ${pad("Criterion", 30)}| ${pad("Points", 8)}| ${pad("Status", 10)}| Details`; - const sep = `|${"-".repeat(32)}|${"-".repeat(10)}|${"-".repeat(12)}|${"-".repeat(30)}`; - console.log(header); - console.log(sep); - - let totalScore = 0; - let maxScore = 0; - - for (const check of checks) { - const result = check.check(resolved); - totalScore += result.points; - maxScore += check.maxPoints; - const status = result.points > 0 ? "PASS" : "FAIL"; - const detail = result.found.length > 0 ? result.found.join(", ") : result.missing[0]; - - console.log(`| ${pad(check.name, 30)}| ${pad(result.points + "/" + check.maxPoints, 8)}| ${pad(status, 10)}| ${detail}`); - } - - // Final score - console.log("\n" + "-".repeat(80)); - console.log(` TOTAL SCORE: ${totalScore} / ${maxScore} (${Math.round((totalScore / maxScore) * 100)}%)`); - - // Grade - const pct = totalScore / maxScore; - let grade: string; - if (pct >= 0.9) grade = "A -- Repository is a strong system of record"; - else if (pct >= 0.7) grade = "B -- Good foundation, minor gaps"; - else if (pct >= 0.5) grade = "C -- Partial structure, significant gaps"; - else if (pct >= 0.3) grade = "D -- Minimal structure, hard for agents to navigate"; - else grade = "F -- Repository lacks discoverability signals"; - - console.log(` GRADE: ${grade}`); - console.log("=".repeat(80) + "\n"); -} - -// --------------------------------------------------------------------------- -// Main -// --------------------------------------------------------------------------- - -const target = process.argv[2] || process.cwd(); -scoreRepo(target); diff --git a/courses/learn-harness-engineering/docs/es/lectures/lecture-03-why-the-repository-must-become-the-system-of-record/code/system-of-record-checklist.md b/courses/learn-harness-engineering/docs/es/lectures/lecture-03-why-the-repository-must-become-the-system-of-record/code/system-of-record-checklist.md deleted file mode 100644 index 05881f53..00000000 --- a/courses/learn-harness-engineering/docs/es/lectures/lecture-03-why-the-repository-must-become-the-system-of-record/code/system-of-record-checklist.md +++ /dev/null @@ -1,11 +0,0 @@ -# Lista de Verificación del Sistema de Registro - -¿Puede un agente nuevo descubrir lo siguiente solo desde el repositorio? - -- ¿Qué producto se está construyendo? -- ¿Qué debería hacer la app para los usuarios? -- ¿Cómo está organizado el código? -- ¿Cómo se inicia la app? -- ¿Cómo se verifica el estado de salud? -- ¿Qué trabajo está actualmente en progreso? -- ¿Qué estándares de calidad importan? diff --git a/courses/learn-harness-engineering/docs/es/lectures/lecture-03-why-the-repository-must-become-the-system-of-record/index.md b/courses/learn-harness-engineering/docs/es/lectures/lecture-03-why-the-repository-must-become-the-system-of-record/index.md deleted file mode 100644 index ffc5cd10..00000000 --- a/courses/learn-harness-engineering/docs/es/lectures/lecture-03-why-the-repository-must-become-the-system-of-record/index.md +++ /dev/null @@ -1,137 +0,0 @@ -[中文版本 →](../../../zh/lectures/lecture-03-why-the-repository-must-become-the-system-of-record/) - -> Ejemplos de código: [code/](https://github.com/walkinglabs/learn-harness-engineering/blob/main/docs/es/lectures/lecture-03-why-the-repository-must-become-the-system-of-record/code/) -> Proyecto práctico: [Proyecto 02. Espacio de trabajo legible para agentes](./../../projects/project-02-agent-readable-workspace/index.md) - -# Lección 03. Convierte el repositorio en la fuente única de verdad - -Las decisiones de arquitectura de tu equipo están dispersas entre Confluence, Slack, Jira y la cabeza de unos cuantos ingenieros senior. Para los humanos esto apenas funciona: puedes preguntar a un compañero, buscar en el historial del chat, revisar documentación. Si todo falla, puedes acorralar a alguien en la sala de descanso. Pero para un agente de IA, la información que no está en el repositorio simplemente no existe. - -No es una exageración. Piensa en cuáles son realmente las entradas de un agente: prompts de sistema y descripciones de tarea, contenidos de archivos del repositorio y salidas de herramientas. Eso es todo. Tu historial de Slack, tickets de Jira, páginas de Confluence y esa decisión de arquitectura que hablaste con un compañero tomando café un viernes por la tarde: el agente no ve nada de eso. No puede "ir a preguntar a alguien" ni "buscar en el historial del chat". Es un ingeniero encerrado dentro del repositorio; de todo lo que queda fuera no sabe nada. - -Así que la pregunta es: ¿vas a darle a este ingeniero un buen mapa? - -## Qué debe estar en el mapa - -OpenAI lo dice sin rodeos: **la información que no existe en el repo no existe para el agente.** Llaman a esto el principio "repo as spec": el propio repositorio es el documento de especificación con mayor autoridad. - -La documentación de Anthropic sobre agentes de larga duración apunta en la misma dirección: el estado persistente es una condición necesaria para la continuidad en tareas largas. La recuperabilidad del conocimiento entre sesiones determina directamente las tasas de éxito. Y ese estado debe existir en el repositorio, porque es el único almacenamiento estable y accesible que tiene el agente. - -Puedes pensar: "Nuestro equipo es pequeño, el conocimiento está en la cabeza de todos y funciona bien". Para humanos, quizá. Pero si usas un agente, acepta este hecho: el agente no puede preguntar a personas. Todo lo que necesita saber debe estar escrito y colocado donde pueda encontrarlo. - -No se trata de "escribir más documentación". Se trata de "poner la información de decisión en el lugar correcto". Un `ARCHITECTURE.md` de 50 líneas dentro de `src/api/` es diez mil veces más útil que un documento de diseño de 500 páginas en Confluence que nadie mantiene. Es como un mapa de oficina dibujado a mano y pegado a tu escritorio frente a un plano arquitectónico precioso guardado bajo llave: el primero está ahí cuando lo necesitas; el segundo puede ser técnicamente superior, pero es inútil en el momento. - -## Visibilidad del conocimiento - -```mermaid -flowchart LR - Slack["Reglas en Slack"] --> Write["Escríbelas en archivos del repo
AGENTS.md / ARCHITECTURE.md / PROGRESS.md"] - Confluence["Reglas en Confluence"] --> Write - Heads["Reglas en la cabeza de las personas"] --> Write - Jira["Reglas en tickets de Jira"] --> Write - Write --> Repo["Archivos del repositorio"] - Repo --> Agent["Nueva sesión de agente
lee el repo directamente"] - Warning["Si una regla no está en el repo,
el agente no puede verla"] --> Agent -``` - -¿Cómo compruebas si tu mapa es suficientemente bueno? Ejecuta una "prueba de arranque en frío": abre una sesión de agente completamente nueva usando solo el contenido del repo y mira si puede responder cinco preguntas básicas: - -```mermaid -flowchart TB - Q1["¿Qué es este sistema?"] --> A1["AGENTS.md / README"] - Q2["¿Cómo está organizado?"] --> A2["ARCHITECTURE.md / docs de módulos"] - Q3["¿Cómo lo ejecuto?"] --> A3["Makefile / init.sh / scripts de package"] - Q4["¿Cómo lo verifico?"] --> A4["Comandos de test, lint y check"] - Q5["¿Dónde estamos ahora?"] --> A5["PROGRESS.md / feature list / historial git"] - - A1 --> Ready["Una nueva sesión puede empezar a trabajar
sin preguntar a un humano"] - A2 --> Ready - A3 --> Ready - A4 --> Ready - A5 --> Ready -``` - -Si no puede responder, el mapa tiene zonas en blanco. Donde el mapa está en blanco, el agente adivina: las malas conjeturas se convierten en bugs y adivinar demasiado desperdicia contexto. Y cada sesión nueva vuelve a adivinarlo todo. El coste de adivinar siempre es mayor que el coste de dibujar bien el mapa desde el principio. - -## Conceptos clave - -- **Knowledge Visibility Gap**: La proporción del conocimiento total del proyecto que NO está en el repositorio. Cuanto mayor sea la brecha, mayor será la tasa de fallos del agente. ¿Cuánto conocimiento implícito sobre este proyecto vive en tu cabeza? Cuéntalo todo y luego mira cuánto llegó al repo: la diferencia es tu brecha de visibilidad. -- **System of Record**: El repositorio de código como fuente autorizada para decisiones de proyecto, restricciones de arquitectura, estado de ejecución y estándares de verificación. El repo tiene la última palabra; lo demás no cuenta. Como un mapa que marca "carretera cerrada": no irás por ahí. Pero si esa información solo está en la cabeza de alguien, tendrás que preguntarle cada vez. -- **Cold-Start Test**: Las cinco preguntas anteriores. Cuántas puede responder indica qué tan completo es tu mapa. -- **Discovery Cost**: Cuánto presupuesto de contexto consume el agente para encontrar una información clave en el repo. Cuanto más escondida esté, mayor será el coste de descubrimiento y menos presupuesto quedará para la tarea real. Esconder información crítica en un README diez niveles de directorios abajo es como guardar el extintor en una caja fuerte del sótano: existe, pero no lo encuentras cuando lo necesitas. -- **Knowledge Decay Rate**: La proporción de entradas de conocimiento que se vuelven obsoletas por unidad de tiempo. Que la documentación deje de estar sincronizada con el código es el enemigo principal, peor incluso que no tener documentación. -- **ACID Analogy**: Aplicar principios de transacciones de base de datos (Atomicity, Consistency, Isolation, Durability) a la gestión del estado de agentes. Lo desarrollamos abajo. - -## Cómo dibujar un buen mapa - -**Principio 1: El conocimiento vive junto al código.** Una regla sobre autenticación de endpoints API debe estar junto al código de API, no enterrada en un documento global gigante. Pon un documento corto en cada directorio de módulo explicando responsabilidades, interfaces y restricciones especiales. Como etiquetas de estantería en una biblioteca: si quieres libros de historia, vas directo al estante "Historia". No necesitas buscar por toda la biblioteca. - -**Principio 2: Usa un archivo de entrada estandarizado.** `AGENTS.md` (o `CLAUDE.md`) es la página de aterrizaje del agente. No necesita contener toda la información, pero debe permitir que el agente responda rápido tres preguntas: "Qué es este proyecto", "Cómo lo ejecuto" y "Cómo lo verifico". 50-100 líneas bastan. - -**Principio 3: Mínimo pero completo.** Cada pieza de conocimiento debe tener un caso de uso claro. Si eliminar una regla no afecta la calidad de decisión del agente, esa regla no debería existir. Pero cada pregunta de la prueba de arranque en frío debe tener respuesta. Es un equilibrio delicado: ni demasiado, ni demasiado poco, solo lo suficiente. - -**Principio 4: Actualiza junto con el código.** Vincula las actualizaciones de conocimiento a los cambios de código. El enfoque más simple: coloca los documentos de arquitectura en el directorio del módulo correspondiente. Cuando modificas código, ves el documento de forma natural. Después de cambios de código, CI puede recordarte revisar si la documentación necesita actualización. - -**Estructura concreta del repo**: - -``` -project/ -├── AGENTS.md # Entry: project overview, run commands, hard constraints -├── src/ -│ ├── api/ -│ │ ├── ARCHITECTURE.md # API layer architecture decisions -│ │ └── ... -│ ├── db/ -│ │ ├── CONSTRAINTS.md # Database operation hard constraints -│ │ └── ... -│ └── ... -├── PROGRESS.md # Current progress: done, in-progress, blocked -└── Makefile # Standardized commands: setup, test, lint, check -``` - -## Gestionar estado de agentes con principios ACID - -Esta analogía viene de la gestión de transacciones en bases de datos. Puede parecer una complicación excesiva, pero en realidad da un marco muy práctico: - -- **Atomicity**: Cada "operación lógica" (por ejemplo, "añadir nuevo endpoint y actualizar tests") recibe un commit de git. Si falla a mitad de camino, `git stash` para revertir. Todo o nada: no hay "medio hecho". -- **Consistency**: Define predicados de verificación de "estado consistente": todos los tests pasan, lint no reporta errores. El agente ejecuta verificación después de cada operación; los estados intermedios inconsistentes no se commitean. Como una transferencia bancaria: no puedes debitar sin acreditar. -- **Isolation**: Cuando varios agentes trabajan en paralelo, diseña archivos de estado que eviten condiciones de carrera. Enfoque simple: cada agente usa su propio archivo de progreso, o se usan ramas de git para aislar. Dos cocineros no pueden sazonar la misma olla al mismo tiempo: ¿quién se responsabiliza si queda demasiado salada? -- **Durability**: El conocimiento crítico del proyecto vive en archivos versionados por git. El estado temporal puede quedarse en memoria de sesión, pero el conocimiento entre sesiones debe persistirse en archivos. Lo que está en tu cabeza no cuenta; solo cuenta lo que está escrito. - -## Una historia real de transformación - -Un equipo mantenía una plataforma de e-commerce con unos 30 microservicios. Las decisiones de arquitectura (protocolos de comunicación entre servicios, estrategias de consistencia de datos, reglas de versionado de API) estaban dispersas entre Confluence (parcialmente obsoleto), Slack (difícil de buscar), la cabeza de algunos ingenieros senior (no escalable) y comentarios de código esporádicos (no sistemáticos). - -Después de introducir agentes de IA, el 70% de las tareas requería intervención humana. Casi todos los fallos implicaban que el agente violaba alguna restricción implícita de "todo el mundo lo sabe pero nadie lo escribió". Es como un empleado nuevo a quien nadie le dijo "tienes que publicar tu pedido de almuerzo en el chat del grupo": adivina mal, le regañan, pero después de regañarlo nadie escribe la regla. - -El equipo ejecutó una transformación: -1. Creó `AGENTS.md` en la raíz del repo con resumen del proyecto, versiones del stack técnico y restricciones globales duras -2. Añadió `ARCHITECTURE.md` en cada directorio de microservicio describiendo responsabilidades, interfaces y dependencias -3. Creó un `CONSTRAINTS.md` centralizado con restricciones duras en lenguaje explícito "MUST/MUST NOT" -4. Añadió `PROGRESS.md` en cada directorio de servicio para seguir el estado actual del trabajo - -Después de la transformación, el mismo agente podía responder todas las preguntas clave del proyecto en arranque en frío, y la calidad de finalización de tareas mejoró significativamente. - -## Ideas clave - -- El conocimiento que no está en el repo no existe para el agente. Poner decisiones críticas en el repo es la inversión más básica en harness: dibuja un buen mapa para no perderte. -- Usa la "prueba de arranque en frío" para evaluar la calidad del repo: ¿puede una sesión fresca responder cinco preguntas básicas usando solo el contenido del repo? -- El conocimiento debe estar cerca del código, ser mínimo pero completo y actualizarse junto con el código. No se trata de escribir más docs, sino de poner la información en el lugar correcto. -- Usa principios ACID para el estado de agentes: commits atómicos, verificación de consistencia, aislamiento de concurrencia y conocimiento crítico durable. -- La degradación del conocimiento es el mayor enemigo. La documentación desincronizada del código es más peligrosa que no tener documentación: envía al agente en la dirección equivocada mientras cree que va bien. - -## Lecturas adicionales - -- [OpenAI: Harness Engineering](https://openai.com/index/harness-engineering/) -- [Anthropic: Effective Harnesses for Long-Running Agents](https://www.anthropic.com/engineering/effective-harnesses-for-long-running-agents) -- [Infrastructure as Code — Martin Fowler](https://martinfowler.com/bliki/InfrastructureAsCode.html) -- [ADR: Architecture Decision Records](https://adr.github.io/) -- [The Twelve-Factor App](https://12factor.net/) - -## Ejercicios - -1. **Prueba de arranque en frío**: Abre una sesión de agente completamente nueva en tu proyecto (sin contexto verbal, solo contenido del repo). Hazle cinco preguntas: ¿Qué es este sistema? ¿Cómo está organizado? ¿Cómo lo ejecuto? ¿Cómo lo verifico? ¿Cuál es el progreso actual? Registra lo que no puede responder y mejora el repo hasta que pueda. - -2. **Cuantificación de externalización de conocimiento**: Lista todas las decisiones y restricciones importantes para el trabajo de desarrollo en tu proyecto. Marca cada una como dentro o fuera del repo. Calcula tu Knowledge Visibility Gap (proporción fuera del repo). Haz un plan para bajarlo por debajo del 10%. - -3. **Evaluación ACID**: Evalúa la gestión de estado de tu proyecto usando la analogía ACID de esta lección. Atomicity: ¿pueden las operaciones del agente revertirse limpiamente? Consistency: ¿existe verificación de "estado consistente"? Isolation: ¿los agentes concurrentes se pisan entre sí? Durability: ¿todo el conocimiento entre sesiones está persistido? diff --git a/courses/learn-harness-engineering/docs/es/lectures/lecture-04-why-one-giant-instruction-file-fails/code/AGENTS-short.md b/courses/learn-harness-engineering/docs/es/lectures/lecture-04-why-one-giant-instruction-file-fails/code/AGENTS-short.md deleted file mode 100644 index 5f94f4d9..00000000 --- a/courses/learn-harness-engineering/docs/es/lectures/lecture-04-why-one-giant-instruction-file-fails/code/AGENTS-short.md +++ /dev/null @@ -1,15 +0,0 @@ -# AGENTS.md - -## Comienza aquí - -- Lee `docs/ARCHITECTURE.md` -- Lee `docs/PRODUCT.md` -- Usa `npm run dev` para iniciar la app -- Usa `npm run check` antes de marcar el trabajo como completo - -## Reglas estrictas - -- No cambies los límites de main/preload/renderer de Electron sin leer - `docs/ARCHITECTURE.md` -- No marques una funcionalidad como completa sin verificación -- Deja un estado limpio (clean state) para la próxima sesión diff --git a/courses/learn-harness-engineering/docs/es/lectures/lecture-04-why-one-giant-instruction-file-fails/code/anti-patterns.md b/courses/learn-harness-engineering/docs/es/lectures/lecture-04-why-one-giant-instruction-file-fails/code/anti-patterns.md deleted file mode 100644 index e63705e9..00000000 --- a/courses/learn-harness-engineering/docs/es/lectures/lecture-04-why-one-giant-instruction-file-fails/code/anti-patterns.md +++ /dev/null @@ -1,7 +0,0 @@ -# Antipatrones en Archivos de Instrucciones - -- Poner todo el conocimiento del repositorio en un solo archivo -- Repetir la misma regla en múltiples lugares -- Codificar reglas obsoletas que nadie audita -- Escribir instrucciones condicionales tan específicas que rara vez aplican -- Incrustar manuales largos de herramientas en el contexto de inicio diff --git a/courses/learn-harness-engineering/docs/es/lectures/lecture-04-why-one-giant-instruction-file-fails/code/index.md b/courses/learn-harness-engineering/docs/es/lectures/lecture-04-why-one-giant-instruction-file-fails/code/index.md deleted file mode 100644 index ffad41c4..00000000 --- a/courses/learn-harness-engineering/docs/es/lectures/lecture-04-why-one-giant-instruction-file-fails/code/index.md +++ /dev/null @@ -1,7 +0,0 @@ -# Código de la Clase 04 - -Usa esta carpeta para ejemplos de: - -- archivos de instrucciones monolíticos -- puntos de entrada cortos -- patrones de divulgación progresiva (progressive disclosure) diff --git a/courses/learn-harness-engineering/docs/es/lectures/lecture-04-why-one-giant-instruction-file-fails/code/split-vs-monolithic.ts b/courses/learn-harness-engineering/docs/es/lectures/lecture-04-why-one-giant-instruction-file-fails/code/split-vs-monolithic.ts deleted file mode 100644 index 59a96328..00000000 --- a/courses/learn-harness-engineering/docs/es/lectures/lecture-04-why-one-giant-instruction-file-fails/code/split-vs-monolithic.ts +++ /dev/null @@ -1,209 +0,0 @@ -/** - * split-vs-monolithic.ts - * - * Creates a monolithic instruction file (~200 lines) and then shows how - * splitting into 4 focused files dramatically reduces the context needed - * for any single query. Simulates an "agent" searching for a specific rule - * and measures how many lines it must read in each approach. - * - * Run: npx tsx docs/lectures/lecture-04-why-one-giant-instruction-file-fails/code/split-vs-monolithic.ts - */ - -// --------------------------------------------------------------------------- -// Simulated monolithic instruction file (200 lines of rules) -// --------------------------------------------------------------------------- - -const monolithicInstructions: { lineNumber: number; section: string; content: string }[] = []; - -// Section 1: Project Overview (lines 1-50) -for (let i = 1; i <= 50; i++) { - monolithicInstructions.push({ - lineNumber: i, - section: "Project Overview", - content: i === 25 ? "This project uses React 18 with TypeScript strict mode." : `Overview detail line ${i}`, - }); -} - -// Section 2: Code Style Rules (lines 51-100) -for (let i = 51; i <= 100; i++) { - monolithicInstructions.push({ - lineNumber: i, - section: "Code Style", - content: - i === 72 - ? "RULE: Always use explicit return types on exported functions." - : i === 78 - ? "RULE: Use const assertions for immutable arrays." - : `Style rule detail line ${i}`, - }); -} - -// Section 3: Testing Standards (lines 101-150) -for (let i = 101; i <= 150; i++) { - monolithicInstructions.push({ - lineNumber: i, - section: "Testing", - content: - i === 120 - ? "RULE: Every new endpoint must have integration tests." - : i === 135 - ? "RULE: Test files must mirror the source file structure." - : `Testing detail line ${i}`, - }); -} - -// Section 4: Deployment Rules (lines 151-200) -for (let i = 151; i <= 200; i++) { - monolithicInstructions.push({ - lineNumber: i, - section: "Deployment", - content: - i === 175 - ? "RULE: Never deploy on Fridays. Deploy window is Tue-Thu 10am-3pm." - : `Deployment detail line ${i}`, - }); -} - -// --------------------------------------------------------------------------- -// Split instruction files (4 focused files) -// --------------------------------------------------------------------------- - -const splitInstructions: Record = { - "01-project-overview.md": monolithicInstructions.filter((l) => l.section === "Project Overview"), - "02-code-style.md": monolithicInstructions.filter((l) => l.section === "Code Style"), - "03-testing.md": monolithicInstructions.filter((l) => l.section === "Testing"), - "04-deployment.md": monolithicInstructions.filter((l) => l.section === "Deployment"), -}; - -// --------------------------------------------------------------------------- -// Simulated queries -- the agent needs to find specific rules -// --------------------------------------------------------------------------- - -interface Query { - description: string; - targetRule: string; - relevantSection: string; -} - -const queries: Query[] = [ - { - description: "Find the rule about return types", - targetRule: "explicit return types", - relevantSection: "Code Style", - }, - { - description: "Find the deployment window rule", - targetRule: "deploy on Fridays", - relevantSection: "Deployment", - }, - { - description: "Find the integration test rule", - targetRule: "integration tests", - relevantSection: "Testing", - }, - { - description: "Find the test file structure rule", - targetRule: "mirror the source file", - relevantSection: "Testing", - }, -]; - -// --------------------------------------------------------------------------- -// Search simulation -// --------------------------------------------------------------------------- - -function searchMonolithic(query: Query): { linesRead: number; found: boolean } { - // Agent must scan from the top, reading each line until it finds the rule. - // In the worst case it reads all lines. - let linesRead = 0; - let found = false; - - for (const line of monolithicInstructions) { - linesRead++; - if (line.content.toLowerCase().includes(query.targetRule.toLowerCase())) { - found = true; - break; - } - } - - return { linesRead, found }; -} - -function searchSplit(query: Query): { linesRead: number; found: boolean; fileAccessed: string } { - // Agent knows which file to look in based on the section. - // It only reads lines from that one file. - const fileMap: Record = { - "Project Overview": "01-project-overview.md", - "Code Style": "02-code-style.md", - Testing: "03-testing.md", - Deployment: "04-deployment.md", - }; - - const targetFile = fileMap[query.relevantSection]; - const lines = splitInstructions[targetFile]; - let linesRead = 0; - let found = false; - - for (const line of lines) { - linesRead++; - if (line.content.toLowerCase().includes(query.targetRule.toLowerCase())) { - found = true; - break; - } - } - - return { linesRead, found, fileAccessed: targetFile }; -} - -// --------------------------------------------------------------------------- -// Reporting -// --------------------------------------------------------------------------- - -function pad(s: string, len: number): string { - return s.length >= len ? s : s + " ".repeat(len - s.length); -} - -function run(): void { - console.log("\n" + "=".repeat(90)); - console.log(" MONOLITHIC vs SPLIT INSTRUCTION FILES"); - console.log("=".repeat(90)); - - console.log("\n Monolithic file: 1 file, " + monolithicInstructions.length + " lines total"); - console.log(" Split files: 4 files, ~" + Math.round(monolithicInstructions.length / 4) + " lines each\n"); - - const header = `| ${pad("Query", 42)}| ${pad("Monolithic (lines)", 20)}| ${pad("Split (lines)", 15)}| ${pad("File Accessed", 22)}| Savings`; - const sep = `|${"-".repeat(44)}|${"-".repeat(22)}|${"-".repeat(17)}|${"-".repeat(24)}|${"-".repeat(10)}`; - console.log(header); - console.log(sep); - - let totalMono = 0; - let totalSplit = 0; - - for (const q of queries) { - const mono = searchMonolithic(q); - const split = searchSplit(q); - totalMono += mono.linesRead; - totalSplit += split.linesRead; - - const savings = Math.round(((mono.linesRead - split.linesRead) / mono.linesRead) * 100); - console.log( - `| ${pad(q.description, 42)}| ${pad(String(mono.linesRead), 20)}| ${pad(String(split.linesRead), 15)}| ${pad(split.fileAccessed, 22)}| ${savings}%` - ); - } - - console.log(sep); - const avgMono = Math.round(totalMono / queries.length); - const avgSplit = Math.round(totalSplit / queries.length); - console.log( - `| ${pad("AVERAGE", 42)}| ${pad(String(avgMono), 20)}| ${pad(String(avgSplit), 15)}| ${pad("(targeted file)", 22)}| ${Math.round(((avgMono - avgSplit) / avgMono) * 100)}%` - ); - - console.log("\n" + "=".repeat(90)); - console.log(" KEY INSIGHT"); - console.log("=".repeat(90)); - console.log(" With a monolithic file, the agent must scan up to " + monolithicInstructions.length + " lines for every query."); - console.log(" With split files, it reads only the relevant " + Math.round(monolithicInstructions.length / 4) + "-line file."); - console.log(" This means less context window usage, fewer hallucinations, and faster execution.\n"); -} - -run(); diff --git a/courses/learn-harness-engineering/docs/es/lectures/lecture-04-why-one-giant-instruction-file-fails/index.md b/courses/learn-harness-engineering/docs/es/lectures/lecture-04-why-one-giant-instruction-file-fails/index.md deleted file mode 100644 index 640d9dbe..00000000 --- a/courses/learn-harness-engineering/docs/es/lectures/lecture-04-why-one-giant-instruction-file-fails/index.md +++ /dev/null @@ -1,132 +0,0 @@ -[中文版本 →](../../../zh/lectures/lecture-04-why-one-giant-instruction-file-fails/) - -> Ejemplos de código: [code/](https://github.com/walkinglabs/learn-harness-engineering/blob/main/docs/es/lectures/lecture-04-why-one-giant-instruction-file-fails/code/) -> Proyecto práctico: [Proyecto 02. Espacio de trabajo legible para agentes](./../../projects/project-02-agent-readable-workspace/index.md) - -# Lección 04. Divide las instrucciones entre archivos - -Te tomaste en serio la ingeniería de harness. Creaste un `AGENTS.md` y metiste en él todas las reglas, restricciones y lecciones aprendidas que se te ocurrieron. Un mes después el archivo tenía 300 líneas; dos meses después, 450; tres meses después, 600. Entonces notas que el rendimiento del agente está empeorando: para corregir un bug simple, consume mucho contexto procesando instrucciones de despliegue irrelevantes; una restricción de seguridad crítica enterrada en la línea 300 se ignora por completo; tres reglas contradictorias de estilo hacen que el agente elija una al azar cada vez. - -Esta es la trampa del "archivo gigante de instrucciones". Es como sobrecargar una maleta: todo parece útil, así que lo metes hasta que la cremallera está a punto de reventar. Para encontrar ropa interior de cambio tienes que vaciar toda la bolsa. Cargas una maleta llena, pero en realidad usas quizá un tercio de lo que hay dentro. - -## El ciclo vicioso de fondo - -El ciclo vicioso más común funciona así: el agente comete un error, tú dices "añadamos una regla para evitar esto", la agregas a `AGENTS.md`, funciona temporalmente, el agente comete otro error, agregas otra regla, repites, y el archivo se hincha sin control. - -No es culpa tuya. Es una reacción muy natural: añadir una regla cada vez que algo sale mal parece razonable, como meter una cosa más en la bolsa cada vez que sales de casa "por si acaso". Pero el efecto acumulado es desastroso. Veamos qué se rompe exactamente. - -**El presupuesto de contexto se consume vivo.** La ventana de contexto del agente es finita. Supón que tu agente tiene una ventana de 200K tokens (un tamaño estándar en Claude). Un archivo de instrucciones inflado puede consumir 10-20K tokens. ¿Parece que todavía queda mucho espacio? Pero una tarea compleja puede requerir leer decenas de archivos fuente, la salida de herramientas también ocupa contexto y el historial de conversación se acumula. Para cuando el agente necesita entender el código, el presupuesto ya está apretado, como una maleta tan llena de cosas "por si acaso" que ya no cabe el portátil. - -**Perdido en el medio.** El artículo "Lost in the Middle" (Liu et al., 2023) demostró claramente que los LLM usan la información situada en medio de textos largos con mucha menos eficacia que la del principio o el final. Tu `AGENTS.md` tiene 600 líneas, y en la línea 300 dice "todas las consultas de base de datos deben usar consultas parametrizadas": es una restricción fuerte de seguridad. Pero está enterrada en el medio, y el agente casi seguro la ignorará. Como ese protector solar en el fondo de una maleta demasiado llena: sabes que está ahí, buscas tres veces, no lo encuentras y terminas comprando otro. - -**Conflictos de prioridad.** El archivo mezcla restricciones no negociables ("never use eval()"), guías de diseño importantes ("prefer functional style") y una lección histórica concreta ("la semana pasada se corrigió una fuga de memoria en WebSocket, vigilar patrones similares"). Estas tres reglas tienen niveles de importancia completamente distintos, pero se ven iguales en el archivo. El agente no tiene una señal fiable para distinguirlas, como si tu pasaporte y el cable de carga estuvieran mezclados en la maleta sin forma de saber qué es más urgente. - -**Deterioro de mantenimiento.** Los archivos grandes son inherentemente difíciles de mantener. Las instrucciones obsoletas rara vez se eliminan, porque las consecuencias de borrarlas son inciertas ("quizá algo depende de esta regla"), mientras que añadir instrucciones nuevas parece gratis. Resultado: el archivo solo crece, nunca se reduce, y la relación señal-ruido cae continuamente. Es exactamente como la acumulación de deuda técnica en software. - -**Acumulación de contradicciones.** Instrucciones añadidas en momentos distintos empiezan a contradecirse: una dice "usar TypeScript strict mode", otra dice "algunos archivos legacy permiten tipos any". El agente elige al azar cuál seguir cada vez. Como si tu madre dijera "abrígate" y tu padre dijera "no te pongas demasiada ropa", y tú te quedaras en la puerta sin saber a quién escuchar. - -## Conceptos clave - -- **Instruction Bloat**: Cuando un archivo de instrucciones ocupa más del 10-15% de la ventana de contexto, empieza a desplazar presupuesto que debería usarse para leer código y razonar sobre la tarea. Un `AGENTS.md` de 600 líneas puede consumir 10.000-20.000 tokens, es decir, 8-15% de una ventana de 128K antes de que el agente empiece. -- **Lost in the Middle Effect**: La investigación de Liu et al. en 2023 mostró que los LLM usan la información en medio de textos largos mucho peor que la información del principio o del final. Una restricción crítica enterrada en la línea 300 de un archivo de 600 líneas tiene alta probabilidad de ser ignorada en la práctica. -- **Instruction Signal-to-Noise Ratio (SNR)**: La proporción de instrucciones del archivo que son relevantes para la tarea actual. Verse obligado a leer 50 líneas de instrucciones de despliegue durante un arreglo de bug es bajo SNR. -- **Routing File**: Un archivo de entrada corto cuya función principal es dirigir al agente hacia documentación más detallada, no contenerlo todo. 50-200 líneas bastan. -- **Progressive Disclosure**: Dar primero la información general y los detalles cuando hacen falta. Un buen diseño de harness se parece a un buen diseño de UI: no vuelca todas las opciones al usuario de una vez. -- **Priority Ambiguity**: Cuando todas las instrucciones aparecen con el mismo formato y en el mismo lugar, el agente no puede distinguir restricciones duras no negociables de guías suaves. - -## Arquitectura de instrucciones - -```mermaid -flowchart LR - Mono["Un AGENTS.md de 600 líneas"] --> MonoLoad["Incluso un bug pequeño
debe leer reglas de despliegue y notas viejas"] - MonoLoad --> MonoRisk["Las reglas importantes enterradas en el medio
son fáciles de pasar por alto"] - - Router["AGENTS.md corto"] --> Topics["Cargar docs de API / DB / testing
solo cuando la tarea los necesita"] - Topics --> RoutedResult["Queda más contexto para leer código
y verificar"] -``` - -```mermaid -flowchart TB - File["Archivo de instrucciones de 600 líneas"] --> Top["Sección superior
quick start + restricciones duras"] - File --> Mid["Sección central
regla crítica de seguridad en la línea 300"] - File --> Bot["Sección final
checklist explícita de cierre"] - Top --> Seen["Alta probabilidad de recordarse"] - Bot --> Seen - Mid --> Missed["Alta probabilidad de diluirse u omitirse"] -``` - -## Cómo dividirlo - -El principio central: mantén a mano la información que se necesita con frecuencia, guarda aparte la que se necesita ocasionalmente y elimina lo que no vas a usar. - -El archivo de entrada `AGENTS.md` debe mantenerse en 50-200 líneas y contener solo lo que se usa con más frecuencia: resumen del proyecto (una o dos frases), comandos de primera ejecución (`make setup && make test`), restricciones globales duras (no más de 15 reglas no negociables) y enlaces a documentos temáticos (descripción de una línea + condición de aplicabilidad). - -```markdown -# AGENTS.md - -## Resumen del proyecto -Python 3.11 FastAPI backend, PostgreSQL 15 database. - -## Quick Start -- Install: `make setup` -- Test: `make test` -- Full verification: `make check` - -## Hard Constraints -- All APIs must use OAuth 2.0 authentication -- All database queries must use SQLAlchemy 2.0 syntax -- All PRs must pass pytest + mypy --strict + ruff check - -## Topic Docs -- [API Design Patterns](docs/api-patterns.md) — Required reading when adding endpoints -- [Database Rules](docs/database-rules.md) — Required when modifying database operations -- [Testing Standards](docs/testing-standards.md) — Reference when writing tests -``` - -Cada documento temático debe tener 50-150 líneas, organizado por tema en `docs/` o junto al módulo correspondiente. El agente solo lo lee cuando hace falta. Como cubos de organización dentro de una maleta: ropa interior en uno, artículos de aseo en otro, cargadores en un tercero. Encontrar algo no requiere vaciar toda la bolsa. - -Parte de la información conviene ponerla directamente en el código: definiciones de tipos, comentarios de interfaz, explicaciones en archivos de configuración. El agente la ve naturalmente al leer el código, así que no hace falta duplicarla en instrucciones. - -Cada instrucción debería tener una fuente ("¿por qué se añadió esta regla?"), una condición de aplicabilidad ("¿cuándo hace falta?") y una condición de caducidad ("¿en qué circunstancias se puede eliminar?"). Audita con regularidad y elimina entradas obsoletas, redundantes o contradictorias. Gestiona tus instrucciones como gestionas dependencias de código: las dependencias no usadas deben borrarse, de lo contrario solo ralentizan el sistema. - -Si una instrucción debe estar en el archivo de entrada, ponla arriba o abajo, nunca en el medio. El efecto "lost in the middle" nos dice que los LLM usan mucho mejor la información de los extremos que la del centro. Pero el mejor enfoque es mover las instrucciones a documentos temáticos que se cargan bajo demanda. - -OpenAI y Anthropic apoyan implícitamente el enfoque de división. OpenAI dice que los archivos de entrada deben ser "cortos y orientados al enrutamiento"; Anthropic dice que la información de control para agentes de larga duración debe ser "concisa y de alta prioridad". Ambos dicen lo mismo: no metas todo en un solo archivo. Una maleta necesita organización, no fuerza bruta. - -## Ejemplo real - -El `AGENTS.md` de un equipo SaaS creció de 50 a 600 líneas. Mezclaba versiones del stack técnico, estándares de código, notas históricas de bugs, guías de uso de API, procedimientos de despliegue y preferencias personales de miembros del equipo: la maleta entera estaba a punto de reventar. - -El rendimiento del agente empezó a caer de forma visible: durante arreglos de bugs simples gastaba mucho contexto procesando instrucciones de despliegue irrelevantes; la restricción de seguridad "todas las consultas de base de datos deben usar consultas parametrizadas" estaba enterrada en la línea 300 y se ignoraba con frecuencia; tres reglas contradictorias de estilo producían comportamiento aleatorio. - -El equipo ejecutó una "reorganización de maleta": -1. `AGENTS.md` se redujo a 80 líneas: solo resumen del proyecto, comandos de ejecución y 15 restricciones globales duras -2. Se crearon documentos temáticos: `docs/api-patterns.md` (120 líneas), `docs/database-rules.md` (60 líneas), `docs/testing-standards.md` (80 líneas) -3. Se añadieron enlaces a esos documentos en el archivo de enrutamiento -4. Las notas históricas se convirtieron en tests o se eliminaron - -Después de refactorizar, la tasa de éxito del mismo conjunto de tareas pasó de 45% a 72%. El cumplimiento de la restricción de seguridad pasó de 60% a 95%, porque se movió del medio del archivo a la parte superior del archivo de enrutamiento y dejó de perderse en el medio. - -## Ideas clave - -- "Añadir una regla" alivia el dolor a corto plazo y envenena a largo plazo. Antes de añadir una regla, pregunta: ¿esto estaría mejor en un documento temático? No sigas metiendo cosas en la maleta. -- El archivo de entrada es un router, no una enciclopedia. 50-200 líneas con resumen, restricciones duras y enlaces. -- Aprovecha el efecto "lost in the middle": la información importante va arriba o abajo; la información menos importante se mueve a documentos temáticos. -- Gestiona el crecimiento de instrucciones como deuda técnica. Auditorías periódicas; cada instrucción necesita fuente, condición de aplicabilidad y condición de caducidad. -- Tras dividir, mejora el SNR y el agente gasta más presupuesto de contexto en la tarea real, no en procesar instrucciones irrelevantes. - -## Lecturas adicionales - -- [OpenAI: Harness Engineering](https://openai.com/index/harness-engineering/) -- [Anthropic: Effective Harnesses for Long-Running Agents](https://www.anthropic.com/engineering/effective-harnesses-for-long-running-agents) -- [Lost in the Middle: How Language Models Use Long Contexts](https://arxiv.org/abs/2307.03172) -- [HumanLayer: Harness Engineering for Coding Agents](https://humanlayer.dev/articles/harness-engineering-for-coding-agents/) -- [Nielsen Norman Group: Progressive Disclosure](https://www.nngroup.com/articles/progressive-disclosure/) - -## Ejercicios - -1. **Auditoría de SNR**: Toma tu archivo actual de instrucciones de entrada y lista todas las instrucciones. Elige 5 tipos comunes de tareas y marca si cada instrucción es relevante para cada tarea. Calcula el SNR por tipo de tarea. Las instrucciones que son ruido para la mayoría de tareas deben moverse a documentos temáticos. - -2. **Refactor de progressive disclosure**: Si tienes un archivo de instrucciones de más de 300 líneas, divídelo en: (a) un archivo de enrutamiento de menos de 100 líneas, (b) 3-5 documentos temáticos. Ejecuta el mismo conjunto de tareas (al menos 5) antes y después, y compara tasas de éxito. - -3. **Verificación de lost in the middle**: En un archivo de instrucciones largo, coloca una restricción crítica arriba, en el medio y abajo respectivamente, ejecutando el mismo conjunto de tareas cada vez (al menos 5 ejecuciones por posición). Mira si cambia la tasa de cumplimiento. Puede sorprenderte la fuerza del efecto de posición. diff --git a/courses/learn-harness-engineering/docs/es/lectures/lecture-05-why-long-running-tasks-lose-continuity/code/continuity-checklist.md b/courses/learn-harness-engineering/docs/es/lectures/lecture-05-why-long-running-tasks-lose-continuity/code/continuity-checklist.md deleted file mode 100644 index 70506a65..00000000 --- a/courses/learn-harness-engineering/docs/es/lectures/lecture-05-why-long-running-tasks-lose-continuity/code/continuity-checklist.md +++ /dev/null @@ -1,6 +0,0 @@ -# Lista de Verificación de Continuidad - -- ¿Puede un agente nuevo identificar el trabajo reciente en menos de cinco minutos? -- ¿Está documentada la ruta de inicio estable actual? -- ¿Está el trabajo sin terminar claramente identificado? -- ¿Es visible la siguiente mejor tarea sin leer registros de chat antiguos? diff --git a/courses/learn-harness-engineering/docs/es/lectures/lecture-05-why-long-running-tasks-lose-continuity/code/index.md b/courses/learn-harness-engineering/docs/es/lectures/lecture-05-why-long-running-tasks-lose-continuity/code/index.md deleted file mode 100644 index d16697e7..00000000 --- a/courses/learn-harness-engineering/docs/es/lectures/lecture-05-why-long-running-tasks-lose-continuity/code/index.md +++ /dev/null @@ -1,7 +0,0 @@ -# Código de la Clase 05 - -Usa esta carpeta para ejemplos de: - -- tareas multi-sesión rotas -- artefactos de continuidad faltantes -- patrones de recuperación de continuidad diff --git a/courses/learn-harness-engineering/docs/es/lectures/lecture-05-why-long-running-tasks-lose-continuity/code/session-handoff.md b/courses/learn-harness-engineering/docs/es/lectures/lecture-05-why-long-running-tasks-lose-continuity/code/session-handoff.md deleted file mode 100644 index de7cc2fe..00000000 --- a/courses/learn-harness-engineering/docs/es/lectures/lecture-05-why-long-running-tasks-lose-continuity/code/session-handoff.md +++ /dev/null @@ -1,17 +0,0 @@ -# Ejemplo de Entrega de Sesión - -## Completado - -- Añadido soporte de importación de Markdown -- Añadida una lista básica de documentos en el renderer - -## Roto o No Verificado - -- La importación funciona para `.md` pero falla para archivos `.txt` grandes -- La app arranca, pero la vista de detalles no está conectada - -## Siguiente Mejor Paso - -- Corregir la ruta de importación de `.txt` -- Verificar la importación end-to-end -- Luego añadir el panel de detalles del documento diff --git a/courses/learn-harness-engineering/docs/es/lectures/lecture-05-why-long-running-tasks-lose-continuity/code/session-simulator.ts b/courses/learn-harness-engineering/docs/es/lectures/lecture-05-why-long-running-tasks-lose-continuity/code/session-simulator.ts deleted file mode 100644 index 69f9c3e2..00000000 --- a/courses/learn-harness-engineering/docs/es/lectures/lecture-05-why-long-running-tasks-lose-continuity/code/session-simulator.ts +++ /dev/null @@ -1,162 +0,0 @@ -/** - * session-simulator.ts - * - * Simulates two sessions working on a multi-step task. - * Run 1: No handoff file -- Session B starts from scratch and duplicates work. - * Run 2: With handoff file -- Session B picks up where Session A left off. - * - * Run: npx tsx docs/lectures/lecture-05-why-long-running-tasks-lose-continuity/code/session-simulator.ts - */ - -// --------------------------------------------------------------------------- -// Types -// --------------------------------------------------------------------------- - -interface TaskStep { - id: number; - name: string; - durationMs: number; -} - -interface SessionResult { - session: string; - stepsCompleted: number; - totalDurationMs: number; - duplicatedSteps: number; - output: string[]; -} - -// --------------------------------------------------------------------------- -// Task definition -// --------------------------------------------------------------------------- - -const taskSteps: TaskStep[] = [ - { id: 1, name: "Read project structure", durationMs: 50 }, - { id: 2, name: "Understand existing auth module", durationMs: 80 }, - { id: 3, name: "Design new search endpoint", durationMs: 60 }, - { id: 4, name: "Implement search endpoint", durationMs: 100 }, - { id: 5, name: "Write integration tests", durationMs: 70 }, - { id: 6, name: "Update documentation", durationMs: 40 }, -]; - -// --------------------------------------------------------------------------- -// Simulated session executor -// --------------------------------------------------------------------------- - -/** Simulate a session doing work. */ -function runSession( - sessionName: string, - startStep: number, - endStep: number -): SessionResult { - const output: string[] = []; - let totalDuration = 0; - - for (let i = startStep; i <= endStep; i++) { - const step = taskSteps.find((s) => s.id === i)!; - totalDuration += step.durationMs; - output.push(` [${sessionName}] Step ${step.id}: ${step.name} (${step.durationMs}ms)`); - } - - return { - session: sessionName, - stepsCompleted: endStep - startStep + 1, - totalDurationMs: totalDuration, - duplicatedSteps: 0, - output, - }; -} - -// --------------------------------------------------------------------------- -// Run 1: No handoff file -// --------------------------------------------------------------------------- - -function simulateNoHandoff(): { sessionA: SessionResult; sessionB: SessionResult } { - // Session A does steps 1-3 before timing out - const sessionA = runSession("Session A", 1, 3); - - // Session B has no context, starts from scratch - const sessionB = runSession("Session B", 1, 6); - sessionB.duplicatedSteps = 3; // Redoes steps 1-3 that A already did - - return { sessionA, sessionB }; -} - -// --------------------------------------------------------------------------- -// Run 2: With handoff file -// --------------------------------------------------------------------------- - -function simulateWithHandoff(): { sessionA: SessionResult; sessionB: SessionResult } { - // Session A does steps 1-3 and writes a handoff file - const sessionA = runSession("Session A", 1, 3); - - // Session B reads handoff, starts from step 4 - const sessionB = runSession("Session B", 4, 6); - sessionB.duplicatedSteps = 0; - - return { sessionA, sessionB }; -} - -// --------------------------------------------------------------------------- -// Reporting -// --------------------------------------------------------------------------- - -function pad(s: string, len: number): string { - return s.length >= len ? s : s + " ".repeat(len - s.length); -} - -function printRun(title: string, result: { sessionA: SessionResult; sessionB: SessionResult }): void { - console.log("\n" + "=".repeat(80)); - console.log(` ${title}`); - console.log("=".repeat(80)); - - console.log("\n Session A output:"); - for (const line of result.sessionA.output) { - console.log(line); - } - - console.log("\n Session B output:"); - for (const line of result.sessionB.output) { - console.log(line); - } - - console.log("\n Session B notes:"); - if (result.sessionB.duplicatedSteps > 0) { - console.log(` - Redid ${result.sessionB.duplicatedSteps} steps that Session A already completed`); - console.log(` - Wasted ${result.sessionB.totalDurationMs - (taskSteps.slice(3).reduce((s, t) => s + t.durationMs, 0))}ms on duplicate work`); - } else { - console.log(" - Read handoff file, continued from step 4"); - console.log(" - No duplicate work performed"); - } -} - -function printComparison(): void { - const noHandoff = simulateNoHandoff(); - const withHandoff = simulateWithHandoff(); - - printRun("RUN 1: NO HANDOFF FILE", noHandoff); - printRun("RUN 2: WITH HANDOFF FILE", withHandoff); - - // Comparison table - console.log("\n" + "=".repeat(80)); - console.log(" COMPARISON TABLE"); - console.log("=".repeat(80) + "\n"); - - const header = `| ${pad("Metric", 35)}| ${pad("No Handoff", 15)}| ${pad("With Handoff", 15)}|`; - const sep = `|${"-".repeat(37)}|${"-".repeat(17)}|${"-".repeat(17)}|`; - console.log(header); - console.log(sep); - - const noTotal = noHandoff.sessionA.totalDurationMs + noHandoff.sessionB.totalDurationMs; - const withTotal = withHandoff.sessionA.totalDurationMs + withHandoff.sessionB.totalDurationMs; - - console.log(`| ${pad("Session A steps completed", 35)}| ${pad(String(noHandoff.sessionA.stepsCompleted), 15)}| ${pad(String(withHandoff.sessionA.stepsCompleted), 15)}|`); - console.log(`| ${pad("Session B steps completed", 35)}| ${pad(String(noHandoff.sessionB.stepsCompleted), 15)}| ${pad(String(withHandoff.sessionB.stepsCompleted), 15)}|`); - console.log(`| ${pad("Duplicated steps", 35)}| ${pad(String(noHandoff.sessionB.duplicatedSteps), 15)}| ${pad(String(withHandoff.sessionB.duplicatedSteps), 15)}|`); - console.log(`| ${pad("Total work (ms)", 35)}| ${pad(String(noTotal), 15)}| ${pad(String(withTotal), 15)}|`); - console.log(`| ${pad("Time saved by handoff", 35)}| ${pad("-", 15)}| ${pad(String(noTotal - withTotal) + "ms", 15)}|`); - - console.log("\n A handoff file eliminates duplicate work and ensures continuity across sessions.\n"); -} - -printComparison(); diff --git a/courses/learn-harness-engineering/docs/es/lectures/lecture-05-why-long-running-tasks-lose-continuity/index.md b/courses/learn-harness-engineering/docs/es/lectures/lecture-05-why-long-running-tasks-lose-continuity/index.md deleted file mode 100644 index 2610d5b5..00000000 --- a/courses/learn-harness-engineering/docs/es/lectures/lecture-05-why-long-running-tasks-lose-continuity/index.md +++ /dev/null @@ -1,180 +0,0 @@ -[中文版本 →](../../../zh/lectures/lecture-05-why-long-running-tasks-lose-continuity/) - -> Ejemplos de código: [code/](https://github.com/walkinglabs/learn-harness-engineering/blob/main/docs/es/lectures/lecture-05-why-long-running-tasks-lose-continuity/code/) -> Proyecto práctico: [Project 03. Multi-session continuity](./../../projects/project-03-multi-session-continuity/index.md) - -# Lección 05. Mantén vivo el contexto entre sesiones - -Le pides a Claude Code que implemente una funcionalidad completa. Se ejecuta durante 30 minutos, hace la mayor parte del trabajo, pero el contexto se está agotando. Inicias una nueva sesión para continuar — y descubres que no recuerda qué decisiones se tomaron la última vez, por qué se eligió la opción A sobre la B, qué archivos ya fueron modificados o en qué estado están las pruebas. Dedica 15 minutos a re-explorar el proyecto, y podría ser inconsistente con el enfoque anterior. - -Imagina si fueras un artesano que olvida todo cada mañana al despertar. Tendrías que volver a familiarizarte con todo el sitio de construcción — qué pared está a medio construir, por qué se eligieron ladrillos rojos en lugar de azules, dónde llegaron las tuberías. Peor aún, podrías arrancar una ventana que ya fue instalada ayer, simplemente porque no recordabas que ya estaba hecha. - -Esta es exactamente la dificultad que enfrentan los agentes de codificación con IA en tareas entre sesiones. Esta lección explica por qué los agentes "se quedan en blanco" durante tareas largas, y cómo la persistencia estructurada del estado puede hacerlos como un artesano que lleva un diario confiable — sigue siendo amnésico, pero el diario lo recuerda todo. - -## Las ventanas de contexto no son infinitas - -Las ventanas de contexto son finitas. Esto no se resuelve con actualizaciones de modelos — incluso si los tamaños de ventana crecen a 1M tokens, las tareas complejas seguirán agotándolas. Porque los agentes no solo están generando código; están entendiendo bases de código, rastreando su propio historial de decisiones, procesando la salida de herramientas y manteniendo el contexto de conversación. Toda esta información crece más rápido que la expansión de la ventana. - -Un problema más profundo: la información que produce el agente no tiene la misma importancia. Los pasos de razonamiento intermedio contienen el "por qué" de las decisiones — por qué se eligió la opción B sobre la A, por qué esta biblioteca en lugar de aquella, por qué se omitió una optimización particular. La salida final solo contiene el "qué" — el código en sí. Las estrategias de compaction suelen preservar lo segundo pero pierden lo primero. La siguiente sesión ve el código pero no sabe por qué está escrito de esa manera, y podría "optimizar" una decisión de diseño deliberada. - -Anthropic descubrió algo fascinante en su investigación de agentes de larga duración: cuando los agentes perciben que el contexto se está agotando, exhiben un comportamiento de "convergencia prematura" — apresurándose a terminar el trabajo actual, saltando pasos de verificación o eligiendo una solución simple sobre la óptima. Es como darse cuenta de que se está acabando el tiempo en un examen y adivinar rápidamente las preguntas de opción múltiple restantes. Anthropic llama a esto "ansiedad de contexto." - -## Flujo de continuidad entre sesiones - -Sin artefactos de continuidad, cada nueva sesión es un desastre: - -```mermaid -flowchart LR - S1["Session 1
feature is half done"] --> End1["Context is nearly full
session ends"] - End1 --> S2["Session 2 starts fresh"] - S2 --> Guess["Re-read folders, rerun tests,
guess why the code was written this way"] - Guess --> Drift["Work gets repeated
and recovery is slow"] -``` - -Con artefactos de continuidad, las nuevas sesiones pueden retomar rápidamente: - -```mermaid -flowchart LR - Work["Session 1 work"] --> Progress["PROGRESS.md
done / in progress / next step"] - Work --> Decisions["DECISIONS.md
why this approach was chosen"] - Work --> Verify["Verification notes
which tests pass and fail"] - Work --> Commit["Git checkpoint
exact repo state"] - - Progress --> Rebuild["Session 2 rebuild"] - Decisions --> Rebuild - Verify --> Rebuild - Commit --> Rebuild - - Rebuild --> Resume["New session picks up quickly"] -``` - -## Conceptos clave - -- **Las ventanas de contexto son finitas**: No importa qué tamaño de ventana se reclame (128K, 200K, 1M), las tareas largas eventualmente las agotarán. Tras el agotamiento, se requiere compaction (perdiendo información) o reset (nueva sesión). Ambas pierden algo. -- **Artefactos de continuidad**: Archivos de estado persistido que permiten a una nueva sesión retomar sin ambigüedad donde la anterior lo dejó. La forma básica: registro de progreso + registro de verificación + próximas acciones. Ese diario del artesano. -- **Costo de reconstrucción**: El tiempo que una nueva sesión necesita para alcanzar un estado ejecutable. Los buenos harness pueden comprimir el costo de reconstrucción de 15 minutos a 3 minutos. -- **Deriva (Drift)**: La brecha entre la comprensión del agente y el estado real del repositorio de código. Cada límite de sesión introduce deriva; sin control, se acumula. -- **Ansiedad de contexto**: Un fenómeno observado por Anthropic — los agentes exhiben un comportamiento de convergencia prematura cuando se acercan a los límites de contexto percibidos, terminando tareas temprano para evitar pérdida de información. Es una ansiedad irracional por los recursos. -- **Compaction vs reset**: La compaction resume el contexto dentro de la misma sesión (conserva el "qué," puede perder el "por qué"); el reset abre una nueva sesión reconstruyendo desde artefactos persistidos (limpio pero depende de la completitud de los artefactos). - -## Qué sucede cuando se rompe la continuidad - -La sesión anterior gastó un presupuesto significativo de contexto analizando tres enfoques y eligiendo la opción B. El agente de esta sesión no conoce ese análisis y podría volver a decidir basándose en información incompleta — potencialmente eligiendo la opción A. Como el artesano amnésico que no recuerda por qué se eligieron los ladrillos rojos, mira los azules hoy y piensa que son más bonitos, y derriba la pared de ayer para reconstruirla. - -Peor aún es el trabajo duplicado. El agente no está seguro de si cierto trabajo ya fue completado y lo hace de nuevo. O peor — hace la mitad, descubre un conflicto con la implementación existente y tiene que rehacerlo. En un sitio de construcción, dos equipos no pueden construir la misma pared simultáneamente — pero sin registros de progreso, el nuevo equipo no tiene idea de que alguien ya está trabajando en ella. - -A lo largo de varias sesiones, la dirección de implementación puede haberse desviado silenciosamente de los requisitos originales. Cada nueva sesión tiene una comprensión ligeramente diferente de los objetivos del proyecto. Como un juego de teléfono roto — después de que diez personas pasan el mensaje, "recógeme un café" podría convertirse "cómprame una cafetera." - -También está la brecha de verificación. Los resultados de verificación de la sesión anterior (qué pruebas pasan, cuáles fallan, por qué fallan) no fueron registrados. La nueva sesión tiene que volver a ejecutar toda la verificación para entender el estado actual. Cada sesión vuelve a diagnosticar desde cero, desperdiciando contexto precioso cada vez. - -Tanto OpenAI como Anthropic enfatizan la persistencia estructurada del estado en su documentación. El artículo de ingeniería de harness de OpenAI trata el repositorio como un "registro operativo" — los resultados de cada operación deberían dejar evidencia rastreable en el repositorio. La documentación de agentes de larga duración de Anthropic recomienda específicamente "handoff files" — documentos estructurados que contienen el estado actual, problemas conocidos y próximas acciones. - -## Un diario para el artesano amnésico - -Enfoque central: **Trata al agente como un ingeniero brillante con amnesia.** Antes de que "termine su turno," debe escribir información crítica para que el agente del siguiente "turno" pueda retomar rápidamente. - -**Herramienta 1: Archivo de progreso (PROGRESS.md).** El artefacto de continuidad más básico — el núcleo del diario: - -```markdown -# Project Progress - -## Current State -- Latest commit: abc1234 (feat: add user preferences endpoint) -- Test status: 42/43 passing (test_pagination_edge_case failing) -- Lint: passing - -## Completed -- [x] User model and database migration -- [x] Basic CRUD endpoints -- [x] Auth middleware integration - -## In Progress -- [ ] Pagination feature (90% - edge case test failing) - -## Known Issues -- test_pagination_edge_case returns 500 on empty result sets -- Need to confirm whether deleted users should appear in listings - -## Next Steps -1. Fix pagination edge case bug -2. Add "include deleted users" query parameter -3. Update API documentation -``` - -**Herramienta 2: Registro de decisiones (DECISIONS.md).** Registra decisiones de diseño importantes y sus razones. No necesitas documentos de diseño detallados — solo "qué decisión, por qué, cuándo" — las notas en el diario: - -```markdown -# Design Decisions - -## 2024-01-15: Use Redis for user preferences caching -- Reason: High read frequency (every API call), small data size -- Rejected alternative: PostgreSQL materialized view (high change frequency makes maintenance cost not worthwhile) -- Constraint: Cache TTL of 5 minutes, active invalidation on write -``` - -**Herramienta 3: Commits de git como puntos de control.** Haz commit después de completar cada unidad atómica de trabajo. Los mensajes de commit deberían explicar qué se hizo y por qué. Estos son snapshots de estado gratuitos y automáticamente versionados. - -**Herramienta 4: init.sh o flujo de inicialización del harness.** Especifica en `AGENTS.md` las rutinas de "entrada" y "salida": - -```markdown -## At session start (clock in) -1. Read PROGRESS.md for current state -2. Read DECISIONS.md for important decisions -3. Run make check to confirm repo is in consistent state -4. Continue from PROGRESS.md "Next Steps" section - -## Before session end (clock out) -1. Update PROGRESS.md -2. Run make check to confirm consistent state -3. Commit all completed work -``` - -**Estrategia mixta**: No todas las tareas necesitan un reset de contexto. Las tareas cortas (menos de 30 minutos) pueden completarse dentro de una sesión. Las tareas largas (que abarcan sesiones) deben usar archivos de progreso y registros de decisiones para la continuidad. Criterio de decisión: si una tarea necesita más del 60% de la ventana, comienza a preparar la entrega. - -### Profundización en la ansiedad de contexto - -La investigación de marzo de 2026 de Anthropic reveló además las manifestaciones específicas de la ansiedad de contexto: en Sonnet 4.5, cuando el contexto se acerca al límite de la ventana, el agente muestra un fuerte comportamiento de "convergencia prematura." Es como darse cuenta de que casi se acaba el tiempo en un examen y llenar rápidamente respuestas aleatorias en las opciones múltiples. - -Dos estrategias abordan esto: - -**Compaction**: Resumir la conversación temprana dentro de la misma sesión. Ventaja: mantiene la continuidad, el agente puede ver el "qué." Desventaja: el "por qué" se pierde a menudo en los resúmenes — por qué se eligió la opción B sobre la A, por qué se omitió una optimización particular. Más críticamente, la compaction no elimina la ansiedad de contexto — el agente sabe que el contexto fue grande alguna vez, y psicológicamente todavía tiende a apresurarse hacia el cierre. - -**Reset de contexto**: Limpiar completamente el contexto, abrir una nueva sesión, reconstruir desde artefactos persistidos. Ventaja: estado mental limpio — la nueva sesión no tiene la ansiedad de "se me está acabando el tiempo." Desventaja: depende de la completitud de los artefactos de entrega. Si el diario falta información crítica, la nueva sesión puede perder tiempo yendo en la dirección equivocada. - -Datos reales de Anthropic: para Sonnet 4.5, la ansiedad de contexto es lo suficientemente severa que la compaction por sí sola no es suficiente — el reset de contexto se convierte en un componente crítico del diseño del harness. Pero para Opus 4.5, este comportamiento está muy reducido, y la compaction puede gestionar el contexto sin depender de resets. Esto significa: **el diseño del harness necesita un entendimiento específico del modelo objetivo, no una plantilla de talla única.** - -> Fuente: [Anthropic: Harness design for long-running application development](https://www.anthropic.com/engineering/harness-design-long-running-apps) - -## Ejemplo del mundo real - -Un agente fue encargado de implementar un sistema de blog con autenticación de usuarios — 12 puntos de funcionalidad, estimadas 5 sesiones necesarias. - -**Línea base sin el diario**: La sesión 1 implementó el modelo de usuario y las rutas básicas. La sesión 2 comenzó sin que el agente recordara el contrato de interfaz del middleware de autenticación, dedicando ~15 minutos a inferir la intención de diseño anterior. Para la sesión 3, la deriva acumulada hizo que el agente comenzara a reimplementar funcionalidades ya completadas. Para la sesión 5, el repositorio contenía mucho código redundante pero la funcionalidad central de autenticación todavía no había pasado las pruebas end-to-end. Solo 7 de 12 puntos de funcionalidad completados, 3 con problemas ocultos de corrección. Como el artesano que nunca escribe en su diario — para el día cinco, el sitio de construcción es un caos, algunas paredes construidas dos veces, algunas que debieron construirse nunca se empezaron. - -**Con el diario**: Usando archivos de progreso, registros de decisiones, registros de verificación y puntos de control de git. El informe de estado se actualizó automáticamente al final de cada sesión. El costo de reconstrucción de la sesión 2 bajó a ~3 minutos. Para la sesión 5, los 12 puntos de funcionalidad fueron completados y verificados. - -Comparación cuantitativa: tiempo de reconstrucción reducido ~78%, tasa de completitud de funcionalidades del 58% al 100%, tasa de defectos ocultos del 43% al 8%. El artesano sigue siendo amnésico, pero con el diario, cada día comienza donde el anterior se detuvo, no desde cero. - -## Ideas clave - -- Las ventanas de contexto son un recurso finito. Las tareas largas abarcarán sesiones, y las sesiones perderán información — como el artesano que olvida cada día, esto es una realidad objetiva. -- La solución no son ventanas más grandes — es mejor persistencia del estado. Archivos de progreso + registros de decisiones + puntos de control de git — dale al artesano amnésico un diario confiable. -- Trata al agente como un ingeniero con amnesia: antes de "terminar el turno," escribe qué se hizo, por qué y qué sigue. -- El costo de reconstrucción es la métrica clave. Los buenos harness deberían llevar a las nuevas sesiones a un estado ejecutable en menos de 3 minutos. -- Estrategia mixta: tareas cortas dentro de las sesiones, tareas largas con artefactos estructurados para la continuidad. - -## Lecturas adicionales - -- [Anthropic: Effective Harnesses for Long-Running Agents](https://www.anthropic.com/engineering/effective-harnesses-for-long-running-agents) -- [OpenAI: Harness Engineering](https://openai.com/index/harness-engineering/) -- [Lost in the Middle: How Language Models Use Long Contexts](https://arxiv.org/abs/2307.03172) -- [Claude Code Documentation](https://docs.anthropic.com/es/docs/claude-code) -- [HumanLayer: Harness Engineering for Coding Agents](https://humanlayer.dev/articles/harness-engineering-for-coding-agents/) - -## Ejercicios - -1. **Medición de pérdida de continuidad**: Elige una tarea de desarrollo que necesite al menos 3 sesiones. Sin proporcionar ningún artefacto de continuidad, registra al inicio de cada sesión cuánto contexto gasta el agente en "averiguar qué pasó la última vez." Después de cada sesión, crea un archivo de progreso y deja que la siguiente sesión comience desde él. Compara los costos de reconstrucción con y sin archivos de progreso. - -2. **Diseño de plantilla de entrega**: Diseña una plantilla de entrega mínima con cuatro campos: estado del repositorio (commit hash), estado de ejecución (tasa de aprobación de pruebas), bloqueadores, próximas acciones. Deja que una sesión de agente completamente nueva restaure el estado del proyecto usando solo esta plantilla. Registra las ambigüedades encontradas durante la restauración, itera para mejorar la plantilla. - -3. **Experimento de estrategia mixta**: En una tarea de desarrollo de 5 sesiones, compara tres estrategias: (a) siempre iniciar sesiones nuevas + archivos de progreso, (b) hacer lo más posible en una sesión (compaction de contexto), (c) estrategia mixta (tareas cortas dentro de la sesión, tareas largas entre sesiones + archivos de progreso). Compara tiempo de reconstrucción, tasa de completitud de funcionalidades y consistencia de decisiones. diff --git a/courses/learn-harness-engineering/docs/es/lectures/lecture-06-why-initialization-needs-its-own-phase/code/index.md b/courses/learn-harness-engineering/docs/es/lectures/lecture-06-why-initialization-needs-its-own-phase/code/index.md deleted file mode 100644 index 1a1f054c..00000000 --- a/courses/learn-harness-engineering/docs/es/lectures/lecture-06-why-initialization-needs-its-own-phase/code/index.md +++ /dev/null @@ -1,8 +0,0 @@ -# Código de la Clase 06 - -Usa esta carpeta para ejemplos de: - -- salidas del inicializador -- scripts de inicialización -- archivos de progreso -- scaffolding de primera ejecución diff --git a/courses/learn-harness-engineering/docs/es/lectures/lecture-06-why-initialization-needs-its-own-phase/code/init-check.ts b/courses/learn-harness-engineering/docs/es/lectures/lecture-06-why-initialization-needs-its-own-phase/code/init-check.ts deleted file mode 100644 index 52dcd985..00000000 --- a/courses/learn-harness-engineering/docs/es/lectures/lecture-06-why-initialization-needs-its-own-phase/code/init-check.ts +++ /dev/null @@ -1,296 +0,0 @@ -/** - * init-check.ts - * - * Programmatically checks initialization prerequisites: - * - Node.js version - * - Dependencies installed (node_modules exists) - * - Data directory exists - * - Config files present - * - * Simulates running with and without an explicit init phase, - * showing how missing prerequisites cause silent failures later. - * - * Run: npx tsx docs/lectures/lecture-06-why-initialization-needs-its-own-phase/code/init-check.ts - */ - -import * as fs from "node:fs"; -import * as path from "node:path"; -import * as childProcess from "node:child_process"; - -// --------------------------------------------------------------------------- -// Types -// --------------------------------------------------------------------------- - -interface CheckItem { - name: string; - category: string; - check: () => { pass: boolean; detail: string }; - impactIfMissing: string; -} - -interface CheckResult { - name: string; - category: string; - passed: boolean; - detail: string; - impactIfMissing: string; -} - -// --------------------------------------------------------------------------- -// Checks -// --------------------------------------------------------------------------- - -function createChecks(targetDir: string): CheckItem[] { - return [ - { - name: "Node.js version >= 18", - category: "Runtime", - check: () => { - try { - const version = process.version; // e.g. "v20.11.0" - const major = parseInt(version.replace("v", "").split(".")[0], 10); - return { - pass: major >= 18, - detail: `Detected: ${version} (need >= v18)`, - }; - } catch { - return { pass: false, detail: "Could not detect Node.js version" }; - } - }, - impactIfMissing: "TypeScript features and built-in APIs unavailable", - }, - { - name: "package.json exists", - category: "Config", - check: () => { - const p = path.join(targetDir, "package.json"); - const exists = fs.existsSync(p); - return { - pass: exists, - detail: exists ? `Found: ${p}` : "Not found in target directory", - }; - }, - impactIfMissing: "Cannot install dependencies or run scripts", - }, - { - name: "Dependencies installed (node_modules)", - category: "Dependencies", - check: () => { - const p = path.join(targetDir, "node_modules"); - const exists = fs.existsSync(p); - return { - pass: exists, - detail: exists ? `Found: ${p}` : "node_modules/ not found -- run npm install", - }; - }, - impactIfMissing: "All imports fail at runtime", - }, - { - name: "TypeScript available", - category: "Toolchain", - check: () => { - try { - const result = childProcess.execSync("npx tsc --version", { - encoding: "utf-8", - timeout: 10000, - stdio: ["pipe", "pipe", "pipe"], - }).trim(); - return { pass: true, detail: `Detected: ${result}` }; - } catch { - return { pass: false, detail: "TypeScript not available via npx" }; - } - }, - impactIfMissing: "Cannot compile TypeScript files", - }, - { - name: "tsconfig.json exists", - category: "Config", - check: () => { - const p = path.join(targetDir, "tsconfig.json"); - const exists = fs.existsSync(p); - return { - pass: exists, - detail: exists ? `Found: ${p}` : "Not found", - }; - }, - impactIfMissing: "TypeScript compiler uses defaults, may not match project needs", - }, - { - name: "Source directory exists", - category: "Structure", - check: () => { - const candidates = ["src", "lib", "app"]; - const found = candidates.filter((d) => { - const p = path.join(targetDir, d); - return fs.existsSync(p) && fs.statSync(p).isDirectory(); - }); - return { - pass: found.length > 0, - detail: found.length > 0 ? `Found: ${found.join(", ")}` : "No src/ lib/ or app/ directory found", - }; - }, - impactIfMissing: "Agent cannot locate source files to modify", - }, - { - name: "Test directory exists", - category: "Structure", - check: () => { - const candidates = ["test", "tests", "__tests__", "spec"]; - const found = candidates.filter((d) => { - const p = path.join(targetDir, d); - return fs.existsSync(p) && fs.statSync(p).isDirectory(); - }); - return { - pass: found.length > 0, - detail: found.length > 0 ? `Found: ${found.join(", ")}` : "No test directory found", - }; - }, - impactIfMissing: "Agent cannot find or run existing tests", - }, - { - name: "Git repository initialized", - category: "Version Control", - check: () => { - const gitDir = path.join(targetDir, ".git"); - const exists = fs.existsSync(gitDir); - return { - pass: exists, - detail: exists ? "Git repository detected" : "No .git directory found", - }; - }, - impactIfMissing: "No rollback capability, no change history", - }, - ]; -} - -// --------------------------------------------------------------------------- -// Simulation: with and without init phase -// --------------------------------------------------------------------------- - -interface SimResult { - scenario: string; - checksRun: boolean; - failuresBeforeWork: number; - workAttempted: boolean; - workSucceeded: boolean; - timeWastedMs: number; -} - -function simulateWithoutInit(): SimResult { - // Agent skips init, goes straight to work. - // Encounters failures one at a time as it hits missing prerequisites. - const checks = createChecks(process.cwd()); - let failures = 0; - let timeWasted = 0; - - for (const check of checks) { - const result = check.check(); - if (!result.pass) { - failures++; - timeWasted += 200; // Agent spends time discovering each missing piece - } - } - - return { - scenario: "NO INIT PHASE", - checksRun: false, - failuresBeforeWork: 0, // Didn't check upfront - workAttempted: true, - workSucceeded: failures === 0, - timeWastedMs: timeWasted, - }; -} - -function simulateWithInit(): SimResult { - // Agent runs init phase first, discovers all issues upfront. - const checks = createChecks(process.cwd()); - let failures = 0; - - for (const check of checks) { - if (!check.check().pass) { - failures++; - } - } - - return { - scenario: "WITH INIT PHASE", - checksRun: true, - failuresBeforeWork: failures, - workAttempted: failures === 0, - workSucceeded: failures === 0, - timeWastedMs: 0, - }; -} - -// --------------------------------------------------------------------------- -// Reporting -// --------------------------------------------------------------------------- - -function pad(s: string, len: number): string { - return s.length >= len ? s : s + " ".repeat(len - s.length); -} - -function run(): void { - const targetDir = process.cwd(); - console.log("\n" + "=".repeat(80)); - console.log(" INITIALIZATION PREREQUISITE CHECK"); - console.log("=".repeat(80)); - console.log(` Target: ${targetDir}\n`); - - const checks = createChecks(targetDir); - const results: CheckResult[] = checks.map((c) => { - const r = c.check(); - return { - name: c.name, - category: c.category, - passed: r.pass, - detail: r.detail, - impactIfMissing: c.impactIfMissing, - }; - }); - - // Detailed report - const header = `| ${pad("Check", 35)}| ${pad("Category", 12)}| ${pad("Status", 6)}| Detail`; - const sep = `|${"-".repeat(37)}|${"-".repeat(14)}|${"-".repeat(8)}|${"-".repeat(40)}`; - console.log(header); - console.log(sep); - - for (const r of results) { - const status = r.passed ? "PASS" : "FAIL"; - console.log(`| ${pad(r.name, 35)}| ${pad(r.category, 12)}| ${pad(status, 6)}| ${r.detail}`); - } - - const passCount = results.filter((r) => r.passed).length; - const failCount = results.filter((r) => !r.passed).length; - - console.log("\n" + "-".repeat(80)); - console.log(` Results: ${passCount} passed, ${failCount} failed out of ${results.length} checks`); - - if (failCount > 0) { - console.log("\n FAILED CHECKS -- Impact if not resolved:"); - for (const r of results.filter((r) => !r.passed)) { - console.log(` - ${r.name}: ${r.impactIfMissing}`); - } - } - - // Comparison: with vs without init - const noInit = simulateWithoutInit(); - const withInit = simulateWithInit(); - - console.log("\n" + "=".repeat(80)); - console.log(" INIT PHASE COMPARISON"); - console.log("=".repeat(80) + "\n"); - - const cHeader = `| ${pad("Metric", 35)}| ${pad("No Init Phase", 18)}| ${pad("With Init Phase", 18)}|`; - const cSep = `|${"-".repeat(37)}|${"-".repeat(20)}|${"-".repeat(20)}|`; - console.log(cHeader); - console.log(cSep); - console.log(`| ${pad("Prerequisites checked upfront", 35)}| ${pad("No", 18)}| ${pad("Yes", 18)}|`); - console.log(`| ${pad("Work attempted despite issues", 35)}| ${pad(String(noInit.workAttempted), 18)}| ${pad(String(withInit.workAttempted), 18)}|`); - console.log(`| ${pad("Time wasted discovering issues late", 35)}| ${pad(noInit.timeWastedMs + "ms", 18)}| ${pad(withInit.timeWastedMs + "ms", 18)}|`); - console.log(`| ${pad("Work succeeded", 35)}| ${pad(String(noInit.workSucceeded), 18)}| ${pad(String(withInit.workSucceeded), 18)}|`); - - console.log("\n An explicit init phase catches problems before they waste time.\n"); -} - -run(); diff --git a/courses/learn-harness-engineering/docs/es/lectures/lecture-06-why-initialization-needs-its-own-phase/code/init.sh b/courses/learn-harness-engineering/docs/es/lectures/lecture-06-why-initialization-needs-its-own-phase/code/init.sh deleted file mode 100644 index 2c6e1b0c..00000000 --- a/courses/learn-harness-engineering/docs/es/lectures/lecture-06-why-initialization-needs-its-own-phase/code/init.sh +++ /dev/null @@ -1,10 +0,0 @@ -#!/usr/bin/env bash -set -euo pipefail - -echo "[init] installing dependencies" -npm install - -echo "[init] starting the docs site is optional" -echo "[init] use npm run docs:dev for course docs" - -echo "[init] project-specific startup would go here" diff --git a/courses/learn-harness-engineering/docs/es/lectures/lecture-06-why-initialization-needs-its-own-phase/code/initializer-output-checklist.md b/courses/learn-harness-engineering/docs/es/lectures/lecture-06-why-initialization-needs-its-own-phase/code/initializer-output-checklist.md deleted file mode 100644 index 063ab25c..00000000 --- a/courses/learn-harness-engineering/docs/es/lectures/lecture-06-why-initialization-needs-its-own-phase/code/initializer-output-checklist.md +++ /dev/null @@ -1,7 +0,0 @@ -# Lista de Verificación de Salida del Inicializador - -- ¿Hay un comando de inicio canónico? -- ¿Hay un comando de verificación canónico? -- ¿Hay un primer artefacto de progreso? -- ¿Hay un primer commit estable? -- ¿Hay una superficie de features visible para sesiones posteriores? diff --git a/courses/learn-harness-engineering/docs/es/lectures/lecture-06-why-initialization-needs-its-own-phase/index.md b/courses/learn-harness-engineering/docs/es/lectures/lecture-06-why-initialization-needs-its-own-phase/index.md deleted file mode 100644 index b1a221c9..00000000 --- a/courses/learn-harness-engineering/docs/es/lectures/lecture-06-why-initialization-needs-its-own-phase/index.md +++ /dev/null @@ -1,157 +0,0 @@ -[中文版本 →](../../../zh/lectures/lecture-06-why-initialization-needs-its-own-phase/) - -> Ejemplos de código: [code/](https://github.com/walkinglabs/learn-harness-engineering/blob/main/docs/es/lectures/lecture-06-why-initialization-needs-its-own-phase/code/) -> Proyecto práctico: [Project 03. Multi-session continuity](./../../projects/project-03-multi-session-continuity/index.md) - -# Lección 06. Inicializa antes de cada sesión del agente - -Inicias una nueva sesión de agente y dices "añade una funcionalidad de búsqueda." Salta directamente a codificar — un entusiasmo admirable. Después de 20 minutos descubre que el framework de pruebas no está configurado correctamente, dedica otros 10 a arreglar eso, luego el formato del script de migración de base de datos está mal, más ajustes. La funcionalidad de búsqueda eventualmente se añade, pero toda la sesión fue ineficiente — la mayor parte del tiempo se fue en "averiguar cómo funciona este proyecto" en lugar de escribir la funcionalidad de búsqueda. - -El mejor enfoque: antes de dejar que el agente comience a trabajar, usa una fase separada para preparar el entorno base, hacer pasar los comandos de verificación y entender la estructura del proyecto. Es como construir una casa — no viertes los cimientos y levantas paredes simultáneamente. Si lo haces, las paredes se levantan antes de que los cimientos hayan curado, y todo el edificio tiene que ser demolido y empezado de nuevo. Vierte los cimientos primero, deja que curen, luego construye las paredes — limpio y eficiente. - -Esta lección explica por qué la inicialización debe ser una fase separada, no mezclada con la implementación. - -## Cimientos y paredes: dos trabajos fundamentalmente diferentes - -La inicialización y la implementación tienen objetivos de optimización completamente diferentes. La fase de implementación optimiza para: maximizar la cantidad y calidad de las funcionalidades verificadas. La fase de inicialización optimiza para: maximizar la fiabilidad y eficiencia de toda la implementación posterior. - -Cuando mezclas inicialización e implementación, el agente enfrenta un problema de optimización multi-objetivo — construir infraestructura simultáneamente y escribir código de funcionalidades. Sin una priorización explícita, el agente naturalmente tiende a escribir código (porque eso es una salida directamente visible) mientras sacrifica la infraestructura (porque su valor solo se muestra en sesiones posteriores). Es como decirle a un equipo de construcción que simultáneamente vierta los cimientos y construya las paredes — probablemente se apresurarán a construir paredes porque las paredes son visibles y demostrables. Pero una casa con cimientos defectuosos tiene problemas sistémicos a largo plazo. - -## Ciclo de vida de la inicialización - -```mermaid -flowchart TB - subgraph Wrong["Mixed session (wrong)"] - W1["Start feature work immediately"] --> W2["Discover env and test gaps mid-task"] - W2 --> W3["Accumulate unverified code"] - W3 --> W4["Next session must rediscover project state"] - end - - subgraph Right["Dedicated initialization (right)"] - R1["Session 1: environment runnable"] --> R2["Example test passing"] - R2 --> R3["Bootstrap contract + task list written"] - R3 --> R4["Clean checkpoint committed"] - R4 --> R5["Later sessions start directly on verified tasks"] - end -``` - -## Qué sucede cuando los mezclas - -El problema más directo: los cimientos no se asientan correctamente. El agente gasta el 80% de su esfuerzo en código de funcionalidades y el 20% configurando casualmente algo de infraestructura. El framework de pruebas está configurado pero nunca verificado, las reglas de lint están establecidas pero demasiado laxas, no se creó ningún archivo de progreso. Estos defectos no son obvios en la primera sesión (porque el agente todavía recuerda lo que hizo), pero salen a la luz en la segunda sesión — el nuevo agente no sabe cómo ejecutar, probar o en qué punto están las cosas. Cimientos chapuceros, edificio inestable. - -Un costo más oculto es la "acumulación no verificada" — el código de funcionalidades escrito antes de que el framework de pruebas esté configurado es código sin verificación. Cuando finalmente vuelves para añadir pruebas a ese código, podrías descubrir que el diseño estaba mal desde el principio — si lo hubieras sabido, lo habrías implementado de manera diferente. Como colocar baldosas sobre hormigón húmedo — cuando descubres que el suelo no está nivelado, todas las baldosas tienen que ser levantadas y rehechas. - -El presupuesto de sesión también se está desperdiciando. El trabajo de inicialización (configurar entornos, preparar pruebas, entender la estructura del proyecto) consume un presupuesto significativo, dejando menos para la implementación real de funcionalidades. Resultado: la primera sesión solo completa la mitad de las funcionalidades, y la segunda sesión tiene que empezar de nuevo a entender el proyecto. Presupuesto gastado en los cimientos, pero los cimientos tampoco están sólidos — ningún objetivo cumplido. - -El problema más fácilmente pasado por alto son las minas de suposiciones implícitas. Las decisiones que el agente toma durante la inicialización (qué framework de pruebas, cómo organizar directorios, gestión de dependencias) — si no se registran explícitamente, las sesiones posteriores no pueden entender estas decisiones. Peor aún, las sesiones posteriores podrían tomar decisiones contradictorias. El primer equipo de construcción usó cimientos de hormigón, el segundo equipo no lo sabe y clavó pilotes de madera en ellos — los cimientos se agrietan. - -La investigación de Anthropic sobre desarrollo de aplicaciones de larga duración recomienda explícitamente separar la inicialización de la implementación. Sus datos experimentales: los proyectos que usan una fase de inicialización dedicada mostraron un 31% más de tasa de completitud de funcionalidades en escenarios de múltiples sesiones comparado con enfoques mixtos. La perspectiva clave — el tiempo invertido en la fase de inicialización se recupera completamente en las siguientes 3-4 sesiones. Cuanto más sólidos los cimientos, más rápido se levantan las paredes. - -La guía de ingeniería de harness de Codex de OpenAI también enfatiza el principio del "repositorio como registro operativo" — establece una estructura operativa clara desde la primera ejecución, o cada nueva sesión tendrá que re-inferir las convenciones del proyecto. - -## Conceptos clave - -- **Fase de inicialización**: La primera fase en el ciclo de vida del agente — sin implementación de funcionalidades, solo estableciendo los prerrequisitos para todas las fases de implementación posteriores. La salida no es código, es infraestructura. -- **Contrato de bootstrap**: Las condiciones bajo las cuales un proyecto puede ser operado sin ambigüedad por una nueva sesión de agente — puede iniciar, puede probar, puede ver el progreso, puede retomar los siguientes pasos. Cuatro condiciones, todas requeridas. -- **Arranque en frío vs arranque en caliente**: El arranque en frío es desde un directorio vacío donde el agente debe adivinar la estructura del proyecto; el arranque en caliente es desde una plantilla o proyecto existente donde la infraestructura ya está en su lugar. El arranque en caliente supera ampliamente al arranque en frío — como empezar a trabajar en un sitio con agua corriente y electricidad versus comenzar desde un terreno baldío. -- **Preparación para entrega**: El proyecto está en un estado en cualquier momento dado donde un agente nuevo puede tomar el control. No se necesita explicación verbal — solo el contenido del repositorio. -- **Tiempo hasta la primera verificación**: El tiempo desde el inicio del proyecto hasta que el primer punto de funcionalidad pasa la verificación. Esta es la métrica central para medir la eficiencia de inicialización. -- **Usabilidad posterior**: La mejor medida de la calidad de la inicialización — la proporción de sesiones posteriores que pueden ejecutar tareas exitosamente sin depender de conocimiento implícito. - -## Cómo hacer bien la inicialización - -**Trata la inicialización como una fase dedicada.** La primera sesión solo hace inicialización — nada de código de funcionalidades de negocio. La inicialización produce: - -**1. Entorno ejecutable.** El proyecto arranca, las dependencias están instaladas, sin problemas de entorno. Cimientos vertidos, sin grietas. - -**2. Framework de pruebas verificable.** Al menos una prueba de ejemplo pasa. Esto demuestra que el framework de pruebas en sí está correctamente configurado — como poner un pilar sobre los cimientos para demostrar que puede soportar peso. - -**3. Documento de contrato de bootstrap.** Un documento claro que indica a las sesiones posteriores: -```markdown -# Initialization Contract - -## Start Commands -- Install dependencies: `make setup` -- Start dev server: `make dev` -- Run tests: `make test` -- Full verification: `make check` - -## Current State -- All dependencies installed and locked -- Test framework configured (Vitest + React Testing Library) -- Example test passing (1/1) -- Lint rules configured (ESLint + Prettier) - -## Project Structure -- src/ — Source code -- src/components/ — React components -- src/api/ — API client -- tests/ — Test files -``` - -**4. Desglose de tareas.** Divide todo el proyecto en una lista ordenada de tareas, cada una con criterios de aceptación claros: -```markdown -# Task Breakdown - -## Task 1: User Authentication Basics -- Implement JWT auth middleware -- Add login/register endpoints -- Acceptance: pytest tests/test_auth.py all passing - -## Task 2: User Profile Page -- Implement user profile CRUD -- Add profile edit form -- Acceptance: pytest tests/test_profile.py all passing - -## Task 3: Search Feature -- ... -``` - -**5. Commit de git como punto de control.** Después de que se completa la inicialización, haz commit de un punto de control limpio. Todo el trabajo posterior comienza desde este punto de control. - -**Estrategia de arranque en caliente**: No comiences desde un directorio vacío. Usa una plantilla de proyecto (create-react-app, fastapi-template, etc.) para preestablecer la estructura de directorios estándar, la configuración de dependencias y el framework de pruebas. Integra los pasos comunes de inicialización en la plantilla, dejando solo el trabajo de inicialización específico del proyecto. Como empezar a trabajar en un sitio con agua corriente y electricidad — diez mil veces mejor que comenzar desde un terreno baldío. - -**Criterios de finalización de inicialización**: No "cuánto código se escribió," sino si se cumplen las cuatro condiciones del contrato de bootstrap — puede iniciar, puede probar, puede ver el progreso, puede retomar los siguientes pasos. Usa esta lista de verificación para validar la inicialización: - -```markdown -## Initialization Acceptance Checklist -- [ ] `make setup` succeeds from scratch -- [ ] `make test` has at least one passing test -- [ ] A new agent session can answer "how to run" and "how to test" from repo contents alone -- [ ] Task breakdown file exists with at least 3 tasks -- [ ] Everything committed to git -``` - -## Ejemplo del mundo real - -Dos enfoques de inicialización para un proyecto frontend de React: - -**Enfoque mixto (verter cimientos y construir paredes simultáneamente)**: El agente simultáneamente creó el scaffolding del proyecto e implementó la primera funcionalidad en la sesión 1. Al final de la sesión, el repositorio tenía código ejecutable pero: sin documentación explícita de comandos de inicio/prueba, sin archivo de seguimiento de progreso, sin desglose de tareas. La sesión 2 dedicó ~20 minutos a inferir la estructura del proyecto, el framework de pruebas y el proceso de construcción — como un nuevo equipo de construcción que llega a un sitio sin saber cuánto avanzaron los cimientos o dónde van las tuberías, teniendo que cavar agujeros uno por uno para averiguarlo. - -**Inicialización dedicada (cimientos primero)**: La sesión 1 solo hizo inicialización — creó la estructura de directorios desde una plantilla, configuró el framework de pruebas (Vitest + React Testing Library), escribió y verificó una prueba de ejemplo, creó el documento de contrato de bootstrap y el archivo de desglose de tareas, hizo commit del punto de control inicial. El tiempo de reconstrucción de la sesión 2 fue menos de 3 minutos, y comenzó a trabajar directamente desde la lista de tareas — el equipo llega, echa un vistazo al plano y sabe exactamente dónde retomar. - -Comparación del ciclo completo del proyecto: el tiempo total de reconstrucción del enfoque mixto (a través de todas las sesiones) fue ~60% más que el enfoque de inicialización dedicada. Los 20 minutos extra gastados en inicialización fueron recuperados muchas veces en las sesiones posteriores. Como cimientos sólidos haciendo que las paredes se levanten más rápido — lento es rápido. - -## Ideas clave - -- La inicialización y la implementación tienen diferentes objetivos de optimización — mezclarlas solo arrastra a ambas hacia abajo. Vierte los cimientos primero, luego construye las paredes. -- La salida de la inicialización no es código, es infraestructura: entorno ejecutable, pruebas verificables, contrato de bootstrap, desglose de tareas. -- Valida la inicialización con las cuatro condiciones del contrato de bootstrap: puede iniciar, puede probar, puede ver el progreso, puede retomar los siguientes pasos. -- El arranque en caliente supera al arranque en frío. Usa plantillas de proyecto para preestablecer infraestructura estandarizada. -- El tiempo invertido en inicialización se recupera completamente en las siguientes 3-4 sesiones. Esto no es un costo extra — es una inversión inicial. Cuanto más sólidos los cimientos, más rápido se levanta el edificio. - -## Lecturas adicionales - -- [Anthropic: Effective Harnesses for Long-Running Agents](https://www.anthropic.com/engineering/effective-harnesses-for-long-running-agents) -- [OpenAI: Harness Engineering](https://openai.com/index/harness-engineering/) -- [HumanLayer: Harness Engineering for Coding Agents](https://humanlayer.dev/articles/harness-engineering-for-coding-agents/) -- [Infrastructure as Code — Martin Fowler](https://martinfowler.com/bliki/InfrastructureAsCode.html) -- [SWE-agent: Agent-Computer Interfaces](https://github.com/princeton-nlp/SWE-agent) - -## Ejercicios - -1. **Diseño de contrato de bootstrap**: Escribe un contrato de bootstrap completo para un proyecto que estés desarrollando. Luego abre una sesión de agente completamente nueva, muéstrale solo el contenido del repositorio (sin contexto verbal), y haz que intente iniciar el proyecto, ejecutar las pruebas y entender el progreso actual. Registra cada problema que encuentre — cada uno corresponde a una cláusula faltante en tu contrato de bootstrap. - -2. **Experimento comparativo**: Elige un proyecto nuevo de complejidad moderada. Enfoque A: deja que el agente inicialice y haga la primera implementación simultáneamente. Enfoque B: dedica una sesión a la inicialización, comienza la implementación en la sesión 2. Después de 4 sesiones, compara: tiempo hasta la primera verificación, costo de reconstrucción, tasa de completitud de funcionalidades. - -3. **Lista de verificación de aceptación de inicialización**: Diseña una lista de verificación de aceptación de inicialización para tu proyecto. Haz que una sesión de agente nueva ejecute cada elemento de la lista y registra cuáles pasan y cuáles fallan. Los elementos que fallan son donde tu harness necesita fortalecerse. diff --git a/courses/learn-harness-engineering/docs/es/lectures/lecture-07-why-agents-overreach-and-under-finish/code/index.md b/courses/learn-harness-engineering/docs/es/lectures/lecture-07-why-agents-overreach-and-under-finish/code/index.md deleted file mode 100644 index b5ca566e..00000000 --- a/courses/learn-harness-engineering/docs/es/lectures/lecture-07-why-agents-overreach-and-under-finish/code/index.md +++ /dev/null @@ -1,8 +0,0 @@ -# Código de la Clase 07 - -Usa esta carpeta para ejemplos de: - -- fallos de un solo intento -- prompts de tareas sobredimensionadas -- conformación incremental de tareas -- superficies de features estructuradas diff --git a/courses/learn-harness-engineering/docs/es/lectures/lecture-07-why-agents-overreach-and-under-finish/code/next-task-template.md b/courses/learn-harness-engineering/docs/es/lectures/lecture-07-why-agents-overreach-and-under-finish/code/next-task-template.md deleted file mode 100644 index d45c563c..00000000 --- a/courses/learn-harness-engineering/docs/es/lectures/lecture-07-why-agents-overreach-and-under-finish/code/next-task-template.md +++ /dev/null @@ -1,6 +0,0 @@ -# Plantilla de Siguiente Tarea - -- Feature actual de mayor prioridad: -- Por qué este feature es el siguiente: -- Qué cuenta como aprobado: -- Qué no debe cambiarse durante este paso: diff --git a/courses/learn-harness-engineering/docs/es/lectures/lecture-07-why-agents-overreach-and-under-finish/code/scope-surface-example.md b/courses/learn-harness-engineering/docs/es/lectures/lecture-07-why-agents-overreach-and-under-finish/code/scope-surface-example.md deleted file mode 100644 index 0055b451..00000000 --- a/courses/learn-harness-engineering/docs/es/lectures/lecture-07-why-agents-overreach-and-under-finish/code/scope-surface-example.md +++ /dev/null @@ -1,17 +0,0 @@ -# Ejemplo de Superficie de Alcance - -Tarea: - -- Añadir indexación a la app de conocimiento Electron - -Forma de alcance mala: - -- "Implementar indexación" - -Forma de alcance mejor: - -- Analizar documentos importados -- Dividir documentos en fragmentos -- Persistir metadatos de fragmentos -- Mostrar estado de indexación en la UI -- Añadir una acción de reindexación diff --git a/courses/learn-harness-engineering/docs/es/lectures/lecture-07-why-agents-overreach-and-under-finish/code/scope-tracker.ts b/courses/learn-harness-engineering/docs/es/lectures/lecture-07-why-agents-overreach-and-under-finish/code/scope-tracker.ts deleted file mode 100644 index c587a1dc..00000000 --- a/courses/learn-harness-engineering/docs/es/lectures/lecture-07-why-agents-overreach-and-under-finish/code/scope-tracker.ts +++ /dev/null @@ -1,151 +0,0 @@ -/** - * scope-tracker.ts - * - * Reads a feature list and a change log. Enforces single-active-feature - * policy. Given a log of changes, flags any changes outside the active - * feature scope. Demonstrates how scope drift happens and how the tracker - * catches it. - * - * Run: npx tsx docs/lectures/lecture-07-why-agents-overreach-and-under-finish/code/scope-tracker.ts - */ - -// --------------------------------------------------------------------------- -// Types -// --------------------------------------------------------------------------- - -interface Feature { - id: string; - name: string; - status: "active" | "pending" | "done"; -} - -interface ChangeLogEntry { - step: number; - file: string; - description: string; - featureId: string; // The feature this change claims to belong to -} - -// --------------------------------------------------------------------------- -// Sample data -// --------------------------------------------------------------------------- - -const features: Feature[] = [ - { id: "F-001", name: "Search endpoint", status: "active" }, - { id: "F-002", name: "Delete endpoint", status: "pending" }, - { id: "F-003", name: "Rate limiting", status: "pending" }, - { id: "F-004", name: "User dashboard", status: "pending" }, -]; - -// A realistic change log where the agent gradually drifts from the active feature -const changeLog: ChangeLogEntry[] = [ - { step: 1, file: "src/routes/search.ts", description: "Add search route handler", featureId: "F-001" }, - { step: 2, file: "src/routes/search.ts", description: "Add query parameter validation", featureId: "F-001" }, - { step: 3, file: "src/routes/search.ts", description: "Add search results pagination", featureId: "F-001" }, - { step: 4, file: "src/routes/delete.ts", description: "Add delete route handler", featureId: "F-002" }, // DRIFT - { step: 5, file: "src/middleware/rate-limit.ts", description: "Add rate limiter middleware", featureId: "F-003" }, // DRIFT - { step: 6, file: "src/routes/search.ts", description: "Integrate rate limiter into search", featureId: "F-001" }, - { step: 7, file: "src/dashboard/ui.tsx", description: "Create dashboard layout component", featureId: "F-004" }, // DRIFT - { step: 8, file: "src/routes/search.ts", description: "Add search response formatting", featureId: "F-001" }, - { step: 9, file: "src/routes/delete.ts", description: "Add delete confirmation logic", featureId: "F-002" }, // DRIFT - { step: 10, file: "src/routes/search.ts", description: "Add search tests", featureId: "F-001" }, -]; - -// --------------------------------------------------------------------------- -// Scope tracker -// --------------------------------------------------------------------------- - -interface ScopeCheckResult { - step: number; - file: string; - description: string; - featureId: string; - inScope: boolean; - activeFeature: string; -} - -function trackScope( - featureList: Feature[], - changes: ChangeLogEntry[] -): ScopeCheckResult[] { - const activeFeatures = featureList.filter((f) => f.status === "active"); - const activeIds = new Set(activeFeatures.map((f) => f.id)); - const activeNames = activeFeatures.map((f) => f.name).join(", "); - - return changes.map((change) => ({ - step: change.step, - file: change.file, - description: change.description, - featureId: change.featureId, - inScope: activeIds.has(change.featureId), - activeFeature: activeNames, - })); -} - -// --------------------------------------------------------------------------- -// Reporting -// --------------------------------------------------------------------------- - -function pad(s: string, len: number): string { - return s.length >= len ? s : s + " ".repeat(len - s.length); -} - -function run(): void { - const results = trackScope(features, changeLog); - - console.log("\n" + "=".repeat(100)); - console.log(" SCOPE TRACKER -- Single Active Feature Enforcement"); - console.log("=".repeat(100)); - - console.log("\n Active feature: " + features.filter((f) => f.status === "active").map((f) => `${f.id} (${f.name})`).join(", ")); - console.log(" Pending features: " + features.filter((f) => f.status === "pending").map((f) => `${f.id} (${f.name})`).join(", ")); - - // Detailed change log - console.log("\n" + "-".repeat(100)); - const header = `| ${pad("Step", 5)}| ${pad("File", 35)}| ${pad("Description", 40)}| ${pad("Feature", 8)}| ${pad("In Scope", 10)}|`; - const sep = `|${"-".repeat(7)}|${"-".repeat(37)}|${"-".repeat(42)}|${"-".repeat(10)}|${"-".repeat(12)}|`; - console.log(header); - console.log(sep); - - let inScopeCount = 0; - let driftCount = 0; - - for (const r of results) { - const scopeLabel = r.inScope ? "OK" : "DRIFT"; - if (r.inScope) inScopeCount++; - else driftCount++; - - const marker = r.inScope ? " " : ">>"; - console.log(`${marker}| ${pad(String(r.step), 5)}| ${pad(r.file, 35)}| ${pad(r.description, 40)}| ${pad(r.featureId, 8)}| ${pad(scopeLabel, 10)}|`); - } - - // Summary - console.log("\n" + "=".repeat(100)); - console.log(" SCOPE DRIFT SUMMARY"); - console.log("=".repeat(100) + "\n"); - - const sHeader = `| ${pad("Metric", 40)}| ${pad("Value", 15)}|`; - const sSep = `|${"-".repeat(42)}|${"-".repeat(17)}|`; - console.log(sHeader); - console.log(sSep); - console.log(`| ${pad("Total changes", 40)}| ${pad(String(results.length), 15)}|`); - console.log(`| ${pad("Changes within active scope (F-001)", 40)}| ${pad(String(inScopeCount), 15)}|`); - console.log(`| ${pad("Changes outside active scope (DRIFT)", 40)}| ${pad(String(driftCount), 15)}|`); - console.log(`| ${pad("Features touched (total)", 40)}| ${pad(String(new Set(results.map((r) => r.featureId)).size), 15)}|`); - - // Drift detail - const driftFeatures = [...new Set(results.filter((r) => !r.inScope).map((r) => r.featureId))]; - if (driftFeatures.length > 0) { - console.log("\n DRIFTED FEATURES:"); - for (const fid of driftFeatures) { - const feat = features.find((f) => f.id === fid); - const driftChanges = results.filter((r) => r.featureId === fid); - console.log(` ${fid} (${feat?.name}): ${driftChanges.length} unauthorized changes`); - } - } - - console.log("\n Without a scope tracker, the agent silently worked on " + driftFeatures.length + " unrelated features."); - console.log(" The tracker catches this drift and enforces the single-active-feature policy.\n"); -} - -run(); diff --git a/courses/learn-harness-engineering/docs/es/lectures/lecture-07-why-agents-overreach-and-under-finish/index.md b/courses/learn-harness-engineering/docs/es/lectures/lecture-07-why-agents-overreach-and-under-finish/index.md deleted file mode 100644 index b8139ea3..00000000 --- a/courses/learn-harness-engineering/docs/es/lectures/lecture-07-why-agents-overreach-and-under-finish/index.md +++ /dev/null @@ -1,134 +0,0 @@ -[中文版本 →](../../../zh/lectures/lecture-07-why-agents-overreach-and-under-finish/) - -> Ejemplos de código: [code/](https://github.com/walkinglabs/learn-harness-engineering/blob/main/docs/es/lectures/lecture-07-why-agents-overreach-and-under-finish/code/) -> Proyecto práctico: [Project 04. Runtime feedback and scope control](./../../projects/project-04-incremental-indexing/index.md) - -# Lección 07. Define límites claros para las tareas del agente - -Le dices a Claude Code que "añada autenticación de usuarios a este proyecto," y comienza a modificar el esquema de la base de datos, escribir rutas, cambiar componentes del frontend y — de paso — refactorizar el middleware de manejo de errores. Dos horas después verificas: 12 archivos modificados, 800 líneas de código nuevo, y ni una sola funcionalidad funciona end-to-end. - -Morder más de lo que puedes masticar — este dicho se aplica a los agentes de IA especialmente bien. Los agentes nacen con un impulso de "hacer un poco más" — ven cosas relacionadas y simplemente las manejan de paso, como alguien que va al supermercado por una botella de salsa de soya y sale empujando un carrito lleno. El problema es, los humanos que compran demasiado solo desperdician dinero; los agentes haciendo demasiadas cosas simultáneamente significa que ninguna se hace bien. - -El blog de ingeniería de Anthropic "Effective harnesses for long-running agents" establece claramente: cuando los prompts son demasiado amplios, los agentes tienden a "comenzar múltiples cosas a la vez" en lugar de "terminar una cosa primero." Las prácticas de ingeniería de Codex de OpenAI encontraron lo mismo — las tareas sin controles explícitos de alcance ven las tasas de completitud desplomarse. Este no es un problema del modelo — es un problema del harness. No trazaste el límite. - -## La atención es un recurso finito - -Esto no es una metáfora — es matemática. Supón que la capacidad de contexto del agente es C y activa k tareas simultáneamente. Cada tarea recibe un promedio de C/k recursos de razonamiento. Cuando C/k cae por debajo del umbral mínimo necesario para completar una sola tarea, ninguna se termina. Tu estómago solo tiene cierto tamaño — mete diez dumplings de una vez y no los digerirás todos, solo tendrás diez casos de indigestión. - -El comportamiento real de Claude Code es revelador. Pídele que "añada registro de usuarios" y podría: - -1. Crear un modelo de User -2. Escribir la ruta de registro -3. Notar que necesita verificación de email, así que añadir un servicio de correo -4. Ver que las contraseñas necesitan hashing, así que incorporar bcrypt -5. Notar que el manejo de errores es inconsistente, así que refactorizar el middleware de errores global -6. Ver que la estructura de archivos de prueba está desordenada, así que reorganizar el directorio - -Seis pasos después, cada uno está a medio hacer. Sin verificación end-to-end, acoplamiento complejo entre el código a medio cocinar, y la siguiente sesión que tenga que recoger los pedazos estará completamente perdida. Como alguien cocinando seis platos simultáneamente — cada plato está en la sartén pero ninguno ha sido emplatado. Todos se queman. - -Los datos experimentales de Anthropic apoyan esto directamente: los agentes que usan una estrategia de "pequeño siguiente paso" (equivalente a WIP=1) muestran un 37% más de tasa de completitud de tareas que los agentes que usan prompts amplios. Más interesante aún, el número de líneas de código generadas por los agentes está débilmente correlacionado negativamente con la completitud real de funcionalidades — más código escrito, menos funcionalidades completadas. Morder más de lo que puedes masticar, comprobado con datos. - -## Flujo de trabajo WIP=1 - -```mermaid -flowchart LR - Queue["Feature queue"] --> Pick["Pick exactly one task"] - Pick --> Active["Only one active item"] - Active --> Verify["Run end-to-end verification"] - Verify -->|pass| Commit["Commit and unlock next task"] - Verify -->|fail| Active - Commit --> Queue -``` - -```mermaid -flowchart TB - Budget["Available reasoning budget = C"] --> One["WIP = 1
C / 1 per task"] - Budget --> Many["WIP = 5
C / 5 per task"] - - One --> Finish["One feature reaches passing state"] - Many --> Partial["Five partial implementations"] - Partial --> VCR["Low verified completion rate
high rework next session"] -``` - -## Conceptos clave - -- **Sobrealcance (Overreach)**: El agente activa más tareas en una sola sesión de lo óptimo. Es cuantificable — hacer 5 funcionalidades con 0 pasando end-to-end es sobrealcance. -- **Sub-completitud (Under-finish)**: La proporción de tareas que pasan verificación end-to-end, de todas las tareas activadas, cae por debajo del umbral. Código escrito pero pruebas sin pasar es sub-completitud. -- **Límite WIP (Work-in-Progress Limit)**: De la metodología Kanban. Idea central: limitar cuántas tareas están en progreso a la vez. Para agentes, WIP=1 es el valor predeterminado más seguro — termina una antes de comenzar la siguiente. Como un buffet — no llenes tu plato, termina un plato y luego vuelve por el siguiente. -- **Evidencia de completitud**: La condición verificable que una tarea debe satisfacer para pasar de "en progreso" a "terminada." Sin esto, los agentes sustituyen "el código se ve bien" por "el comportamiento pasa las pruebas." -- **Superficie de alcance (Scope Surface)**: Una estructura DAG donde cada nodo es una unidad de trabajo y las aristas son dependencias. Los estados se limitan a cuatro: not_started, active, blocked, passing. -- **Presión de completitud**: La fuerza restrictiva que el harness ejerce a través de límites WIP y requisitos de evidencia de completitud, forzando al agente a terminar la tarea actual antes de comenzar una nueva. - -## El sobrealcance y la sub-completitud son simbióticos - -Estos dos problemas no son independientes — se amplifican mutuamente. El sobrealcance diluye la atención, la atención diluida causa sub-completitud, y el código a medio terminar que queda aumenta la complejidad del sistema, lo que a su vez impulsa más sobrealcance en la siguiente tarea. Un ciclo vicioso. - -En términos de Kanban: la Ley de Little nos dice L = lambda * W. Si el trabajo en progreso L es demasiado alto (haciendo demasiadas cosas a la vez), el tiempo de entrega W para cada tarea inevitablemente aumenta. Para los agentes, esto significa que cada funcionalidad tarda más desde el inicio hasta la completitud verificada, y la probabilidad de fallo crece. - -Este es un problema antiguo en el mundo humano también — Steve McConnell documentó en *Rapid Development* que el scope creep es la principal causa de fracaso de proyectos. Pero los humanos al menos tienen la intuición de "ya he hecho suficiente." Los agentes no tienen ninguna. Generar la siguiente idea le cuesta al modelo casi nada en tokens extra — escribir "déjame arreglar esto también mientras estoy aquí" apenas se nota — pero cada modificación adicional diluye la atención del agente. Como un buffet donde cada plato extra tiene un costo marginal cercano a cero, pero tu estómago solo tiene cierta capacidad. - -## Cómo hacerlo bien - -### 1. Haz cumplir WIP=1 - -Este es el método más directo y efectivo. En tu harness, dile al agente explícitamente: **solo una tarea tiene permitido estar en estado "active" en cualquier momento.** En el CLAUDE.md de Claude Code o el AGENTS.md de Codex, escribe: - -``` -## Work Rules -- Work on one feature at a time -- Only start the next feature after the current one passes end-to-end verification -- Don't "also refactor" feature B while implementing feature A -``` - -Como comer en un buffet — un plato a la vez, termínalo antes de volver por más. - -### 2. Define evidencia de completitud explícita para cada tarea - -Terminado no es "el código está escrito" — es "la verificación del comportamiento pasa." En tu feature list, cada entrada necesita un comando de verificación: - -``` -F01: User Registration - Verification: curl -X POST /api/register -d '{"email":"test@example.com","password":"123456"}' | jq .status == 201 - State: passing -``` - -### 3. Externaliza la superficie de alcance - -Usa un archivo legible por máquina (JSON o Markdown) para registrar todos los estados de las tareas. Cualquier nueva sesión puede leer este archivo y saber inmediatamente: ¿qué tarea está activa? ¿Qué comportamiento cuenta como terminado? ¿Qué verificaciones han pasado? - -### 4. Monitorea la tasa de completitud verificada - -El harness debería rastrear continuamente el VCR (Verified Completion Rate) = tareas verificadas / tareas activadas. Bloquea nuevas activaciones de tareas cuando VCR < 1.0. - -## Caso del mundo real - -Un proyecto de API REST con 8 funcionalidades, se compararon dos estrategias: - -**Modo buffet (sin restricciones)**: El agente activa 5 funcionalidades simultáneamente en la sesión 1. Produce ~800 líneas en 12 archivos. Tasa de aprobación de pruebas end-to-end: 20% — solo el registro de usuarios funciona. Las otras 4 funcionalidades: esquema de base de datos creado pero falta lógica de validación, rutas definidas pero devolviendo formatos de respuesta incorrectos. Como alguien cocinando seis platos a la vez, solo uno es apenas comestible. Al final de la sesión 3, solo 3 de 8 funcionalidades completadas. - -**Modo plato único (WIP=1)**: El agente trabaja solo en el registro de usuarios en la sesión 1. Produce ~200 líneas en 4 archivos. Pruebas end-to-end: 100% pasando. Hace commit de una implementación limpia y verificada. Al final de la sesión 4, 7 de 8 funcionalidades completadas (la 8va bloqueada por una dependencia externa). - -Resultado: menos código total (800 vs 1200 líneas) pero más código efectivo. Tasa de completitud: 87.5% vs 37.5%. Toma un bocado a la vez, y realmente comes más. - -## Ideas clave - -- **WIP=1 es la configuración predeterminada segura para harness de agentes** — termina una, luego comienza la siguiente; no intentes paralelizar. No te puedes poner gordo en un bocado. -- **La evidencia de completitud debe ser ejecutable** — "el código se ve bien" no cuenta; "curl devuelve 201" sí. -- **La superficie de alcance debe externalizarse como archivo** — no solo mencionada en la conversación, sino registrada en un formato legible por máquina en el repositorio. -- **El sobrealcance y la sub-completitud son simbióticos** — resolver uno resuelve el otro. -- **"Haz menos pero termina" siempre vence a "haz más pero deja a medias"** — las líneas de código del agente y la tasa de completitud de funcionalidades están negativamente correlacionadas. La calidad siempre vence a la cantidad. - -## Lecturas adicionales - -- [Effective harnesses for long-running agents - Anthropic](https://www.anthropic.com/engineering/effective-harnesses-for-long-running-agents) — Blog de ingeniería de Anthropic, discusión detallada de la estrategia de "pequeño siguiente paso" -- [Harness Engineering - OpenAI](https://openai.com/index/harness-engineering/) — Tratamiento completo de ingeniería de harness de OpenAI -- [Kanban: Successful Evolutionary Change - David Anderson](https://www.goodreads.com/book/show/1070822.Kanban) — La fuente clásica sobre límites WIP -- [Rapid Development - Steve McConnell](https://www.goodreads.com/book/show/125171.Rapid_Development) — Datos empíricos sobre el scope creep como principal causa de fracaso de proyectos - -## Ejercicios - -1. **Atomización de tareas**: Toma un requisito amplio (por ejemplo, "implementa un sistema de gestión de usuarios") y divídelo en al menos 5 unidades de trabajo atómicas. Para cada unidad, especifica: (a) una descripción de comportamiento única, (b) un comando de verificación ejecutable, (c) dependencias. Verifica si la descomposición satisface la restricción WIP=1. - -2. **Experimento comparativo**: Ejecuta el mismo proyecto dos veces — una sin restricciones, otra con WIP=1 impuesto. Compara: tasa de completitud verificada, líneas totales de código, ratio de código efectivo. - -3. **Auditoría de evidencia de completitud**: Revisa la salida de una ejecución reciente de un agente, clasificando cada cambio de código como "comportamiento completado," "comportamiento incompleto" o "scaffolding." Añade los comandos de verificación faltantes para cada comportamiento incompleto. diff --git a/courses/learn-harness-engineering/docs/es/lectures/lecture-08-why-feature-lists-are-harness-primitives/code/feature-list-validator.ts b/courses/learn-harness-engineering/docs/es/lectures/lecture-08-why-feature-lists-are-harness-primitives/code/feature-list-validator.ts deleted file mode 100644 index 52d6a2b9..00000000 --- a/courses/learn-harness-engineering/docs/es/lectures/lecture-08-why-feature-lists-are-harness-primitives/code/feature-list-validator.ts +++ /dev/null @@ -1,218 +0,0 @@ -/** - * feature-list-validator.ts - * - * Reads a feature_list.json, validates its schema, and checks for features - * marked "pass" without verification evidence. Outputs a structured report. - * Can run against any project directory that has a feature_list.json. - * - * Usage: - * npx tsx docs/lectures/lecture-08.../code/feature-list-validator.ts [path-to-dir] - * (defaults to the directory containing this script) - * - * Run: npx tsx docs/lectures/lecture-08-why-feature-lists-are-harness-primitives/code/feature-list-validator.ts - */ - -import * as fs from "node:fs"; -import * as path from "node:path"; - -// --------------------------------------------------------------------------- -// Types -// --------------------------------------------------------------------------- - -interface FeatureEntry { - id?: string; - category?: string; - description?: string; - verification?: string[]; - passes?: boolean; - // Allow unknown fields for flexibility - [key: string]: unknown; -} - -interface ValidationResult { - featureId: string; - schemaValid: boolean; - schemaErrors: string[]; - hasVerification: boolean; - markedPassWithoutEvidence: boolean; - passes: boolean; - verificationCount: number; -} - -// --------------------------------------------------------------------------- -// Schema validation -// --------------------------------------------------------------------------- - -function validateSchema(entry: FeatureEntry, index: number): string[] { - const errors: string[] = []; - const label = entry.id ?? `entry ${index + 1}`; - - if (!entry.id || typeof entry.id !== "string") { - errors.push(`[${label}] Missing or invalid 'id' field`); - } - if (!entry.category || typeof entry.category !== "string") { - errors.push(`[${label}] Missing or invalid 'category' field`); - } - if (!entry.description || typeof entry.description !== "string") { - errors.push(`[${label}] Missing or invalid 'description' field`); - } - if (entry.verification !== undefined && !Array.isArray(entry.verification)) { - errors.push(`[${label}] 'verification' must be an array if present`); - } - if (entry.passes !== undefined && typeof entry.passes !== "boolean") { - errors.push(`[${label}] 'passes' must be a boolean if present`); - } - - return errors; -} - -// --------------------------------------------------------------------------- -// Evidence validation -// --------------------------------------------------------------------------- - -function checkEvidence(entry: FeatureEntry): { - hasVerification: boolean; - markedPassWithoutEvidence: boolean; -} { - const hasVerification = Array.isArray(entry.verification) && entry.verification.length > 0; - const markedPassWithoutEvidence = entry.passes === true && !hasVerification; - - return { hasVerification, markedPassWithoutEvidence }; -} - -// --------------------------------------------------------------------------- -// Process feature list -// --------------------------------------------------------------------------- - -function processFeatureList(entries: FeatureEntry[]): ValidationResult[] { - return entries.map((entry, index) => { - const schemaErrors = validateSchema(entry, index); - const evidence = checkEvidence(entry); - - return { - featureId: (entry.id as string) ?? `entry-${index + 1}`, - schemaValid: schemaErrors.length === 0, - schemaErrors, - hasVerification: evidence.hasVerification, - markedPassWithoutEvidence: evidence.markedPassWithoutEvidence, - passes: entry.passes === true, - verificationCount: Array.isArray(entry.verification) ? entry.verification.length : 0, - }; - }); -} - -// --------------------------------------------------------------------------- -// Reporting -// --------------------------------------------------------------------------- - -function pad(s: string, len: number): string { - return s.length >= len ? s : s + " ".repeat(len - s.length); -} - -function run(): void { - // Resolve target directory - const scriptDir = path.dirname(new URL(import.meta.url).pathname); - const targetDir = process.argv[2] - ? path.resolve(process.argv[2]) - : scriptDir; - - const filePath = path.join(targetDir, "feature_list.json"); - - console.log("\n" + "=".repeat(90)); - console.log(" FEATURE LIST VALIDATOR"); - console.log("=".repeat(90)); - console.log(` Reading: ${filePath}\n`); - - if (!fs.existsSync(filePath)) { - console.error(` ERROR: feature_list.json not found at ${filePath}`); - console.error(" Usage: npx tsx feature-list-validator.ts [path-to-directory-containing-feature_list.json]\n"); - process.exit(1); - } - - let entries: FeatureEntry[]; - try { - const raw = fs.readFileSync(filePath, "utf-8"); - entries = JSON.parse(raw); - } catch (err) { - console.error(` ERROR: Could not parse feature_list.json: ${err}`); - process.exit(1); - } - - if (!Array.isArray(entries)) { - console.error(" ERROR: feature_list.json must contain a JSON array at the top level."); - process.exit(1); - } - - // For demo purposes, also validate an extended test set - const demoEntries: FeatureEntry[] = [ - ...entries, - { - id: "qna-002", - category: "import", - description: "User can import a PDF document.", - verification: ["Upload a PDF file", "Verify it appears in the document list"], - passes: true, - }, - { - id: "qna-003", - category: "grounded_qa", - description: "System hallucination rate is below 5%.", - verification: [], // Empty -- no evidence - passes: true, // Marked as pass WITHOUT evidence - }, - { - id: "missing-fields", - // Missing 'category' and 'description' - passes: true, - } as FeatureEntry, - ]; - - const results = processFeatureList(demoEntries); - - // Print report - const header = `| ${pad("Feature ID", 14)}| ${pad("Schema", 8)}| ${pad("Passes", 7)}| ${pad("Verifications", 14)}| ${pad("Evidence?", 12)}| Notes`; - const sep = `|${"-".repeat(16)}|${"-".repeat(10)}|${"-".repeat(9)}|${"-".repeat(16)}|${"-".repeat(14)}|${"-".repeat(30)}`; - console.log(header); - console.log(sep); - - for (const r of results) { - const schemaLabel = r.schemaValid ? "OK" : "INVALID"; - const passesLabel = r.passes ? "PASS" : "FAIL"; - const evidenceLabel = r.hasVerification ? "Present" : "MISSING"; - const notes = r.markedPassWithoutEvidence - ? "FLAGGED: passes without evidence!" - : r.schemaErrors.length > 0 - ? r.schemaErrors[0] - : ""; - - const marker = r.markedPassWithoutEvidence ? ">>" : r.schemaValid ? " " : "!!"; - console.log( - `${marker}| ${pad(r.featureId, 14)}| ${pad(schemaLabel, 8)}| ${pad(passesLabel, 7)}| ${pad(String(r.verificationCount), 14)}| ${pad(evidenceLabel, 12)}| ${notes}` - ); - } - - // Summary - const total = results.length; - const schemaOk = results.filter((r) => r.schemaValid).length; - const passing = results.filter((r) => r.passes).length; - const flagged = results.filter((r) => r.markedPassWithoutEvidence).length; - const withEvidence = results.filter((r) => r.hasVerification).length; - - console.log("\n" + "-".repeat(90)); - console.log(" SUMMARY"); - console.log("-".repeat(90)); - console.log(` Total features: ${total}`); - console.log(` Schema valid: ${schemaOk}/${total}`); - console.log(` Marked as passing: ${passing}/${total}`); - console.log(` With verification evidence: ${withEvidence}/${total}`); - console.log(` Flagged (pass without evidence): ${flagged}`); - - if (flagged > 0) { - console.log(`\n WARNING: ${flagged} feature(s) marked as "pass" without any verification evidence.`); - console.log(" These features need verification before they can be trusted.\n"); - } else { - console.log("\n All passing features have verification evidence. Feature list is healthy.\n"); - } -} - -run(); diff --git a/courses/learn-harness-engineering/docs/es/lectures/lecture-08-why-feature-lists-are-harness-primitives/code/feature_list.json b/courses/learn-harness-engineering/docs/es/lectures/lecture-08-why-feature-lists-are-harness-primitives/code/feature_list.json deleted file mode 100644 index 8b973e31..00000000 --- a/courses/learn-harness-engineering/docs/es/lectures/lecture-08-why-feature-lists-are-harness-primitives/code/feature_list.json +++ /dev/null @@ -1,15 +0,0 @@ -[ - { - "id": "qna-001", - "category": "grounded_qa", - "description": "User can ask a question about an imported document and receive an answer with visible citations.", - "verification": [ - "Import a markdown document", - "Open the Q&A panel", - "Ask a question about known content", - "Verify the answer is returned", - "Verify one or more citations are displayed" - ], - "passes": false - } -] diff --git a/courses/learn-harness-engineering/docs/es/lectures/lecture-08-why-feature-lists-are-harness-primitives/code/index.md b/courses/learn-harness-engineering/docs/es/lectures/lecture-08-why-feature-lists-are-harness-primitives/code/index.md deleted file mode 100644 index ac855d98..00000000 --- a/courses/learn-harness-engineering/docs/es/lectures/lecture-08-why-feature-lists-are-harness-primitives/code/index.md +++ /dev/null @@ -1,8 +0,0 @@ -# Código de la Clase 08 - -Usa esta carpeta para ejemplos de: - -- gating de estado de aprobación -- verificación end-to-end -- criterios de completitud débiles vs fuertes -- ejemplos de bucle evaluador diff --git a/courses/learn-harness-engineering/docs/es/lectures/lecture-08-why-feature-lists-are-harness-primitives/code/pass-gate-policy.md b/courses/learn-harness-engineering/docs/es/lectures/lecture-08-why-feature-lists-are-harness-primitives/code/pass-gate-policy.md deleted file mode 100644 index 6d9083d7..00000000 --- a/courses/learn-harness-engineering/docs/es/lectures/lecture-08-why-feature-lists-are-harness-primitives/code/pass-gate-policy.md +++ /dev/null @@ -1,8 +0,0 @@ -# Política de Puerta de Aprobación - -Un feature solo puede pasar de `passes: false` a `passes: true` cuando: - -- el flujo de trabajo esperado ha sido ejecutado -- la evidencia de éxito está registrada -- no hay errores bloqueantes en la ruta probada -- la implementación no deja la app en un estado roto o ambiguo diff --git a/courses/learn-harness-engineering/docs/es/lectures/lecture-08-why-feature-lists-are-harness-primitives/index.md b/courses/learn-harness-engineering/docs/es/lectures/lecture-08-why-feature-lists-are-harness-primitives/index.md deleted file mode 100644 index 2ac67ff0..00000000 --- a/courses/learn-harness-engineering/docs/es/lectures/lecture-08-why-feature-lists-are-harness-primitives/index.md +++ /dev/null @@ -1,138 +0,0 @@ -[中文版本 →](../../../zh/lectures/lecture-08-why-feature-lists-are-harness-primitives/) - -> Ejemplos de código: [code/](https://github.com/walkinglabs/learn-harness-engineering/blob/main/docs/es/lectures/lecture-08-why-feature-lists-are-harness-primitives/code/) -> Proyecto práctico: [Proyecto 04. Feedback en runtime y control de alcance](./../../projects/project-04-incremental-indexing/index.md) - -# Lección 08. Usa listas de funciones para limitar al agente - -Pides a un agente que construya un sitio de e-commerce. Cuando termina, te dice "done". Miras el código: la autenticación de usuario funciona, pero el botón de checkout del carrito no hace nada y el flujo de pago no está conectado. El problema: nunca le dijiste qué significa "done", así que usó su propio estándar: "escribí mucho código y parece bastante completo". - -Para muchas personas, las listas de funciones son solo una nota: escribes cosas para no olvidarlas y luego las dejas de lado. Pero en el mundo del harness, una lista de funciones no es una nota para humanos: es la columna vertebral de todo el harness. El scheduler depende de ella para elegir tareas, el verifier para juzgar la finalización y el handoff reporter para generar resúmenes. Si rompes la columna, todo el cuerpo queda paralizado. - -Anthropic y OpenAI enfatizan lo mismo: **los artefactos deben externalizarse.** El estado de las funciones debe vivir en un archivo machine-readable dentro del repo, no en texto de conversación sin estructura. - -## Los agentes no saben qué significa "done" - -Ni Claude Code ni Codex saben automáticamente qué quieres decir con "done". Dices "añade una función de carrito de compras", y el modelo puede interpretar eso como "escribe un componente Cart y un método addToCart". Pero tú querías "el usuario puede navegar productos, añadirlos al carrito y completar el checkout end-to-end". Sin una lista de funciones, esta brecha de entendimiento persiste. El agente usa su estándar implícito, normalmente "el código no tiene errores de sintaxis obvios". Lo que necesitas es verificación conductual end-to-end. Es como pedirle a un amigo que compre fruta: dices "compra algo de fruta" y vuelve con limones. Su fruta y tu fruta no son la misma fruta. - -Mira esta nota de progreso común: - -``` -Did user auth, shopping cart mostly done, still need payments -``` - -¿Puede una nueva sesión de agente responder preguntas a partir de esta nota? ¿Qué significa "mostly done"? ¿Qué tests pasó el carrito? ¿Qué bloquea los pagos? La respuesta a todo es "nadie lo sabe". Es como decirle al médico "me duele el estómago, últimamente más o menos bien": ¿qué medicina puede recetar? - -Resultado: la nueva sesión gasta 20 minutos infiriendo el estado del proyecto y quizá reimplementa funciones ya terminadas. Los datos de ingeniería de Anthropic muestran que buenos registros de progreso reducen el tiempo de diagnóstico al iniciar sesión entre 60% y 80%. - -## Máquina de estados de funciones - -```mermaid -flowchart LR - Feature["Una fila de función"] --> Behavior["Comportamiento
por ejemplo: POST /cart/items returns 201"] - Feature --> Check["Comando de verificación
el check exacto que ejecutar"] - Feature --> State["Estado
not_started / active / blocked / passing"] - - Behavior --> Complete["Solo con los tres campos
la fila de función es usable"] - Check --> Complete - State --> Complete -``` - -```mermaid -flowchart LR - List["feature_list.json / features.md"] --> Scheduler["Elegir el siguiente item not_started"] - Scheduler --> Agent["El agente trabaja en ese único item"] - Agent --> Verifier["Ejecutar el comando de verificación del item"] - Verifier -->|pass| Passing["Marcar como passing
y escribir evidencia"] - Verifier -->|fail| Active["Mantenerlo active"] - Verifier -->|dependency issue| Blocked["Marcarlo blocked"] - Passing --> Handoff["Actualizar handoff note
y progreso actual"] - Active --> Agent -``` - -## Conceptos clave - -- **Las listas de funciones son primitives del harness**: No son "herramientas opcionales de planificación", sino estructuras de datos fundacionales de las que dependen los demás componentes del harness. Como las estructuras de tablas en una base de datos: no puedes decir "omitamos las primary keys". -- **Estructura triple**: Cada función es una triple `(descripción de comportamiento, comando de verificación, estado actual)`. Si falta cualquier elemento, el item está incompleto. -- **Modelo de máquina de estados**: Cada función tiene cuatro estados: `not_started`, `active`, `blocked`, `passing`. Las transiciones de estado las controla el harness, no las cambia libremente el agente. -- **Pass-state gating**: La única forma de pasar de `active` a `passing` es que el comando de verificación se ejecute con éxito. Esto es irreversible: una vez `passing`, no vuelve atrás. Como aprobar un examen: aprobaste, no puedes cambiar la nota retroactivamente. -- **Single source of truth**: Toda la información sobre "qué hay que hacer" debe derivarse de una sola lista de funciones. Sin contradicciones entre la lista y el historial de conversación. -- **Back-pressure**: El número de funciones que aún no han pasado es la presión que el harness ejerce sobre el agente. Presión cero = proyecto completo. - -## Por qué las listas de funciones deben ser "primitives" - -Los documentos son para que los lean humanos; los primitives son para que los ejecuten sistemas. Los documentos pueden ignorarse; los primitives no pueden saltarse. - -Piensa en restricciones de triggers de base de datos frente a checks en la capa de aplicación: las primeras las impone el motor de base de datos, ningún SQL puede saltarlas; las segundas dependen de la corrección del código de aplicación y pueden omitirse por accidente. Como primitive del harness, la lista de funciones sirve a cuatro componentes: - -1. **Scheduler**: Lee estados y elige la siguiente función `not_started`. Como un sistema de planificación de producción en fábrica. -2. **Verifier**: Ejecuta comandos de verificación y decide si permite transiciones de estado. Como inspección de calidad. -3. **Handoff reporter**: Genera automáticamente resúmenes de traspaso de sesión desde la lista. Como un reporte automático de cambio de turno. -4. **Progress tracker**: Cuenta la distribución de estados y ofrece métricas de salud del proyecto. Como un dashboard. - -## Cómo hacerlo bien - -### 1. Define un formato mínimo de lista de funciones - -No necesitas un sistema complejo: sirve un archivo Markdown estructurado o JSON. La clave es que cada entrada tenga la triple: - -```json -{ - "id": "F03", - "behavior": "POST /cart/items with {product_id, quantity} returns 201", - "verification": "curl -X POST http://localhost:3000/api/cart/items -H 'Content-Type: application/json' -d '{\"product_id\":1,\"quantity\":2}' | jq .status == 201", - "state": "passing", - "evidence": "commit abc123, test output log" -} -``` - -### 2. Deja que el harness controle las transiciones - -El agente no puede cambiar directamente el estado de una función a `passing`. Solo puede enviar una solicitud de verificación; el harness ejecuta el comando y decide si permite la transición. Esto es pass-state gating. - -### 3. Escribe las reglas en CLAUDE.md - -``` -## Feature List Rules -- Feature list file: /docs/features.md -- Only one feature active at a time -- Verification command must pass before marking as passing -- Don't modify feature list states yourself — the verification script updates them automatically -``` - -### 4. Calibra la granularidad - -Cada función debe tener alcance "completable en una sesión". Si es demasiado amplia no terminará; si es demasiado pequeña, crece el coste de gestión. "El usuario puede añadir items al carrito" tiene buena granularidad. "Implementar el carrito" es demasiado amplio. "Crear el campo name en el modelo Cart" es demasiado estrecho. Como cortar un filete: ni la pieza entera ni carne molida. - -## Caso real - -Una plataforma e-commerce con 10 funciones. Se compararon dos enfoques de seguimiento: - -**Modo memo**: El agente usa notas sin estructura. Después de 3 sesiones, las notas quedan como "hice user auth y product list, shopping cart casi listo pero con bugs, payments sin empezar". La nueva sesión necesita 20 minutos para inferir estado y acaba reimplementando funciones ya completadas. Como una lista de compras que dice "leche, pan y esa cosa": en la tienda sigues sin saber qué comprar. - -**Modo columna vertebral**: Cada función tiene estado claro y comando de verificación. La nueva sesión lee la lista y en 3 minutos sabe: F01-F05 están `passing`, F06 está `active`, F07-F10 están `not_started`. Continúa directamente desde F06, sin retrabajo. - -Resultado cuantificado: los proyectos que usan listas de funciones estructuradas muestran una tasa de finalización de funciones 45% mayor que el seguimiento libre, con cero implementaciones duplicadas. - -## Ideas clave - -- **Las listas de funciones son la columna vertebral del harness**, no notas para humanos. Scheduler, verifier y handoff reporter dependen de ellas. -- **Cada función debe tener la triple**: descripción de comportamiento + comando de verificación + estado actual. Si falta un elemento, está incompleta, como un taburete de tres patas al que le falta una. -- **Las transiciones de estado las controla el harness**: el agente no puede cambiar estados por su cuenta. Pasar verificación es el único camino de promoción. -- **La lista de funciones es el single source of truth del proyecto**: toda la información de "qué hacer" deriva de una lista. -- **Calibra la granularidad a "completable en una sesión".** - -## Lecturas adicionales - -- [Building Effective Agents - Anthropic](https://www.anthropic.com/research/building-effective-agents) — Identifica explícitamente la lista de funciones como "core data structure" para controlar el alcance del agente -- [Harness Engineering - OpenAI](https://openai.com/index/harness-engineering/) — Enfatiza el principio de "externalizar artefactos" -- [Design by Contract - Bertrand Meyer](https://www.goodreads.com/book/show/130439.Object_Oriented_Software_Construction) — Principios de diseño por contrato, base teórica de las listas de funciones -- [How Google Tests Software](https://www.goodreads.com/book/show/13563030-how-google-tests-software) — Pirámide de tests y prácticas de especificación conductual - -## Ejercicios - -1. **Diseño de lista de funciones**: Define un esquema JSON mínimo. Incluye id, descripción de comportamiento, comando de verificación, estado actual y referencia de evidencia. Úsalo para describir un proyecto real con 5 funciones. - -2. **Comparación de estricticidad de verificación**: Elige 3 funciones y diseña una verificación "laxa" (por ejemplo, "el código no tiene errores de sintaxis") y una "estricta" (por ejemplo, "el test end-to-end pasa"). Compara la tasa de falsos positivos. - -3. **Auditoría del principio single source**: Revisa un proyecto con agentes y busca información de alcance que contradiga la lista de funciones (requisitos implícitos en conversaciones, comentarios TODO en código, etc.). Diseña un plan para unificar toda la información en la lista. diff --git a/courses/learn-harness-engineering/docs/es/lectures/lecture-09-why-agents-declare-victory-too-early/code/clean-state-checklist.md b/courses/learn-harness-engineering/docs/es/lectures/lecture-09-why-agents-declare-victory-too-early/code/clean-state-checklist.md deleted file mode 100644 index f8ab4db5..00000000 --- a/courses/learn-harness-engineering/docs/es/lectures/lecture-09-why-agents-declare-victory-too-early/code/clean-state-checklist.md +++ /dev/null @@ -1,6 +0,0 @@ -# Lista de Verificación de Estado Limpio - -- La app arranca sin reparación manual -- El progreso actual está registrado -- No queda ningún paso de importación o indexación a medias sin documentar -- El siguiente agente puede ejecutar la ruta estándar de inicio y verificación inmediatamente diff --git a/courses/learn-harness-engineering/docs/es/lectures/lecture-09-why-agents-declare-victory-too-early/code/index.md b/courses/learn-harness-engineering/docs/es/lectures/lecture-09-why-agents-declare-victory-too-early/code/index.md deleted file mode 100644 index ac64bcc2..00000000 --- a/courses/learn-harness-engineering/docs/es/lectures/lecture-09-why-agents-declare-victory-too-early/code/index.md +++ /dev/null @@ -1,8 +0,0 @@ -# Código de la Clase 09 - -Usa esta carpeta para ejemplos de: - -- logs como feedback -- visibilidad del estado en runtime -- verificaciones de estado limpio -- ejemplos de recuperación diff --git a/courses/learn-harness-engineering/docs/es/lectures/lecture-09-why-agents-declare-victory-too-early/code/victory-detector.ts b/courses/learn-harness-engineering/docs/es/lectures/lecture-09-why-agents-declare-victory-too-early/code/victory-detector.ts deleted file mode 100644 index fad1a3ba..00000000 --- a/courses/learn-harness-engineering/docs/es/lectures/lecture-09-why-agents-declare-victory-too-early/code/victory-detector.ts +++ /dev/null @@ -1,241 +0,0 @@ -/** - * victory-detector.ts - * - * Simulates an agent that claims task completion, then checks the claimed - * state against actual verification. Outputs: claimed vs actual, highlighting - * gaps between what the agent said and what's really true. - * - * Run: npx tsx docs/lectures/lecture-09-why-agents-declare-victory-too-early/code/victory-detector.ts - */ - -// --------------------------------------------------------------------------- -// Types -// --------------------------------------------------------------------------- - -interface Task { - name: string; - claimedComplete: boolean; - checks: VerificationCheck[]; -} - -interface VerificationCheck { - description: string; - claimed: boolean; // What the agent says - actual: boolean; // What is actually true - severity: "critical" | "warning"; -} - -// --------------------------------------------------------------------------- -// Simulated tasks with claimed vs actual states -// --------------------------------------------------------------------------- - -const tasks: Task[] = [ - { - name: "Add search endpoint", - claimedComplete: true, - checks: [ - { - description: "Route handler exists", - claimed: true, - actual: true, - severity: "critical", - }, - { - description: "Authentication middleware applied", - claimed: true, - actual: false, // Agent forgot auth - severity: "critical", - }, - { - description: "Input validation added", - claimed: true, - actual: true, - severity: "critical", - }, - { - description: "Unit tests written", - claimed: true, - actual: false, // Agent skipped tests - severity: "critical", - }, - { - description: "Integration tests pass", - claimed: true, - actual: false, // No tests to run - severity: "critical", - }, - { - description: "Documentation updated", - claimed: true, - actual: false, // Agent skipped docs - severity: "warning", - }, - ], - }, - { - name: "Fix pagination bug", - claimedComplete: true, - checks: [ - { - description: "Bug reproduction case confirmed", - claimed: true, - actual: true, - severity: "critical", - }, - { - description: "Root cause identified", - claimed: true, - actual: true, - severity: "critical", - }, - { - description: "Fix applied", - claimed: true, - actual: true, - severity: "critical", - }, - { - description: "Edge cases tested", - claimed: true, - actual: false, // Only happy path tested - severity: "warning", - }, - { - description: "Regression tests added", - claimed: true, - actual: false, - severity: "critical", - }, - ], - }, - { - name: "Refactor auth module", - claimedComplete: true, - checks: [ - { - description: "Old code removed", - claimed: true, - actual: false, // Old code still present - severity: "warning", - }, - { - description: "All existing tests still pass", - claimed: true, - actual: false, // Two tests broke - severity: "critical", - }, - { - description: "New auth flow works", - claimed: true, - actual: true, - severity: "critical", - }, - { - description: "Migration guide written", - claimed: true, - actual: false, - severity: "warning", - }, - ], - }, -]; - -// --------------------------------------------------------------------------- -// Verification -// --------------------------------------------------------------------------- - -interface TaskVerification { - taskName: string; - claimedComplete: boolean; - actuallyComplete: boolean; - totalChecks: number; - claimedPassing: number; - actualPassing: number; - gaps: { description: string; severity: string }[]; -} - -function verifyTask(task: Task): TaskVerification { - const claimedPassing = task.checks.filter((c) => c.claimed).length; - const actualPassing = task.checks.filter((c) => c.actual).length; - const gaps = task.checks.filter((c) => c.claimed && !c.actual); - - return { - taskName: task.name, - claimedComplete: task.claimedComplete, - actuallyComplete: gaps.length === 0, - totalChecks: task.checks.length, - claimedPassing, - actualPassing, - gaps: gaps.map((g) => ({ description: g.description, severity: g.severity })), - }; -} - -// --------------------------------------------------------------------------- -// Reporting -// --------------------------------------------------------------------------- - -function pad(s: string, len: number): string { - return s.length >= len ? s : s + " ".repeat(len - s.length); -} - -function run(): void { - console.log("\n" + "=".repeat(90)); - console.log(" VICTORY DETECTOR -- Claimed vs Actual Verification"); - console.log("=".repeat(90)); - - const verifications = tasks.map(verifyTask); - - for (const v of verifications) { - console.log("\n Task: " + v.taskName); - console.log(" " + "-".repeat(70)); - console.log(` Agent claimed: ${v.claimedComplete ? "COMPLETE" : "NOT COMPLETE"}`); - console.log(` Actually is: ${v.actuallyComplete ? "COMPLETE" : "INCOMPLETE"}`); - - if (!v.actuallyComplete) { - console.log(` MISMATCH: Agent declared victory but ${v.gaps.length} check(s) failed!`); - } - - // Detailed checks for this task - const task = tasks.find((t) => t.name === v.taskName)!; - console.log("\n Check detail:"); - const header = ` | ${pad("Check", 40)}| ${pad("Claimed", 9)}| ${pad("Actual", 9)}| ${pad("Gap?", 6)}|`; - const sep = ` |${"-".repeat(42)}|${"-".repeat(11)}|${"-".repeat(11)}|${"-".repeat(8)}|`; - console.log(header); - console.log(sep); - - for (const check of task.checks) { - const claimedLabel = check.claimed ? "PASS" : "FAIL"; - const actualLabel = check.actual ? "PASS" : "FAIL"; - const gapLabel = check.claimed && !check.actual ? "GAP" : ""; - const marker = gapLabel ? ">>" : " "; - console.log(`${marker}| ${pad(check.description, 40)}| ${pad(claimedLabel, 9)}| ${pad(actualLabel, 9)}| ${pad(gapLabel, 6)}|`); - } - } - - // Overall summary - console.log("\n" + "=".repeat(90)); - console.log(" OVERALL SUMMARY"); - console.log("=".repeat(90) + "\n"); - - const sHeader = `| ${pad("Task", 28)}| ${pad("Claimed", 10)}| ${pad("Actual", 10)}| ${pad("Gaps", 6)}| ${pad("Accuracy", 10)}|`; - const sSep = `|${"-".repeat(30)}|${"-".repeat(12)}|${"-".repeat(12)}|${"-".repeat(8)}|${"-".repeat(12)}|`; - console.log(sHeader); - console.log(sSep); - - for (const v of verifications) { - const accuracy = Math.round((v.actualPassing / v.totalChecks) * 100); - const cLabel = v.claimedComplete ? "Done" : "Pending"; - const aLabel = v.actuallyComplete ? "Done" : "Incomplete"; - console.log(`| ${pad(v.taskName, 28)}| ${pad(cLabel, 10)}| ${pad(aLabel, 10)}| ${pad(String(v.gaps.length), 6)}| ${pad(accuracy + "%", 10)}|`); - } - - const totalGaps = verifications.reduce((s, v) => s + v.gaps.length, 0); - const falseVictories = verifications.filter((v) => v.claimedComplete && !v.actuallyComplete).length; - - console.log("\n False victories: " + falseVictories + " of " + verifications.length + " tasks"); - console.log(" Total undetected gaps: " + totalGaps); - console.log("\n The agent declared 'done' on every task, but verification reveals " + totalGaps + " unmet criteria."); - console.log(" Without explicit verification, premature victory declarations go unchecked.\n"); -} - -run(); diff --git a/courses/learn-harness-engineering/docs/es/lectures/lecture-09-why-agents-declare-victory-too-early/index.md b/courses/learn-harness-engineering/docs/es/lectures/lecture-09-why-agents-declare-victory-too-early/index.md deleted file mode 100644 index 3471a2eb..00000000 --- a/courses/learn-harness-engineering/docs/es/lectures/lecture-09-why-agents-declare-victory-too-early/index.md +++ /dev/null @@ -1,145 +0,0 @@ -[中文版本 →](../../../zh/lectures/lecture-09-why-agents-declare-victory-too-early/) - -> Code examples for this lecture: [code/](https://github.com/walkinglabs/learn-harness-engineering/blob/main/docs/es/lectures/lecture-09-why-agents-declare-victory-too-early/code/) -> Hands-on practice: [Project 05. Let the agent verify its own work](./../../projects/project-05-grounded-qa-verification/index.md) - -# Lección 09. Evita que los agentes declaren victoria demasiado pronto - -Le pides a un agent que implemente una funcionalidad de "restablecimiento de contraseña". Modifica el esquema de la base de datos, escribe el endpoint de la API, añade la plantilla de correo electrónico, ejecuta las pruebas unitarias (todas pasan) y luego te dice con confianza "ya está". Cuando realmente intentas ejecutarlo—el enlace de restablecimiento no se puede enviar (falta la configuración del servicio de correo electrónico), la migración de la base de datos falla a mitad de camino (inconsistencia en el esquema), y el flujo end-to-end no se ha ejecutado ni una sola vez. - -Esta sensación no debería resultar desconocida—es como llenar todo el examen, ser el primero en entregarlo con confianza, y luego suspender cuando salen las notas. Que el examen esté lleno no significa que las respuestas sean correctas. - -No es un incidente aislado. El clásico artículo de 2017 en ICML de Guo et al. demostró: **las redes neuronales modernas son sistemáticamente excesivamente seguras**—la confianza reportada por los modelos es significativamente mayor que su precisión real. Lo mismo se aplica a los agentes de codificación con IA: "sienten" que han terminado, pero en realidad, están lejos de hacerlo. Tu harness debe reemplazar los "sentimientos" del agent con verificación externalizada y basada en ejecución. - -## La pendiente resbaladiza - -Las declaraciones prematuras de finalización casi siempre siguen el mismo patrón: el código parece correcto—la sintaxis es correcta, la lógica parece razonable, y el análisis estático no muestra errores obvios. Pero el harness no impone una verificación de ejecución exhaustiva, así que el agent se salta ejecutarlo realmente o solo ejecuta pruebas parciales. Ejecuta pruebas unitarias pero se salta las de integración; ejecuta pruebas pero no verifica la cobertura. En última instancia, "el código parece bien" se toma como evidencia de que "la funcionalidad está completa". Y el examen se entrega. - -Se pierde información en cada paso. Desde las especificaciones de la tarea hasta la implementación del código y el comportamiento en tiempo de ejecución, cada transformación puede introducir sesgos, y cada verificación omitida exacerba la asimetría de información. - -## Verificación de terminación en tres capas - -```mermaid -flowchart LR - Claim["Agent says: done"] --> L1["First run
lint / typecheck"] - L1 --> L2["Then run
tests and startup checks"] - L2 --> L3["Finally run
complete user flow"] - L3 --> Done["Pass all three to be done"] -``` - -```mermaid -flowchart LR - A["Code is written
unit tests are green"] --> B["But app didn't really start
full flow never ran"] - B --> C["Config, DB, external service issues
all remain hidden"] - C --> D["So agent declares victory too early"] -``` - -## Conceptos clave - -- **Declaración prematura de finalización**: El agent afirma que la tarea está completa, pero aún existen especificaciones de corrección sin cumplir. El problema central: el agent juzga basándose en la confianza local a nivel de código, mientras que la corrección a nivel de sistema requiere verificación global. -- **Sesgo de calibración de confianza**: La brecha sistemática entre la confianza auto-reportada por el agent sobre la finalización y la calidad real de la finalización. Para tareas complejas de múltiples archivos, este sesgo es significativamente positivo—el agent siempre está más seguro de lo que realmente rinde. Es como un estudiante que siempre sobreestima su calificación después de un examen. -- **Criterios de terminación**: Un conjunto claro y ejecutable de condiciones de juicio definidas en el harness. El agent debe satisfacer todas las condiciones antes de declarar la finalización. "Hecho" pasa de ser un juicio subjetivo a una determinación objetiva. -- **Verificación-Validación de doble puerta**: La primera capa de verificación comprueba "si el código implementó correctamente el comportamiento especificado"; la segunda capa de validación comprueba "si el comportamiento a nivel de sistema cumple con los requisitos end-to-end". Ambas deben pasar para considerar la tarea completa. -- **Señales de retroalimentación en tiempo de ejecución**: Logs, estados de procesos y health checks de la ejecución del programa. Esta es la base objetiva para que el harness juzgue la calidad de la finalización. -- **Restricción de prioridad de finalización**: Primero verificar la corrección funcional, luego manejar el rendimiento, y finalmente abordar el estilo. Se prohíbe la refactorización hasta que la funcionalidad central esté verificada. - -## Pasar las pruebas unitarias ≠ Tarea completada - -Esta es la trampa más común, y la más peligrosa. El agent escribió el código, ejecutó las pruebas unitarias, todo está en verde, y dijo "listo". Pero la filosofía de diseño de las pruebas unitarias—aislar la unidad bajo prueba y mockear las dependencias—es precisamente lo que las hace incapaces de detectar problemas entre componentes: - -**Incompatibilidad de interfaces**: La ruta de archivo pasada por el proceso de render al preload script es una ruta relativa, pero el preload script espera una ruta absoluta. Sus respectivas pruebas unitarias usaron mocks y pasaron. El problema solo se descubre durante las pruebas end-to-end. Es como cada músico de una banda practicando perfectamente por su cuenta, solo para darse cuenta de que están en tonos diferentes cuando tocan juntos. - -**Errores de propagación de estado**: Una migración de base de datos cambia el esquema de la tabla, pero la capa de caché del ORM todavía mantiene entradas de caché para el esquema anterior. Las pruebas unitarias proporcionan un entorno mock fresco cada vez, lo cual no expondrá esta inconsistencia de estado entre capas. - -**Dependencia del entorno**: El código se comporta correctamente en el entorno de prueba (donde todo está mockeado) pero falla en el entorno real debido a diferencias de configuración, latencia de red o indisponibilidad del servicio. Es como cantar perfectamente en la sala de ensayo, pero encontrarse con problemas de equipo de audio en el escenario. - -### "Refactorizar de paso" es veneno para el juicio de finalización - -Claude Code tiene un patrón de comportamiento común: comienza a refactorizar código, optimizar el rendimiento y mejorar el estilo antes de que la funcionalidad central haya pasado la verificación. La cita de Knuth, "La optimización prematura es la raíz de todos los males," adquiere un nuevo significado en el escenario del agent—la refactorización altera la frontera entre el código verificado y el no verificado, potencialmente rompiendo rutas de código que antes eran implícitamente correctas. Es como volver a copiar tus respuestas de opción múltiple para mejorar el formato antes de haber terminado las preguntas de desarrollo—no solo pierdes tiempo, sino que podrías copiarlas mal. - -### Sesgo sistemático en la autoevaluación - -Anthropic descubrió un patrón de falla más profundo en su investigación de 2026: **cuando se le pide a un agent que evalúe su propio trabajo, proporciona sistemáticamente evaluaciones excesivamente positivas—incluso cuando un observador humano consideraría que la calidad es claramente insuficiente.** Es como pedirle a un estudiante que califique su propio examen—siempre será particularmente indulgente con sus propias respuestas. - -Este problema es especialmente grave en tareas subjetivas (como la estética de diseño)—si un "diseño es exquisito" es una cuestión de juicio, y el agent se inclina confiablemente hacia lo positivo. Incluso en tareas con resultados verificables, el rendimiento del agent puede verse afectado por un juicio deficiente. - -La solución no es hacer que el agent sea "más objetivo"—el mismo modelo que genera y evalúa inherentemente favorece ser generoso consigo mismo. **La solución es separar al "trabajador" del "verificador".** Así como un estudiante no debería calificar su propio examen—necesitas un calificador independiente. - -Un agent evaluador independiente, ajustado específicamente para ser "exigente", es mucho más efectivo que hacer que el agent generador se evalúe a sí mismo. Datos experimentales de Anthropic: - -| Architecture | Runtime | Cost | Core Features Working? | -|--------------|---------|------|------------------------| -| Single Agent (bare run) | 20 mins | $9 | No (game entities unresponsive to input) | -| Three Agents (planner + generator + evaluator) | 6 hours | $200 | Yes (game is fully playable) | - -Este es exactamente el mismo modelo (Opus 4.5) con exactamente el mismo prompt ("build a 2D retro game editor"). La única diferencia es el harness—desde "ejecución sin arnés" hasta "el planner expande los requisitos → el generador implementa funcionalidad por funcionalidad → el evaluador realiza pruebas reales de clics usando Playwright". - -> Fuente: [Anthropic: Harness design for long-running application development](https://www.anthropic.com/engineering/harness-design-long-running-apps) - -## Cómo evitar las entregas prematuras - -### 1. Externalizar el juicio de terminación - -El juicio de finalización no debería ser hecho por el agent mismo. El harness debe ejecutar independientemente la validación de terminación, usando señales en tiempo de ejecución como entrada, no la confianza del agent. Escríbelo claramente en `CLAUDE.md`: - -``` -## Definition of Done -- Feature complete = end-to-end verification passed, not "code is written" -- Required verification levels: - 1. Unit tests pass - 2. Integration tests pass - 3. End-to-end flow verification passes -- Do not proceed to level 2 if level 1 fails -- Do not proceed to level 3 if level 2 fails -``` - -### 2. Construir una validación de terminación en tres capas - -- **Capa 1: Sintaxis y análisis estático**. Menor costo, menor información, pero debe pasar. Esta es la verificación mínima—debes escribir las palabras correctamente antes de mirar nada más. -- **Capa 2: Verificación de comportamiento en tiempo de ejecución**. Ejecución de pruebas, verificaciones de inicio de la aplicación, validación de rutas críticas. Esta es la evidencia central de finalización. No basta con escribirlo; debe ejecutarse. -- **Capa 3: Confirmación a nivel de sistema**. Pruebas end-to-end, validación de integración, simulación de escenarios de usuario. La última línea de defensa contra declaraciones prematuras. No basta con que se ejecute; debe ejecutarse correctamente. - -### 3. Diseñar buenas "correcciones con bolígrafo rojo" para los agentes - -OpenAI introdujo un patrón particularmente efectivo durante su práctica con Codex: **los mensajes de error para los agentes deben incluir instrucciones de corrección**. No simplemente dibujes una gran X roja como un calificador perezoso; sé como un buen profesor y escribe "así es como deberías cambiar esto" en los márgenes. No uses `"Test failed"`, sino usa `"Test failed: POST /api/reset-password returned 500. Check that the email service config exists in environment variables. The template file should be at templates/reset-email.html."` Esta retroalimentación específica y accionable permite al agent autocorregirse sin intervención humana. - -### 4. Capturar señales en tiempo de ejecución - -Las señales efectivas en tiempo de ejecución incluyen: -- ¿La aplicación se inició exitosamente y alcanzó un estado listo? -- ¿Las rutas de funcionalidad crítica se ejecutaron exitosamente en tiempo de ejecución? -- ¿Las escrituras en base de datos, operaciones de archivos y otros efectos secundarios fueron correctos? -- ¿Se limpiaron los recursos temporales? - -## Caso del mundo real - -**Tarea**: Implementar la funcionalidad de restablecimiento de contraseña de usuario. Involucra operaciones de base de datos, envío de correo electrónico y modificaciones de endpoints de API. - -**Ruta de entrega prematura**: El agent modifica el esquema de la base de datos, escribe el endpoint de la API, añade la plantilla de correo electrónico, ejecuta las pruebas unitarias (pasa) y declara la finalización. El examen está completamente lleno. - -**Deducciones reales**: (1) Flujo end-to-end sin probar—el envío y verificación real del enlace de restablecimiento nunca se confirmó. (2) La migración de base de datos falló después de una ejecución parcial, causando inconsistencia en el esquema. (3) Faltaba la configuración del servicio de correo electrónico en el entorno destino. - -**Intervención del harness**: Validación de terminación impuesta—(1) Iniciar la aplicación completa para verificar la accesibilidad del endpoint de restablecimiento; (2) Ejecutar el flujo completo de restablecimiento; (3) Verificar la consistencia del estado de la base de datos. Todos los defectos se encontraron dentro de la sesión, ahorrando 5-10x el costo de correcciones posteriores. El calificador independiente encontró los problemas reales. - -## Ideas clave - -- **Los agentes son sistemáticamente excesivamente seguros**—el sesgo de calibración de confianza es una realidad objetiva. Llenar el examen no significa que lo hayas hecho bien. -- **El juicio de finalización debe externalizarse**—el harness verifica independientemente; no confíes en los "sentimientos" del agent. Los estudiantes no pueden calificar sus propios exámenes. -- **Las tres capas de validación son esenciales**—sintaxis pasando, comportamiento pasando, sistema pasando, progresando capa por capa. -- **Los mensajes de error deben ser como las correcciones con bolígrafo rojo de un buen profesor**—incluir pasos específicos de corrección para que el agent pueda autocorregirse. -- **Sin refactorización hasta que la funcionalidad central esté verificada**—la restricción de prioridad de finalización es la clave para prevenir la optimización prematura. - -## Lecturas adicionales - -- [On Calibration of Modern Neural Networks - Guo et al.](https://arxiv.org/abs/1706.04599) — Demuestra que las redes neuronales modernas son sistemáticamente excesivamente seguras -- [Building Effective Agents - Anthropic](https://www.anthropic.com/research/building-effective-agents) — El papel crítico de la evidencia en tiempo de ejecución en el juicio de finalización -- [Harness Engineering - OpenAI](https://openai.com/index/harness-engineering/) — La declaración prematura de finalización es uno de los principales modos de falla de los agentes -- [The Art of Software Testing - Myers](https://www.goodreads.com/book/show/137543.The_Art_of_Software_Testing) — Referencia clásica sobre jerarquías de métodos de prueba y su efectividad - -## Ejercicios - -1. **Diseño de función de validación de terminación**: Diseña una validación de terminación completa para una tarea que involucre una migración de base de datos y una modificación de API. Enumera las señales en tiempo de ejecución requeridas y los criterios de aprobación/rechazo para cada señal. Ejecútala en una tarea real y registra qué problemas ocultos encuentra. - -2. **Medición del sesgo de calibración**: Elige 10 tipos diferentes de tareas de codificación, y registra la confianza de finalización auto-reportada por el agent versus la calidad real de finalización. Calcula el valor del sesgo y analiza su relación con la complejidad de la tarea. - -3. **Experimento de defensa en múltiples capas**: Ejecuta tres configuraciones en el mismo conjunto de tareas—(a) solo análisis estático, (b) añadir pruebas unitarias, (c) validación completa en tres capas. Compara la proporción de declaraciones prematuras de finalización y el número de defectos no detectados. diff --git a/courses/learn-harness-engineering/docs/es/lectures/lecture-10-why-end-to-end-testing-changes-results/code/architecture-rules.md b/courses/learn-harness-engineering/docs/es/lectures/lecture-10-why-end-to-end-testing-changes-results/code/architecture-rules.md deleted file mode 100644 index a6a864d9..00000000 --- a/courses/learn-harness-engineering/docs/es/lectures/lecture-10-why-end-to-end-testing-changes-results/code/architecture-rules.md +++ /dev/null @@ -1,6 +0,0 @@ -# Reglas de Arquitectura de Electron - -- El código del renderer no puede acceder directamente al sistema de archivos. -- Preload es el único puente entre el renderer y Electron main. -- La lógica de recuperación e indexación vive en módulos de servicio, no en componentes UI. -- El logging debe ser estructurado y emitido desde los límites de servicio. diff --git a/courses/learn-harness-engineering/docs/es/lectures/lecture-10-why-end-to-end-testing-changes-results/code/e2e-runner.ts b/courses/learn-harness-engineering/docs/es/lectures/lecture-10-why-end-to-end-testing-changes-results/code/e2e-runner.ts deleted file mode 100644 index 24284165..00000000 --- a/courses/learn-harness-engineering/docs/es/lectures/lecture-10-why-end-to-end-testing-changes-results/code/e2e-runner.ts +++ /dev/null @@ -1,253 +0,0 @@ -/** - * e2e-runner.ts - * - * A minimal E2E test harness. Defines test cases as user action sequences - * (import doc -> index -> ask question -> verify citation). Simulates - * running them and shows the difference between "unit tests pass" and - * "full pipeline works". - * - * Run: npx tsx docs/lectures/lecture-10-why-end-to-end-testing-changes-results/code/e2e-runner.ts - */ - -// --------------------------------------------------------------------------- -// Types -// --------------------------------------------------------------------------- - -interface PipelineStep { - name: string; - unitTestPasses: boolean; - // Simulated actual behavior in the pipeline - actualBehavior: "works" | "fails" | "partial"; - failureReason?: string; -} - -interface TestCase { - name: string; - steps: PipelineStep[]; -} - -interface TestResult { - testCase: string; - unitTestsPassed: number; - unitTestsTotal: number; - unitTestResult: "PASS" | "FAIL"; - e2eResult: "PASS" | "FAIL"; - e2eFailureStep?: string; - e2eFailureReason?: string; -} - -// --------------------------------------------------------------------------- -// Test cases -- realistic scenarios where unit tests pass but E2E fails -// --------------------------------------------------------------------------- - -const testCases: TestCase[] = [ - { - name: "Import document and ask question", - steps: [ - { - name: "Parse uploaded document", - unitTestPasses: true, - actualBehavior: "works", - }, - { - name: "Store document chunks", - unitTestPasses: true, - actualBehavior: "works", - }, - { - name: "Index chunks for retrieval", - unitTestPasses: true, - actualBehavior: "partial", // Indexes but with wrong embedding dimensions - failureReason: "Embedding dimension mismatch between indexer and retriever", - }, - { - name: "Retrieve relevant chunks", - unitTestPasses: true, // Unit test uses mock data with correct dimensions - actualBehavior: "fails", - failureReason: "Empty results due to dimension mismatch from previous step", - }, - { - name: "Generate answer with citations", - unitTestPasses: true, // Unit test provides pre-retrieved chunks - actualBehavior: "fails", - failureReason: "No chunks retrieved, so answer has no citations", - }, - ], - }, - { - name: "Delete document and verify removal", - steps: [ - { - name: "Find document by ID", - unitTestPasses: true, - actualBehavior: "works", - }, - { - name: "Delete document record", - unitTestPasses: true, - actualBehavior: "works", - }, - { - name: "Remove indexed chunks", - unitTestPasses: true, - actualBehavior: "fails", // Orphaned chunks remain in the index - failureReason: "Index cleanup query timed out, chunks remain orphaned", - }, - { - name: "Verify document not in search results", - unitTestPasses: true, // Unit test mocks the search - actualBehavior: "fails", - failureReason: "Orphaned chunks from previous step still appear in results", - }, - ], - }, - { - name: "Multi-user concurrent access", - steps: [ - { - name: "User A imports document", - unitTestPasses: true, - actualBehavior: "works", - }, - { - name: "User B imports document", - unitTestPasses: true, - actualBehavior: "works", - }, - { - name: "User A queries their document", - unitTestPasses: true, - actualBehavior: "partial", // Cross-contamination of results - failureReason: "No user-scoping on retrieval, returns chunks from User B's doc", - }, - { - name: "Verify only User A's results returned", - unitTestPasses: true, - actualBehavior: "fails", - failureReason: "Results include documents from other users", - }, - ], - }, -]; - -// --------------------------------------------------------------------------- -// Run tests -// --------------------------------------------------------------------------- - -function runUnitTests(tc: TestCase): { passed: number; total: number } { - const passed = tc.steps.filter((s) => s.unitTestPasses).length; - return { passed, total: tc.steps.length }; -} - -function runE2ETest(tc: TestCase): { pass: boolean; failureStep?: string; failureReason?: string } { - // Pipeline: if any step actually fails, the whole E2E fails - for (const step of tc.steps) { - if (step.actualBehavior === "fails") { - return { - pass: false, - failureStep: step.name, - failureReason: step.failureReason ?? "Unknown failure", - }; - } - // "partial" means the step technically completes but creates problems downstream - // We let it continue but track it - } - // Check if any step was "partial" (which may cause downstream issues) - const partialSteps = tc.steps.filter((s) => s.actualBehavior === "partial"); - if (partialSteps.length > 0) { - // The partial steps may or may not cause overall failure - // In our simulation, partial steps always lead to failure downstream - // unless there's an explicit "fails" step that already caught it - // This case means all steps were either "works" or "partial" - return { - pass: false, - failureStep: partialSteps[partialSteps.length - 1].name, - failureReason: partialSteps[partialSteps.length - 1].failureReason ?? "Partial completion", - }; - } - - return { pass: true }; -} - -// --------------------------------------------------------------------------- -// Reporting -// --------------------------------------------------------------------------- - -function pad(s: string, len: number): string { - return s.length >= len ? s : s + " ".repeat(len - s.length); -} - -function run(): void { - console.log("\n" + "=".repeat(95)); - console.log(" E2E TEST RUNNER -- Unit Tests vs Full Pipeline"); - console.log("=".repeat(95)); - - const results: TestResult[] = testCases.map((tc) => { - const unit = runUnitTests(tc); - const e2e = runE2ETest(tc); - - return { - testCase: tc.name, - unitTestsPassed: unit.passed, - unitTestsTotal: unit.total, - unitTestResult: unit.passed === unit.total ? "PASS" : "FAIL", - e2eResult: e2e.pass ? "PASS" : "FAIL", - e2eFailureStep: e2e.failureStep, - e2eFailureReason: e2e.failureReason, - }; - }); - - // Per-test-case detail - for (let i = 0; i < testCases.length; i++) { - const tc = testCases[i]; - const r = results[i]; - - console.log("\n Test Case: " + tc.name); - console.log(" " + "-".repeat(70)); - - const stepHeader = ` | ${pad("Step", 40)}| ${pad("Unit Test", 11)}| ${pad("E2E Actual", 12)}|`; - const stepSep = ` |${"-".repeat(42)}|${"-".repeat(13)}|${"-".repeat(14)}|`; - console.log(stepHeader); - console.log(stepSep); - - for (const step of tc.steps) { - const utLabel = step.unitTestPasses ? "PASS" : "FAIL"; - let e2eLabel: string; - if (step.actualBehavior === "works") e2eLabel = "PASS"; - else if (step.actualBehavior === "partial") e2eLabel = "PARTIAL*"; - else e2eLabel = "FAIL"; - - const marker = step.actualBehavior !== "works" ? ">>" : " "; - console.log(`${marker}| ${pad(step.name, 40)}| ${pad(utLabel, 11)}| ${pad(e2eLabel, 12)}|`); - } - - if (r.e2eFailureStep) { - console.log(`\n E2E Failure at: ${r.e2eFailureStep}`); - console.log(` Reason: ${r.e2eFailureReason}`); - } - } - - // Summary comparison - console.log("\n" + "=".repeat(95)); - console.log(" COMPARISON: Unit Tests vs E2E Tests"); - console.log("=".repeat(95) + "\n"); - - const header = `| ${pad("Test Case", 35)}| ${pad("Unit Tests", 15)}| ${pad("E2E Result", 12)}| Discrepancy`; - const sep = `|${"-".repeat(37)}|${"-".repeat(17)}|${"-".repeat(14)}|${"-".repeat(30)}`; - console.log(header); - console.log(sep); - - for (const r of results) { - const utLabel = `${r.unitTestsPassed}/${r.unitTestsTotal} ${r.unitTestResult}`; - const discrepancy = r.unitTestResult === "PASS" && r.e2eResult === "FAIL"; - const discLabel = discrepancy ? "UNIT PASS BUT E2E FAIL" : "Consistent"; - - console.log(`| ${pad(r.testCase, 35)}| ${pad(utLabel, 15)}| ${pad(r.e2eResult, 12)}| ${discLabel}`); - } - - const falseConfidence = results.filter((r) => r.unitTestResult === "PASS" && r.e2eResult === "FAIL").length; - console.log("\n False confidence count: " + falseConfidence + " of " + results.length); - console.log(" Unit tests pass but E2E reveals integration failures that units cannot catch.\n"); -} - -run(); diff --git a/courses/learn-harness-engineering/docs/es/lectures/lecture-10-why-end-to-end-testing-changes-results/code/index.md b/courses/learn-harness-engineering/docs/es/lectures/lecture-10-why-end-to-end-testing-changes-results/code/index.md deleted file mode 100644 index 6337ff6f..00000000 --- a/courses/learn-harness-engineering/docs/es/lectures/lecture-10-why-end-to-end-testing-changes-results/code/index.md +++ /dev/null @@ -1,8 +0,0 @@ -# Código de la Clase 10 - -Usa esta carpeta para ejemplos de: - -- restricciones de arquitectura -- pruebas estructurales -- invariantes de gusto (taste invariants) -- mensajes de lint orientados a la remediación diff --git a/courses/learn-harness-engineering/docs/es/lectures/lecture-10-why-end-to-end-testing-changes-results/code/review-feedback-to-rule.md b/courses/learn-harness-engineering/docs/es/lectures/lecture-10-why-end-to-end-testing-changes-results/code/review-feedback-to-rule.md deleted file mode 100644 index a587c6df..00000000 --- a/courses/learn-harness-engineering/docs/es/lectures/lecture-10-why-end-to-end-testing-changes-results/code/review-feedback-to-rule.md +++ /dev/null @@ -1,10 +0,0 @@ -# Ejemplo: Convertir Feedback de Review en una Regla - -Comentario de review repetido: - -> No uses utilidades del sistema de archivos desde el renderer. Usa el puente preload. - -Regla de harness promovida: - -- añadir una regla de lint o import que prevenga el uso de `fs` en código del renderer -- añadir texto de remediación explicando la frontera de preload diff --git a/courses/learn-harness-engineering/docs/es/lectures/lecture-10-why-end-to-end-testing-changes-results/index.md b/courses/learn-harness-engineering/docs/es/lectures/lecture-10-why-end-to-end-testing-changes-results/index.md deleted file mode 100644 index 17370f06..00000000 --- a/courses/learn-harness-engineering/docs/es/lectures/lecture-10-why-end-to-end-testing-changes-results/index.md +++ /dev/null @@ -1,158 +0,0 @@ -[中文版本 →](../../../zh/lectures/lecture-10-why-end-to-end-testing-changes-results/) - -> Code examples for this lecture: [code/](https://github.com/walkinglabs/learn-harness-engineering/blob/main/docs/es/lectures/lecture-10-why-end-to-end-testing-changes-results/code/) -> Hands-on practice: [Project 05. Let the agent verify its own work](./../../projects/project-05-grounded-qa-verification/index.md) - -# Lección 10. Solo las pruebas end-to-end son verificación real - -Le pides al agent que añada una funcionalidad de exportación de archivos a una aplicación Electron. Escribe el componente del proceso de render, el preload script y la lógica de la capa de servicio. Las pruebas unitarias de cada componente pasan perfectamente. El agent dice: "Ya está." Cuando realmente haces clic en el botón de exportación—el formato de la ruta del archivo es incorrecto, la barra de progreso no se actualiza, y exportar archivos grandes causa una fuga de memoria. Cinco defectos en los límites de componentes, y las pruebas unitarias no detectaron ni uno solo. - -Es como un ensayo de coro—cada parte vocal suena perfecta cuando se canta individualmente, pero cuando cantan juntos, las sopranos van medio tiempo más rápido que los bajos, y el acompañamiento está un semitono desfasado de la melodía principal. Cada parte es "correcta" por sí sola, pero el conjunto está desafinado. - -La Pirámide de Testing de Google nos dice: un gran número de pruebas unitarias son la base, pero si te detienes ahí, perderás sistemáticamente los problemas de interacción entre componentes. Para los agentes de codificación con IA, este problema es aún más grave—los agentes tienden a ejecutar solo las pruebas más rápidas y luego declarar la finalización. **Solo las pruebas end-to-end pueden demostrar que no existen defectos a nivel de sistema.** - -## Los puntos ciegos de las pruebas unitarias - -La filosofía de diseño de las pruebas unitarias es el aislamiento—mockear dependencias y enfocarse únicamente en la unidad bajo prueba. Esta filosofía hace que las pruebas unitarias sean rápidas y precisas, pero también crea puntos ciegos sistemáticos. Es como hacer que cada parte vocal practique con auriculares durante un ensayo de coro—suena bien para ellos, pero los problemas solo emergen cuando se unen: - -**Incompatibilidad de interfaces**: La ruta de archivo pasada por el proceso de render al preload script es una ruta relativa, pero el preload script espera una ruta absoluta. Sus respectivas pruebas unitarias usaron mocks y pasaron. El problema solo se descubre cuando se ejecuta el flujo end-to-end—como dos partes vocales practicando independientemente y sintiéndose bien, solo para darse cuenta durante el conjunto que una canta en compás de 4/4 y la otra en 3/4. - -**Errores de propagación de estado**: Una migración de base de datos cambia el esquema de la tabla, pero la capa de caché del ORM todavía mantiene entradas de caché para el esquema anterior. Las pruebas unitarias proporcionan un entorno mock completamente nuevo cada vez, lo cual no expondrá esta inconsistencia de estado entre capas. Es como cambiar la letra de una canción, pero alguien todavía canta la versión antigua. - -**Problemas de ciclo de vida de recursos**: La adquisición y liberación de file handles, conexiones de base de datos y sockets de red abarcan múltiples componentes. Las pruebas unitarias crean y destruyen recursos independientes para cada prueba, sin exponer la contención de recursos o las fugas. Es como cada parte vocal tomando turnos para usar los micrófonos durante el ensayo, pero cuando todos suben al escenario juntos, no hay suficientes micrófonos. - -**Dependencia del entorno**: El código se comporta correctamente en el entorno de prueba (donde todo está mockeado) pero falla en el entorno real debido a diferencias de configuración, latencia de red o indisponibilidad del servicio. Como cantar perfectamente en la sala de ensayo, pero encontrar retroalimentación acústica e interferencia del viento en un festival al aire libre. - -## Las pruebas end-to-end no solo cambian los resultados, cambian el comportamiento - -Esto es algo que muchas personas no logran comprender: cuando un agent sabe que su trabajo será sometido a pruebas end-to-end, su comportamiento de codificación cambia. - -1. **Considerar las interacciones entre componentes**: Mientras escribe código, pensará en "cómo se conecta esta interfaz con el upstream", en lugar de enfocarse solo en una única función. Así como saber que eventualmente cantarán juntos, prestarás atención a las otras partes vocales durante la práctica. -2. **Respetar los límites de la arquitectura**: En sistemas con restricciones arquitectónicas, las pruebas end-to-end obligan al agent a adherirse a las reglas de límites. Como una partitura marcada con "crescendo aquí", tienes que seguirla. -3. **Manejar rutas de error**: Las pruebas end-to-end generalmente incluyen escenarios de falla, obligando al agent a considerar el manejo de excepciones. Es como simular "qué pasaría si el micrófono muriera repentinamente" durante el ensayo, para que sepas qué hacer. - -## Pirámide de testing y promoción de retroalimentación de revisiones - -```mermaid -flowchart TB - subgraph Unit["Unit tests only check isolated parts"] - U1["Renderer tests"] - U2["Preload tests"] - U3["Service tests"] - end - - subgraph E2E["E2E runs through the real system"] - R["Click renderer button"] --> P["Preload bridge"] - P --> S["Service layer"] - S --> F["File System / OS"] - F --> Result["Actual exported file"] - end -``` - -```mermaid -flowchart LR - Review["Review feedback:
renderer cannot import fs directly"] --> Rule["Add a direct fs import check"] - Rule --> Message["Tell agent in error message
to move file access to preload"] - Message --> Harness["Add this check to harness"] - Harness --> Stronger["Will fail immediately next time"] -``` - -En las prácticas de ingeniería de Codex, OpenAI enfatiza: **los mensajes de error escritos para agentes deben incluir instrucciones de corrección.** No simplemente escribas `"Direct filesystem access in renderer"`; escribe `"Direct filesystem access in renderer. All file operations must go through the preload bridge. Move this call to preload/file-ops.ts and invoke it via window.api."` Esto convierte las reglas arquitectónicas en un bucle de autocorrección. Como un director de coro que no solo dice "cantaste mal eso", sino que dice "estuviste medio tiempo rápido aquí, escucha el ritmo de los altos, y entra en el compás 32." - -## Conceptos clave - -- **Defectos en los límites de componentes**: El componente A y B pasan sus pruebas unitarias, pero su interacción produce un comportamiento incorrecto. Este es el tipo de problema que las pruebas end-to-end detectan mejor—como partes del coro que son individualmente correctas pero desafinadas juntas. -- **Gradiente de adecuación de pruebas**: Defectos detectados por pruebas unitarias <= defectos detectados por pruebas de integración <= defectos detectados por pruebas end-to-end. Cada capa superior aumenta la capacidad de detección. -- **Reglas de cumplimiento de límites arquitectónicos**: Convertir reglas de documentos de arquitectura (como "el proceso de render no puede acceder al sistema de archivos directamente") en verificaciones ejecutables y automatizadas. De "escritas en papel" a "ejecutándose en CI." -- **Promoción de retroalimentación de revisiones**: Convertir comentarios repetidos de revisiones de código en pruebas automatizadas. Cada vez que se encuentra un problema recurrente, añadir una regla, y el harness automáticamente se fortalece. Como un director que convierte los errores comunes de ensayo en ejercicios de calentamiento—la próxima vez que se cometa el mismo error, el ejercicio mismo lo expone sin que el director necesite decir nada. -- **Mensajes de error orientados al agent**: Los mensajes de falla no deberían solo indicar "qué salió mal", sino también decirle al agent exactamente cómo solucionarlo. Esto convierte las fallas de pruebas en bucles de retroalimentación autocorrectivos. - -## Cómo hacerlo - -### 0. Definir los límites arquitectónicos primero, luego escribir pruebas E2E - -El prerrequisito para las pruebas end-to-end son límites de sistema claros. Si la arquitectura es un plato de espaguetis, las pruebas end-to-end solo probarán "este plato de espaguetis funciona", no te dirán dónde se violaron las intenciones de diseño. Es como un coro que ni siquiera se ha dividido en partes vocales—por mucho que ensayen, no sonará bien. - -La experiencia de OpenAI: **para bases de código generadas por agentes, las restricciones arquitectónicas deben ser prerrequisitos tempranos establecidos desde el primer día, no algo a considerar cuando el equipo crezca.** La razón es simple—los agentes copiarán los patrones existentes en el repositorio, incluso si esos patrones son desiguales o subóptimos. Sin restricciones arquitectónicas, el agent introducirá más desviaciones en cada sesión. - -OpenAI adoptó una "Arquitectura de Dominio en Capas"—cada dominio de negocio se divide en capas fijas: Types → Config → Repo → Service → Runtime → UI. Las dependencias fluyen estrictamente hacia adelante, y las preocupaciones entre dominios entran a través de interfaces de Providers explícitas. Cualquier otra dependencia está prohibida y se cumple mecánicamente mediante linting personalizado. - -Principio clave: **Impedir invariantes, no microgestionar la implementación.** Por ejemplo, requerir "los datos se analizan en el límite", pero no dictar qué biblioteca usar. Los mensajes de error deben incluir instrucciones de corrección—no solo decir "violación", sino decirle al agent exactamente cómo cambiarlo. - -> Fuente: [OpenAI: Harness engineering: leveraging Codex in an agent-first world](https://openai.com/index/harness-engineering/) - -### 1. El harness debe incluir una capa end-to-end - -Hazlo explícito en tu flujo de validación: para tareas que involucren cambios entre componentes, pasar las pruebas end-to-end es un prerrequisito para la finalización: - -``` -## Validation Hierarchy -- Level 1: Unit tests (Must pass) -- Level 2: Integration tests (Must pass) -- Level 3: End-to-end tests (Must pass when cross-component changes are involved) -- Skipping any required level = Not Complete -``` - -### 2. Convertir reglas arquitectónicas en verificaciones ejecutables - -Cada restricción arquitectónica debe tener una prueba o regla de lint correspondiente: - -```bash -# Check if the render process directly calls Node.js APIs -grep -r "require('fs')" src/renderer/ && exit 1 || echo "OK: no direct fs access in renderer" -``` - -### 3. Diseñar mensajes de error orientados al agent - -Los mensajes de falla deben contener tres elementos: qué salió mal, por qué, y cómo solucionarlo: - -``` -ERROR: Found direct import of 'fs' in src/renderer/App.tsx:12 -WHY: Renderer process has no access to Node.js APIs for security -FIX: Move file operations to src/preload/file-ops.ts and call via window.api.readFile() -``` - -### 4. Establecer un proceso de promoción de retroalimentación de revisiones - -Cada vez que se encuentre un nuevo tipo de error del agent durante una revisión de código, conviértelo en una verificación automatizada. Un mes después, tu harness será significativamente más fuerte que al inicio del mes. Es como las notas de ensayo de un coro—registrar los problemas encontrados en cada ensayo para poder verificarlos antes del siguiente. Con el tiempo, los errores comunes disminuyen, y la música se vuelve más armoniosa. - -## Caso del mundo real - -**Tarea**: Implementar una funcionalidad de exportación de archivos en una aplicación Electron. Involucra la UI del proceso de render, el proxy del sistema de archivos del preload script y la transformación de datos de la capa de servicio. - -**Cantando las partes individualmente (Pruebas unitarias pasaron)**: Pruebas del componente de render (pasaron, operaciones de archivo mockeadas), pruebas del preload script (pasaron, sistema de archivos mockeado), pruebas de la capa de servicio (pasaron, fuente de datos mockeada). El agent declara la finalización. - -**Cantando juntos (Defectos revelados por las pruebas end-to-end)**: - -| Defect | Description | Unit Test | E2E | -|--------|-------------|-----------|-----| -| Interface Mismatch | Inconsistent file path format | Missed | Caught | -| State Propagation | Export progress not sent back to UI via IPC | Missed | Caught | -| Resource Leak | Large file export handles not released | Missed | Caught | -| Permission Issue | Different permissions in packaged environment | Missed | Caught | -| Error Propagation | Service layer exceptions didn't reach UI layer | Missed | Caught | - -Los 5 defectos fueron detectados por las pruebas end-to-end, mientras que las pruebas unitarias no detectaron ninguno. El costo fue un aumento en el tiempo de prueba de 2 segundos a 15 segundos—completamente aceptable en un flujo de trabajo de agent. Por muy bien que cada parte cante individualmente, no puede superar un ensayo completo del conjunto. - -## Ideas clave - -- **Las pruebas unitarias son sistemáticamente ciegas a los defectos en los límites de componentes**—su diseño de aislamiento es precisamente lo que les impide detectar problemas de interacción. Que todos canten correctamente no significa que el coro no esté desafinado. -- **Las pruebas end-to-end no solo detectan defectos, cambian el comportamiento de codificación del agent**—haciéndolo enfocarse más en la integración y los límites. -- **Las reglas arquitectónicas deben ser ejecutables**—no escritas en un documento esperando ser leídas, sino verificadas automáticamente en cada commit. -- **Los mensajes de error deben estar diseñados para los agentes**—incluyendo pasos específicos sobre "cómo solucionarlo" para formar un bucle autocorrectivo. -- **La promoción de retroalimentación de revisiones hace que el harness se fortalezca automáticamente**—cada categoría de defecto capturado se convierte en una línea de defensa permanente. - -## Lecturas adicionales - -- [How Google Tests Software - Whittaker et al.](https://www.goodreads.com/book/show/13563030-how-google-tests-software) — La fuente clásica del modelo de Pirámide de Testing -- [Harness Engineering - OpenAI](https://openai.com/index/harness-engineering/) — Prácticas de ingeniería para la ejecución automatizada de restricciones arquitectónicas -- [Chaos Engineering - Netflix (Basiri et al.)](https://ieeexplore.ieee.org/document/7466237) — Inyección proactiva de fallas para verificar la resiliencia del sistema -- [QuickCheck - Claessen & Hughes](https://www.cs.tufts.edu/~nr/cs257/archive/john-hughes/quick.pdf) — Metodología de property testing, situada entre las pruebas de ejemplo y la verificación formal - -## Ejercicios - -1. **Detección de defectos entre componentes**: Elige una tarea de modificación que involucre al menos tres componentes. Primero, ejecuta solo pruebas unitarias y registra los resultados, luego ejecuta pruebas end-to-end. Analiza a qué tipo de problema de interacción entre capas pertenece cada defecto adicional descubierto. - -2. **Automatización de reglas arquitectónicas**: Elige una restricción arquitectónica de tu proyecto y conviértela en una verificación ejecutable (con un mensaje de error orientado al agent). Intégrala en el harness y verifica su efectividad con una tarea de referencia. - -3. **Promoción de retroalimentación de revisiones**: Encuentra un tipo de comentario recurrente en tu historial de revisiones de código y conviértelo en una verificación automatizada usando el proceso de cinco pasos. Compara la frecuencia del problema antes y después de la promoción. diff --git a/courses/learn-harness-engineering/docs/es/lectures/lecture-11-why-observability-belongs-inside-the-harness/code/evaluator-rubric.md b/courses/learn-harness-engineering/docs/es/lectures/lecture-11-why-observability-belongs-inside-the-harness/code/evaluator-rubric.md deleted file mode 100644 index f832c88a..00000000 --- a/courses/learn-harness-engineering/docs/es/lectures/lecture-11-why-observability-belongs-inside-the-harness/code/evaluator-rubric.md +++ /dev/null @@ -1,8 +0,0 @@ -# Ejemplo de Rúbrica de Evaluación - -Usa puntuación de 1-5 para cada dimensión: - -- Fundamentación: ¿las respuestas están claramente vinculadas a las fuentes importadas? -- Calidad de citación: ¿las referencias a fuentes son visibles y específicas? -- Funcionalidad: ¿puede el usuario completar el flujo de pregunta-respuesta? -- Coherencia del producto: ¿el flujo de trabajo se siente integrado? diff --git a/courses/learn-harness-engineering/docs/es/lectures/lecture-11-why-observability-belongs-inside-the-harness/code/index.md b/courses/learn-harness-engineering/docs/es/lectures/lecture-11-why-observability-belongs-inside-the-harness/code/index.md deleted file mode 100644 index 79d52cad..00000000 --- a/courses/learn-harness-engineering/docs/es/lectures/lecture-11-why-observability-belongs-inside-the-harness/code/index.md +++ /dev/null @@ -1,8 +0,0 @@ -# Código de la Clase 11 - -Usa esta carpeta para ejemplos de: - -- salidas del planificador -- rúbricas de evaluación -- bucles generador/evaluador -- comparaciones de agente único vs multi-rol diff --git a/courses/learn-harness-engineering/docs/es/lectures/lecture-11-why-observability-belongs-inside-the-harness/code/runtime-logger.ts b/courses/learn-harness-engineering/docs/es/lectures/lecture-11-why-observability-belongs-inside-the-harness/code/runtime-logger.ts deleted file mode 100644 index 213dc198..00000000 --- a/courses/learn-harness-engineering/docs/es/lectures/lecture-11-why-observability-belongs-inside-the-harness/code/runtime-logger.ts +++ /dev/null @@ -1,260 +0,0 @@ -/** - * runtime-logger.ts - * - * A structured logging module demo. Shows ad-hoc console.log output vs - * structured JSON log output when diagnosing a failure. Includes a seeded - * failure scenario and demonstrates how structured logs pinpoint the issue - * faster. - * - * Run: npx tsx docs/lectures/lecture-11-why-observability-belongs-inside-the-harness/code/runtime-logger.ts - */ - -// --------------------------------------------------------------------------- -// Types -// --------------------------------------------------------------------------- - -interface StructuredLogEntry { - timestamp: string; - level: "info" | "warn" | "error" | "debug"; - component: string; - action: string; - durationMs?: number; - input?: unknown; - output?: unknown; - error?: string; - correlationId: string; -} - -// --------------------------------------------------------------------------- -// Simulated pipeline with a seeded failure -// --------------------------------------------------------------------------- - -const CORRELATION_ID = "req-" + Math.random().toString(36).slice(2, 8); - -// Simulated stages of a document Q&A pipeline -interface PipelineStage { - component: string; - action: string; - durationMs: number; - success: boolean; - errorMessage?: string; - input?: unknown; - output?: unknown; -} - -function runPipeline(): PipelineStage[] { - return [ - { - component: "DocumentLoader", - action: "parse_upload", - durationMs: 45, - success: true, - input: { filename: "report.pdf", size: "2.3MB" }, - output: { chunks: 47 }, - }, - { - component: "ChunkIndexer", - action: "embed_and_store", - durationMs: 230, - success: true, - input: { chunks: 47 }, - output: { indexed: 47, embeddingDim: 1536 }, - }, - { - component: "QueryRouter", - action: "route_query", - durationMs: 12, - success: true, - input: { query: "What is the revenue target?" }, - output: { routedTo: "RetrievalEngine" }, - }, - { - // SEEDED FAILURE: Retrieval returns 0 results due to dimension mismatch - component: "RetrievalEngine", - action: "semantic_search", - durationMs: 180, - success: false, - errorMessage: "Vector dimension mismatch: query embedding dim=768, index embedding dim=1536", - input: { query: "What is the revenue target?", topK: 5 }, - output: { results: 0 }, - }, - { - component: "AnswerGenerator", - action: "generate_with_citations", - durationMs: 1500, - success: true, // Doesn't crash, but produces a bad answer - input: { context: [], query: "What is the revenue target?" }, - output: { answer: "I could not find relevant information.", citations: 0 }, - }, - ]; -} - -// --------------------------------------------------------------------------- -// Ad-hoc logging (console.log style) -// --------------------------------------------------------------------------- - -function printAdHocLog(stages: PipelineStage[]): void { - console.log("Starting document Q&A pipeline..."); - console.log("User uploaded report.pdf"); - - for (const stage of stages) { - if (stage.success) { - console.log(`${stage.component}: ${stage.action} done (${stage.durationMs}ms)`); - } else { - console.log(`${stage.component}: something went wrong`); - } - } - - console.log("Pipeline finished. Answer: I could not find relevant information."); -} - -// --------------------------------------------------------------------------- -// Structured logging (JSON) -// --------------------------------------------------------------------------- - -function printStructuredLog(stages: PipelineStage[]): StructuredLogEntry[] { - const entries: StructuredLogEntry[] = []; - - for (const stage of stages) { - entries.push({ - timestamp: new Date().toISOString(), - level: stage.success ? "info" : "error", - component: stage.component, - action: stage.action, - durationMs: stage.durationMs, - input: stage.input, - output: stage.output, - error: stage.errorMessage, - correlationId: CORRELATION_ID, - }); - } - - return entries; -} - -// --------------------------------------------------------------------------- -// Diagnose failure from structured logs -// --------------------------------------------------------------------------- - -function diagnoseFromStructured(entries: StructuredLogEntry[]): string[] { - const diagnosis: string[] = []; - - // Find errors - const errors = entries.filter((e) => e.level === "error"); - if (errors.length > 0) { - diagnosis.push("ERRORS FOUND:"); - for (const err of errors) { - diagnosis.push(` - ${err.component}.${err.action}: ${err.error}`); - } - } - - // Check for latency spikes - const slowStages = entries.filter((e) => (e.durationMs ?? 0) > 1000); - if (slowStages.length > 0) { - diagnosis.push("LATENCY SPIKES:"); - for (const s of slowStages) { - diagnosis.push(` - ${s.component}.${s.action}: ${s.durationMs}ms`); - } - } - - // Check for cascading failures - const emptyOutputs = entries.filter((e) => { - const out = e.output as Record | undefined; - return out && typeof out === "object" && "results" in out && (out.results as number) === 0; - }); - if (emptyOutputs.length > 0) { - diagnosis.push("EMPTY OUTPUTS (possible cascade):"); - for (const e of emptyOutputs) { - diagnosis.push(` - ${e.component}.${e.action}: returned 0 results`); - } - } - - return diagnosis; -} - -// --------------------------------------------------------------------------- -// Reporting -// --------------------------------------------------------------------------- - -function pad(s: string, len: number): string { - return s.length >= len ? s : s + " ".repeat(len - s.length); -} - -function run(): void { - const pipeline = runPipeline(); - - console.log("\n" + "=".repeat(90)); - console.log(" OBSERVABILITY DEMO -- Ad-hoc vs Structured Logging"); - console.log("=".repeat(90)); - - // --- Ad-hoc output --- - console.log("\n" + "-".repeat(90)); - console.log(" SCENARIO A: Ad-hoc console.log output"); - console.log("-".repeat(90) + "\n"); - printAdHocLog(pipeline); - console.log("\n Diagnosis from ad-hoc logs: ??? Hard to tell what went wrong."); - console.log(" The failure message is vague: 'something went wrong'."); - console.log(" No dimensions, no input/output data, no correlation ID."); - - // --- Structured output --- - console.log("\n" + "-".repeat(90)); - console.log(" SCENARIO B: Structured JSON log output"); - console.log("-".repeat(90) + "\n"); - - const structuredEntries = printStructuredLog(pipeline); - - const header = `| ${pad("Timestamp", 26)}| ${pad("Level", 6)}| ${pad("Component", 20)}| ${pad("Action", 25)}| ${pad("Duration", 9)}| Error?`; - const sep = `|${"-".repeat(28)}|${"-".repeat(8)}|${"-".repeat(22)}|${"-".repeat(27)}|${"-".repeat(11)}|${"-".repeat(30)}`; - console.log(header); - console.log(sep); - - for (const entry of structuredEntries) { - const hasError = entry.error ? entry.error.slice(0, 30) : ""; - const marker = entry.level === "error" ? ">>" : " "; - console.log( - `${marker}| ${pad(entry.timestamp, 26)}| ${pad(entry.level, 6)}| ${pad(entry.component, 20)}| ${pad(entry.action, 25)}| ${pad(String(entry.durationMs) + "ms", 9)}| ${hasError}` - ); - } - - // --- Diagnosis --- - console.log("\n" + "-".repeat(90)); - console.log(" AUTOMATED DIAGNOSIS FROM STRUCTURED LOGS"); - console.log("-".repeat(90) + "\n"); - - const errors = structuredEntries.filter((e) => e.level === "error"); - for (const err of errors) { - console.log(` ROOT CAUSE: ${err.component}.${err.action}`); - console.log(` Error: ${err.error}`); - console.log(` Input: ${JSON.stringify(err.input)}`); - console.log(` Output: ${JSON.stringify(err.output)}`); - console.log(` Correlation ID: ${err.correlationId}`); - } - - // Downstream impact - console.log("\n DOWNSTREAM IMPACT:"); - const answerGen = structuredEntries.find((e) => e.component === "AnswerGenerator"); - if (answerGen) { - const out = answerGen.output as Record; - console.log(` AnswerGenerator received empty context (${JSON.stringify(answerGen.input)})`); - console.log(` Produced answer: "${out.answer}" with ${out.citations} citations`); - } - - // Comparison summary - console.log("\n" + "=".repeat(90)); - console.log(" COMPARISON"); - console.log("=".repeat(90) + "\n"); - - const cHeader = `| ${pad("Metric", 35)}| ${pad("Ad-hoc Logs", 18)}| ${pad("Structured Logs", 18)}|`; - const cSep = `|${"-".repeat(37)}|${"-".repeat(20)}|${"-".repeat(20)}|`; - console.log(cHeader); - console.log(cSep); - console.log(`| ${pad("Root cause identifiable", 35)}| ${pad("No", 18)}| ${pad("Yes", 18)}|`); - console.log(`| ${pad("Input/output traceable", 35)}| ${pad("No", 18)}| ${pad("Yes", 18)}|`); - console.log(`| ${pad("Correlation across steps", 35)}| ${pad("No", 18)}| ${pad("Yes", 18)}|`); - console.log(`| ${pad("Machine-parseable", 35)}| ${pad("No", 18)}| ${pad("Yes", 18)}|`); - console.log(`| ${pad("Time to diagnose", 35)}| ${pad("Minutes (manual)", 18)}| ${pad("Seconds (auto)", 18)}|`); - - console.log("\n Structured logging transforms debugging from guesswork into a deterministic lookup.\n"); -} - -run(); diff --git a/courses/learn-harness-engineering/docs/es/lectures/lecture-11-why-observability-belongs-inside-the-harness/code/sprint-contract.md b/courses/learn-harness-engineering/docs/es/lectures/lecture-11-why-observability-belongs-inside-the-harness/code/sprint-contract.md deleted file mode 100644 index 18e22e0e..00000000 --- a/courses/learn-harness-engineering/docs/es/lectures/lecture-11-why-observability-belongs-inside-the-harness/code/sprint-contract.md +++ /dev/null @@ -1,12 +0,0 @@ -# Ejemplo de Contrato de Sprint - -Objetivo del sprint: - -- Añadir citaciones visibles a los resultados de Q&A fundamentado - -"Hecho" significa: - -- El usuario hace una pregunta -- La app devuelve una respuesta -- Se muestra al menos una citación -- Hacer clic en una citación abre la ubicación fuente en la vista de documento diff --git a/courses/learn-harness-engineering/docs/es/lectures/lecture-11-why-observability-belongs-inside-the-harness/index.md b/courses/learn-harness-engineering/docs/es/lectures/lecture-11-why-observability-belongs-inside-the-harness/index.md deleted file mode 100644 index 09160e2f..00000000 --- a/courses/learn-harness-engineering/docs/es/lectures/lecture-11-why-observability-belongs-inside-the-harness/index.md +++ /dev/null @@ -1,154 +0,0 @@ -[中文版本 →](../../../zh/lectures/lecture-11-why-observability-belongs-inside-the-harness/) - -> Ejemplos de código: [code/](https://github.com/walkinglabs/learn-harness-engineering/blob/main/docs/es/lectures/lecture-11-why-observability-belongs-inside-the-harness/code/) -> Proyecto práctico: [Proyecto 06. Harness completo (capstone)](./../../projects/project-06-runtime-observability-and-debugging/index.md) - -# Lección 11. Haz observable el runtime del agente - -## Qué problema resuelve esta lección - -Le pides a un agente que implemente una función. Corre durante 20 minutos, modifica muchos archivos y luego dice: "terminado, pero fallan dos tests". Preguntas por qué fallan: "no estoy seguro, quizá sea un problema de timing". Preguntas qué rutas críticas cambió: "déjame mirar el código...". - -No se trata de que al agente le falte capacidad. El problema es que tu harness no proporciona suficiente observabilidad. **Sin observabilidad, los agentes toman decisiones bajo incertidumbre, las evaluaciones se vuelven juicios subjetivos y los reintentos son tanteos a ciegas.** Tanto OpenAI como Anthropic tratan la fiabilidad como un problema de evidencia: el harness debe exponer el comportamiento en runtime y las señales de evaluación de una forma que guíe la siguiente decisión. - -## Conceptos clave - -- **Observabilidad de runtime**: señales de sistema como logs, trazas, eventos de proceso y health checks. Responde "qué hizo el sistema". -- **Observabilidad de proceso**: visibilidad sobre artefactos de decisión del harness, como planes, rúbricas de puntuación y criterios de aceptación. Responde "por qué debería aceptarse este cambio". -- **Traza de tarea**: registro completo del camino de decisión desde el inicio hasta la finalización, análogo al tracing de requests en sistemas distribuidos. Cada paso del agente queda registrado con su contexto. -- **Sprint contract**: acuerdo de corto plazo negociado antes de programar, que especifica alcance, estándares de verificación y exclusiones. Es la herramienta central de la observabilidad de proceso. -- **Rúbrica de evaluación**: transforma la evaluación de calidad de juicio subjetivo a puntuación estructurada basada en evidencia. Hace que distintos evaluadores produzcan resultados similares para la misma salida. -- **Observabilidad por capas**: observabilidad de sistema y de proceso diseñadas juntas y reforzándose. Las señales de runtime explican el comportamiento; los artefactos de proceso explican la intención. - -## Observabilidad por capas - -```mermaid -flowchart LR - Contract["Escribir primero la tarea
qué cambiar / qué no cambiar / criterios de aprobación"] --> Generator["Generator"] - Generator --> Signals["Recoger logs, trazas
y health checks durante la ejecución"] - Contract --> Review["Revisar el resultado punto por punto
comportamiento / tests / límites"] - Signals --> Review - Review --> Verdict["Señalar el check fallido
y dónde corregirlo"] - Verdict --> Generator -``` - -## Por qué ocurre - -### El coste real de no tener observabilidad - -Cuando un harness no tiene observabilidad, aparecen sistemáticamente cuatro tipos de problemas: - -**No se distingue "correcto" de "parece correcto"**: una función puede verse perfecta en revisión de código — sintaxis correcta, lógica razonable. Pero en runtime, un error en un edge case produce resultados incorrectos con ciertas entradas. Solo las trazas de runtime muestran que la ruta real de ejecución se desvió de lo esperado. - -**La evaluación se vuelve mística**: sin rúbricas y criterios de aceptación, los evaluadores, humanos o agentes, dependen de supuestos implícitos. La misma salida puede recibir evaluaciones muy distintas. La calidad deja de ser reproducible. - -**Los reintentos se vuelven conjeturas ciegas**: cuando el agente no sabe por qué falló algo, la dirección del reintento es aleatoria. Puede corregir rutas de código irrelevantes mientras ignora la causa raíz. Cada reintento ciego cuesta tokens y tiempo. - -**Acantilado de información en el handoff**: cuando se entrega trabajo incompleto a la siguiente sesión, la falta de observabilidad obliga a diagnosticar el estado del sistema desde cero. Las observaciones de Anthropic muestran que ese diagnóstico redundante puede consumir 30-50% del tiempo total de sesión. - -### Un escenario realista con Claude Code - -Imagina un harness con workflow de tres roles, planner-generator-evaluator, ejecutando la tarea "añadir dark mode a la app". - -**Sin observabilidad**: el planner produce una descripción vaga. El generator implementa dark mode con base en esa vaguedad, pero no coincide con las expectativas implícitas del planner. El evaluator rechaza según sus propios estándares implícitos, sin articular qué está mal. El generator reintenta a ciegas. El ciclo se repite 3-4 veces, toma unos 45 minutos y produce algo apenas aceptable. - -**Con observabilidad completa**: el planner produce un sprint contract que lista los componentes a modificar, los estándares de verificación y las exclusiones. El generator implementa según el contrato. La observabilidad de runtime registra cómo carga y aplica estilos cada componente. El evaluator usa una rúbrica con evidencias concretas por dimensión. Una iteración produce un resultado de alta calidad en unos 15 minutos. - -La diferencia de eficiencia es 3x. Lo único que cambió fue la observabilidad. - -### Por qué los agentes no pueden resolverlo solos - -Podrías pensar: "¿no puede el agente imprimir sus propios logs?" Los problemas son: - -1. El agente no sabe lo que no sabe; no registrará señales que no sabe que necesita. -2. Los formatos de log son inconsistentes; distintas sesiones usan distintos formatos, lo que impide análisis sistemático. -3. La observabilidad de proceso no se resuelve con logs; sprint contracts y rúbricas son artefactos estructurados que necesitan soporte a nivel de harness. - -## Cómo hacerlo bien - -### 1. Integrar la recogida de señales de runtime en el harness - -No dependas de que el agente imprima sus propios logs. El harness debería recoger automáticamente estas señales: - -- **Ciclo de vida de la aplicación**: estados de startup, ready, running y shutdown -- **Ejecución de rutas de función**: registros de rutas críticas con puntos de entrada, checkpoints y salidas -- **Flujo de datos**: registros de datos que pasan entre componentes -- **Uso de recursos**: patrones anómalos, por ejemplo memoria que crece sin parar -- **Errores y excepciones**: contexto completo, no solo mensajes de error - -### 2. Implementar sprint contracts - -Antes de iniciar cada tarea, el generator y el evaluator, que pueden ser invocaciones distintas del mismo agente, negocian un contrato: - -```markdown -# Sprint Contract: Dark Mode Support - -## Scope -- Modify the theme toggle component -- Update global CSS variables -- Add dark mode tests - -## Verification Standards -- Visual regression tests pass for each component -- Main flow end-to-end tests pass -- No flash of unstyled content (FOUC) - -## Exclusions -- Not handling print styles -- Not handling third-party component dark mode -``` - -### 3. Establecer una rúbrica de evaluación - -Convierte "¿está bien o no?" en puntuación cuantificable: - -```markdown -# Scoring Rubric - -| Dimension | A | B | C | D | -|-----------|---|---|---|---| -| Code correctness | All tests pass | Main flow passes | Partial pass | Build fails | -| Architecture compliance | Fully compliant | Minor deviations | Obvious deviations | Serious violations | -| Test coverage | Main + edge cases | Main flow only | Only skeleton | No tests | -``` - -### 4. Estandarizar con OpenTelemetry - -Crea una traza para cada sesión de harness, un span para cada tarea y sub-spans para cada paso de verificación. Usa atributos estándar para anotar información clave. Así, los datos de observabilidad se integran con herramientas como Jaeger o Zipkin. - -## Caso real - -Un harness con workflow planner-generator-evaluator ejecuta "añadir soporte de dark mode": - -**Versión no observable**: 3-4 rondas de reintentos ciegos, 45 minutos, resultado apenas aceptable. El evaluator dice "no se siente bien" pero no puede precisar por qué. El generator pierde tiempo en direcciones equivocadas. - -**Versión completamente observable**: -- El sprint contract aclara alcance, estándares y exclusiones -- Las trazas de runtime registran el proceso de carga de estilos de cada componente -- La rúbrica da una evaluación estructurada por dimensiones -- Una iteración produce resultados de alta calidad en 15 minutos - -Mejora de eficiencia 3x, calidad más estable y evaluaciones reproducibles. - -## Ideas clave - -- **La observabilidad es una propiedad arquitectónica del harness**, no una función que se añade al final. -- **Ambas capas de observabilidad son esenciales**: las señales de runtime explican "qué pasó"; los artefactos de proceso explican "por qué se hizo así". -- **Los sprint contracts alinean por adelantado**, evitando que el generator construya algo que el evaluator rechaza por razones previsibles. -- **Las rúbricas hacen reproducible la evaluación**, de modo que distintos evaluadores produzcan puntuaciones parecidas. -- **La falta de observabilidad desperdicia 30-50% del tiempo de sesión en diagnóstico redundante.** - -## Lecturas adicionales - -- [Observability Engineering - Charity Majors](https://www.honeycomb.io/blog/observability-engineering-book) — marco teórico y práctico para observability engineering moderna -- [Dapper - Google (Sigelman et al.)](https://research.google/pubs/pub36356/) — práctica pionera de tracing distribuido a gran escala -- [Harness Design - Anthropic](https://www.anthropic.com/engineering/harness-design-long-running-apps) — introducción de sprint contracts y rúbricas de evaluación -- [Site Reliability Engineering - Google](https://sre.google/sre-book/table-of-contents/) — aplicación sistemática de observabilidad en producción - -## Ejercicios - -1. **Análisis de brecha de observabilidad**: audita tu harness actual en la capa de sistema y de proceso. Encuentra estados que no se distinguen con las señales existentes y propone señales nuevas. - -2. **Práctica de sprint contract**: escribe un sprint contract para una tarea real. Haz que el agente lo ejecute y compara eficiencia y calidad con y sin contrato. - -3. **Construcción de traza de tarea**: registra cada paso de un agente durante una tarea completa de codificación. Anota con convenciones semánticas de OpenTelemetry y analiza dónde falta señal suficiente para decidir. diff --git a/courses/learn-harness-engineering/docs/es/lectures/lecture-12-why-every-session-must-leave-a-clean-state/code/benchmark-comparison-template.md b/courses/learn-harness-engineering/docs/es/lectures/lecture-12-why-every-session-must-leave-a-clean-state/code/benchmark-comparison-template.md deleted file mode 100644 index 407d67f8..00000000 --- a/courses/learn-harness-engineering/docs/es/lectures/lecture-12-why-every-session-must-leave-a-clean-state/code/benchmark-comparison-template.md +++ /dev/null @@ -1,18 +0,0 @@ -# Plantilla de Comparación de Benchmark - -Harness A: - -- tasa de completitud -- reintentos promedio -- bugs detectados antes de la revisión humana - -Harness B: - -- tasa de completitud -- reintentos promedio -- bugs detectados antes de la revisión humana - -Interpretación: - -- ¿Qué harness cambió el resultado? -- ¿Qué harness cambió el costo de obtener el resultado? diff --git a/courses/learn-harness-engineering/docs/es/lectures/lecture-12-why-every-session-must-leave-a-clean-state/code/benchmark-runner.ts b/courses/learn-harness-engineering/docs/es/lectures/lecture-12-why-every-session-must-leave-a-clean-state/code/benchmark-runner.ts deleted file mode 100644 index 0388cad7..00000000 --- a/courses/learn-harness-engineering/docs/es/lectures/lecture-12-why-every-session-must-leave-a-clean-state/code/benchmark-runner.ts +++ /dev/null @@ -1,216 +0,0 @@ -/** - * benchmark-runner.ts - * - * Reads a benchmark task definition (JSON array of tasks with pass criteria), - * "executes" each task, records timing and pass/fail, outputs a comparison - * report showing which tasks pass and which fail. - * - * Run: npx tsx docs/lectures/lecture-12-why-every-session-must-leave-a-clean-state/code/benchmark-runner.ts - */ - -// --------------------------------------------------------------------------- -// Types -// --------------------------------------------------------------------------- - -interface BenchmarkTask { - id: string; - name: string; - category: string; - passCriteria: string[]; - expectedDurationMs: number; - // Simulated actual results - actualDurationMs: number; - actualPass: boolean; - failureReason?: string; -} - -interface BenchmarkResult { - id: string; - name: string; - category: string; - criteriaTotal: number; - criteriaPassed: number; - pass: boolean; - expectedMs: number; - actualMs: number; - durationDelta: number; - failureReason?: string; -} - -// --------------------------------------------------------------------------- -// Benchmark task definitions -// --------------------------------------------------------------------------- - -const benchmarkTasks: BenchmarkTask[] = [ - { - id: "bench-001", - name: "Import markdown document", - category: "Document Pipeline", - passCriteria: ["File accepted", "Chunks created", "Metadata stored"], - expectedDurationMs: 100, - actualDurationMs: 95, - actualPass: true, - }, - { - id: "bench-002", - name: "Import PDF document", - category: "Document Pipeline", - passCriteria: ["File accepted", "Text extracted", "Chunks created", "Metadata stored"], - expectedDurationMs: 200, - actualDurationMs: 310, - actualPass: false, - failureReason: "PDF text extraction failed on page 3 -- encoding issue", - }, - { - id: "bench-003", - name: "Ask grounded question", - category: "Q&A Pipeline", - passCriteria: ["Query received", "Relevant chunks retrieved", "Answer generated", "Citations present"], - expectedDurationMs: 500, - actualDurationMs: 480, - actualPass: true, - }, - { - id: "bench-004", - name: "Ask question with no relevant docs", - category: "Q&A Pipeline", - passCriteria: ["Query received", "Graceful 'no results' message", "No hallucinated citations"], - expectedDurationMs: 400, - actualDurationMs: 420, - actualPass: false, - failureReason: "Model hallucinated a citation instead of saying 'no results'", - }, - { - id: "bench-005", - name: "Delete imported document", - category: "Document Pipeline", - passCriteria: ["Document removed from list", "Chunks removed from index", "No orphan data"], - expectedDurationMs: 150, - actualDurationMs: 145, - actualPass: true, - }, - { - id: "bench-006", - name: "Concurrent user access", - category: "Security", - passCriteria: ["Users isolated", "No cross-user data leak", "Performance within SLA"], - expectedDurationMs: 300, - actualDurationMs: 850, - actualPass: false, - failureReason: "Cross-user data leak detected + response time exceeded SLA (850ms > 500ms)", - }, - { - id: "bench-007", - name: "Session continuity after restart", - category: "Reliability", - passCriteria: ["State persisted", "Session recovers", "No data loss"], - expectedDurationMs: 200, - actualDurationMs: 180, - actualPass: true, - }, - { - id: "bench-008", - name: "API rate limiting", - category: "Security", - passCriteria: ["Rate limit enforced", "429 response after limit", "Legitimate traffic unaffected"], - expectedDurationMs: 100, - actualDurationMs: 100, - actualPass: true, - }, -]; - -// --------------------------------------------------------------------------- -// Execution simulation -// --------------------------------------------------------------------------- - -function executeBenchmark(tasks: BenchmarkTask[]): BenchmarkResult[] { - return tasks.map((task) => ({ - id: task.id, - name: task.name, - category: task.category, - criteriaTotal: task.passCriteria.length, - criteriaPassed: task.actualPass ? task.passCriteria.length : Math.floor(task.passCriteria.length * 0.5), - pass: task.actualPass, - expectedMs: task.expectedDurationMs, - actualMs: task.actualDurationMs, - durationDelta: task.actualDurationMs - task.expectedDurationMs, - failureReason: task.failureReason, - })); -} - -// --------------------------------------------------------------------------- -// Reporting -// --------------------------------------------------------------------------- - -function pad(s: string, len: number): string { - return s.length >= len ? s : s + " ".repeat(len - s.length); -} - -function run(): void { - console.log("\n" + "=".repeat(100)); - console.log(" BENCHMARK RUNNER -- Task Execution Report"); - console.log("=".repeat(100)); - - const results = executeBenchmark(benchmarkTasks); - - // Detailed results - const header = `| ${pad("ID", 10)}| ${pad("Task", 35)}| ${pad("Category", 18)}| ${pad("Pass?", 6)}| ${pad("Criteria", 10)}| ${pad("Expected", 10)}| ${pad("Actual", 10)}| ${pad("Delta", 8)}|`; - const sep = `|${"-".repeat(12)}|${"-".repeat(37)}|${"-".repeat(20)}|${"-".repeat(8)}|${"-".repeat(12)}|${"-".repeat(12)}|${"-".repeat(12)}|${"-".repeat(10)}|`; - console.log("\n" + header); - console.log(sep); - - for (const r of results) { - const passLabel = r.pass ? "PASS" : "FAIL"; - const criteriaLabel = `${r.criteriaPassed}/${r.criteriaTotal}`; - const deltaLabel = r.durationDelta >= 0 ? `+${r.durationDelta}ms` : `${r.durationDelta}ms`; - const marker = r.pass ? " " : ">>"; - - console.log( - `${marker}| ${pad(r.id, 10)}| ${pad(r.name, 35)}| ${pad(r.category, 18)}| ${pad(passLabel, 6)}| ${pad(criteriaLabel, 10)}| ${pad(r.expectedMs + "ms", 10)}| ${pad(r.actualMs + "ms", 10)}| ${pad(deltaLabel, 8)}|` - ); - } - - // Failure details - const failures = results.filter((r) => !r.pass); - if (failures.length > 0) { - console.log("\n" + "-".repeat(100)); - console.log(" FAILURE DETAILS"); - console.log("-".repeat(100)); - for (const f of failures) { - console.log(`\n [${f.id}] ${f.name}`); - console.log(` Category: ${f.category}`); - console.log(` Criteria: ${f.criteriaPassed}/${f.criteriaTotal} passed`); - console.log(` Reason: ${f.failureReason ?? "Unknown"}`); - console.log(` Timing: Expected ${f.expectedMs}ms, actual ${f.actualMs}ms (delta: ${f.durationDelta >= 0 ? "+" : ""}${f.durationDelta}ms)`); - } - } - - // Summary - console.log("\n" + "=".repeat(100)); - console.log(" SUMMARY BY CATEGORY"); - console.log("=".repeat(100) + "\n"); - - const categories = [...new Set(results.map((r) => r.category))]; - const catHeader = `| ${pad("Category", 20)}| ${pad("Total", 8)}| ${pad("Passed", 8)}| ${pad("Failed", 8)}| ${pad("Pass Rate", 12)}|`; - const catSep = `|${"-".repeat(22)}|${"-".repeat(10)}|${"-".repeat(10)}|${"-".repeat(10)}|${"-".repeat(14)}|`; - console.log(catHeader); - console.log(catSep); - - for (const cat of categories) { - const catResults = results.filter((r) => r.category === cat); - const catPassed = catResults.filter((r) => r.pass).length; - const catFailed = catResults.filter((r) => !r.pass).length; - const rate = Math.round((catPassed / catResults.length) * 100); - - console.log(`| ${pad(cat, 20)}| ${pad(String(catResults.length), 8)}| ${pad(String(catPassed), 8)}| ${pad(String(catFailed), 8)}| ${pad(rate + "%", 12)}|`); - } - - // Overall - const totalPassed = results.filter((r) => r.pass).length; - const totalFailed = results.filter((r) => !r.pass).length; - console.log("\n" + "-".repeat(100)); - console.log(` OVERALL: ${totalPassed}/${results.length} passed (${Math.round((totalPassed / results.length) * 100)}%), ${totalFailed} failures`); - console.log("=".repeat(100) + "\n"); -} - -run(); diff --git a/courses/learn-harness-engineering/docs/es/lectures/lecture-12-why-every-session-must-leave-a-clean-state/code/cleanup-loop.md b/courses/learn-harness-engineering/docs/es/lectures/lecture-12-why-every-session-must-leave-a-clean-state/code/cleanup-loop.md deleted file mode 100644 index 8a3e00ae..00000000 --- a/courses/learn-harness-engineering/docs/es/lectures/lecture-12-why-every-session-must-leave-a-clean-state/code/cleanup-loop.md +++ /dev/null @@ -1,9 +0,0 @@ -# Ejemplo de Bucle de Limpieza - -Tareas de limpieza recurrentes: - -- escanear docs obsoletos -- escanear violaciones estructurales -- actualizar notas de calidad -- abrir PRs de limpieza dirigidos -- reejecutar un slice de benchmark fijo después de la limpieza diff --git a/courses/learn-harness-engineering/docs/es/lectures/lecture-12-why-every-session-must-leave-a-clean-state/code/cleanup-scanner.ts b/courses/learn-harness-engineering/docs/es/lectures/lecture-12-why-every-session-must-leave-a-clean-state/code/cleanup-scanner.ts deleted file mode 100644 index 6b35905c..00000000 --- a/courses/learn-harness-engineering/docs/es/lectures/lecture-12-why-every-session-must-leave-a-clean-state/code/cleanup-scanner.ts +++ /dev/null @@ -1,336 +0,0 @@ -/** - * cleanup-scanner.ts - * - * Scans a project directory for stale artifacts, dead code, structural - * violations, and outputs a cleanup report. Helps enforce the "clean state - * at end of every session" principle. - * - * Usage: - * npx tsx docs/lectures/lecture-12.../code/cleanup-scanner.ts [path] - * (defaults to current working directory) - * - * Run: npx tsx docs/lectures/lecture-12-why-every-session-must-leave-a-clean-state/code/cleanup-scanner.ts - */ - -import * as fs from "node:fs"; -import * as path from "node:path"; - -// --------------------------------------------------------------------------- -// Types -// --------------------------------------------------------------------------- - -interface ScanResult { - category: string; - check: string; - severity: "critical" | "warning" | "info"; - found: string[]; - description: string; -} - -// --------------------------------------------------------------------------- -// Scanner checks -// --------------------------------------------------------------------------- - -interface ScannerCheck { - category: string; - name: string; - severity: "critical" | "warning" | "info"; - description: string; - scan: (dir: string) => string[]; -} - -function createChecks(): ScannerCheck[] { - return [ - { - category: "Stale Artifacts", - name: "Temporary files (*.tmp, *.bak, *.swp)", - severity: "warning", - description: "Leftover temp files from editing or processing", - scan: (dir) => findFiles(dir, [".tmp", ".bak", ".swp", "~"]), - }, - { - category: "Stale Artifacts", - name: "Debug/log files in source", - severity: "warning", - description: "Log files that should not be in the source tree", - scan: (dir) => { - const found: string[] = []; - const logPatterns = [".log"]; - for (const p of findFiles(dir, logPatterns)) { - // Only flag log files in source directories - if (p.includes("/src/") || p.includes("/lib/") || p.includes("/app/")) { - found.push(p); - } - } - return found; - }, - }, - { - category: "Dead Code", - name: "Unused imports (placeholder detection)", - severity: "info", - description: "Files that may contain unused code (heuristic scan)", - scan: (dir) => { - const found: string[] = []; - const srcDirs = ["src", "lib", "app"]; - for (const srcDir of srcDirs) { - const srcPath = path.join(dir, srcDir); - if (fs.existsSync(srcPath)) { - scanForPatterns(srcPath, ["TODO:", "FIXME:", "HACK:", "XXX:"], found, dir); - } - } - return found; - }, - }, - { - category: "Structural Violations", - name: "Missing .gitignore", - severity: "warning", - description: "No .gitignore file found -- risk of committing build artifacts", - scan: (dir) => fs.existsSync(path.join(dir, ".gitignore")) ? [] : [".gitignore (MISSING)"], - }, - { - category: "Structural Violations", - name: "Build artifacts in source", - severity: "critical", - description: "Compiled output (dist/, build/) mixed with source", - scan: (dir) => { - const found: string[] = []; - // Check if dist/ or build/ exists inside src/ - const dirs = ["src/dist", "src/build", "lib/dist", "lib/build"]; - for (const d of dirs) { - if (fs.existsSync(path.join(dir, d))) { - found.push(d + "/"); - } - } - return found; - }, - }, - { - category: "Structural Violations", - name: "node_modules in source tree", - severity: "critical", - description: "node_modules directory found outside root (nested dependency)", - scan: (dir) => { - const found: string[] = []; - const srcDirs = ["src", "lib", "app", "test", "tests"]; - for (const srcDir of srcDirs) { - const nmPath = path.join(dir, srcDir, "node_modules"); - if (fs.existsSync(nmPath)) { - found.push(srcDir + "/node_modules/"); - } - } - return found; - }, - }, - { - category: "Session Cleanliness", - name: "Uncommitted changes indicator", - severity: "info", - description: "Checks for common artifacts of incomplete sessions", - scan: (dir) => { - const found: string[] = []; - const indicators = [ - "WIP.md", "IN_PROGRESS.md", "scratch.ts", "scratch.js", - "debug.ts", "test-manual.ts", "temp.ts", - ]; - for (const f of indicators) { - if (fs.existsSync(path.join(dir, f))) { - found.push(f); - } - } - return found; - }, - }, - { - category: "Session Cleanliness", - name: "Empty directories", - severity: "info", - description: "Empty directories that may be leftover from incomplete work", - scan: (dir) => { - const found: string[] = []; - const commonDirs = ["src", "lib", "app", "test", "tests", "docs"]; - for (const d of commonDirs) { - const dp = path.join(dir, d); - if (fs.existsSync(dp) && fs.statSync(dp).isDirectory()) { - try { - const contents = fs.readdirSync(dp); - if (contents.length === 0) { - found.push(d + "/"); - } - } catch { - // Skip directories we can't read - } - } - } - return found; - }, - }, - { - category: "Configuration", - name: "Environment files in source", - severity: "critical", - description: ".env files that may contain secrets", - scan: (dir) => { - const found: string[] = []; - const envFiles = [".env", ".env.local", ".env.production", ".env.staging"]; - for (const f of envFiles) { - if (fs.existsSync(path.join(dir, f))) { - found.push(f); - } - } - return found; - }, - }, - ]; -} - -// --------------------------------------------------------------------------- -// Helper functions -// --------------------------------------------------------------------------- - -function findFiles(dir: string, extensions: string[]): string[] { - const found: string[] = []; - - function walk(current: string, depth: number): void { - if (depth > 4) return; // Limit recursion depth - try { - const entries = fs.readdirSync(current, { withFileTypes: true }); - for (const entry of entries) { - // Skip common non-source directories - if (entry.isDirectory() && ["node_modules", ".git", "dist", "build", ".next", "coverage"].includes(entry.name)) { - continue; - } - - const fullPath = path.join(current, entry.name); - const relativePath = path.relative(dir, fullPath); - - if (entry.isDirectory()) { - walk(fullPath, depth + 1); - } else if (entry.isFile()) { - if (extensions.some((ext) => entry.name.endsWith(ext))) { - found.push(relativePath); - } - } - } - } catch { - // Skip directories we can't read - } - } - - walk(dir, 0); - return found; -} - -function scanForPatterns(dir: string, patterns: string[], results: string[], baseDir: string): void { - try { - const entries = fs.readdirSync(dir, { withFileTypes: true }); - for (const entry of entries) { - if (entry.isDirectory()) { - if (!["node_modules", ".git", "dist", "build"].includes(entry.name)) { - scanForPatterns(path.join(dir, entry.name), patterns, results, baseDir); - } - continue; - } - - const fullPath = path.join(dir, entry.name); - if (!entry.name.endsWith(".ts") && !entry.name.endsWith(".tsx") && !entry.name.endsWith(".js")) { - continue; - } - - try { - const content = fs.readFileSync(fullPath, "utf-8"); - const lines = content.split("\n"); - for (let i = 0; i < Math.min(lines.length, 50); i++) { - for (const pattern of patterns) { - if (lines[i].includes(pattern)) { - const relative = path.relative(baseDir, fullPath); - results.push(`${relative}:${i + 1} contains ${pattern}`); - } - } - } - } catch { - // Skip files we can't read - } - } - } catch { - // Skip directories we can't read - } -} - -// --------------------------------------------------------------------------- -// Reporting -// --------------------------------------------------------------------------- - -function pad(s: string, len: number): string { - return s.length >= len ? s : s + " ".repeat(len - s.length); -} - -function run(): void { - const targetDir = process.argv[2] ? path.resolve(process.argv[2]) : process.cwd(); - - console.log("\n" + "=".repeat(90)); - console.log(" CLEANUP SCANNER -- Stale Artifact & Violation Report"); - console.log("=".repeat(90)); - console.log(` Scanning: ${targetDir}\n`); - - const checks = createChecks(); - const results: ScanResult[] = []; - - for (const check of checks) { - const found = check.scan(targetDir); - results.push({ - category: check.category, - check: check.name, - severity: check.severity, - found, - description: check.description, - }); - } - - // Report - const header = `| ${pad("Category", 20)}| ${pad("Check", 40)}| ${pad("Severity", 10)}| ${pad("Count", 6)}| Details`; - const sep = `|${"-".repeat(22)}|${"-".repeat(42)}|${"-".repeat(12)}|${"-".repeat(8)}|${"-".repeat(30)}`; - console.log(header); - console.log(sep); - - for (const r of results) { - const count = r.found.length; - const detail = count > 0 ? r.found.slice(0, 2).join(", ") : "(clean)"; - const marker = count > 0 && r.severity === "critical" ? ">>" : count > 0 ? " * " : " "; - - console.log(`${marker}| ${pad(r.category, 20)}| ${pad(r.check, 40)}| ${pad(r.severity, 10)}| ${pad(String(count), 6)}| ${detail}`); - } - - // Summary - const criticals = results.filter((r) => r.severity === "critical" && r.found.length > 0); - const warnings = results.filter((r) => r.severity === "warning" && r.found.length > 0); - const infos = results.filter((r) => r.severity === "info" && r.found.length > 0); - const clean = results.filter((r) => r.found.length === 0); - - console.log("\n" + "=".repeat(90)); - console.log(" CLEANUP SUMMARY"); - console.log("=".repeat(90) + "\n"); - - console.log(` Critical issues: ${criticals.length}`); - console.log(` Warnings: ${warnings.length}`); - console.log(` Info items: ${infos.length}`); - console.log(` Clean checks: ${clean.length}/${results.length}`); - - if (criticals.length > 0) { - console.log("\n ACTION REQUIRED (Critical):"); - for (const c of criticals) { - console.log(` - ${c.check}: ${c.found.length} item(s) found`); - console.log(` ${c.description}`); - } - } - - const totalIssues = results.reduce((s, r) => s + r.found.length, 0); - if (totalIssues === 0) { - console.log("\n Project is in a clean state. No issues found.\n"); - } else { - console.log(`\n Total issues found: ${totalIssues}. Run this scanner at the end of every session to maintain clean state.\n`); - } -} - -run(); diff --git a/courses/learn-harness-engineering/docs/es/lectures/lecture-12-why-every-session-must-leave-a-clean-state/code/index.md b/courses/learn-harness-engineering/docs/es/lectures/lecture-12-why-every-session-must-leave-a-clean-state/code/index.md deleted file mode 100644 index c0d65241..00000000 --- a/courses/learn-harness-engineering/docs/es/lectures/lecture-12-why-every-session-must-leave-a-clean-state/code/index.md +++ /dev/null @@ -1,8 +0,0 @@ -# Código de la Clase 12 - -Usa esta carpeta para ejemplos de: - -- slices de benchmark -- tareas de limpieza -- ejemplos de reducción de entropía -- ejecuciones de harness repetibles diff --git a/courses/learn-harness-engineering/docs/es/lectures/lecture-12-why-every-session-must-leave-a-clean-state/index.md b/courses/learn-harness-engineering/docs/es/lectures/lecture-12-why-every-session-must-leave-a-clean-state/index.md deleted file mode 100644 index c9bf3cf1..00000000 --- a/courses/learn-harness-engineering/docs/es/lectures/lecture-12-why-every-session-must-leave-a-clean-state/index.md +++ /dev/null @@ -1,184 +0,0 @@ -[中文版本 →](../../../zh/lectures/lecture-12-why-every-session-must-leave-a-clean-state/) - -> Ejemplos de código: [code/](https://github.com/walkinglabs/learn-harness-engineering/blob/main/docs/es/lectures/lecture-12-why-every-session-must-leave-a-clean-state/code/) -> Proyecto práctico: [Project 06. Complete harness (Capstone)](./../../projects/project-06-runtime-observability-and-debugging/index.md) - -# Lección 12. Deja un handoff limpio al final de cada sesión - -Tu agent ejecuta toda la tarde, modifica 20 archivos, hace commit del código, la sesión termina. La siguiente sesión del agent comienza y descubre inmediatamente: el build está roto, las pruebas están en rojo, los archivos temporales de debug están por todas partes, la feature list no se actualizó, y el progreso es completamente incierto. La nueva sesión dedica sus primeros 30 minutos solo a averiguar "qué hizo realmente la última sesión." - -Tanto OpenAI como Anthropic afirman claramente: **la confiabilidad a largo plazo depende de la disciplina operacional, no solo del éxito en una única ejecución.** La calidad del estado al final de la sesión determina directamente la eficiencia de la siguiente sesión. Piensa en ello como las buenas prácticas de Git—cada commit debería ser un cambio atómico y compilable, no un montón de código a medio terminar. - -## Conceptos clave - -- **Clean state**: El sistema satisface cinco condiciones al final de la sesión—el build pasa, las pruebas pasan, el progreso está registrado, no hay artefactos obsoletos, y la ruta de inicio está disponible. Faltar cualquiera significa que la sesión no está "terminada." -- **Integridad de sesión**: Análogo a las transacciones de base de datos—o bien se hace commit completo y se deja un clean state, o se hace rollback al último estado consistente. No hay término medio. -- **Documento de calidad**: Un artefacto activo que registra continuamente las calificaciones de calidad de cada módulo. No es una evaluación única, sino un rastreador que muestra si la base de código se está fortaleciendo o debilitando con el tiempo. -- **Bucle de limpieza**: Una sesión de mantenimiento regular orientada a reducir sistemáticamente la entropía en la base de código. No es una corrección de emergencia, sino operaciones rutinarias. -- **Simplificación del harness**: A medida que mejoran las capacidades del modelo, eliminar periódicamente los componentes del harness que ya no son necesarios. Una restricción esencial hoy puede ser una sobrecarga innecesaria en tres meses. -- **Limpieza idempotente**: Las operaciones de limpieza producen el mismo resultado independientemente de cuántas veces se ejecuten. Asegura que la limpieza siga siendo segura incluso en escenarios de reintento tras fallo. - -## Cinco dimensiones del clean state - -```mermaid -flowchart LR - Work["Feature work complete"] --> Build{"Build passes?"} - Build -->|yes| Test{"Tests pass?"} - Build -->|no| Fix["Fix before exit"] - Test -->|yes| Record["Update feature list + progress"] - Test -->|no| Fix - Record --> Cleanup["Remove temp artifacts / debug code"] - Cleanup --> Startup{"Standard startup path works?"} - Startup -->|yes| Clean["Clean handoff"] - Startup -->|no| Fix - Fix --> Build -``` - -```mermaid -flowchart LR - Dirty["Session ends with
red tests / temp files / no progress update"] --> Diagnose["Next session first has to
figure out what happened"] - Diagnose --> Fragile["New work starts on a messy repo"] - Fragile --> More["More debug files, more broken checks,
more unclear progress"] - More --> Dirty - - Clean["Session ends with
green tests / updated progress / temp files removed"] --> Fast["Next session can start coding immediately"] - Fast --> Stable["No need to rescue the repo first"] - Stable --> Clean -``` - -## Por qué ocurre - -### El crecimiento de la entropía es el estado por defecto - -Las leyes de Lehman sobre la evolución del software nos dicen: los sistemas sometidos a cambios continuos aumentarán inevitablemente en complejidad a menos que se gestionen activamente. Esto es especialmente cierto para los agentes de codificación con IA—cada sesión introduce cambios, y sin limpieza al salir, la deuda técnica se acumula exponencialmente. - -Los datos reales son reveladores. Un proyecto desarrollado con agentes durante 12 semanas, sin estrategia de limpieza: - -- Semana 1: Tasa de build exitoso 100%, tasa de pruebas exitosas 100%, inicio de nueva sesión 5 min -- Semana 4: Build 95%, pruebas 92%, inicio 15 min -- Semana 8: Build 82%, pruebas 78%, inicio 35 min -- Semana 12: Build 68%, pruebas 61%, inicio 60+ min - -Mismo proyecto con una estrategia de limpieza: - -- Semana 1: 100%, 100%, 5 min -- Semana 12: 97%, 95%, 9 min - -Después de 12 semanas: la tasa de build exitoso difiere en 29 puntos porcentuales, el tiempo de inicio de nueva sesión difiere en un 85%. Esto no es teórico—es una diferencia observada. - -### Cinco dimensiones del clean state - -El clean state no es solo "el código compila." Son cinco dimensiones evaluadas juntas: - -**Dimensión de build**: ¿El código compila sin errores? Esto es lo más básico—la siguiente sesión no debería tener que corregir errores de build primero. - -**Dimensión de pruebas**: ¿Pasán todas las pruebas? Incluyendo las pruebas que existían antes de la sesión—la sesión es responsable de no romper la funcionalidad existente. Y debería verificarse en CI, no solo "funciona en mi máquina." - -**Dimensión de progreso**: ¿Está el progreso actual registrado en un artefacto legible por máquina? Subtareas completadas con sus criterios de aprobación, subtareas en progreso pero incompletas con su estado actual, subtareas aún no iniciadas. Los buenos registros de progreso reducen un 60-80% del tiempo de diagnóstico al inicio de la sesión. - -**Dimensión de artefactos**: ¿Hay artefactos temporales obsoletos o ambiguos? Logs de debug, archivos temporales, código comentado, marcadores TODO—todos estos aumentan la carga cognitiva para la siguiente sesión. - -**Dimensión de inicio**: ¿Está disponible la ruta de inicio estándar? ¿Puede la siguiente sesión comenzar a trabajar sin intervención manual? Inicialización del entorno, carga de la base de código, adquisición de contexto, selección de tareas—estas rutas no deben estar rotas. - -### "Limpiar después" significa nunca limpiar - -La trampa mental más común es "no hay tiempo para limpiar esta sesión, lo haré la próxima vez." Pero la siguiente sesión del agent no sabe lo que dejaste atrás—ve un desorden de código y un estado incierto. Dedicará un tiempo significativo a inferir "qué partes de este código son intencionales y cuáles son temporales." - -Peor aún, cada sesión tiene sus propios objetivos de tarea. La nueva sesión está ahí para hacer trabajo nuevo, no para limpiar el desastre de la sesión anterior. Ignorará el caos y comenzará trabajo nuevo encima de él, introduciendo más caos sobre el caos. Este es el bucle de retroalimentación positiva de la entropía. - -## Cómo hacerlo bien - -### 1. Clean state como requisito de finalización - -Define explícitamente en el harness: **finalización de sesión = la tarea pasa la verificación Y la verificación de clean state pasa.** Faltar cualquiera de las dos significa que la sesión no está completa. Escríbelo en CLAUDE.md: - -``` -## Session Exit Checklist -- [ ] Build passes (npm run build) -- [ ] All tests pass (npm test) -- [ ] Feature list updated -- [ ] No debug code remaining (console.log, debugger, TODO) -- [ ] Standard startup path available (npm run dev) -``` - -### 2. Estrategia de limpieza en modo dual - -Combina dos modos de limpieza: - -**Limpieza inmediata (al final de cada sesión)**: Limpiar artefactos temporales creados durante la sesión, actualizar el estado de la feature list, asegurar que el build y las pruebas pasen. Esta es limpieza de "conteo de referencias." - -**Limpieza periódica (semanal)**: Escaneo completo del sistema—manejar problemas estructurales acumulados, actualizar documentos de calidad, ejecutar pruebas de referencia para detectar desviaciones. Esta es limpieza de "trazado." - -### 3. Mantener un documento de calidad - -Un documento de calidad es un artefacto activo que califica continuamente cada módulo: - -```markdown -# Quality Document - -## User Authentication Module (Quality: A) -- Verification passing: Yes -- Agent understandable: Yes -- Test stability: Stable -- Architecture boundaries: Compliant -- Code conventions: Followed - -## Payment Module (Quality: C) -- Verification passing: Partial (payment callback untested) -- Agent understandable: Difficult (logic spread across 3 files) -- Test stability: Unstable (2 flaky tests) -- Architecture boundaries: Violations present -- Code conventions: Partially followed -``` - -Las nuevas sesiones leen este documento e inmediatamente saben dónde priorizar. Corrige primero el módulo con la calificación más baja. - -### 4. Simplificar periódicamente el harness - -Una perspectiva importante de Anthropic: **cada componente del harness existe porque el modelo no puede hacer algo de manera confiable por sí solo. Pero a medida que los modelos mejoran, estos supuestos se vuelven obsoletos.** Una restricción esencial hace tres meses puede ser una sobrecarga innecesaria hoy. - -Práctica recomendada: Cada mes, elige un componente del harness, desactívalo temporalmente, y ejecuta tareas de referencia. Si los resultados no se degradan, elimínalo permanentemente. Si se degradan, restáuralo o reemplázalo con una alternativa más ligera. - -### 5. Las operaciones de limpieza deben ser idempotentes - -Los scripts de limpieza deben ser seguros para ejecutar repetidamente: - -```bash -# Idempotent cleanup operations -rm -f /tmp/debug-*.log # -f ensures no error when files don't exist -git checkout -- .env.local # Restore to known state -npm run test # Verify cleanup didn't break anything -``` - -## Caso del mundo real - -Una aplicación Electron desarrollada con agentes durante 12 semanas, comparando dos enfoques: - -**Sin estrategia de limpieza** (grupo de control): Semana 12, tasa de build exitoso 68%, tasa de pruebas exitosas 61%, inicio de nueva sesión 60+ min, artefactos obsoletos 103. - -**Con estrategia de limpieza** (grupo experimental): Verificación completa de clean state al final de cada sesión + bucle de limpieza semanal. Semana 12, tasa de build exitoso 97%, tasa de pruebas exitosas 95%, inicio de nueva sesión 9 min, artefactos obsoletos 11. - -En la semana 12, la tasa de build exitoso del grupo experimental es 29 puntos porcentuales más alta, la tasa de pruebas exitosas 34 puntos más alta, y el tiempo de inicio de nueva sesión un 85% menor. - -## Ideas clave - -- **El clean state es una condición necesaria para la finalización de la sesión**—no es limpieza opcional, sino parte de la "definición de terminado." -- **Las cinco dimensiones son todas necesarias**—build, pruebas, progreso, artefactos, inicio—cada una debe verificarse explícitamente. -- **Los documentos de calidad hacen rastreable la salud de la base de código**—solo puedes arreglar lo que sabes que se está degradando. -- **Simplificar periódicamente el harness**—a medida que mejoran las capacidades del modelo, elimina las restricciones que ya no son necesarias. -- **"Limpiar después" equivale a nunca limpiar**—el crecimiento de la entropía es el estado por defecto; solo la limpieza activa lo contrarresta. - -## Lecturas adicionales - -- [Clean Code - Robert C. Martin](https://www.goodreads.com/book/show/3735293-clean-code) — Principios sistemáticos de limpieza del código -- [Harness Engineering - OpenAI](https://openai.com/index/harness-engineering/) — Reproducibilidad como requisito central de diseño del harness -- [Effective Harnesses - Anthropic](https://www.anthropic.com/engineering/effective-harnesses-for-long-running-agents) — El papel crítico de las salidas de sesión limpias para la confiabilidad a largo plazo -- [Programs, Life Cycles, and Laws of Software Evolution - Lehman](https://ieeexplore.ieee.org/document/1702314) — Las leyes de evolución del software que demuestran que la complejidad del sistema inevitablemente crece sin mantenimiento activo - -## Ejercicios - -1. **Checklist de clean state**: Diseña un checklist de salida de sesión para tu base de código que cubra las cinco dimensiones. Aplícalo durante 5 sesiones consecutivas y registra las violaciones por dimensión. - -2. **Comparación de referencia**: Usa un conjunto fijo de tareas con dos variantes de harness (con/sin requisitos de clean state). Compara la tasa de finalización, la cantidad de reintentos y la tasa de escape de defectos. - -3. **Práctica de simplificación del harness**: Elige un componente del harness, desactívalo temporalmente, y ejecuta tareas de referencia. Compara los resultados con y sin él. Decide si mantenerlo, eliminarlo o reemplazarlo. diff --git a/courses/learn-harness-engineering/docs/es/projects/index.md b/courses/learn-harness-engineering/docs/es/projects/index.md deleted file mode 100644 index eda7f112..00000000 --- a/courses/learn-harness-engineering/docs/es/projects/index.md +++ /dev/null @@ -1,23 +0,0 @@ -# Bienvenido a Proyectos - -Esta es la sección práctica de Learn Harness Engineering. Leer las lecciones no basta: necesitas construir los entornos tú mismo y observar cómo se comportan Codex, Claude Code u otros agentes de IA bajo reglas distintas. - -## Resumen de proyectos - -El curso incluye 6 proyectos prácticos y progresivos que enseñan a construir desde cero un entorno agentic fiable: - -1. **Solo prompt vs reglas primero**: compara el rendimiento de un agente con solo un prompt frente a un harness básico. -2. **Workspace legible para el agente**: aprende a estructurar el repositorio para que sea cómodo para la IA y a establecer mecanismos de handoff. -3. **Continuidad entre sesiones**: diseña archivos de estado y scripts de inicialización para que el agente pueda reanudar el trabajo entre sesiones. -4. **Feedback de runtime y control de alcance**: introduce herramientas que permiten al agente probar su propio código y corregir errores durante la ejecución. -5. **Autoverificación y separación de roles**: construye un mecanismo de revisión independiente para evitar alucinaciones y declaraciones prematuras de éxito. -6. **Harness completo (capstone)**: ensambla un entorno final de trabajo agentic observable y end-to-end. - -## Cómo avanzar - -Cada carpeta de proyecto suele contener: -- `starter/`: tu workspace inicial. -- `solution/`: una implementación de referencia si te atascas. -- Instrucciones de tarea con contexto y objetivos concretos. - -Usa tu AI Coding Agent preferido, por ejemplo Claude Code, Cursor o Trae, para completar las tareas dentro del directorio `starter/`. diff --git a/courses/learn-harness-engineering/docs/es/projects/project-01-baseline-vs-minimal-harness/index.md b/courses/learn-harness-engineering/docs/es/projects/project-01-baseline-vs-minimal-harness/index.md deleted file mode 100644 index d986d203..00000000 --- a/courses/learn-harness-engineering/docs/es/projects/project-01-baseline-vs-minimal-harness/index.md +++ /dev/null @@ -1,25 +0,0 @@ -[中文版本 →](../../../zh/projects/project-01-baseline-vs-minimal-harness/) - -> Lecciones relacionadas: [Lección 01. Los modelos fuertes no significan ejecución fiable](./../../lectures/lecture-01-why-capable-agents-still-fail/index.md) · [Lección 02. Qué significa realmente harness](./../../lectures/lecture-02-what-a-harness-actually-is/index.md) -> Archivos de plantilla: [templates/](https://github.com/walkinglabs/learn-harness-engineering/blob/main/docs/es/resources/templates/) - -# Proyecto 01. Solo prompt vs reglas primero: cuánta diferencia produce - -## Qué harás - -Construye el esqueleto mínimo de una aplicación Electron de base de conocimiento: una ventana con lista de documentos a la izquierda, panel de Q&A a la derecha y un directorio de datos local. La tarea no es compleja. Lo complejo es cómo consigues que el agente la complete. - -La ejecutarás dos veces. Primera vez: solo un prompt, sin preparación. Segunda vez: con `AGENTS.md`, `init.sh` y `feature_list.json` ya colocados en el repositorio. Luego compara. - -El núcleo de este proyecto no es escribir código, sino descubrir cuánta distancia hay entre "dedicar 15 minutos a preparar reglas primero" y "dejar que el agente avance sin más". - -## Herramientas - -- Claude Code o Codex (elige uno y úsalo en ambas ejecuciones) -- Git (gestionar ramas y comparar) -- Node.js + Electron (stack del proyecto) -- Un temporizador (registrar la duración de cada ejecución) - -## Mecanismo de harness - -Harness mínimo: `AGENTS.md` + `init.sh` + `feature_list.json` diff --git a/courses/learn-harness-engineering/docs/es/projects/project-02-agent-readable-workspace/index.md b/courses/learn-harness-engineering/docs/es/projects/project-02-agent-readable-workspace/index.md deleted file mode 100644 index 10b2e027..00000000 --- a/courses/learn-harness-engineering/docs/es/projects/project-02-agent-readable-workspace/index.md +++ /dev/null @@ -1,22 +0,0 @@ -[中文版本 →](../../../zh/projects/project-02-agent-readable-workspace/) - -> Lecciones relacionadas: [Lección 03. Convierte el repositorio en tu única fuente de verdad](./../../lectures/lecture-03-why-the-repository-must-become-the-system-of-record/index.md) · [Lección 04. Divide las instrucciones en varios archivos](./../../lectures/lecture-04-why-one-giant-instruction-file-fails/index.md) -> Archivos de plantilla: [templates/](https://github.com/walkinglabs/learn-harness-engineering/blob/main/docs/es/resources/templates/) - -# Proyecto 02. Haz el proyecto legible y retoma el trabajo donde lo dejaste - -## Qué harás - -Añade "legibilidad" al repositorio para que un agente nuevo pueda entender rápidamente la estructura del proyecto, conocer el progreso actual y retomar el trabajo. En concreto: implementa importación de documentos, vista de detalle y persistencia local, completado en dos sesiones. - -Lo ejecutarás dos veces: primero sin ayuda, y después con `ARCHITECTURE.md`, `PRODUCT.md` y `session-handoff.md` ya colocados en el repositorio. - -## Herramientas - -- Claude Code o Codex -- Git -- Node.js + Electron - -## Mecanismo de harness - -Workspace legible para el agente + archivos de estado persistentes diff --git a/courses/learn-harness-engineering/docs/es/projects/project-03-multi-session-continuity/index.md b/courses/learn-harness-engineering/docs/es/projects/project-03-multi-session-continuity/index.md deleted file mode 100644 index 5803e61e..00000000 --- a/courses/learn-harness-engineering/docs/es/projects/project-03-multi-session-continuity/index.md +++ /dev/null @@ -1,22 +0,0 @@ -[中文版本 →](../../../zh/projects/project-03-multi-session-continuity/) - -> Lecciones relacionadas: [Lección 05. Mantén vivo el contexto entre sesiones](./../../lectures/lecture-05-why-long-running-tasks-lose-continuity/index.md) · [Lección 06. Inicializa antes de cada sesión del agente](./../../lectures/lecture-06-why-initialization-needs-its-own-phase/index.md) -> Archivos de plantilla: [templates/](https://github.com/walkinglabs/learn-harness-engineering/blob/main/docs/es/resources/templates/) - -# Proyecto 03. Mantén al agente trabajando tras reiniciar sesiones - -## Qué harás - -Añade control de alcance y gates de verificación al agente. Implementa particionado de documentos, extracción de metadatos, visualización del progreso de indexación y flujo de Q&A con citas. Usa `feature_list.json` para seguir el estado de las funciones: una función a la vez, sin marcar `pass` sin evidencia de verificación. - -Lo ejecutarás dos veces: primero sin restricciones, luego con aplicación estricta de reglas. - -## Herramientas - -- Claude Code o Codex -- Git -- Node.js + Electron - -## Mecanismo de harness - -Registro de progreso + handoff de sesión + continuidad multi-sesión diff --git a/courses/learn-harness-engineering/docs/es/projects/project-04-incremental-indexing/index.md b/courses/learn-harness-engineering/docs/es/projects/project-04-incremental-indexing/index.md deleted file mode 100644 index 3364c158..00000000 --- a/courses/learn-harness-engineering/docs/es/projects/project-04-incremental-indexing/index.md +++ /dev/null @@ -1,22 +0,0 @@ -[中文版本 →](../../../zh/projects/project-04-incremental-indexing/) - -> Lecciones relacionadas: [Lección 07. Define límites claros de tarea para los agentes](./../../lectures/lecture-07-why-agents-overreach-and-under-finish/index.md) · [Lección 08. Usa listas de funciones para restringir lo que hace el agente](./../../lectures/lecture-08-why-feature-lists-are-harness-primitives/index.md) -> Archivos de plantilla: [templates/](https://github.com/walkinglabs/learn-harness-engineering/blob/main/docs/es/resources/templates/) - -# Proyecto 04. Usa feedback de runtime para corregir el comportamiento del agente - -## Qué harás - -Añade observabilidad de runtime, como logs de arranque, logs de importación/indexación y estados de error, además de restricciones arquitectónicas para evitar violaciones entre capas. Introduce un bug de runtime para que el agente lo corrija. - -Lo ejecutarás dos veces: primero sin logs ni restricciones, después con herramientas y reglas adecuadas. - -## Herramientas - -- Claude Code o Codex -- Git -- Node.js + Electron - -## Mecanismo de harness - -Feedback de runtime + control de alcance + indexación incremental diff --git a/courses/learn-harness-engineering/docs/es/projects/project-05-grounded-qa-verification/index.md b/courses/learn-harness-engineering/docs/es/projects/project-05-grounded-qa-verification/index.md deleted file mode 100644 index 153e81d9..00000000 --- a/courses/learn-harness-engineering/docs/es/projects/project-05-grounded-qa-verification/index.md +++ /dev/null @@ -1,22 +0,0 @@ -[中文版本 →](../../../zh/projects/project-05-grounded-qa-verification/) - -> Lecciones relacionadas: [Lección 09. Evita que los agentes declaren victoria demasiado pronto](./../../lectures/lecture-09-why-agents-declare-victory-too-early/index.md) · [Lección 10. Solo una ejecución de pipeline completo cuenta como verificación real](./../../lectures/lecture-10-why-end-to-end-testing-changes-results/index.md) -> Archivos de plantilla: [templates/](https://github.com/walkinglabs/learn-harness-engineering/blob/main/docs/es/resources/templates/) - -# Proyecto 05. Haz que el agente verifique su propio trabajo - -## Qué harás - -Implementa separación de roles: un generator que implementa, un evaluator que revisa y opcionalmente un planner. Ejecuta tres veces para medir el efecto de cada rol añadido. - -Elige una mejora sustancial de función, como conversación multi-turno, rediseño del panel de citas o filtrado de documentos, y mantenla igual en todas las ejecuciones. - -## Herramientas - -- Claude Code o Codex -- Git -- Node.js + Electron - -## Mecanismo de harness - -Autoverificación + Q&A con fundamento + finalización basada en evidencia diff --git a/courses/learn-harness-engineering/docs/es/projects/project-06-runtime-observability-and-debugging/index.md b/courses/learn-harness-engineering/docs/es/projects/project-06-runtime-observability-and-debugging/index.md deleted file mode 100644 index d1e9ebfc..00000000 --- a/courses/learn-harness-engineering/docs/es/projects/project-06-runtime-observability-and-debugging/index.md +++ /dev/null @@ -1,25 +0,0 @@ -[中文版本 →](../../../zh/projects/project-06-runtime-observability-and-debugging/) - -> Lecciones relacionadas: [Lección 11. Haz observable el runtime del agente](./../../lectures/lecture-11-why-observability-belongs-inside-the-harness/index.md) · [Lección 12. Handoff limpio al final de cada sesión](./../../lectures/lecture-12-why-every-session-must-leave-a-clean-state/index.md) -> Archivos de plantilla: [templates/](https://github.com/walkinglabs/learn-harness-engineering/blob/main/docs/es/resources/templates/) - -# Proyecto 06. Construye un harness completo para agentes (capstone) - -## Qué harás - -Este es el proyecto final. Ensambla todo lo aprendido en los primeros cinco proyectos, ejecuta un benchmark completo y luego haz una pasada de limpieza para verificar que la calidad sea mantenible. - -Usa un conjunto fijo de tareas multi-función que cubra una porción completa del producto: importación de documentos, indexación, Q&A con citas, observabilidad de runtime y estado de repositorio legible y reiniciable. Primero ejecuta con un baseline de harness débil, luego con tu harness más fuerte, después limpia y vuelve a ejecutar. Finalmente, realiza un experimento de ablación del harness: elimina un componente cada vez y observa cuáles importan realmente. - -## Herramientas - -- Claude Code o Codex -- Git -- Node.js + Electron -- Plantilla de documento de calidad -- Rúbrica de evaluación -- Todos los componentes de harness acumulados en los primeros cinco proyectos - -## Mecanismo de harness - -Harness completo: todos los mecanismos + observabilidad + estudio de ablación diff --git a/courses/learn-harness-engineering/docs/es/resources/index.md b/courses/learn-harness-engineering/docs/es/resources/index.md deleted file mode 100644 index e276d2e7..00000000 --- a/courses/learn-harness-engineering/docs/es/resources/index.md +++ /dev/null @@ -1,50 +0,0 @@ -# Biblioteca de recursos en español - -Esta carpeta convierte los métodos del curso en plantillas listas para copiar y referencias compactas que puedes usar en un repositorio real. - -## Cuándo usarla - -Empieza aquí cuando quieras que Codex, Claude Code u otro agente de programación trabaje durante varias sesiones sin tener que redescubrir constantemente el setup, el estado y el alcance. - -Es especialmente útil cuando: - -- el trabajo abarca varias sesiones -- hay muchas funciones y es fácil dejarlas a medias -- los agentes tienden a declarar victoria demasiado pronto -- los pasos de arranque se redescubren cada vez - -## Empieza aquí - -Para una configuración mínima, empieza con: - -- instrucciones raíz: [`templates/AGENTS.md`](./templates/AGENTS.md) o [`templates/CLAUDE.md`](./templates/CLAUDE.md) -- estado de funciones: [`templates/feature_list.json`](./templates/feature_list.json) -- registro de progreso: [`templates/claude-progress.md`](./templates/claude-progress.md) -- referencia del script de arranque: `docs/es/resources/templates/init.sh` - -Luego añade: - -- handoff de sesión: [`templates/session-handoff.md`](./templates/session-handoff.md) -- checklist de salida limpia: [`templates/clean-state-checklist.md`](./templates/clean-state-checklist.md) -- rúbrica de evaluación: [`templates/evaluator-rubric.md`](./templates/evaluator-rubric.md) - -Si quieres una estructura de repositorio más completa al estilo OpenAI del artículo "Harness engineering", usa el paquete avanzado: - -- [`openai-advanced/index.md`](./openai-advanced/index.md) - -## Estructura de la biblioteca - -- [`templates/`](./templates/index.md): plantillas para copiar en un repositorio real -- [`reference/`](./reference/index.md): notas de método, flujo de arranque y mapas de modos de fallo -- [`openai-advanced/`](./openai-advanced/index.md): esqueleto avanzado de repositorio, documentos system-of-record y plantillas de gobernanza agent-first - -## Paquete mínimo recomendado - -- `AGENTS.md` o `CLAUDE.md` -- `feature_list.json` -- `claude-progress.md` -- `init.sh` - -Estos cuatro archivos bastan para hacer que la mayoría de los flujos con agentes sean notablemente más estables. - -Cuando el repositorio crezca hasta convertirse en un sistema de larga duración con varios dominios, planes activos, puntuación de calidad y políticas de fiabilidad, pasa al paquete [`openai-advanced/`](./openai-advanced/index.md) en lugar de estirar demasiado el paquete mínimo. diff --git a/courses/learn-harness-engineering/docs/es/resources/openai-advanced/index.md b/courses/learn-harness-engineering/docs/es/resources/openai-advanced/index.md deleted file mode 100644 index ff03b014..00000000 --- a/courses/learn-harness-engineering/docs/es/resources/openai-advanced/index.md +++ /dev/null @@ -1,79 +0,0 @@ -# Pack Avanzado de OpenAI - -Esta carpeta empaqueta la estructura de repositorio más opinionada descrita en -el artículo de OpenAI "Harness engineering: leveraging Codex in an agent-first world" -en archivos de inicio listos para copiar. - -Usa este pack cuando el harness mínimo ya no sea suficiente y tu repositorio -ahora necesite: - -- un `AGENTS.md` corto estilo enrutador -- docs duraderos de sistema de registro dentro del repositorio -- planes de ejecución activos y completados -- archivos explícitos de políticas de producto, fiabilidad, seguridad y frontend -- puntuación de calidad por dominio de producto y capa arquitectónica -- carpetas de material de referencia amigable para modelos -- procedimientos operativos estándar para arquitectura, captura de conocimiento y validación en runtime - -## Diseño de Inicio Incluido - -El pack de inicio en [`repo-template/`](./repo-template/index.md) refleja la -siguiente estructura: - -```text -AGENTS.md -ARCHITECTURE.md -docs/ -├── design-docs/ -│ ├── index.md -│ └── core-beliefs.md -├── exec-plans/ -│ ├── active/ -│ ├── completed/ -│ └── tech-debt-tracker.md -├── generated/ -│ └── db-schema.md -├── product-specs/ -│ ├── index.md -│ └── new-user-onboarding.md -├── references/ -│ ├── design-system-reference-llms.txt -│ ├── nixpacks-llms.txt -│ └── uv-llms.txt -├── DESIGN.md -├── FRONTEND.md -├── PLANS.md -├── PRODUCT_SENSE.md -├── QUALITY_SCORE.md -├── RELIABILITY.md -└── SECURITY.md -``` - -## Cómo Adoptarlo - -1. Comienza con el pack mínimo si tu repositorio aún es pequeño. -2. Copia los archivos en [`repo-template/`](./repo-template/index.md) a tu - propio repositorio cuando necesites una estructura más fuerte. -3. Mantén `AGENTS.md` corto. Trátalo como un enrutador hacia los docs más profundos, no como una enciclopedia. -4. Actualiza los docs de calidad, fiabilidad y planes como parte del trabajo normal, no como un día de limpieza separado. -5. Mantén los artefactos generados y las referencias externas explícitos para que los agentes puedan encontrarlos sin depender del historial de chat. - -## Biblioteca de SOPs - -La carpeta [`sops/`](./sops/index.md) convierte los diagramas del artículo en -procedimientos operativos paso a paso: - -- configuración de arquitectura de dominios por capas -- codificación de conocimiento no visto en el repositorio -- stack de observabilidad local y flujo de trabajo de bucle de retroalimentación -- bucle de validación con Chrome DevTools para trabajo de UI - -## Principios de Diseño - -- Punto de entrada corto, docs más profundos enlazados -- Repositorio como sistema de registro -- Verificaciones mecánicas sobre reglas memorizadas -- Planes e historial de calidad viven junto al código -- Limpieza y simplificación son responsabilidades de primera clase - -Este pack es intencionalmente opinionado, pero debe adaptarse a tu proyecto en lugar de copiarse a ciegas. diff --git a/courses/learn-harness-engineering/docs/es/resources/openai-advanced/repo-template/AGENTS.md b/courses/learn-harness-engineering/docs/es/resources/openai-advanced/repo-template/AGENTS.md deleted file mode 100644 index 881994f7..00000000 --- a/courses/learn-harness-engineering/docs/es/resources/openai-advanced/repo-template/AGENTS.md +++ /dev/null @@ -1,61 +0,0 @@ -# AGENTS.md - -Este repositorio está optimizado para trabajo de agentes de codificación de larga duración. Mantén este -archivo corto. Úsalo como la capa de enrutamiento hacia los documentos del sistema de referencia, no como un -volcado gigante de instrucciones. - -## Flujo de inicio - -Antes de cambiar código: - -1. Confirma la raíz del repositorio con `pwd`. -2. Lee `ARCHITECTURE.md` para el mapa actual del sistema y las reglas estrictas de dependencia. -3. Lee `docs/QUALITY_SCORE.md` para ver qué dominios o capas son más débiles. -4. Lee `docs/PLANS.md`, luego abre el plan activo en el que estás trabajando. -5. Lee la especificación de producto relevante en `docs/product-specs/`. -6. Ejecuta la ruta estándar de arranque y verificación para este repositorio. -7. Si la verificación basal está fallando, repara la línea base antes de agregar alcance. - -## Mapa de enrutamiento - -- `ARCHITECTURE.md`: mapa de dominio, modelo de capas, reglas de dependencia -- `docs/design-docs/index.md`: decisiones de diseño y creencias fundamentales -- `docs/product-specs/index.md`: comportamientos actuales del producto y criterios de aceptación -- `docs/PLANS.md`: ciclo de vida de planes y política de planes de ejecución -- `docs/QUALITY_SCORE.md`: salud de dominios de producto y capas -- `docs/RELIABILITY.md`: señales en tiempo de ejecución, benchmarks y expectativas de reinicio -- `docs/SECURITY.md`: secretos, sandbox, datos y reglas de acciones externas -- `docs/FRONTEND.md`: restricciones de UI, reglas del sistema de diseño, verificaciones de accesibilidad - -## Contrato de trabajo - -- Trabaja desde un plan delimitado o una porción de funcionalidad a la vez. -- No marques el trabajo como completado solo por inspección de código; se requiere - evidencia ejecutable. -- Si cambias un comportamiento, actualiza los documentos de producto, plan o fiabilidad - correspondientes en la misma sesión. -- Si ves comentarios de revisión repetidos, promuévelos a una regla mecánica, verificación - o linter en lugar de volver a explicarlo en el chat. -- Mantén el material generado en `docs/generated/` y las referencias fuente en - `docs/references/`. -- Prefiere agregar documentos pequeños y actuales en lugar de hacer crecer este archivo. - -## Definición de completado - -Un cambio está completo solo cuando todas las siguientes condiciones son verdaderas: - -- el comportamiento objetivo está implementado -- la verificación requerida se ejecutó realmente -- la evidencia está vinculada desde el plan o documento de calidad relevante -- los documentos afectados permanecen actualizados -- el repositorio puede reiniciarse limpiamente desde la ruta de inicio estándar - -## Fin de sesión - -Antes de terminar una sesión: - -1. Actualiza el plan de ejecución activo. -2. Actualiza `docs/QUALITY_SCORE.md` si algún dominio o capa cambió significativamente. -3. Registra nueva deuda técnica en `docs/exec-plans/tech-debt-tracker.md` si la pospusiste. -4. Mueve los planes terminados a `docs/exec-plans/completed/` cuando sea apropiado. -5. Deja el repositorio en un estado reiniciable con una próxima acción clara. diff --git a/courses/learn-harness-engineering/docs/es/resources/openai-advanced/repo-template/ARCHITECTURE.md b/courses/learn-harness-engineering/docs/es/resources/openai-advanced/repo-template/ARCHITECTURE.md deleted file mode 100644 index a611cacf..00000000 --- a/courses/learn-harness-engineering/docs/es/resources/openai-advanced/repo-template/ARCHITECTURE.md +++ /dev/null @@ -1,57 +0,0 @@ -# ARCHITECTURE.md - -Este archivo es el mapa de nivel superior del sistema. Debe mantenerse conciso y apuntar -a documentos más profundos cuando sea necesario. - -## Forma del sistema - -- Producto: `[replace with product name]` -- Flujo de usuario principal: `[replace with main workflow]` -- Superficies de ejecución: `[desktop / web / cli / services / workers]` -- Fuente de verdad para el comportamiento del producto: `docs/product-specs/` - -## Mapa de dominios - -| Dominio | Propósito | Puntos de entrada principales | Spec relacionada | -|---------|-----------|-------------------------------|------------------| -| `[domain-a]` | `[what it owns]` | `[modules / routes / commands]` | `[spec path]` | -| `[domain-b]` | `[what it owns]` | `[modules / routes / commands]` | `[spec path]` | - -## Modelo de capas - -Usa un modelo direccional fijo para que los agentes no inventen arquitectura ad hoc: - -`Types -> Config -> Repo -> Service -> Runtime -> UI` - -Las preocupaciones transversales deben entrar a través de límites explícitos de proveedor o -adaptador en lugar de alcanzar directamente a través de las capas. - -## Reglas estrictas de dependencia - -- Las capas inferiores no deben depender de las capas superiores. -- La UI no debe evadir los contratos de tiempo de ejecución o de servicio. -- El acceso a datos debe ingresar a través de repositorios o adaptadores equivalentes. -- Las utilidades compartidas deben permanecer genéricas y no deben acumular lógica de dominio. -- Las nuevas dependencias deben justificarse en el plan o documento de diseño correspondiente. - -## Interfaces transversales - -| Preocupación | Límite aprobado | Notas | -|-------------|-----------------|-------| -| Logging y trazabilidad | `[provider / utility path]` | `[structured only, no ad hoc console use]` | -| Autenticación | `[provider path]` | `[token/session rules]` | -| APIs externas | `[client or provider path]` | `[rate limit / retry guidance]` | -| Feature flags | `[flag boundary]` | `[ownership]` | - -## Puntos calientes actuales - -- `[area that is hardest for agents to change safely]` -- `[area with weak boundaries or fragile tests]` - -## Lista de verificación de cambios - -Cuando modifiques código relevante para la arquitectura: - -1. Actualiza este archivo si el mapa de dominios o los límites permitidos cambiaron. -2. Actualiza el documento de diseño relacionado en `docs/design-docs/` si el razonamiento cambió. -3. Agrega o actualiza una verificación ejecutable si la regla debe aplicarse mecánicamente. diff --git a/courses/learn-harness-engineering/docs/es/resources/openai-advanced/repo-template/docs/DESIGN.md b/courses/learn-harness-engineering/docs/es/resources/openai-advanced/repo-template/docs/DESIGN.md deleted file mode 100644 index 7c0c7943..00000000 --- a/courses/learn-harness-engineering/docs/es/resources/openai-advanced/repo-template/docs/DESIGN.md +++ /dev/null @@ -1,28 +0,0 @@ -# DESIGN.md - -Este archivo es el punto de entrada de diseño. Mantenlo breve y úsalo para enrutar hacia los -archivos más detallados en `docs/design-docs/`. - -## Propósito - -Registrar decisiones duraderas de diseño de producto y sistema que deberían sobrevivir más allá de -una sola conversación, sprint o memoria de revisor. - -## Lee esto cuando - -- necesites la filosofía de diseño actual -- estés a punto de introducir un nuevo patrón -- necesites saber qué decisiones de diseño están resueltas frente a las que aún están abiertas - -## Documentos de diseño canónicos - -- `docs/design-docs/index.md`: índice de documentos aceptados, propuestos y deprecados -- `docs/design-docs/core-beliefs.md`: creencias agent-first a nivel de proyecto - -## Reglas de diseño - -- Mantén los documentos de diseño pequeños y actualizados. -- Prefiere un documento por área de decisión. -- Vincula los documentos de diseño desde planes y especificaciones cuando un cambio depende de ellos. -- Si una regla de diseño se vuelve operativamente crítica, promuévela a una verificación - automatizada o actualiza `ARCHITECTURE.md`. diff --git a/courses/learn-harness-engineering/docs/es/resources/openai-advanced/repo-template/docs/FRONTEND.md b/courses/learn-harness-engineering/docs/es/resources/openai-advanced/repo-template/docs/FRONTEND.md deleted file mode 100644 index 3dab26ab..00000000 --- a/courses/learn-harness-engineering/docs/es/resources/openai-advanced/repo-template/docs/FRONTEND.md +++ /dev/null @@ -1,24 +0,0 @@ -# FRONTEND.md - -Este archivo define expectativas estables del frontend para que los agentes no inventen -patrones de UI de forma impredecible. - -## Principios de UI - -- Optimiza para la claridad antes que para la novedad. -- Mantén los flujos de interacción descubribles y reiniciables. -- Prefiere un pequeño número de componentes reutilizables sobre variantes de un solo uso. -- Las verificaciones de accesibilidad son parte de la verificación normal, no trabajo de pulido. - -## Barreras de protección - -- Documenta el sistema de diseño o la biblioteca de componentes en `docs/references/`. -- Registra los estados clave visibles para el usuario: vacío, cargando, éxito, error, reintento. -- Mantén el texto, el comportamiento del teclado y la jerarquía visual consistentes entre flujos. -- Cuando se corrija un error de UI, agrega o actualiza el paso de validación correspondiente. - -## Expectativas de verificación - -- Captura evidencia para los recorridos de usuario críticos. -- Registra los pasos de validación del navegador o tiempo de ejecución en el plan relevante. -- Si las regresiones visuales son comunes, estandariza las verificaciones de capturas de pantalla o DOM. diff --git a/courses/learn-harness-engineering/docs/es/resources/openai-advanced/repo-template/docs/PLANS.md b/courses/learn-harness-engineering/docs/es/resources/openai-advanced/repo-template/docs/PLANS.md deleted file mode 100644 index 107ff466..00000000 --- a/courses/learn-harness-engineering/docs/es/resources/openai-advanced/repo-template/docs/PLANS.md +++ /dev/null @@ -1,34 +0,0 @@ -# PLANS.md - -Este archivo define cómo se crean, actualizan, completan y archivan los planes de ejecución. - -## Cuándo Se Requiere Un Plan - -Crea un plan de ejecución cuando el trabajo: - -- abarca más de una sesión -- modifica más de un subsistema -- tiene riesgos no triviales de verificación o despliegue -- depende de decisiones abiertas que deben registrarse - -## Ubicaciones de Planes - -- `docs/exec-plans/active/`: planes que actualmente dirigen el trabajo -- `docs/exec-plans/completed/`: planes finalizados conservados para contexto futuro del agente -- `docs/exec-plans/tech-debt-tracker.md`: trabajo pospuesto y seguimientos - -## Secciones Mínimas del Plan - -- objetivo -- alcance y fuera de alcance -- ruta de verificación -- riesgos y bloqueos -- registro de progreso -- decisiones abiertas - -## Reglas de Operación - -- Un plan activo debe tener un paso actual claramente asignado. -- Actualiza el plan a medida que avanza el trabajo; no lo trates como texto estático. -- Si una decisión cambia la dirección de implementación, regístrala en el plan. -- Mueve los planes finalizados a `completed/` para que los agentes puedan descubrir el contexto previo. diff --git a/courses/learn-harness-engineering/docs/es/resources/openai-advanced/repo-template/docs/PRODUCT_SENSE.md b/courses/learn-harness-engineering/docs/es/resources/openai-advanced/repo-template/docs/PRODUCT_SENSE.md deleted file mode 100644 index 123fe67f..00000000 --- a/courses/learn-harness-engineering/docs/es/resources/openai-advanced/repo-template/docs/PRODUCT_SENSE.md +++ /dev/null @@ -1,25 +0,0 @@ -# PRODUCT_SENSE.md - -Este archivo captura el criterio de producto duradero que los agentes no pueden inferir -confiablemente solo desde el código. - -## Núcleo del Producto - -- Usuario principal: `[reemplazar]` -- Trabajo por hacer: `[reemplazar]` -- Frustración principal a eliminar: `[reemplazar]` -- Nivel de calidad para aceptación: `[reemplazar]` - -## Reglas de Producto - -- Favorecer la fiabilidad visible para el usuario sobre la cantidad de funcionalidades. -- Tratar el comportamiento ambiguo como un vacío de especificación, no como permiso para adivinar. -- Si la implementación cambia lo que los usuarios ven o confían, actualizar la spec correspondiente. -- Usar specs de producto para flujos concretos, y usar este archivo para prioridades de producto transversales. - -## Patrones de No-Acción - -- Acciones destructivas ocultas -- Fallo silencioso sin retroalimentación al usuario -- Fuente de verdad poco clara para estado visible -- Funcionalidades que no se pueden explicar en una sola frase diff --git a/courses/learn-harness-engineering/docs/es/resources/openai-advanced/repo-template/docs/QUALITY_SCORE.md b/courses/learn-harness-engineering/docs/es/resources/openai-advanced/repo-template/docs/QUALITY_SCORE.md deleted file mode 100644 index a03d6288..00000000 --- a/courses/learn-harness-engineering/docs/es/resources/openai-advanced/repo-template/docs/QUALITY_SCORE.md +++ /dev/null @@ -1,39 +0,0 @@ -# QUALITY_SCORE.md - -Este documento realiza un seguimiento de si el repositorio se está fortaleciendo o debilitando con el tiempo. - -## Escala de Calificación - -- `A`: verificado, legible, estable, límites enforcementados -- `B`: funcionando con brechas menores -- `C`: parcialmente funcionando, confusión o inestabilidad notable -- `D`: roto, inseguro o estructuralmente poco claro - -## Dominios de Producto - -| Dominio | Calificación | Verificación | Legibilidad del Agente | Estabilidad de Tests | Brechas Clave | Última Actualización | -|---------|-------------|-------------|----------------------|---------------------|---------------|---------------------| -| `[domain-a]` | - | - | - | - | - | - | -| `[domain-b]` | - | - | - | - | - | - | -| `[domain-c]` | - | - | - | - | - | - | - -## Capas Arquitectónicas - -| Capa | Calificación | Enforcement de Límites | Legibilidad del Agente | Brechas Clave | Última Actualización | -|------|-------------|----------------------|----------------------|---------------|---------------------| -| Types | - | - | - | - | - | -| Services | - | - | - | - | - | -| Runtime | - | - | - | - | - | -| UI | - | - | - | - | - | - -## Instantáneas de Benchmark - -| Fecha | Variante del Harness | Tasa de Completitud | Reintentos | Defectos Antes de Revisión | Notas | -|------|---------------------|-------------------|-----------|---------------------------|------| -| YYYY-MM-DD | `[baseline / improved / simplified]` | - | - | - | - | - -## Registro de Simplificación - -| Fecha | Componente Eliminado | Resultado | Decisión | -|------|---------------------|----------|----------| -| YYYY-MM-DD | `[component]` | `[degraded / unchanged]` | `[restore / keep removed]` | diff --git a/courses/learn-harness-engineering/docs/es/resources/openai-advanced/repo-template/docs/RELIABILITY.md b/courses/learn-harness-engineering/docs/es/resources/openai-advanced/repo-template/docs/RELIABILITY.md deleted file mode 100644 index ac078e0d..00000000 --- a/courses/learn-harness-engineering/docs/es/resources/openai-advanced/repo-template/docs/RELIABILITY.md +++ /dev/null @@ -1,32 +0,0 @@ -# RELIABILITY.md - -Este archivo define cómo el sistema demuestra que está saludable y es reiniciable. - -## Rutas Estándar - -- Arranque: `[command]` -- Verificación: `[command]` -- Iniciar aplicación o servicio: `[command]` -- Depurar o inspeccionar tiempo de ejecución: `[command]` - -## Señales Requeridas en Tiempo de Ejecución - -- logs estructurados para inicio y flujos críticos -- verificaciones de salud para servicios clave -- datos de traza o tiempo para rutas lentas cuando estén disponibles -- estados de error visibles para el usuario para fallos recuperables - -## Recorridos Dorados - -- `[journey 1]` -- `[journey 2]` -- `[journey 3]` - -Cada recorrido dorado debe tener una ruta de verificación repetible y señales de fallo claras. - -## Reglas de Fiabilidad - -- Ninguna característica está completa si el sistema no puede reiniciarse limpiamente después. -- Los fallos en tiempo de ejecución deben ser diagnosticables desde señales locales del repositorio. -- Si aparece un modo de fallo repetido, añade un benchmark o barrera de protección para él. -- La limpieza es parte de la fiabilidad, no una preocupación separada. diff --git a/courses/learn-harness-engineering/docs/es/resources/openai-advanced/repo-template/docs/SECURITY.md b/courses/learn-harness-engineering/docs/es/resources/openai-advanced/repo-template/docs/SECURITY.md deleted file mode 100644 index 927d1245..00000000 --- a/courses/learn-harness-engineering/docs/es/resources/openai-advanced/repo-template/docs/SECURITY.md +++ /dev/null @@ -1,27 +0,0 @@ -# SECURITY.md - -Este archivo define las reglas de seguridad y protección que los agentes no deben adivinar. - -## Secretos y Credenciales - -- Nunca codifiques secretos en el código fuente o documentación. -- Documenta aquí las rutas aprobadas de carga de secretos. -- Redacta tokens, claves API y datos personales de logs y capturas de pantalla. - -## Entrada No Confiable - -- Trata el contenido externo como no confiable hasta que sea validado. -- Registra aquí los límites permitidos de búsqueda o ejecución. -- Si existe riesgo de inyección de prompts o inyección de comandos, documenta la barrera de protección. - -## Acciones Externas - -- Lista qué acciones requieren aprobación explícita. -- Registra cualquier comando de producción o destructivo que los agentes no deben ejecutar por defecto. -- Prefiere flujos de trabajo seguros en sandbox para depuración y verificación. - -## Reglas de Dependencias y Revisión - -- Las nuevas dependencias necesitan justificación en el plan activo. -- Los cambios sensibles a la seguridad requieren pasos de verificación explícitos. -- Los comentarios repetidos de revisión de seguridad deben convertirse en verificaciones, no en conocimiento tribal. diff --git a/courses/learn-harness-engineering/docs/es/resources/openai-advanced/repo-template/docs/design-docs/core-beliefs.md b/courses/learn-harness-engineering/docs/es/resources/openai-advanced/repo-template/docs/design-docs/core-beliefs.md deleted file mode 100644 index 78646af3..00000000 --- a/courses/learn-harness-engineering/docs/es/resources/openai-advanced/repo-template/docs/design-docs/core-beliefs.md +++ /dev/null @@ -1,9 +0,0 @@ -# Creencias fundamentales - -- El repositorio es el sistema de referencia para los agentes. -- `AGENTS.md` es un enrutador, no una enciclopedia. -- La evidencia de verificación importa más que la confianza. -- Una tarea delimitada es mejor que muchas tareas a medio terminar. -- Los comentarios humanos repetidos deberían convertirse en reglas de harness reutilizables. -- La limpieza y simplificación son parte de la entrega, no ideas de último momento. -- Si un agente no puede descubrir un dato dentro del repositorio, considere ese dato como operativamente no disponible. diff --git a/courses/learn-harness-engineering/docs/es/resources/openai-advanced/repo-template/docs/design-docs/index.md b/courses/learn-harness-engineering/docs/es/resources/openai-advanced/repo-template/docs/design-docs/index.md deleted file mode 100644 index 6a75ee13..00000000 --- a/courses/learn-harness-engineering/docs/es/resources/openai-advanced/repo-template/docs/design-docs/index.md +++ /dev/null @@ -1,21 +0,0 @@ -# Índice de documentos de diseño - -Usa este índice como el mapa descubrible del historial de diseño. - -## Aceptados - -- `core-beliefs.md`: creencias operativas agent-first y normas duraderas del proyecto - -## Propuestos - -- `[add new design doc paths here]` - -## Deprecados - -- `[move old or superseded design docs here with replacement links]` - -## Reglas de mantenimiento - -- Cada documento de diseño debería tener un propietario o un disparador de actualización. -- Elimina documentos obsoletos o márcalos como deprecados en lugar de dejar que se desvíen. -- Vincula los planes de ejecución activos a los documentos de diseño de los que dependen. diff --git a/courses/learn-harness-engineering/docs/es/resources/openai-advanced/repo-template/docs/exec-plans/active/index.md b/courses/learn-harness-engineering/docs/es/resources/openai-advanced/repo-template/docs/exec-plans/active/index.md deleted file mode 100644 index cbc60b2f..00000000 --- a/courses/learn-harness-engineering/docs/es/resources/openai-advanced/repo-template/docs/exec-plans/active/index.md +++ /dev/null @@ -1,10 +0,0 @@ -# Planes activos - -Mantén un archivo markdown por cada plan de ejecución activo en esta carpeta. - -Patrón de nombre de archivo sugerido: - -- `YYYY-MM-DD-short-topic.md` - -Cada plan activo debería estar lo suficientemente actualizado para que una sesión nueva -de agente pueda reanudar el trabajo solo desde el repositorio. diff --git a/courses/learn-harness-engineering/docs/es/resources/openai-advanced/repo-template/docs/exec-plans/completed/index.md b/courses/learn-harness-engineering/docs/es/resources/openai-advanced/repo-template/docs/exec-plans/completed/index.md deleted file mode 100644 index 2be9ba3e..00000000 --- a/courses/learn-harness-engineering/docs/es/resources/openai-advanced/repo-template/docs/exec-plans/completed/index.md +++ /dev/null @@ -1,5 +0,0 @@ -# Planes Completados - -Mueve los planes finalizados aquí en lugar de eliminarlos. Los planes completados son parte -de la superficie de memoria del repositorio y ayudan a ejecuciones futuras del agente a entender -por qué el código tiene el aspecto que tiene. diff --git a/courses/learn-harness-engineering/docs/es/resources/openai-advanced/repo-template/docs/exec-plans/tech-debt-tracker.md b/courses/learn-harness-engineering/docs/es/resources/openai-advanced/repo-template/docs/exec-plans/tech-debt-tracker.md deleted file mode 100644 index 4abcfac6..00000000 --- a/courses/learn-harness-engineering/docs/es/resources/openai-advanced/repo-template/docs/exec-plans/tech-debt-tracker.md +++ /dev/null @@ -1,7 +0,0 @@ -# Rastreador de deuda técnica - -Usa este archivo para deuda que es real, reconocida e intencionalmente pospuesta. - -| Fecha | Área | Deuda | Por qué se pospuso | Riesgo | Próximo disparador | -|-------|------|-------|--------------------|--------|--------------------| -| YYYY-MM-DD | `[area]` | `[debt]` | `[reason]` | `[risk]` | `[when to revisit]` | diff --git a/courses/learn-harness-engineering/docs/es/resources/openai-advanced/repo-template/docs/generated/db-schema.md b/courses/learn-harness-engineering/docs/es/resources/openai-advanced/repo-template/docs/generated/db-schema.md deleted file mode 100644 index 8a9419ee..00000000 --- a/courses/learn-harness-engineering/docs/es/resources/openai-advanced/repo-template/docs/generated/db-schema.md +++ /dev/null @@ -1,14 +0,0 @@ -# Esquema de Base de Datos - -Usa esta carpeta para artefactos generados o derivados que los agentes deberían poder -inspeccionar sin necesidad de ingeniería inversa desde el código. - -## Fuente - -- Generado desde: `[command or source path]` -- Última actualización: `YYYY-MM-DD` - -## Notas - -- No edites manualmente las secciones generadas. -- Regenera este archivo cuando el esquema subyacente cambie. diff --git a/courses/learn-harness-engineering/docs/es/resources/openai-advanced/repo-template/docs/product-specs/index.md b/courses/learn-harness-engineering/docs/es/resources/openai-advanced/repo-template/docs/product-specs/index.md deleted file mode 100644 index 4793fa0f..00000000 --- a/courses/learn-harness-engineering/docs/es/resources/openai-advanced/repo-template/docs/product-specs/index.md +++ /dev/null @@ -1,13 +0,0 @@ -# Índice de Especificaciones de Producto - -Usa esta carpeta para las especificaciones actuales del comportamiento visible al usuario. - -## Especificaciones Activas - -- `new-user-onboarding.md` - -## Reglas - -- Las especificaciones deben describir el comportamiento visible al usuario y los criterios de aceptación. -- Si la implementación diverge de la especificación, actualiza una de ellas en la misma sesión. -- Mantén este índice actualizado para que un agente nuevo pueda descubrir el alcance del producto rápidamente. diff --git a/courses/learn-harness-engineering/docs/es/resources/openai-advanced/repo-template/docs/product-specs/new-user-onboarding.md b/courses/learn-harness-engineering/docs/es/resources/openai-advanced/repo-template/docs/product-specs/new-user-onboarding.md deleted file mode 100644 index 693935ff..00000000 --- a/courses/learn-harness-engineering/docs/es/resources/openai-advanced/repo-template/docs/product-specs/new-user-onboarding.md +++ /dev/null @@ -1,26 +0,0 @@ -# Onboarding de Nuevo Usuario - -## Objetivo - -Describe la experiencia de primera ejecución que debería tener un usuario nuevo. - -## Condiciones de Entrada - -- `[state before the flow starts]` - -## Flujo de Usuario - -1. `[step one]` -2. `[step two]` -3. `[step three]` - -## Criterios de Aceptación - -- `[observable outcome]` -- `[observable outcome]` -- `[observable outcome]` - -## Estados de Fallo - -- `[recoverable error and user feedback]` -- `[blocked state and fallback]` diff --git a/courses/learn-harness-engineering/docs/es/resources/openai-advanced/repo-template/docs/references/design-system-reference-llms.txt b/courses/learn-harness-engineering/docs/es/resources/openai-advanced/repo-template/docs/references/design-system-reference-llms.txt deleted file mode 100644 index 6f1c612c..00000000 --- a/courses/learn-harness-engineering/docs/es/resources/openai-advanced/repo-template/docs/references/design-system-reference-llms.txt +++ /dev/null @@ -1,9 +0,0 @@ -Purpose: store model-friendly reference material for the design system. - -Suggested contents: -- component naming rules -- spacing and typography tokens -- state variants -- accessibility expectations - -Keep this file concise and refresh it when the upstream design system changes. diff --git a/courses/learn-harness-engineering/docs/es/resources/openai-advanced/repo-template/docs/references/nixpacks-llms.txt b/courses/learn-harness-engineering/docs/es/resources/openai-advanced/repo-template/docs/references/nixpacks-llms.txt deleted file mode 100644 index c30cc9c2..00000000 --- a/courses/learn-harness-engineering/docs/es/resources/openai-advanced/repo-template/docs/references/nixpacks-llms.txt +++ /dev/null @@ -1,8 +0,0 @@ -Purpose: store a clean agent-readable extract of the deployment or packaging -rules your repository depends on. - -Suggested contents: -- build entrypoints -- runtime assumptions -- environment variable expectations -- common failure signatures diff --git a/courses/learn-harness-engineering/docs/es/resources/openai-advanced/repo-template/docs/references/uv-llms.txt b/courses/learn-harness-engineering/docs/es/resources/openai-advanced/repo-template/docs/references/uv-llms.txt deleted file mode 100644 index f2ceba9a..00000000 --- a/courses/learn-harness-engineering/docs/es/resources/openai-advanced/repo-template/docs/references/uv-llms.txt +++ /dev/null @@ -1,8 +0,0 @@ -Purpose: store a compact reference for your Python package and environment -workflow when `uv` or similar tooling matters to the repo. - -Suggested contents: -- install and sync commands -- lockfile policy -- virtualenv expectations -- verification commands diff --git a/courses/learn-harness-engineering/docs/es/resources/openai-advanced/repo-template/index.md b/courses/learn-harness-engineering/docs/es/resources/openai-advanced/repo-template/index.md deleted file mode 100644 index 5abcb1f9..00000000 --- a/courses/learn-harness-engineering/docs/es/resources/openai-advanced/repo-template/index.md +++ /dev/null @@ -1,24 +0,0 @@ -# Plantilla de Repositorio Avanzada - -Copia este starter en un repositorio real cuando quieras una superficie de documentación -agent-first estilo OpenAI en lugar de solo un harness mínimo. - -## Orden de Copia - -1. Copia `AGENTS.md` y `ARCHITECTURE.md` a la raíz del repositorio. -2. Copia todo el árbol `docs/`. -3. Rellena `docs/PRODUCT_SENSE.md`, `docs/QUALITY_SCORE.md` y - `docs/RELIABILITY.md` primero. -4. Añade tu primer plan activo en `docs/exec-plans/active/`. -5. Mantén los archivos de entrada cortos y enruta el detalle a los documentos enlazados. - -## Para Qué Optimiza Esta Plantilla - -- contexto duradero local al repositorio -- revelación progresiva en lugar de un archivo de instrucciones gigante -- ciclo de vida explícito de planes -- seguimiento de calidad a lo largo del tiempo -- límites legibles para agentes y humanos - -Trata cada archivo aquí como un starter. Reemplaza los marcadores de posición, ejemplos y comandos -de muestra con los detalles específicos de tu proyecto real antes de depender de él. diff --git a/courses/learn-harness-engineering/docs/es/resources/openai-advanced/sops/chrome-devtools-validation-loop.md b/courses/learn-harness-engineering/docs/es/resources/openai-advanced/sops/chrome-devtools-validation-loop.md deleted file mode 100644 index ecea3643..00000000 --- a/courses/learn-harness-engineering/docs/es/resources/openai-advanced/sops/chrome-devtools-validation-loop.md +++ /dev/null @@ -1,51 +0,0 @@ -# SOP: Bucle de Validación con Chrome DevTools - -Usa este SOP cuando el trabajo de UI depende de la interacción real en tiempo de ejecución y las capturas de pantalla, -el estado del DOM y la salida de la consola importan más que la inspección del código por sí sola. - -## Objetivo - -Convertir la validación de UI en un bucle de interacción repetible que el agente pueda ejecutar hasta -que el recorrido esté limpio. - -## Bucle Principal - -1. Selecciona la página objetivo o la instancia de la aplicación. -2. Limpia el ruido obsoleto de la consola. -3. Captura el estado ANTES. -4. Dispara la ruta de UI. -5. Observa los eventos en tiempo de ejecución durante la interacción. -6. Captura el estado DESPUÉS. -7. Aplica la corrección y reinicia la aplicación si es necesario. -8. Vuelve a ejecutar la validación hasta que el recorrido esté limpio. - -## Entradas Requeridas - -- un comando de inicio estable -- un recorrido de UI reproducible -- una forma de capturar el DOM, la consola o capturas de pantalla -- una regla sobre qué cuenta como "limpio" - -## SOP de Ejecución - -1. Escribe el recorrido objetivo en el plan activo. -2. Define el éxito en términos observables: texto presente, botón habilitado, error eliminado, - consola limpia, solicitud exitosa. -3. Captura el estado inicial antes de la interacción. -4. Dispara exactamente una ruta a la vez. -5. Registra los eventos en tiempo de ejecución, los cambios del DOM y la salida visible. -6. Si el recorrido falla, corrige la capa responsable más pequeña y reinicia. -7. Vuelve a ejecutar la misma ruta y compara la evidencia ANTES/DESPUÉS. - -## Criterios de Limpieza - -- el estado visible intencionado está presente -- los errores inesperados están ausentes -- el ruido de la consola está comprendido o eliminado -- volver a ejecutar la misma ruta produce el mismo resultado - -## Artefactos del Repositorio A Actualizar - -- plan de ejecución activo -- `docs/RELIABILITY.md` si el recorrido se convierte en una ruta dorada -- especificación de producto si el comportamiento visible cambió diff --git a/courses/learn-harness-engineering/docs/es/resources/openai-advanced/sops/encode-knowledge-into-repo.md b/courses/learn-harness-engineering/docs/es/resources/openai-advanced/sops/encode-knowledge-into-repo.md deleted file mode 100644 index 80987112..00000000 --- a/courses/learn-harness-engineering/docs/es/resources/openai-advanced/sops/encode-knowledge-into-repo.md +++ /dev/null @@ -1,45 +0,0 @@ -# SOP: Codificar Conocimiento No Visible En El Repositorio - -Usa este SOP cuando un contexto importante todavía reside en Google Docs, hilos de chat, -tickets o en la cabeza de las personas. - -## Objetivo - -Hacer que el conocimiento invisible para el agente sea descubrible en el código base para que una sesión nueva -pueda actuar sobre él sin depender de una conversación previa. - -## Señales de Disparo - -- El agente sigue preguntando cómo funciona el sistema. -- Las personas dicen "decidimos esto en Slack" o "sigue lo que X dijo la semana pasada." -- Las revisiones referencian reglas de producto o seguridad que no están escritas en el repositorio. -- Las sesiones nuevas repiten trabajo de descubrimiento que ya debería estar resuelto. - -## SOP de Ejecución - -1. Lista las fuentes de conocimiento invisible: docs, chats, reglas tácitas del equipo, decisiones verbales. -2. Para cada fuente, pregunta: ¿es esto arquitectura, comportamiento de producto, política de seguridad, - expectativa de fiabilidad, contexto de plan o material de referencia? -3. Codifícalo en el artefacto del repositorio correspondiente: - - arquitectura -> `ARCHITECTURE.md` - - comportamiento de producto -> `docs/product-specs/` - - razonamiento de diseño -> `docs/design-docs/` - - estado de ejecución -> `docs/exec-plans/` - - referencias externas repetidas -> `docs/references/` - - expectativas de calidad o fiabilidad -> `docs/QUALITY_SCORE.md` o `docs/RELIABILITY.md` -4. Reemplaza declaraciones vagas con redacción operacionalmente útil. -5. Elimina o deprecia copias obsoletas para que el repositorio mantenga una única verdad descubrible. - -## Buenas Reglas de Codificación - -- Escribe para la descubribilidad, no para la completitud literaria. -- Prefiere documentos cortos con nombres de archivo claros. -- Enlaza artefactos relacionados entre sí. -- Almacena reglas duraderas, no transcripciones de reuniones. -- Actualiza el repositorio en la misma sesión en que se toma la decisión. - -## Definición de Completado - -- Un agente nuevo puede descubrir la regla relevante sin preguntar a un humano. -- El mismo hecho no está disperso en múltiples archivos contradictorios. -- El nuevo artefacto reside cerca del código o flujo de trabajo que gobierna. diff --git a/courses/learn-harness-engineering/docs/es/resources/openai-advanced/sops/index.md b/courses/learn-harness-engineering/docs/es/resources/openai-advanced/sops/index.md deleted file mode 100644 index d6369c92..00000000 --- a/courses/learn-harness-engineering/docs/es/resources/openai-advanced/sops/index.md +++ /dev/null @@ -1,25 +0,0 @@ -# SOPs Avanzados de OpenAI - -Estos SOPs traducen los patrones operacionales del artículo en playbooks de ejecución concretos -que puedes seguir o adaptar. - -## SOPs Incluidos - -- [`layered-domain-architecture.md`](./layered-domain-architecture.md): - establece capas explícitas y límites transversales -- [`encode-knowledge-into-repo.md`](./encode-knowledge-into-repo.md): - mueve el conocimiento invisible desde chat, docs y memoria a archivos locales del repositorio -- [`observability-feedback-loop.md`](./observability-feedback-loop.md): - proporciona a los agentes logs, métricas, trazas y un bucle de depuración repetible -- [`chrome-devtools-validation-loop.md`](./chrome-devtools-validation-loop.md): - usa automatización del navegador y capturas para validar el comportamiento de UI hasta que esté limpio - -## Cómo Usarlos - -1. Elige el SOP que coincida con tu cuello de botella actual. -2. Usa la lista de verificación para configurar los artefactos o herramientas faltantes. -3. Codifica las reglas resultantes en los docs de tu copia de `repo-template/`. -4. Convierte los comentarios de revisión repetidos en verificaciones, scripts o barreras de protección. - -Estos no están pensados para seguirse a ciegas. Están pensados para hacer el harness -más legible, enforcementable y repetible. diff --git a/courses/learn-harness-engineering/docs/es/resources/openai-advanced/sops/layered-domain-architecture.md b/courses/learn-harness-engineering/docs/es/resources/openai-advanced/sops/layered-domain-architecture.md deleted file mode 100644 index e30a82f0..00000000 --- a/courses/learn-harness-engineering/docs/es/resources/openai-advanced/sops/layered-domain-architecture.md +++ /dev/null @@ -1,50 +0,0 @@ -# SOP: Arquitectura de Dominio en Capas - -Usa este SOP cuando el agente sigue violando límites, duplicando lógica entre capas, -o produciendo código que se vuelve difícil de revisar después de algunas sesiones. - -## Objetivo - -Hacer que los límites del dominio sean lo suficientemente explícitos para que los agentes -puedan moverse rápidamente sin degradar silenciosamente la estructura. - -## Modelo Objetivo - -Dentro de un dominio de negocio, prefiere este flujo direccional: - -`Types -> Config -> Repo -> Service -> Runtime -> UI` - -Las preocupaciones transversales deben entrar a través de proveedores o adaptadores explícitos. -Las utilidades compartidas permanecen fuera del dominio y no deben acumular lógica de dominio. - -## Lista de Verificación de Configuración - -- Define los dominios actuales en `ARCHITECTURE.md`. -- Escribe las direcciones de dependencia permitidas en `ARCHITECTURE.md`. -- Registra las interfaces transversales como auth, telemetría y APIs externas. -- Añade una nota breve para la violación de límite más difícil actualmente. -- Decide qué debe ser enforcementado mecánicamente por lint, tests o scripts. - -## SOP de Ejecución - -1. Mapea el código base en dominios antes de tocar el estilo de implementación. -2. Para cada dominio, identifica la secuencia de capas permitida. -3. Identifica todas las preocupaciones transversales y enrútalas a través de proveedores o adaptadores. -4. Mueve la lógica compartida ambigua al dominio propietario o a utilidades verdaderamente genéricas. -5. Documenta las reglas en `ARCHITECTURE.md`. -6. Añade una barrera de protección ejecutable para la violación de mayor costo. -7. Actualiza la puntuación de calidad después del cambio. - -## Definición de Completado - -- Un agente nuevo puede decir qué capa es propietaria de un cambio. -- El código de UI ya no accede directamente al repositorio o efectos secundarios externos. -- Las preocupaciones transversales tienen puntos de entrada nombrados. -- Al menos un límite importante es enforcementado mecánicamente. - -## Artefactos del Repositorio A Actualizar - -- `ARCHITECTURE.md` -- `docs/QUALITY_SCORE.md` -- `docs/design-docs/` cuando el razonamiento cambió -- `docs/PLANS.md` o el plan de ejecución activo diff --git a/courses/learn-harness-engineering/docs/es/resources/openai-advanced/sops/observability-feedback-loop.md b/courses/learn-harness-engineering/docs/es/resources/openai-advanced/sops/observability-feedback-loop.md deleted file mode 100644 index b4e349cb..00000000 --- a/courses/learn-harness-engineering/docs/es/resources/openai-advanced/sops/observability-feedback-loop.md +++ /dev/null @@ -1,44 +0,0 @@ -# SOP: Bucle de Retroalimentación de Observabilidad - -Usa este SOP cuando la depuración es lenta, los agentes siguen afirmando éxito sin -evidencia, o el comportamiento en tiempo de ejecución es más difícil de inspeccionar que el código en sí. - -## Objetivo - -Proporcionar al agente un bucle de retroalimentación local sobre logs, métricas, trazas y cargas de -trabajo ejecutables para que pueda razonar desde la ejecución, no solo desde la inspección del código. - -## Stack Mínimo - -- la aplicación emite logs estructurados -- la aplicación emite métricas y trazas cuando sea factible -- capa local de distribución o recopilación -- interfaces de consulta para logs, métricas y trazas -- carga de trabajo repetible o recorrido de usuario para volver a ejecutar después de cada cambio - -## SOP de Ejecución - -1. Define los recorridos dorados en tiempo de ejecución que más importan. -2. Añade logs estructurados al inicio y a la ruta crítica. -3. Añade métricas para latencia, conteos de fallos o profundidad de cola donde sea útil. -4. Añade trazas o marcadores de tiempo para flujos lentos o de múltiples pasos. -5. Haz que las señales sean consultables desde el entorno de desarrollo local. -6. Proporciona al agente una carga de trabajo o escenario repetible para volver a ejecutar. -7. Requiere el bucle: consultar -> correlacionar -> razonar -> implementar -> reiniciar -> - volver a ejecutar -> verificar. - -## Lista de Verificación de Sesión de Depuración - -- ¿Qué falló? -- ¿Qué señal prueba el fallo? -- ¿Qué capa es propietaria del fallo? -- ¿Qué cambió después de la corrección? -- ¿La aplicación se reinició limpiamente? -- ¿La misma carga de trabajo pasó después de volver a ejecutar? - -## Definición de Completado - -- El agente puede explicar un modo de fallo a partir de evidencia en tiempo de ejecución. -- La misma carga de trabajo puede volver a ejecutarse después de cada cambio. -- Reiniciar y volver a ejecutar son parte del bucle de tareas normal. -- Las señales de fiabilidad están documentadas en `docs/RELIABILITY.md`. diff --git a/courses/learn-harness-engineering/docs/es/resources/reference/coding-agent-startup-flow.md b/courses/learn-harness-engineering/docs/es/resources/reference/coding-agent-startup-flow.md deleted file mode 100644 index 7a84cf37..00000000 --- a/courses/learn-harness-engineering/docs/es/resources/reference/coding-agent-startup-flow.md +++ /dev/null @@ -1,33 +0,0 @@ -# Flujo de Inicio del Agente de Codificación - -Usa esto al comienzo de cada sesión después de que la inicialización esté completa. - -## Plantilla de Inicio Fija - -1. Ejecuta `pwd` y confirma la raíz del repositorio. -2. Lee `claude-progress.md`. -3. Lee `feature_list.json`. -4. Revisa los commits recientes con `git log --oneline -5`. -5. Ejecuta `./init.sh`. -6. Ejecuta una ruta de smoke o end-to-end de referencia. -7. Si la referencia está rota, corrígela primero. -8. Selecciona la característica inacabada de mayor prioridad. -9. Trabaja solo en esa característica hasta que esté verificada o explícitamente bloqueada. - -## Por Qué Este Orden Importa - -- `pwd` previene trabajo accidental en el directorio equivocado. -- los archivos de progreso y características recuperan estado duradero antes de que comiencen nuevas ediciones. -- los commits recientes explican qué cambió más recientemente. -- `init.sh` estandariza el inicio en lugar de depender de la memoria. -- la verificación de referencia captura estados iniciales rotos antes de que el trabajo nuevo los oculte. - -## Espejo de Fin de Sesión - -La misma sesión debería terminar: - -1. registrando el progreso -2. actualizando el estado de las características -3. escribiendo una entrega si es necesario -4. haciendo commit del trabajo seguro -5. dejando una ruta de reinicio limpia diff --git a/courses/learn-harness-engineering/docs/es/resources/reference/index.md b/courses/learn-harness-engineering/docs/es/resources/reference/index.md deleted file mode 100644 index 715cd392..00000000 --- a/courses/learn-harness-engineering/docs/es/resources/reference/index.md +++ /dev/null @@ -1,85 +0,0 @@ -# Referencia - -Estas notas explican cómo usar las plantillas como un harness funcional en lugar de una -colección suelta de archivos. - -## Notas de Referencia Interna - -- [`method-map.md`](./method-map.md): mapea los modos de fallo comunes de larga duración al - artefacto o política que los aborda primero -- [`initializer-agent-playbook.md`](./initializer-agent-playbook.md): qué debe dejar atrás el - inicializador antes de que comience el trabajo de características -- [`coding-agent-startup-flow.md`](./coding-agent-startup-flow.md): flujo fijo de inicio de - sesión para ejecuciones de codificación posteriores -- [`prompt-calibration.md`](./prompt-calibration.md): cómo mantener las instrucciones raíz - precisas sin hacerlas hinchadas y frágiles - -## Artículos Principales - -Esta lista es intencionalmente estrecha. Un harness significa el sistema de ejecución alrededor -del modelo: el bucle del agente, ejecución de herramientas, sandboxing, estado, contexto, -verificación, terminación, orquestación y observabilidad. Artículos generales de ingeniería -de prompts o de frameworks amplios de agentes no pertenecen a la lista principal. - -Los tres artículos originales siguen siendo la columna vertebral del curso: - -- [OpenAI: Harness engineering: leveraging Codex in an agent-first world](https://openai.com/index/harness-engineering/) (2026-02-11): repositorios agent-first, contexto repo-local, linting personalizado y barreras de protección estructurales. -- [Anthropic: Effective harnesses for long-running agents](https://www.anthropic.com/engineering/effective-harnesses-for-long-running-agents) (2025-11-26): agente inicializador, agente de codificación, lista de características, registro de progreso y entrega a través de ventanas de contexto. -- [Anthropic: Harness design for long-running application development](https://www.anthropic.com/engineering/harness-design-long-running-apps) (2026-03-24): roles de planificador / generador / evaluador, reinicios de contexto, simplificación del harness y supuestos obsoletos. - -Solo se añaden algunos artículos altamente relevantes de 2026: - -- [OpenAI: Unrolling the Codex agent loop](https://openai.com/index/unrolling-the-codex-agent-loop/) (2026-01-23): el harness de tiempo de ejecución de Codex, llamadas de herramientas, crecimiento de contexto y terminación del bucle. -- [Anthropic: Demystifying evals for AI agents](https://www.anthropic.com/engineering/demystifying-evals-for-ai-agents) (2026-01-09): evaluando el modelo y el harness juntos, y distinguiendo harnesses de evaluación de harnesses de agentes. -- [LangChain: Improving Deep Agents with harness engineering](https://www.langchain.com/blog/improving-deep-agents-with-harness-engineering) (2026-02-17): manteniendo el modelo fijo mientras se mejoran los prompts del sistema, herramientas, middleware, trazabilidad y auto-verificación para mover un agente de codificación del Top 30 al Top 5 en Terminal Bench 2.0. -- [Thoughtworks / Martin Fowler: Harness engineering for coding agent users](https://martinfowler.com/articles/harness-engineering.html) (2026-04-02): harnesses de usuario de agentes de codificación como guías de feedforward y sensores de feedback, con controles deterministas e inferenciales. -- [Cursor: Continually improving our agent harness](https://cursor.com/blog/continually-improving-agent-harness) (2026-04-30): tratando el harness como un sistema de producto continuamente mejorado con evals offline, métricas online, taxonomía de errores de herramientas, ajuste específico del modelo y cambio de modelo en medio del chat. - -## Referencias Extendidas de 2026 - -Estas no son fuentes centrales del curso, pero son útiles al diseñar módulos específicos -del harness. Esta sección solo mantiene fuentes cuyo contenido cubre directamente el -bucle del agente, ejecución de herramientas, gestión de contexto, verificación, sandboxing, -capas de control o gobernanza de regresión. Productos puros de agentes, anuncios de plataforma, -casos de estudio de equipos y benchmarks están excluidos. - -- [OpenAI: Unlocking the Codex harness: how we built the App Server](https://openai.com/index/unlocking-the-codex-harness/) (2026-02-04): el harness como un protocolo reutilizable de App Server con ciclo de vida de hilos, resume, fork, diffs e integraciones de cliente. -- [OpenAI Developers: Run long horizon tasks with Codex](https://developers.openai.com/blog/run-long-horizon-tasks-with-codex) (2026-02-23): memoria durable de proyecto, validación de hitos y ejemplos de done-when para tareas de larga duración. -- [OpenAI: The next evolution of the Agents SDK](https://openai.com/index/the-next-evolution-of-the-agents-sdk/) (2026-04-15): harnesses nativos del modelo, ejecución en sandbox y ejecución de archivos/comandos. -- [OpenAI: An open-source spec for Codex orchestration: Symphony](https://openai.com/index/open-source-codex-orchestration-symphony/) (2026-04-27): convirtiendo un issue tracker o tablero de Linear en un plano de control multi-agente. -- [Anthropic: Building a C compiler with a team of parallel Claudes](https://www.anthropic.com/engineering/building-c-compiler) (2026-02-05): equipos de agentes paralelos, locks de tareas, sincronización de git, aislamiento en contenedores y bucles autónomos. -- [Anthropic: Scaling Managed Agents: Decoupling the brain from the hands](https://www.anthropic.com/engineering/managed-agents) (2026-04-08): una vista de meta-harness que separa sesión, harness y sandbox como interfaces intercambiables. -- [Anthropic: An update on recent Claude Code quality reports](https://www.anthropic.com/engineering/april-23-postmortem) (2026-04-23): esfuerzo de razonamiento, poda de contexto y prompts del sistema como cambios del harness que necesitan gobernanza de regresión. -- [LangChain: Context Management for Deep Agents](https://www.langchain.com/blog/context-management-for-deepagents) (2026-01-28): descarga al sistema de archivos, truncamiento de llamadas de herramientas, resumen y evals dirigidos para harnesses de gestión de contexto. -- [LangChain: Tuning Deep Agents to Work Well with Different Models](https://www.langchain.com/blog/tuning-deep-agents-different-models) (2026-04-29): perfiles de harness específicos del modelo para prompts, nombres de herramientas, middleware y configuración de subagentes. -- [LangChain: Continual learning for AI agents](https://www.langchain.com/blog/continual-learning-for-ai-agents) (2026-04-05): dividiendo la mejora del agente en capas de modelo, harness y contexto, impulsado por trazas. -- [Microsoft: Agent Harness in Agent Framework](https://devblogs.microsoft.com/agent-framework/agent-harness-in-agent-framework/) (2026-03-12): harnesses de shell/sistema de archivos, flujo de aprobación, ejecución de shell alojada y compactación de contexto. -- [Google: Announcing ADK for Java 1.0.0](https://developers.googleblog.com/announcing-adk-for-java-100-building-the-future-of-ai-agents-in-java/) (2026-03-30): plugins, compactación de eventos, HITL, servicios de sesión/memoria y A2A como primitivas de harness reutilizables. -- [GitHub: Automate repository tasks with GitHub Agentic Workflows](https://github.blog/ai-and-ml/automate-repository-tasks-with-github-agentic-workflows/) (2026-02-13): GitHub Actions como un ejecutor de flujos de trabajo agentic con salidas seguras, sandboxing, permisos y revisión. -- [AWS: AI agents in enterprises: Best practices with Amazon Bedrock AgentCore](https://aws.amazon.com/blogs/machine-learning/ai-agents-in-enterprises-best-practices-with-amazon-bedrock-agentcore/) (2026-02-03): capas de harness empresarial a través de Runtime, Memory, Gateway, Identity/Policy, Observability y Evaluations. -- [Stripe: Minions: Stripe's one-shot, end-to-end coding agents](https://stripe.dev/blog/minions-stripes-one-shot-end-to-end-coding-agents) (2026-02-09) y [Parte 2](https://stripe.dev/blog/minions-stripes-one-shot-end-to-end-coding-agents-part-2) (2026-02-19): aislamiento en devbox, harnesses de agente personalizados, máquinas de estado de blueprint, archivos de reglas, curación de herramientas MCP, controles de seguridad y bucles de feedback pre-push/CI. -- [Cognition: What We Learned Building Cloud Agents](https://cognition.ai/blog/what-we-learned-building-cloud-agents) (2026-04-23): aislamiento en VM, snapshot/resume de sesión, orquestación, gobernanza, logging de auditoría e integraciones para tiempos de ejecución de agentes en la nube. -- [Cognition: Multi-Agents: What's Actually Working](https://cognition.ai/blog/multi-agents-working) (2026-04-22): bucles generador-verificador, revisores de contexto limpio, enrutamiento smart-friend, coordinación manager-child y límites de comunicación entre agentes. -- [Replit: Decision-Time Guidance: Keeping Replit Agent Reliable](https://blog.replit.com/decision-time-guidance) (2026-01-20, actualizado 2026-01-23): un clasificador ligero inyecta guía situacional corta en el punto de decisión en lugar de meter todas las reglas en el prompt del sistema. -- [Vercel: How we made v0 an effective coding agent](https://vercel.com/blog/how-we-made-v0-an-effective-coding-agent) (2026-01-07): prompts de sistema dinámicos, una capa de reescritura en streaming y autocorrectores deterministas/basados en modelo. -- [Vercel: Introducing deepsec](https://vercel.com/blog/introducing-deepsec-find-and-fix-vulnerabilities-in-your-code-base) (2026-05-04): un harness de agente de codificación enfocado en seguridad con pasos de escaneo, investigación, revalidación, enriquecimiento, exportación, plugin y verificación de rechazos. -- [Sourcegraph: CodeScaleBench](https://sourcegraph.com/blog/codescalebench-testing-coding-agents-on-large-codebases-and-multi-repo-software-engineering-tasks) (2026-03-03): una referencia de harness de eval/herramientas que cubre adopción de herramientas MCP, transcripciones de uso de herramientas, QA de benchmark, puertas de verificador/reproducibilidad e iteración de prompt/prefacio. - -Las referencias generales exclusivamente de 2025 están excluidas de la lista principal. El -artículo original de 2025 de Anthropic sobre harnesses permanece porque es una fuente -fundacional del curso. - -## Orden de Lectura Sugerido - -1. `method-map.md` -2. `initializer-agent-playbook.md` -3. `coding-agent-startup-flow.md` -4. `prompt-calibration.md` -5. OpenAI Harness engineering -6. Anthropic Effective harnesses -7. Anthropic Harness design for long-running application development -8. OpenAI Codex agent loop -9. Anthropic agent evals -10. LangChain Improving Deep Agents -11. Thoughtworks / Martin Fowler Harness engineering for coding agent users -12. Cursor Continually improving our agent harness diff --git a/courses/learn-harness-engineering/docs/es/resources/reference/initializer-agent-playbook.md b/courses/learn-harness-engineering/docs/es/resources/reference/initializer-agent-playbook.md deleted file mode 100644 index b611efed..00000000 --- a/courses/learn-harness-engineering/docs/es/resources/reference/initializer-agent-playbook.md +++ /dev/null @@ -1,37 +0,0 @@ -# Playbook del Agente Inicializador - -Usa este playbook para la primera sesión seria en un repositorio, antes de -que comience el trabajo incremental de características. - -## Objetivo - -Crear una superficie operacional estable para que las sesiones posteriores puedan implementar -comportamiento sin redescubrir comandos de inicio, estado actual o límites de tareas. - -## Salidas Requeridas - -El inicializador debería dejar atrás al menos estos artefactos: - -- un archivo de instrucciones raíz como `AGENTS.md` o `CLAUDE.md` -- una superficie de características legible por máquina como `feature_list.json` -- un artefacto de progreso duradero como `claude-progress.md` -- un ayudante de inicio estándar como `init.sh` -- un commit seguro inicial que capture el scaffold de referencia - -## Lista de Verificación - -1. Define la ruta de inicio estándar. -2. Define la ruta de verificación estándar. -3. Crea el registro de progreso y registra el estado inicial. -4. Descompone el trabajo en características explícitas con estados. -5. Crea el primer commit de referencia limpio. - -## Test de Éxito - -Una sesión nueva sin contexto de chat previo debería poder responder: - -- qué hace este repositorio -- cómo iniciarlo -- cómo verificarlo -- qué está inacabado -- cuál es el mejor próximo paso diff --git a/courses/learn-harness-engineering/docs/es/resources/reference/method-map.md b/courses/learn-harness-engineering/docs/es/resources/reference/method-map.md deleted file mode 100644 index cdca1ead..00000000 --- a/courses/learn-harness-engineering/docs/es/resources/reference/method-map.md +++ /dev/null @@ -1,19 +0,0 @@ -# Mapa de Métodos - -Esta tabla mapea los modos de fallo más comunes de agentes de codificación de larga duración -al artefacto o regla operacional que usualmente los corrige primero. - -| Modo de fallo | Cómo se ve en la práctica | Corrección principal | Artefacto de soporte | -| --- | --- | --- | --- | -| Confusión de inicio en frío | Una sesión nueva pasa la mayor parte de su tiempo redescubriendo la configuración y el estado | Hacer del repositorio el sistema de registro | `claude-progress.md` | -| Expansión de alcance | El agente comienza varias características y no termina ninguna limpiamente | Restringir el alcance activo | `feature_list.json` | -| Completación prematura | El agente afirma haber terminado después de ediciones de código pero antes de una prueba ejecutable | Vincular la completación a la evidencia | `clean-state-checklist.md` | -| Inicio frágil | Cada sesión reaprende cómo arrancar el proyecto | Estandarizar la configuración y verificación | `init.sh` | -| Entrega débil | La siguiente sesión no puede decir qué está verificado, roto o pendiente | Terminar con una entrega explícita | `session-handoff.md` | -| Revisión subjetiva | La calidad de la revisión depende del gusto o la memoria | Puntuar la salida con categorías fijas | `evaluator-rubric.md` | - -## Principio Operacional - -Añade el artefacto más pequeño que aborde directamente el modo de fallo observado. -Evita resolver cada problema de fiabilidad volcando más texto en un archivo de instrucciones -global único. diff --git a/courses/learn-harness-engineering/docs/es/resources/reference/prompt-calibration.md b/courses/learn-harness-engineering/docs/es/resources/reference/prompt-calibration.md deleted file mode 100644 index dedeaf2b..00000000 --- a/courses/learn-harness-engineering/docs/es/resources/reference/prompt-calibration.md +++ /dev/null @@ -1,25 +0,0 @@ -# Calibración de Prompts - -Las instrucciones raíz deben definir el marco operacional, no cada movimiento posible. - -## Mantener En El Archivo Raíz - -- propósito y alcance del repositorio -- ruta de inicio -- ruta de verificación -- restricciones no negociables -- artefactos de estado requeridos -- reglas de fin de sesión - -## Sacar Del Archivo Raíz - -- casos límite históricos largos -- detalles de implementación específicos de un tema -- notas de arquitectura local que pertenecen cerca del código -- ejemplos que solo aplican a un subsistema - -## Regla de Trabajo - -El archivo raíz debería ayudar a una sesión nueva a orientarse rápidamente. Si el archivo -se está convirtiendo en un depósito para cada fallo pasado, divide el detalle en documentos -más pequeños y enlázalos en su lugar. diff --git a/courses/learn-harness-engineering/docs/es/resources/templates/AGENTS.md b/courses/learn-harness-engineering/docs/es/resources/templates/AGENTS.md deleted file mode 100644 index 80bdb665..00000000 --- a/courses/learn-harness-engineering/docs/es/resources/templates/AGENTS.md +++ /dev/null @@ -1,55 +0,0 @@ -# AGENTS.md - -Este repositorio está diseñado para trabajo de agentes de codificación de larga duración. El objetivo no -es maximizar la salida bruta de código. El objetivo es dejar el repositorio en un estado donde la -próxima sesión pueda continuar sin adivinar. - -## Flujo de Trabajo de Inicio - -Antes de escribir código: - -1. Confirma el directorio de trabajo con `pwd`. -2. Lee `claude-progress.md` para el estado verificado más reciente y el próximo paso. -3. Lee `feature_list.json` y elige la característica inacabada de mayor prioridad. -4. Revisa los commits recientes con `git log --oneline -5`. -5. Ejecuta `./init.sh`. -6. Ejecuta la verificación de smoke o end-to-end requerida antes de comenzar trabajo nuevo. - -Si la verificación de referencia ya está fallando, corrígela primero. No apiles trabajo de -características nuevas sobre un estado inicial roto. - -## Reglas de Trabajo - -- Trabaja en una característica a la vez. -- No marques una característica como completa solo porque se añadió código. -- Mantén los cambios dentro del alcance de la característica seleccionada a menos que un bloqueo - fuerce una corrección de soporte estrecha. -- No cambies silenciosamente las reglas de verificación durante la implementación. -- Prefiere artefactos duraderos del repositorio sobre resúmenes de chat. - -## Artefactos Requeridos - -- `feature_list.json`: fuente de verdad para el estado de las características -- `claude-progress.md`: registro de sesión y estado verificado actual -- `init.sh`: ruta estándar de inicio y verificación -- `session-handoff.md`: entrega compacta opcional para sesiones más grandes - -## Definición de Completado - -Una característica está hecha solo cuando todo lo siguiente es cierto: - -- el comportamiento objetivo está implementado -- la verificación requerida realmente se ejecutó -- la evidencia está registrada en `feature_list.json` o `claude-progress.md` -- el repositorio permanece reiniciable desde la ruta de inicio estándar - -## Fin de Sesión - -Antes de terminar una sesión: - -1. Actualiza `claude-progress.md`. -2. Actualiza `feature_list.json`. -3. Registra cualquier riesgo o bloqueo sin resolver. -4. Haz commit con un mensaje descriptivo una vez que el trabajo esté en un estado seguro. -5. Deja el repositorio lo suficientemente limpio para que la próxima sesión pueda ejecutar - `./init.sh` inmediatamente. diff --git a/courses/learn-harness-engineering/docs/es/resources/templates/CLAUDE.md b/courses/learn-harness-engineering/docs/es/resources/templates/CLAUDE.md deleted file mode 100644 index a2299c88..00000000 --- a/courses/learn-harness-engineering/docs/es/resources/templates/CLAUDE.md +++ /dev/null @@ -1,47 +0,0 @@ -# CLAUDE.md - -Estás trabajando en un repositorio diseñado para trabajo de implementación de larga duración. -Prioriza la completación fiable, la continuidad entre sesiones y la verificación explícita -sobre la velocidad. - -## Bucle Operacional - -Al comienzo de cada sesión: - -1. Ejecuta `pwd` y confirma que estás en la raíz del repositorio esperada. -2. Lee `claude-progress.md`. -3. Lee `feature_list.json`. -4. Revisa los commits recientes con `git log --oneline -5`. -5. Ejecuta `./init.sh`. -6. Verifica si la ruta de smoke o end-to-end de referencia ya está rota. - -Luego selecciona exactamente una característica inacabada y trabaja solo en esa característica hasta -que la verifiques o documentes por qué está bloqueada. - -## Reglas - -- Una característica activa a la vez. -- No afirmes completación sin evidencia ejecutable. -- No reescribas la lista de características para ocultar trabajo inacabado. -- No elimines o debilites tests solo para hacer que la tarea parezca completa. -- Usa los artefactos del repositorio como el sistema de registro. - -## Archivos Requeridos - -- `feature_list.json` -- `claude-progress.md` -- `init.sh` -- `session-handoff.md` cuando una entrega compacta es útil - -## Puerta de Completación - -Una característica puede pasar a `passing` solo después de que la verificación requerida tenga éxito -y el resultado esté registrado. - -## Antes de Detenerte - -1. Actualiza el registro de progreso. -2. Actualiza el estado de la característica. -3. Registra qué sigue roto o sin verificar. -4. Haz commit una vez que el repositorio sea seguro para reanudar. -5. Deja una ruta de reinicio limpia para la próxima sesión. diff --git a/courses/learn-harness-engineering/docs/es/resources/templates/claude-progress.md b/courses/learn-harness-engineering/docs/es/resources/templates/claude-progress.md deleted file mode 100644 index ddd26b3d..00000000 --- a/courses/learn-harness-engineering/docs/es/resources/templates/claude-progress.md +++ /dev/null @@ -1,35 +0,0 @@ -# Registro de Progreso - -## Estado Verificado Actual - -- Raíz del repositorio: -- Ruta de inicio estándar: -- Ruta de verificación estándar: -- Característica inacabada de mayor prioridad actual: -- Bloqueador actual: - -## Registro de Sesiones - -### Sesión 001 - -- Fecha: -- Objetivo: -- Completado: -- Verificación ejecutada: -- Evidencia capturada: -- Commits: -- Archivos o artefactos actualizados: -- Riesgo conocido o problema sin resolver: -- Mejor próximo paso: - -### Sesión 002 - -- Fecha: -- Objetivo: -- Completado: -- Verificación ejecutada: -- Evidencia capturada: -- Commits: -- Archivos o artefactos actualizados: -- Riesgo conocido o problema sin resolver: -- Mejor próximo paso: diff --git a/courses/learn-harness-engineering/docs/es/resources/templates/clean-state-checklist.md b/courses/learn-harness-engineering/docs/es/resources/templates/clean-state-checklist.md deleted file mode 100644 index 93408774..00000000 --- a/courses/learn-harness-engineering/docs/es/resources/templates/clean-state-checklist.md +++ /dev/null @@ -1,8 +0,0 @@ -# Lista de Verificación de Estado Limpio - -- [ ] La ruta de inicio estándar sigue funcionando. -- [ ] La ruta de verificación estándar sigue ejecutándose. -- [ ] El progreso actual está registrado en el registro de progreso. -- [ ] El estado de las características refleja qué está realmente pasando versus sin verificar. -- [ ] No hay ningún paso a medio terminar sin documentar. -- [ ] La próxima sesión puede continuar sin reparación manual. diff --git a/courses/learn-harness-engineering/docs/es/resources/templates/evaluator-rubric.md b/courses/learn-harness-engineering/docs/es/resources/templates/evaluator-rubric.md deleted file mode 100644 index 4337d731..00000000 --- a/courses/learn-harness-engineering/docs/es/resources/templates/evaluator-rubric.md +++ /dev/null @@ -1,24 +0,0 @@ -# Rúbrica del Evaluador - -Usa esta rúbrica después de la implementación y antes de la aceptación final. - -| Categoría | Pregunta | Puntuación (0-2) | Notas | -| --- | --- | --- | --- | -| Corrección | ¿El comportamiento implementado coincide con la característica solicitada? | | | -| Verificación | ¿Las verificaciones requeridas se ejecutaron realmente, con evidencia? | | | -| Disciplina de alcance | ¿La sesión se mantuvo dentro del alcance de la característica elegida? | | | -| Fiabilidad | ¿El resultado sobrevive al reinicio o reejecución sin reparación? | | | -| Mantenibilidad | ¿El código y la documentación son lo suficientemente claros para la próxima sesión? | | | -| Preparación de entrega | ¿Puede una sesión nueva continuar el trabajo solo con artefactos del repositorio? | | | - -## Veredicto - -- Aceptar -- Revisar -- Bloquear - -## Seguimiento Requerido - -- Evidencia faltante: -- Correcciones requeridas: -- Próximo disparador de revisión: diff --git a/courses/learn-harness-engineering/docs/es/resources/templates/feature_list.json b/courses/learn-harness-engineering/docs/es/resources/templates/feature_list.json deleted file mode 100644 index 138adc6a..00000000 --- a/courses/learn-harness-engineering/docs/es/resources/templates/feature_list.json +++ /dev/null @@ -1,64 +0,0 @@ -{ - "project": "replace-with-project-name", - "last_updated": "YYYY-MM-DD", - "rules": { - "single_active_feature": true, - "passing_requires_evidence": true, - "do_not_skip_verification": true - }, - "status_legend": { - "not_started": "Work has not begun.", - "in_progress": "The feature is the current active task.", - "blocked": "Work cannot continue until a documented blocker is resolved.", - "passing": "Required verification has passed and evidence is recorded." - }, - "features": [ - { - "id": "chat-001", - "priority": 1, - "area": "chat", - "title": "Create a new conversation", - "user_visible_behavior": "A user can click New Chat and see a fresh empty conversation.", - "status": "not_started", - "verification": [ - "Open the app.", - "Click New Chat.", - "Verify a new conversation appears in the sidebar.", - "Verify the main panel shows an empty conversation state." - ], - "evidence": [], - "notes": "" - }, - { - "id": "chat-002", - "priority": 2, - "area": "chat", - "title": "Send a message in the current conversation", - "user_visible_behavior": "A user can submit a message and see it appear in the active thread.", - "status": "not_started", - "verification": [ - "Open an existing conversation.", - "Type a message into the input.", - "Submit the message.", - "Verify the new message appears in the thread." - ], - "evidence": [], - "notes": "" - }, - { - "id": "chat-003", - "priority": 3, - "area": "chat", - "title": "Persist the active conversation list", - "user_visible_behavior": "A user sees previously created conversations after restarting the app.", - "status": "not_started", - "verification": [ - "Create two conversations.", - "Restart the app.", - "Verify both conversations still appear in the sidebar." - ], - "evidence": [], - "notes": "" - } - ] -} diff --git a/courses/learn-harness-engineering/docs/es/resources/templates/index.md b/courses/learn-harness-engineering/docs/es/resources/templates/index.md deleted file mode 100644 index 39257065..00000000 --- a/courses/learn-harness-engineering/docs/es/resources/templates/index.md +++ /dev/null @@ -1,226 +0,0 @@ -# Guía de Plantillas - -Estas plantillas están listas para copiar en tu propio proyecto. Cada una cumple un propósito específico en el flujo de trabajo del agente. Edita el contenido para que coincida con los comandos, rutas, nombres de características y pasos de verificación de tu proyecto. - -## Cómo Comenzar - -Copia estos cuatro archivos en la raíz de tu proyecto primero: - -1. `AGENTS.md` o `CLAUDE.md` -2. `init.sh` -3. `claude-progress.md` -4. `feature_list.json` - -Añade los archivos restantes a medida que tu proyecto crece. - ---- - -## AGENTS.md - -El archivo de instrucciones raíz. Esta es la primera cosa que el agente lee cuando inicia una sesión. Define las reglas operacionales: qué hacer antes de escribir código, cómo trabajar y cómo cerrar. - -**Cómo usarlo:** - -- Copia al directorio raíz de tu proyecto -- Reemplaza los pasos del flujo de inicio con las rutas y comandos reales de tu proyecto -- Ajusta las reglas de trabajo para que coincidan con las convenciones de tu equipo -- Mantén la sección de definición de completado — es la parte más importante - -**Qué hace por el agente:** - -- Le indica que lea el progreso y el estado de las características antes de comenzar a trabajar -- Lo obliga a trabajar en una característica a la vez -- Requiere evidencia antes de marcar algo como completado -- Define cómo se ve un fin de sesión limpio - -Usa `AGENTS.md` para Codex u otros agentes. Usa `CLAUDE.md` si estás trabajando con Claude Code — la estructura es la misma, solo formateada para el estilo de instrucciones de Claude. - -## init.sh - -El script de inicio. Ejecuta la instalación de dependencias, verificación e imprime el comando de inicio — todo en un solo paso. - -**Cómo usarlo:** - -- Copia a la raíz de tu proyecto -- Edita estas tres variables en la parte superior: - - `INSTALL_CMD` — tu comando de instalación de dependencias (ej. `npm install`, `pip install -r requirements.txt`) - - `VERIFY_CMD` — tu comando de verificación básica (ej. `npm test`, `pytest`) - - `START_CMD` — tu comando de inicio del servidor de desarrollo (ej. `npm run dev`) -- Hazlo ejecutable: `chmod +x init.sh` - -**Qué hace:** - -1. Imprime el directorio actual (para que puedas confirmar que se está ejecutando en el lugar correcto) -2. Instala dependencias -3. Ejecuta el comando de verificación -4. Imprime el comando de inicio (o lo ejecuta si `RUN_START_COMMAND=1` está configurado) - -Si la verificación falla, el agente debe detenerse y corregir la línea base antes de hacer cualquier otra cosa. - -## claude-progress.md - -El registro de progreso. Cada sesión escribe en este archivo, y cada sesión nueva lo lee primero. - -**Cómo usarlo:** - -- Copia a la raíz de tu proyecto -- Rellena la sección "Estado Verificado Actual" con la información de tu proyecto -- Después de cada sesión, actualiza el registro de sesión - -**Qué significa cada campo:** - -- **Estado Verificado Actual** — la única fuente de verdad sobre dónde está el proyecto - - `Repository root directory` — dónde vive el proyecto - - `Standard startup path` — el comando para poner en marcha el proyecto - - `Standard verification path` — el comando para ejecutar los tests - - `Highest priority unfinished feature` — en qué debería trabajar la próxima sesión - - `Current blocker` — cualquier cosa que esté atascada -- **Registro de Sesión** — una entrada por sesión - - `Goal` — lo que planeabas hacer - - `Completed` — lo que realmente se hizo - - `Verification run` — qué tests se ejecutaron - - `Evidence recorded` — qué prueba se capturó - - `Commits` — qué se commiteó - - `Known risks` — qué podría estar roto - - `Next best action` — dónde debería empezar la próxima sesión - -## feature_list.json - -El rastreador de características. Una lista legible por máquina de cada característica que el agente necesita implementar, junto con su estado, pasos de verificación y evidencia. - -**Cómo usarlo:** - -- Copia a la raíz de tu proyecto -- Reemplaza las características de ejemplo con las tuyas -- Cada característica necesita: - - `id` — un identificador único corto - - `priority` — entero, menor = mayor prioridad - - `area` — qué parte de la aplicación (ej. "chat", "import", "search") - - `title` — descripción corta - - `user_visible_behavior` — lo que el usuario debería ver cuando funciona - - `status` — uno de `not_started`, `in_progress`, `blocked`, `passing` - - `verification` — instrucciones paso a paso para confirmar que funciona - - `evidence` — prueba registrada de que la verificación pasó (rellenada por el agente) - - `notes` — cualquier contexto adicional - -**Reglas de estado:** - -- `not_started` — no ha sido tocada -- `in_progress` — la única característica en la que se está trabajando actualmente (solo una a la vez) -- `blocked` — no se puede proceder debido a un problema documentado -- `passing` — la verificación pasó y la evidencia está registrada - -El agente solo debe tener una característica en `in_progress` en cualquier momento. - -## session-handoff.md - -Una nota de entrega compacta entre sesiones. Usa esto cuando una sesión termina y quieres que la próxima la retome rápidamente. - -**Cómo usarlo:** - -- Copia a la raíz de tu proyecto -- Rellénalo al final de cada sesión (o haz que el agente lo rellene) - -**Qué cubre cada sección:** - -- **Actualmente verificado** — qué está confirmado como funcionando y qué verificación se ejecutó -- **Cambios esta sesión** — qué código o infraestructura cambió -- **Aún roto o sin verificar** — problemas conocidos y áreas de riesgo -- **Mejor próxima acción** — qué debería hacer la próxima sesión, y qué no tocar -- **Comandos** — comandos de inicio, verificación y depuración para referencia rápida - -Este archivo es opcional para sesiones pequeñas. Se vuelve importante cuando las sesiones son largas o cuando el proyecto tiene múltiples áreas activas. - -## clean-state-checklist.md - -Una lista de verificación para revisar antes de terminar cada sesión. Asegura que el repositorio esté en un buen estado para que la próxima sesión comience limpiamente. - -**Cómo usarlo:** - -- Copia a la raíz de tu proyecto -- Revísala antes de cerrar una sesión -- El agente también debería verificar estos elementos como parte de su rutina de fin de sesión - -**Qué verifica:** - -- El inicio estándar sigue funcionando -- La verificación estándar sigue ejecutándose -- El registro de progreso está actualizado -- La lista de características refleja el estado real (no hay entradas `passing` falsas) -- No hay trabajo a medio terminar sin registrar -- La próxima sesión puede continuar sin correcciones manuales - -## evaluator-rubric.md - -Una tarjeta de puntuación para revisar la calidad de salida del agente. Usa esto después de una sesión o en hitos del proyecto para evaluar si el trabajo cumple con el estándar. - -**Cómo usarlo:** - -- Copia a la raíz de tu proyecto -- Después de una sesión (o un conjunto de sesiones), puntúa el trabajo del agente en seis dimensiones -- Cada dimensión se puntúa de 0 a 2 - -**Las seis dimensiones:** - -1. **Corrección** — ¿la implementación coincide con el comportamiento objetivo? -2. **Verificación** — ¿las verificaciones requeridas se ejecutaron realmente, con evidencia? -3. **Disciplina de alcance** — ¿el agente se mantuvo dentro de la característica seleccionada? -4. **Fiabilidad** — ¿el resultado sobrevive un reinicio o reejecución? -5. **Mantenibilidad** — ¿el código y la documentación son lo suficientemente claros para la próxima sesión? -6. **Preparación de entrega** — ¿puede una sesión nueva continuar usando solo artefactos del repositorio? - -**Opciones de conclusión:** - -- Aceptar — cumple con el estándar -- Revisar — necesita correcciones antes de aceptar -- Bloquear — problemas fundamentales que necesitan resolverse primero - -**Importante: el evaluador necesita ajuste.** De fábrica, los agentes son malos autojueces — identifican problemas y luego se convencen a sí mismos de aprobar. Necesitarás iterar: - -1. Ejecuta el evaluador en un sprint completado. -2. Compara sus puntuaciones con tu propio juicio humano. -3. Donde diverjan, haz la rúbrica más específica sobre los criterios de aprobación/fallo. -4. Vuelve a ejecutar y verifica la alineación. -5. Repite hasta que el evaluador coincida consistentemente con la revisión humana. - -Planifica 3-5 rondas de ajuste. Registra cada cambio para que puedas rastrear qué mejoró la alineación. - -## quality-document.md - -Una instantánea de calidad que califica cada dominio de producto y capa arquitectónica en tu proyecto. Rastrea la salud del código base a lo largo del tiempo, no solo la salida de sesiones individuales. - -**Cómo usarlo:** - -- Copia a la raíz de tu proyecto -- Antes de comenzar una sesión: léelo para entender dónde el código base es más débil -- Después de una sesión: actualiza las calificaciones basándote en qué cambió -- Con el tiempo: compara instantáneas para ver qué cambios del harness realmente mejoraron la salud del código base - -**Qué califica:** - -- **Dominios de producto** (ej., importación de documentos, flujo de P&R, indexación): cada dominio recibe una calificación (A-D) en estado de verificación, legibilidad del agente, estabilidad de tests y brechas clave -- **Capas arquitectónicas** (ej., proceso principal, preload, renderer, servicios): cada capa recibe una calificación para enforcement de límites y legibilidad del agente - -**Por qué importa:** - -La rúbrica del evaluador puntúa las salidas individuales del agente. El documento de calidad puntúa el código base en sí. Responden a preguntas diferentes: - -- Rúbrica del evaluador: "¿Hizo buen trabajo el agente en esta sesión?" -- Documento de calidad: "¿El proyecto se está fortaleciendo o debilitando con el tiempo?" - -**Cuándo actualizar:** - -- Después de cada sesión significativa -- Antes de comparaciones de benchmark -- Después de pases de limpieza o simplificación -- Al incorporar un nuevo agente o modelo al proyecto - -**Conexión con la simplificación del harness:** - -El documento de calidad también soporta la simplificación del harness. Cada componente del harness codifica un supuesto sobre lo que el modelo no puede hacer. A medida que los modelos mejoran, estos supuestos se vuelven obsoletos. Para verificar si un componente sigue siendo necesario: - -1. Toma una instantánea del documento de calidad. -2. Elimina un componente del harness. -3. Ejecuta el conjunto de tareas de benchmark. -4. Toma otra instantánea. -5. Compara — si las calificaciones no bajaron, el componente era sobrecarga. Si bajaron, restáuralo. diff --git a/courses/learn-harness-engineering/docs/es/resources/templates/init.sh b/courses/learn-harness-engineering/docs/es/resources/templates/init.sh deleted file mode 100755 index d7522d66..00000000 --- a/courses/learn-harness-engineering/docs/es/resources/templates/init.sh +++ /dev/null @@ -1,29 +0,0 @@ -#!/usr/bin/env bash - -set -euo pipefail - -ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" -cd "$ROOT_DIR" - -# Replace these commands with the correct commands for your repository. -INSTALL_CMD=(npm install) -VERIFY_CMD=(npm test) -START_CMD=(npm run dev) - -echo "==> Working directory: $PWD" -echo "==> Syncing dependencies" -"${INSTALL_CMD[@]}" - -echo "==> Running baseline verification" -"${VERIFY_CMD[@]}" - -echo "==> Startup command" -printf ' %q' "${START_CMD[@]}" -printf '\n' - -if [ "${RUN_START_COMMAND:-0}" = "1" ]; then - echo "==> Starting the app" - exec "${START_CMD[@]}" -fi - -echo "Set RUN_START_COMMAND=1 if you want init.sh to launch the app directly." diff --git a/courses/learn-harness-engineering/docs/es/resources/templates/quality-document.md b/courses/learn-harness-engineering/docs/es/resources/templates/quality-document.md deleted file mode 100644 index a248f1b6..00000000 --- a/courses/learn-harness-engineering/docs/es/resources/templates/quality-document.md +++ /dev/null @@ -1,43 +0,0 @@ -# Documento de Calidad - -Una instantánea de calidad para cada dominio de producto y capa arquitectónica. Tanto agentes como humanos pueden usar este documento para entender rápidamente dónde el código base es fuerte y dónde necesita trabajo. - -**Cadencia de actualización:** Después de cada sesión significativa, o antes de comenzar una nueva fase de trabajo. - -**Escala de calificación:** - -- **A**: Toda la verificación pasando, arquitectura limpia, legible para agentes, tests estables -- **B**: Verificación pasando, mayormente limpio, brechas menores en legibilidad o cobertura de tests -- **C**: Parcialmente funcionando, brechas conocidas, algunas áreas de código difíciles de entender para los agentes -- **D**: No funcionando, o problemas estructurales importantes - ---- - -## Dominios de Producto - -| Dominio | Calificación | Verificación | Legibilidad del Agente | Estabilidad de Tests | Brechas Clave | Última Actualización | -|---------|-------------|-------------|----------------------|---------------------|---------------|---------------------| -| Importación de Documentos | - | - | - | - | - | - | -| Gestión de Documentos | - | - | - | - | - | - | -| Indexación de Documentos | - | - | - | - | - | - | -| Flujo de Preguntas y Respuestas | - | - | - | - | - | - | -| Respuestas Fundamentadas | - | - | - | - | - | - | - -## Capas Arquitectónicas - -| Capa | Calificación | Enforcement de Límites | Legibilidad del Agente | Brechas Clave | Última Actualización | -|------|-------------|----------------------|----------------------|---------------|---------------------| -| Proceso Principal | - | - | - | - | - | -| Preload | - | - | - | - | - | -| Renderer | - | - | - | - | - | -| Servicios | - | - | - | - | - | - -## Historial de Cambios - -### YYYY-MM-DD - -- Cambios: -- Dominios promovidos: -- Degradados: -- Nuevas brechas identificadas: -- Brechas cerradas: diff --git a/courses/learn-harness-engineering/docs/es/resources/templates/session-handoff.md b/courses/learn-harness-engineering/docs/es/resources/templates/session-handoff.md deleted file mode 100644 index 2f684e4b..00000000 --- a/courses/learn-harness-engineering/docs/es/resources/templates/session-handoff.md +++ /dev/null @@ -1,30 +0,0 @@ -# Entrega de Sesión - -## Verificado Ahora - -- Qué está funcionando actualmente: -- Qué verificación se ejecutó realmente: - -## Cambiado En Esta Sesión - -- Código o comportamiento añadido: -- Cambios de infraestructura o harness: - -## Roto O Sin Verificar - -- Defecto conocido: -- Ruta sin verificar: -- Riesgo para la próxima sesión: - -## Mejor Próximo Paso - -- Característica inacabada de mayor prioridad: -- Por qué es la siguiente: -- Qué cuenta como aprobado: -- Qué no debe cambiar durante ese paso: - -## Comandos - -- Inicio: -- Verificación: -- Comando de depuración enfocado: diff --git a/courses/learn-harness-engineering/docs/es/skills/index.md b/courses/learn-harness-engineering/docs/es/skills/index.md deleted file mode 100644 index 6987ddb2..00000000 --- a/courses/learn-harness-engineering/docs/es/skills/index.md +++ /dev/null @@ -1,49 +0,0 @@ -# Skills - -Este directorio contiene los skills de agentes de IA incluidos con el curso. Los skills son plantillas de prompt autocontenidas que pueden cargar agentes de programación con IA, como Claude Code, Codex, Cursor o Windsurf, para realizar tareas especializadas. - -## harness-creator - -Un skill de harness engineering de nivel producción para agentes de programación con IA. Te ayuda a crear, evaluar y mejorar los cinco subsistemas centrales de un harness: instrucciones, estado, verificación, alcance y ciclo de vida de la sesión. - -### Qué hace - -- **Crear harnesses desde cero** — AGENTS.md, listas de funciones y flujos de verificación -- **Mejorar harnesses existentes** — evaluación de cinco subsistemas con mejoras priorizadas -- **Diseñar continuidad de sesión** — memoria persistente, seguimiento de progreso y procedimientos de handoff -- **Aplicar patrones de producción** — memoria, context engineering, seguridad de herramientas y coordinación multiagente - -### Inicio rápido - -Los archivos del skill están en el repositorio en [`skills/harness-creator/`](https://github.com/walkinglabs/learn-harness-engineering/tree/main/skills/harness-creator). - -Para usarlo con Claude Code, copia el directorio `harness-creator/` a la ruta de skills de tu proyecto, o apunta tu agente al archivo SKILL.md. - -### Patrones de referencia - -El skill incluye 6 documentos de referencia detallados: - -| Patrón | Cuándo usarlo | -|---------|-------------| -| Memory Persistence | El agente olvida entre sesiones | -| Context Engineering | Gestión del presupuesto de contexto, carga JIT | -| Tool Registry | Seguridad de herramientas, control de concurrencia | -| Multi-Agent Coordination | Paralelismo, flujos de especialización | -| Lifecycle & Bootstrap | Hooks, tareas en segundo plano, inicialización | -| Gotchas | 15 modos de fallo no obvios con correcciones | - -### Plantillas - -El skill incluye plantillas listas para usar: - -- `agents.md` — scaffold de AGENTS.md con reglas de trabajo -- `feature-list.json` — JSON Schema y ejemplo de lista de funciones -- `init.sh` — script estándar de inicialización -- `progress.md` — plantilla de registro de progreso de sesión - -### Cómo se construyó este skill - -`harness-creator` se desarrolló usando la metodología **skill-creator**, el metaskill oficial de Anthropic para crear, probar e iterar skills de agentes. skill-creator ofrece un flujo estructurado de borrador → prueba → evaluación → iteración, con runners de evaluación, graders y un visor de benchmarks. - -- **Fuente de skill-creator**: [anthropics/skills — skill-creator](https://github.com/anthropics/skills/tree/main/skills/skill-creator) -- **Documentación de skills de Claude Code**: [anthropics/claude-code — plugin-dev/skills](https://github.com/anthropics/claude-code/tree/main/plugins/plugin-dev/skills) diff --git a/courses/learn-harness-engineering/docs/fr/index.md b/courses/learn-harness-engineering/docs/fr/index.md deleted file mode 100644 index 2663a031..00000000 --- a/courses/learn-harness-engineering/docs/fr/index.md +++ /dev/null @@ -1,73 +0,0 @@ -# Bienvenue dans Learn Harness Engineering - -Learn Harness Engineering est un cours consacré à l'ingénierie des agents de codage IA. Nous avons étudié et synthétisé des théories et pratiques avancées de Harness Engineering utilisées dans l'industrie. Nos références principales sont : -- [OpenAI: Harness engineering: leveraging Codex in an agent-first world](https://openai.com/index/harness-engineering/) -- [Anthropic: Effective harnesses for long-running agents](https://www.anthropic.com/engineering/effective-harnesses-for-long-running-agents) -- [Anthropic: Harness design for long-running application development](https://www.anthropic.com/engineering/harness-design-long-running-apps) -- [Awesome Harness Engineering](https://github.com/walkinglabs/awesome-harness-engineering) - -Grâce à une conception systématique de l'environnement, de l'état, de la vérification et des mécanismes de contrôle, ce cours montre comment rendre réellement fiables des outils agentiques de codage comme Codex et Claude Code. Vous apprendrez à construire des fonctionnalités, corriger des bugs et automatiser des tâches de développement en encadrant votre assistant de codage IA avec des règles et des limites explicites. - -## Commencer - -Choisissez votre parcours d'apprentissage. Le cours est divisé en leçons théoriques, projets pratiques et bibliothèque de ressources prêtes à copier. - - - -## Le mécanisme central d'un harness - -Un harness ne "rend pas le modèle plus intelligent" ; il met en place un **système de travail** en boucle fermée pour le modèle. Le flux central peut être compris avec ce schéma : - -```mermaid -graph TD - A["Clear Objective
AGENTS.md"] --> B("Initialization
init.sh") - B --> C{"Run Tasks
AI Agent"} - C -->|Encounter Issues| D["Runtime Feedback
CLI / Logs"] - D -->|Auto-fix| C - C -->|Code Completed| E{"Verify & QA
Test suite"} - E -->|Failed| D - E -->|Passed| F["Cleanup & Handoff
claude-progress.md"] - - classDef primary fill:#D95C41,stroke:#C14E36,color:#fff,font-weight:bold; - classDef process fill:#F4F3EE,stroke:#D1D1D1,color:#1A1A1A; - classDef check fill:#EAE8E1,stroke:#B3B3B3,color:#1A1A1A; - - class A,F primary; - class B,D process; - class C,E check; -``` - -## Ce que vous apprendrez - -Voici quelques concepts clés que vous maîtriserez : - -
    -
  • Contraindre le comportement de l'agent avec des règles et des limites explicites.
  • -
  • Maintenir le contexte sur des tâches longues et multi-sessions.
  • -
  • Empêcher les agents de déclarer la victoire trop tôt.
  • -
  • Vérifier le travail avec des tests de pipeline complet et de l'auto-évaluation.
  • -
  • Rendre le runtime observable et débogable.
  • -
- -## Étapes suivantes - -Une fois les concepts de base compris, ces guides permettent d'aller plus loin : - - diff --git a/courses/learn-harness-engineering/docs/fr/lectures/lecture-01-why-capable-agents-still-fail/code/failure-pattern-demo.ts b/courses/learn-harness-engineering/docs/fr/lectures/lecture-01-why-capable-agents-still-fail/code/failure-pattern-demo.ts deleted file mode 100644 index 311e665b..00000000 --- a/courses/learn-harness-engineering/docs/fr/lectures/lecture-01-why-capable-agents-still-fail/code/failure-pattern-demo.ts +++ /dev/null @@ -1,170 +0,0 @@ -/** - * failure-pattern-demo.ts - * - * Simulates the 4-step failure pattern that capable agents fall into: - * 1. Incomplete context - * 2. Locally reasonable changes - * 3. No global verification - * 4. Premature completion - * - * Run: npx tsx docs/lectures/lecture-01-why-capable-agents-still-fail/code/failure-pattern-demo.ts - */ - -// --------------------------------------------------------------------------- -// Types -// --------------------------------------------------------------------------- - -interface StepState { - step: number; - name: string; - contextAvailable: string[]; - contextMissing: string[]; - actionTaken: string; - localOutcome: string; - globalImpact: string; - completed: boolean; -} - -// --------------------------------------------------------------------------- -// Simulated "model" -- a simple decision function that bases its output -// solely on whatever context it has been given. -// --------------------------------------------------------------------------- - -function modelDecide(context: string[], task: string): string { - const has = (s: string) => context.some((c) => c.includes(s)); - - // The task is to "add a search endpoint to the API". - // Correct answer requires knowing about auth middleware and rate limiting. - - if (!has("auth")) { - return "Created new route handler /search without authentication checks"; - } - if (!has("rate-limit")) { - return "Added search route with auth but forgot rate limiting"; - } - if (!has("test-standards")) { - return "Implemented search with auth and rate-limit, but no tests"; - } - return "Fully implemented search endpoint with auth, rate-limit, and tests"; -} - -// --------------------------------------------------------------------------- -// Failure simulation -// --------------------------------------------------------------------------- - -function simulateFailurePattern(): StepState[] { - const steps: StepState[] = []; - - // ---- Step 1: Incomplete Context ---- - const step1Context = ["project structure", "route definitions"]; - const step1Missing = ["auth middleware", "rate-limiting policy", "test standards"]; - const step1Decision = modelDecide(step1Context, "add search endpoint"); - - steps.push({ - step: 1, - name: "Incomplete Context", - contextAvailable: step1Context, - contextMissing: step1Missing, - actionTaken: step1Decision, - localOutcome: "Looks good -- route compiles, returns data", - globalImpact: "Missing auth means unauthenticated access to search", - completed: false, - }); - - // ---- Step 2: Locally Reasonable Changes ---- - // The agent adds auth after a hint, but still lacks other context. - const step2Context = [...step1Context, "auth middleware"]; - const step2Missing = ["rate-limiting policy", "test standards"]; - const step2Decision = modelDecide(step2Context, "add search endpoint"); - - steps.push({ - step: 2, - name: "Locally Reasonable Changes", - contextAvailable: step2Context, - contextMissing: step2Missing, - actionTaken: step2Decision, - localOutcome: "Route has auth -- looks complete locally", - globalImpact: "No rate limiting means the endpoint can be abused", - completed: false, - }); - - // ---- Step 3: No Global Verification ---- - const step3Context = [...step2Context, "rate-limiting policy"]; - const step3Missing = ["test standards"]; - const step3Decision = modelDecide(step3Context, "add search endpoint"); - - steps.push({ - step: 3, - name: "No Global Verification", - contextAvailable: step3Context, - contextMissing: step3Missing, - actionTaken: step3Decision, - localOutcome: "Feature appears fully implemented", - globalImpact: "No tests -- regression risk, violates project standards", - completed: false, - }); - - // ---- Step 4: Premature Completion ---- - steps.push({ - step: 4, - name: "Premature Completion", - contextAvailable: step3Context, - contextMissing: step3Missing, - actionTaken: 'Agent outputs: "Done. Added search endpoint."', - localOutcome: "Agent is satisfied, task marked complete", - globalImpact: "Task is incomplete -- missing tests, no E2E verification", - completed: true, - }); - - return steps; -} - -// --------------------------------------------------------------------------- -// Comparison table -// --------------------------------------------------------------------------- - -function printComparisonTable(steps: StepState[]): void { - console.log("\n" + "=".repeat(90)); - console.log(" FAILURE PATTERN DEMO -- 4 Steps to a Broken Deliverable"); - console.log("=".repeat(90)); - - for (const s of steps) { - console.log(`\n Step ${s.step}: ${s.name}`); - console.log(" " + "-".repeat(60)); - console.log(` Context available : ${s.contextAvailable.join(", ")}`); - console.log(` Context missing : ${s.contextMissing.join(", ") || "(none)"}`); - console.log(` Action taken : ${s.actionTaken}`); - console.log(` Local outcome : ${s.localOutcome}`); - console.log(` Global impact : ${s.globalImpact}`); - console.log(` Marked complete? : ${s.completed ? "YES (premature)" : "No"}`); - } - - // Summary comparison - console.log("\n" + "=".repeat(90)); - console.log(" COMPARISON: What the agent saw vs. what was actually needed"); - console.log("=".repeat(90)); - - const totalRequired = ["project structure", "route definitions", "auth middleware", "rate-limiting policy", "test standards"]; - const finalAvailable = steps[steps.length - 1].contextAvailable; - - const header = "| Criterion | Available | Missing | Status |"; - const sep = "|------------------------|-----------|---------|----------|"; - console.log("\n" + header); - console.log(sep); - - for (const item of totalRequired) { - const avail = finalAvailable.includes(item); - const row = `| ${item.padEnd(23)}| ${(avail ? "Yes" : "No").padEnd(10)}| ${(!avail ? "Yes" : "").padEnd(8)}| ${(avail ? "OK" : "GAP").padEnd(9)}|`; - console.log(row); - } - - const gapCount = totalRequired.filter((i) => !finalAvailable.includes(i)).length; - console.log("\n Result: Agent completed with " + gapCount + " of " + totalRequired.length + " context items missing."); - console.log(" This is the core failure pattern: each step looked reasonable in isolation.\n"); -} - -// --------------------------------------------------------------------------- -// Run -// --------------------------------------------------------------------------- - -printComparisonTable(simulateFailurePattern()); diff --git a/courses/learn-harness-engineering/docs/fr/lectures/lecture-01-why-capable-agents-still-fail/code/failure-signals-checklist.md b/courses/learn-harness-engineering/docs/fr/lectures/lecture-01-why-capable-agents-still-fail/code/failure-signals-checklist.md deleted file mode 100644 index 23e410c0..00000000 --- a/courses/learn-harness-engineering/docs/fr/lectures/lecture-01-why-capable-agents-still-fail/code/failure-signals-checklist.md +++ /dev/null @@ -1,9 +0,0 @@ -# Liste de Contrôle des Signaux d'Échec - -Utilisez cette liste lors de l'examen d'une exécution de harness faible. - -- L'agent a-t-il demandé ou déduit incorrectement comment démarrer l'application ? -- A-t-il créé des répertoires ou abstractions qui ne correspondent pas au produit prévu ? -- S'est-il arrêté après avoir créé une coque UI visible sans un flux complet ? -- A-t-il laissé des notes ou artefacts aidant une future exécution à continuer ? -- Une nouvelle session pourrait-elle comprendre ce qui s'est passé en moins de cinq minutes ? diff --git a/courses/learn-harness-engineering/docs/fr/lectures/lecture-01-why-capable-agents-still-fail/code/index.md b/courses/learn-harness-engineering/docs/fr/lectures/lecture-01-why-capable-agents-still-fail/code/index.md deleted file mode 100644 index fb57a817..00000000 --- a/courses/learn-harness-engineering/docs/fr/lectures/lecture-01-why-capable-agents-still-fail/code/index.md +++ /dev/null @@ -1,7 +0,0 @@ -# Code du Cours 01 - -Utilisez ce dossier pour de petits exemples montrant : - -- un modèle puissant échouant dans un environnement faible -- une configuration de dépôt sous-spécifiée -- des boucles de retour manquantes diff --git a/courses/learn-harness-engineering/docs/fr/lectures/lecture-01-why-capable-agents-still-fail/code/underspecified-task.md b/courses/learn-harness-engineering/docs/fr/lectures/lecture-01-why-capable-agents-still-fail/code/underspecified-task.md deleted file mode 100644 index f7471f3e..00000000 --- a/courses/learn-harness-engineering/docs/fr/lectures/lecture-01-why-capable-agents-still-fail/code/underspecified-task.md +++ /dev/null @@ -1,18 +0,0 @@ -# Exemple de Tâche Sous-Spécifiée - -Construisez une application de bureau de base de connaissances avec réponse aux questions par IA. - -Contraintes : - -- Aucune spécifiée -- Aucune commande de démarrage fournie -- Aucun guide de structure de dossiers -- Aucun modèle de données défini -- Aucun critère d'achèvement explicite - -Résultats typiques de ce type de prompt : - -- l agent invente une structure ad hoc -- l'application peut compiler mais ne démarre pas de manière cohérente -- l'UI peut apparaître avant qu'il n'y ait un chemin d'importation/requête utilisable -- l'agent s'arrête souvent après un succès cosmétique diff --git a/courses/learn-harness-engineering/docs/fr/lectures/lecture-01-why-capable-agents-still-fail/index.md b/courses/learn-harness-engineering/docs/fr/lectures/lecture-01-why-capable-agents-still-fail/index.md deleted file mode 100644 index 45bde6a5..00000000 --- a/courses/learn-harness-engineering/docs/fr/lectures/lecture-01-why-capable-agents-still-fail/index.md +++ /dev/null @@ -1,113 +0,0 @@ -[中文版本 →](../../../zh/lectures/lecture-01-why-capable-agents-still-fail/) - -> Exemples de code : [code/](https://github.com/walkinglabs/learn-harness-engineering/blob/main/docs/fr/lectures/lecture-01-why-capable-agents-still-fail/code/) -> Projet pratique : [Projet 01. Prompt seul vs règles d'abord](./../../projects/project-01-baseline-vs-minimal-harness/index.md) - -# Leçon 01. Les modèles forts ne garantissent pas une exécution fiable - -Vous pensez connaître le monde de l'IA : abonnement Claude Pro, clé API GPT-4o, chiffres du leaderboard SWE-bench en tête. Un jour, vous confiez enfin un vrai projet à un agent IA, plein de confiance. Résultat ? Il ajoute une fonctionnalité mais casse les tests, corrige un bug mais en introduit deux autres, tourne pendant 20 minutes et annonce fièrement « terminé » — puis vous regardez le code, et ce n'est pas du tout ce que vous aviez demandé. - -Votre premier réflexe pourrait être : « ce modèle n'est pas assez bon, il faut passer au niveau supérieur ». Attendez. Avant de sortir la carte bancaire, envisagez que le problème ne soit peut-être pas le modèle. - -Regardons quelques chiffres. Fin 2025, les meilleurs agents de codage atteignent environ 50-60 % sur SWE-bench Verified. Et cela sur des tâches soigneusement choisies, avec descriptions claires et tests existants. Dans votre environnement quotidien — exigences floues, peu ou pas de tests, règles métier implicites partout — ce chiffre baisse encore. - -Derrière ces chiffres se cache pourtant une vérité contre-intuitive. - -## Même cheval, destins différents - -Anthropic a mené une expérience contrôlée. Même prompt (« construire un éditeur de jeux rétro 2D »), même modèle (Opus 4.5). Premier essai : nu, sans support — 20 minutes, 9 dollars, les fonctionnalités centrales ne fonctionnent pas. Deuxième essai : harness complet (architecture planner + generator + evaluator) — 6 heures, 200 dollars, le jeu est jouable. - -Ils n'ont pas changé de modèle. Opus 4.5 restait Opus 4.5. Ce qui a changé, c'est le harnachement. - -L'article d'OpenAI sur le harness engineering le formule simplement : Codex dans un dépôt bien harnessed passe de « peu fiable » à « fiable ». Pas « un peu mieux », mais un changement qualitatif. Comme un pur-sang : vous pouvez le monter sans harnachement adapté, mais vous n'irez ni loin ni vite, et tomber n'a rien d'étonnant. Le harness est cet ensemble de harnachement : **tout ce qui, dans l'infrastructure d'ingénierie, se trouve hors des poids du modèle.** - -## Où les agents se bloquent vraiment - -Qu'est-ce qui se passe concrètement ? - -Le plus courant : la tâche n'a jamais été définie clairement. Vous dites « ajoute une recherche », et l'agent comprend autre chose que vous — rechercher quoi ? Plein texte ou structuré ? Pagination ? Surlignage ? Sans précision, l'agent devine. Une bonne supposition est de la chance ; une mauvaise coûte plus cher à corriger que la précision initiale. C'est comme entrer au restaurant et dire au chef « je veux du poisson » : braisé, vapeur ou en fondue, tout dépend du hasard. - -Même lorsque la tâche est claire, le projet a des conventions implicites que l'agent ignore. Votre équipe utilise SQLAlchemy 2.0, mais l'agent écrit du 1.x par défaut. Tous les endpoints doivent utiliser OAuth 2.0, mais cette règle n'existe que dans votre tête et un message Slack vieux de trois mois. L'agent ne peut pas la voir. Il ne refuse pas d'obéir ; il ne sait littéralement pas que la règle existe. - -L'environnement est aussi un piège. Environnement dev incomplet, dépendances manquantes, versions d'outils incorrectes. L'agent brûle du contexte sur des échecs `pip install` et des versions Node incohérentes au lieu de résoudre la vraie tâche. C'est comme embaucher un excellent menuisier sans lui donner marteau, clous ni établi stable. - -Plus fréquent encore : il n'y a aucun moyen de vérifier. Pas de tests, pas de lint, ou les commandes de vérification ne sont jamais communiquées. L'agent écrit du code, le regarde, décide que ça va et dit « terminé ». Comme un élève qui rend un devoir sans corrigé : il pense avoir bon, mais la correction révèle une pile d'erreurs. Anthropic a aussi observé que les agents, lorsqu'ils sentent le contexte se remplir, se dépêchent de finir, sautent la vérification et choisissent une solution simple plutôt qu'optimale. Ils appellent cela « context anxiety ». - -Les tâches longues sur plusieurs sessions sont encore pires : les découvertes de la session précédente se perdent, et chaque nouvelle session doit réexplorer le projet. Sans état persistant, le taux d'échec grimpe fortement sur les tâches dépassant 30 minutes. - -## Concepts clés - -- **Écart de capacité** : l'écart entre performance sur benchmark et performance réelle. 50-60 % sur SWE-bench Verified signifie que près de la moitié des vrais problèmes restent non résolus. -- **Harness** : tout ce qui est hors du modèle — instructions, outils, environnement, gestion d'état, feedback de vérification. Si ce ne sont pas des poids de modèle, c'est du harness. -- **Échec induit par le harness** : le modèle a la capacité, mais l'environnement d'exécution a des défauts structurels. -- **Écart de vérification** : différence entre la confiance de l'agent et la correction réelle. L'agent dit « terminé » alors que ce n'est pas terminé. -- **Boucle de diagnostic** : exécuter, observer l'échec, l'attribuer à une couche du harness, corriger cette couche, réexécuter. -- **Definition of Done** : ensemble de conditions vérifiables par machine — tests passent, lint propre, types valides. Sans définition explicite, l'agent invente la sienne. - -## Quand ça échoue, corriger le harness d'abord - -Principe central : **quand ça échoue, ne changez pas d'abord de modèle — vérifiez le harness.** Si le même modèle réussit sur des tâches similaires bien structurées, supposez un problème de harness. Comme une voiture en panne : on vérifie d'abord l'essence avant d'accuser le moteur. - -**Attribuer chaque échec à une couche précise.** Ne dites pas « le modèle est nul ». Demandez : la tâche était-elle floue ? Le contexte insuffisant ? La vérification absente ? Reliez chaque échec à une des cinq couches : spécification, contexte, environnement, feedback de vérification, état. Ce sont des couches de diagnostic pratiques, pas les six termes du glossaire ci-dessus. - -**Écrire une Definition of Done explicite.** -``` -Completion criteria: -- New endpoint GET /api/search?q=xxx -- Supports pagination, default 20 items -- Results include highlighted snippets -- All new code passes pytest -- Type checking passes (mypy --strict) -``` - -**Créer un fichier AGENTS.md.** Placez-le à la racine du dépôt pour décrire stack technique, conventions d'architecture et commandes de vérification. C'est la première étape du harness engineering et souvent celle avec le meilleur retour. Un `AGENTS.md` peut valoir plus qu'un modèle plus cher. - -**Construire une boucle de diagnostic.** Traitez chaque échec comme un signal de défaut du harness. Identifiez la couche, corrigez-la, puis évitez que ce mode d'échec revienne. - -**Quantifier les améliorations.** Tenez un journal simple : succès ou échec, couche responsable. Après quelques tours, le goulot d'étranglement devient visible. - -## L'expérience du million de lignes - -OpenAI a mené en 2025 une expérience ambitieuse : utiliser Codex pour construire un produit interne complet depuis un dépôt git vide. Cinq mois plus tard, le dépôt comptait environ un million de lignes — logique applicative, infrastructure, tooling, documentation, outils internes — toutes générées par agents. Trois ingénieurs pilotaient Codex, ouvrant et fusionnant environ 1 500 PRs, soit 3,5 PRs par personne et par jour. - -Contrainte clé : **les humains n'écrivent jamais directement le code.** Ce n'était pas un gadget, mais un moyen de forcer l'équipe à comprendre ce qui change quand le travail principal de l'ingénieur n'est plus d'écrire du code, mais de concevoir des environnements, exprimer l'intention et construire des boucles de feedback. - -Au début, la progression était plus lente que prévu. Non pas parce que Codex était incapable, mais parce que l'environnement était incomplet : il manquait outils, abstractions et structures internes. Le travail des ingénieurs est devenu : découper de grands objectifs en petits blocs (design, code, review, test), laisser l'agent les assembler, puis utiliser ces blocs pour débloquer des tâches plus complexes. Quand quelque chose échouait, la correction était rarement « essaie plus fort », mais plutôt « quelle capacité manque à l'agent, et comment la rendre compréhensible et exécutable ? ». - -Cette expérience prouve directement la thèse de cette leçon : **le même modèle produit des résultats radicalement différents dans un environnement nu et dans un environnement avec harness complet.** Le modèle n'a pas changé. L'environnement, oui. - -> Source : [OpenAI: Harness engineering: leveraging Codex in an agent-first world](https://openai.com/index/harness-engineering/) - -## Un exemple plus terre à terre - -Une équipe a utilisé Claude Sonnet pour ajouter un endpoint API à une application Python moyenne (FastAPI + PostgreSQL + Redis, environ 15 000 lignes). - -Au départ, elle donne une seule phrase : « ajoute les endpoints de préférences utilisateur sous `/api/v2/users` ». Résultat : l'agent consomme 40 % de son contexte à explorer le dépôt, produit un code plausible mais incompatible avec les patterns d'erreur du projet, utilise l'ancienne syntaxe SQLAlchemy et déclare terminé alors que l'endpoint échoue au runtime. La session suivante doit refaire toute la découverte. - -Plus tard, l'équipe ajoute `AGENTS.md`, des commandes de vérification explicites (`pytest tests/api/v2/ && python -m mypy src/`) et des décisions d'architecture. Le même modèle réussit trois exécutions indépendantes, avec environ 60 % d'efficacité de contexte en plus. - -Ils n'ont pas changé le modèle. Ils ont changé le harness. - -## Points clés - -- Capacité du modèle et fiabilité d'exécution sont deux choses différentes. -- Quand ça échoue, vérifiez le harness avant le modèle. -- Chaque échec est un signal : votre harness a un défaut structurel. -- Cinq couches de défense : spécification, contexte, environnement, feedback de vérification, état. -- Un seul `AGENTS.md` peut être plus efficace qu'un modèle plus cher. - -## Pour aller plus loin - -- [OpenAI: Harness Engineering — Leveraging Codex in an Agent-First World](https://openai.com/index/harness-engineering/) -- [Anthropic: Effective Harnesses for Long-Running Agents](https://www.anthropic.com/engineering/effective-harnesses-for-long-running-agents) -- [HumanLayer: Skill Issue — Harness Engineering for Coding Agents](https://humanlayer.dev/articles/harness-engineering-for-coding-agents/) -- [SWE-bench Leaderboard](https://www.swebench.com/) -- [Thoughtworks Technology Radar: Harness Engineering](https://www.thoughtworks.com/radar) - -## Exercices - -1. **Expérience comparative** : choisissez un codebase connu et une tâche non triviale. Exécutez d'abord sans harness, puis avec `AGENTS.md` et commandes de vérification explicites. Comparez les échecs par couche. - -2. **Mesure de l'écart de vérification** : choisissez 5 tâches. Notez si l'agent affirme avoir terminé, puis vérifiez indépendamment. Calculez la proportion de faux « terminé ». - -3. **Pratique de boucle diagnostic** : trouvez une tâche où l'agent échoue souvent. Exécutez, attribuez l'échec à une couche, corrigez-la, réexécutez et mesurez l'amélioration. diff --git a/courses/learn-harness-engineering/docs/fr/lectures/lecture-02-what-a-harness-actually-is/code/harness-components.md b/courses/learn-harness-engineering/docs/fr/lectures/lecture-02-what-a-harness-actually-is/code/harness-components.md deleted file mode 100644 index 5e9de853..00000000 --- a/courses/learn-harness-engineering/docs/fr/lectures/lecture-02-what-a-harness-actually-is/code/harness-components.md +++ /dev/null @@ -1,21 +0,0 @@ -# Exemple de Composants du Harness - -Pour un agent de programmation travaillant dans un dépôt local : - -- Modèle : - le LLM lui-même - -- Harness : - - prompt système - - AGENTS.md - - outil bash - - outils de lecture/écriture de fichiers - - accès git - - système de fichiers local - - scripts de démarrage - - commandes de test - - hooks d'arrêt - - vérifications lint - - boucle d'évaluation (evaluator loop) - -Si vous modifiez l'une des pièces du harness ci-dessus, vous modifiez l'agent effectif. diff --git a/courses/learn-harness-engineering/docs/fr/lectures/lecture-02-what-a-harness-actually-is/code/harness-vs-no-harness.ts b/courses/learn-harness-engineering/docs/fr/lectures/lecture-02-what-a-harness-actually-is/code/harness-vs-no-harness.ts deleted file mode 100644 index d856ba36..00000000 --- a/courses/learn-harness-engineering/docs/fr/lectures/lecture-02-what-a-harness-actually-is/code/harness-vs-no-harness.ts +++ /dev/null @@ -1,180 +0,0 @@ -/** - * harness-vs-no-harness.ts - * - * Side-by-side comparison of the same task executor running with and without - * a harness. The harness version adds explicit rules, verification steps, - * and stop conditions. - * - * Run: npx tsx docs/lectures/lecture-02-what-a-harness-actually-is/code/harness-vs-no-harness.ts - */ - -// --------------------------------------------------------------------------- -// Types & helpers -// --------------------------------------------------------------------------- - -interface TaskResult { - name: string; - passed: boolean; - durationMs: number; - issues: string[]; -} - -function pad(s: string, len: number): string { - return s.length >= len ? s : s + " ".repeat(len - s.length); -} - -// --------------------------------------------------------------------------- -// Simulated task executor -// --------------------------------------------------------------------------- - -/** A simple task that can succeed or fail depending on rules. */ -type Task = { - name: string; - requiresAuth: boolean; - hasTests: boolean; - withinScope: boolean; -}; - -const tasks: Task[] = [ - { name: "Add search endpoint", requiresAuth: true, hasTests: false, withinScope: true }, - { name: "Add delete endpoint", requiresAuth: true, hasTests: true, withinScope: true }, - { name: "Refactor auth middleware", requiresAuth: true, hasTests: true, withinScope: false }, - { name: "Add health check", requiresAuth: false, hasTests: true, withinScope: true }, - { name: "Add rate limiter", requiresAuth: true, hasTests: false, withinScope: true }, -]; - -// --------------------------------------------------------------------------- -// Run WITHOUT harness -- just execute every task, no checks -// --------------------------------------------------------------------------- - -function runWithoutHarness(taskList: Task[]): TaskResult[] { - const results: TaskResult[] = []; - - for (const t of taskList) { - const start = performance.now(); - const issues: string[] = []; - - // The "agent" does the work and calls it done. - let passed = true; - - // Problems that go undetected without a harness - if (t.requiresAuth && !t.hasTests) { - issues.push("No tests for auth-protected endpoint"); - // Agent doesn't notice -- marks as pass anyway - } - if (!t.withinScope) { - issues.push("Task is outside current scope"); - // Agent doesn't notice - } - - const duration = performance.now() - start; - results.push({ name: t.name, passed, durationMs: duration, issues }); - } - - return results; -} - -// --------------------------------------------------------------------------- -// Run WITH harness -- rules, verification, stop conditions -// --------------------------------------------------------------------------- - -function runWithHarness(taskList: Task[]): TaskResult[] { - const rules = { - requireTestsForAuth: true, - enforceScope: true, - }; - - const results: TaskResult[] = []; - - for (const t of taskList) { - const start = performance.now(); - const issues: string[] = []; - let passed = true; - - // Rule: auth endpoints must have tests - if (rules.requireTestsForAuth && t.requiresAuth && !t.hasTests) { - issues.push("BLOCKED: Auth-protected endpoint missing tests"); - passed = false; - } - - // Rule: stay in scope - if (rules.enforceScope && !t.withinScope) { - issues.push("BLOCKED: Task outside active scope -- skipped"); - passed = false; - } - - // Verification: re-check after execution - if (passed && !t.hasTests) { - issues.push("WARNING: No tests exist for this change"); - } - - const duration = performance.now() - start; - results.push({ name: t.name, passed, durationMs: duration, issues }); - } - - return results; -} - -// --------------------------------------------------------------------------- -// Reporting -// --------------------------------------------------------------------------- - -function printComparison( - noHarness: TaskResult[], - withHarness: TaskResult[] -): void { - console.log("\n" + "=".repeat(80)); - console.log(" HARNESS vs NO-HARNESS COMPARISON"); - console.log("=".repeat(80)); - - const header = `| ${pad("Task", 28)}| ${pad("No-Harness", 12)}| ${pad("Issues(NH)", 35)}| ${pad("Harness", 12)}| ${pad("Issues(H)", 35)}|`; - const sep = `|${"-".repeat(30)}|${"-".repeat(14)}|${"-".repeat(37)}|${"-".repeat(14)}|${"-".repeat(37)}|`; - - console.log("\n" + header); - console.log(sep); - - for (let i = 0; i < noHarness.length; i++) { - const nh = noHarness[i]; - const wh = withHarness[i]; - const nhPass = nh.passed ? "PASS" : "FAIL"; - const whPass = wh.passed ? "PASS" : "FAIL"; - const nhIssue = nh.issues[0] ?? "(none)"; - const whIssue = wh.issues[0] ?? "(none)"; - - console.log( - `| ${pad(nh.name, 28)}| ${pad(nhPass, 12)}| ${pad(nhIssue, 35)}| ${pad(whPass, 12)}| ${pad(whIssue, 35)}|` - ); - } - - // Metrics - const nhPassed = noHarness.filter((r) => r.passed).length; - const whPassed = withHarness.filter((r) => r.passed).length; - const nhIssues = noHarness.reduce((sum, r) => sum + r.issues.length, 0); - const whIssues = withHarness.reduce((sum, r) => sum + r.issues.length, 0); - - console.log("\n" + "=".repeat(80)); - console.log(" SUMMARY METRICS"); - console.log("=".repeat(80)); - - const metricsHeader = `| ${pad("Metric", 30)}| ${pad("No Harness", 15)}| ${pad("With Harness", 15)}|`; - const metricsSep = `|${"-".repeat(32)}|${"-".repeat(17)}|${"-".repeat(17)}|`; - console.log("\n" + metricsHeader); - console.log(metricsSep); - console.log(`| ${pad("Tasks passed", 30)}| ${pad(String(nhPassed) + "/" + noHarness.length, 15)}| ${pad(String(whPassed) + "/" + withHarness.length, 15)}|`); - console.log(`| ${pad("Issues detected", 30)}| ${pad(String(nhIssues), 15)}| ${pad(String(whIssues), 15)}|`); - console.log(`| ${pad("False positives (passed but flawed)", 30)}| ${pad(String(nhPassed - truePassCount(noHarness)), 15)}| ${pad(String(whPassed - truePassCount(withHarness)), 15)}|`); - - console.log("\n The harness catches problems the no-harness run silently ignores."); - console.log(" Without a harness, every task 'passes' even when it shouldn't.\n"); -} - -/** Count tasks that actually pass (have tests and are in scope). */ -function truePassCount(results: TaskResult[]): number { - return results.filter((r) => r.passed && r.issues.length === 0).length; -} - -// --------------------------------------------------------------------------- -// Run -// --------------------------------------------------------------------------- - -printComparison(runWithoutHarness(tasks), runWithHarness(tasks)); diff --git a/courses/learn-harness-engineering/docs/fr/lectures/lecture-02-what-a-harness-actually-is/code/index.md b/courses/learn-harness-engineering/docs/fr/lectures/lecture-02-what-a-harness-actually-is/code/index.md deleted file mode 100644 index d59f74d4..00000000 --- a/courses/learn-harness-engineering/docs/fr/lectures/lecture-02-what-a-harness-actually-is/code/index.md +++ /dev/null @@ -1,8 +0,0 @@ -# Code du Cours 02 - -Utilisez ce dossier pour de petits exemples qui distinguent : - -- le comportement du modèle -- le comportement du harness -- les configurations basées uniquement sur les prompts -- les configurations d'agents soutenues par l'environnement diff --git a/courses/learn-harness-engineering/docs/fr/lectures/lecture-02-what-a-harness-actually-is/code/minimal-harness-loop.ts b/courses/learn-harness-engineering/docs/fr/lectures/lecture-02-what-a-harness-actually-is/code/minimal-harness-loop.ts deleted file mode 100644 index e56cddd8..00000000 --- a/courses/learn-harness-engineering/docs/fr/lectures/lecture-02-what-a-harness-actually-is/code/minimal-harness-loop.ts +++ /dev/null @@ -1,36 +0,0 @@ -type Message = { - role: "user" | "assistant"; - content: string; -}; - -type ToolResult = { - ok: boolean; - output: string; -}; - -function runTool(name: string, input: string): ToolResult { - if (name === "read_file") { - return { ok: true, output: `contents of ${input}` }; - } - - return { ok: false, output: `unknown tool: ${name}` }; -} - -export function minimalHarness(messages: Message[]) { - const nextAction = { - tool: "read_file", - input: "README.md" - }; - - const result = runTool(nextAction.tool, nextAction.input); - - return { - messages: [ - ...messages, - { - role: "assistant", - content: `Tool ${nextAction.tool} returned: ${result.output}` - } - ] - }; -} diff --git a/courses/learn-harness-engineering/docs/fr/lectures/lecture-02-what-a-harness-actually-is/index.md b/courses/learn-harness-engineering/docs/fr/lectures/lecture-02-what-a-harness-actually-is/index.md deleted file mode 100644 index 3feee4f7..00000000 --- a/courses/learn-harness-engineering/docs/fr/lectures/lecture-02-what-a-harness-actually-is/index.md +++ /dev/null @@ -1,109 +0,0 @@ -[中文版本 →](../../../zh/lectures/lecture-02-what-a-harness-actually-is/) - -> Exemples de code : [code/](https://github.com/walkinglabs/learn-harness-engineering/blob/main/docs/fr/lectures/lecture-02-what-a-harness-actually-is/code/) -> Projet pratique : [Project 01. Prompt-only vs. rules-first](./../../projects/project-01-baseline-vs-minimal-harness/index.md) - -# Leçon 02. Ce que signifie vraiment harness - -Le mot « harness » est omniprésent dans les cercles d'agents de codage IA, mais honnêtement, la plupart des gens veulent dire « un fichier de prompt » quand ils disent harness. Ce n'est pas un harness. C'est comme ouvrir un restaurant avec seulement des ingrédients — pas de cuisinière, pas de couteaux, pas de recettes, pas de flux de dressage des assiettes. Ce n'est pas un restaurant. C'est un réfrigérateur. - -Cette leçon vous donne une définition précise et opérationnelle du harness. Pas une abstraction académique, mais un cadre que vous pouvez utiliser dès aujourd'hui : un harness se compose de cinq sous-systèmes, chacun avec des responsabilités et des critères d'évaluation clairs. - -## Commençons par une analogie - -Imaginez que vous êtes un ingénieur nouvellement embauché, propulsé dans un projet sans aucune documentation. Pas de README, pas de commentaires dans le code, personne ne vous dit comment lancer les tests, la config CI est enterrée quelque part. Pouvez-vous écrire du bon code ? Peut-être — si vous êtes assez intelligent et patient. Mais vous passerez un temps considérable à « comprendre de quoi parle ce projet » plutôt qu'à « résoudre le problème ». - -Un agent IA fait face exactement à la même situation. Et c'est pire — vous pouvez au moins demander à un collègue. L'agent ne peut voir que les fichiers que vous mettez devant lui et les commandes qu'il peut exécuter. Il ne peut pas taper quelqu'un sur l'épaule et demander « hey, quelle version de l'ORM ce projet utilise-t-il ? » - -OpenAI formule le principe fondamental ainsi : « le repo EST la spécification » — tout le contexte nécessaire devrait se trouver dans le dépôt, livré via des fichiers d'instructions structurés, des commandes de vérification explicites et une organisation claire des répertoires. La documentation d'Anthropic sur les agents longue durée met l'accent sur la persistance de l'état, les chemins de récupération explicites et le suivi structuré de la progression. Les deux entreprises se concentrent sur des aspects différents, mais elles disent la même chose : **tout ce qui se trouve dans l'infrastructure d'ingénierie en dehors du modèle détermine quelle part de la capacité du modèle est réellement exploitée.** - -Regardez quelques outils que vous connaissez déjà : - -**Claude Code** incarne la pensée harness. Il lit `CLAUDE.md` depuis votre repo (étagère à recettes), peut exécuter des commandes shell (râtelier à couteaux), s'exécute dans votre environnement local (cuisinière), maintient un historique de session (plan de travail), et peut lancer des tests et voir les résultats (fenêtre de contrôle qualité). Mais si vous ne lui dites pas comment lancer les tests, la fenêtre de contrôle qualité est en panne — personne ne sait si le plat est complètement cuit. - -**Cursor** suit une logique similaire. Son fichier `.cursorrules` est l'étagère à recettes, le terminal est le râtelier à couteaux, il lit la structure de votre projet et la config lint pour la cuisinière. Mais la gestion d'état de Cursor est relativement faible — fermez l'IDE et rouvrez-le, et le contexte précédent a disparu. - -**Codex** (l'agent de codage d'OpenAI) utilise des git worktrees pour isoler l'environnement d'exécution de chaque tâche, couplé à une pile d'observabilité locale (logs, métriques, traces), de sorte que chaque modification est vérifiée dans un environnement indépendant. Dans les repos avec `AGENTS.md` et des commandes de vérification claires, il performe bien mieux que dans les repos « nus ». - -**AutoGPT** est l'exemple à ne pas suivre — l'absence de gestion structurée de l'état conduit à une accumulation de contexte dans les tâches longues, et l'absence de mécanismes de retour précis fait boucler l'agent. Beaucoup de gens disent qu'AutoGPT « ne fonctionne pas », mais en réalité c'est le harness d'AutoGPT qui ne fonctionne pas — donnez à un chef une cuisinière cassée et même les meilleurs ingrédients ne produiront pas un repas. - -## Concepts clés - -- **Qu'est-ce qu'un harness** : Tout ce qui se trouve dans l'infrastructure d'ingénierie en dehors des poids du modèle. OpenAI résume le travail fondamental de l'ingénieur en trois choses : concevoir des environnements, exprimer l'intention et construire des boucles de retour. Anthropic appelle son Claude Agent SDK un « agent harness généraliste ». -- **Le repo est l'unique source de vérité** : Tout ce que l'agent ne peut pas voir, en pratique, n'existe pas. OpenAI traite le repo comme le « système de référence » — tout le contexte nécessaire doit y vivre, via des fichiers structurés et une organisation claire des répertoires. -- **Donnez un plan, pas un manuel** : L'expérience d'OpenAI — `AGENTS.md` devrait être une page de sommaire, pas une encyclopédie. Une centaine de lignes suffit. Si ça ne tient pas, découpez-le dans le répertoire `docs/` et laissez l'agent lire à la demande. -- **Contraindre, pas micromanager** : Un bon harness utilise des règles exécutables pour contraindre l'agent, plutôt que d'énumérer les instructions une par une. OpenAI dit « imposez des invariants, ne micromanagez pas l'implémentation » ; Anthropic a constaté que les agents font l'éloge de leur propre travail en toute confiance, et la solution est de séparer « celui qui fait le travail » de « celui qui vérifie le travail ». -- **Retirer les composants un par un** : Pour quantifier la contribution marginale de chaque composant du harness, retirez-les un par un et observez quel retrait provoque la plus forte baisse de performance. Anthropic a utilisé cette méthode et a découvert qu'à mesure que les modèles deviennent plus puissants, certains composants cessent d'être critiques — mais de nouveaux apparaissent toujours. - -## Le modèle de harness à cinq sous-systèmes - -Retour à l'analogie de la cuisine. Une cuisine complète possède cinq zones fonctionnelles, et un harness possède cinq sous-systèmes : - -```mermaid -flowchart LR - Rules["Project rules
AGENTS.md / CLAUDE.md"] --> Agent["AI Agent"] - State["Progress and git
PROGRESS.md / commits"] --> Agent - Agent --> Tools["Tools
shell / files / tests"] - Tools --> Env["Runtime
deps / services / versions"] - Env --> Checks["Check results
test / lint / build"] - Checks --> Agent -``` - -**Sous-système d'instructions (étagère à recettes)** : Créez `AGENTS.md` (ou `CLAUDE.md`) contenant un aperçu du projet et son objectif (une phrase), la stack technique et les versions (Python 3.11, FastAPI 0.100+, PostgreSQL 15), les commandes de premier lancement (`make setup`, `make test`), les contraintes strictes non négociables (« Toutes les API doivent utiliser OAuth 2.0 »), et des liens vers une documentation plus détaillée. - -**Sous-système d'outils (râtelier à couteaux)** : Assurez-vous que l'agent dispose d'un accès suffisant aux outils. Ne désactivez pas le shell pour des raisons de « sécurité » — si l'agent ne peut même pas lancer `pip install`, comment est-il censé travailler ? Mais n'ouvrez pas tout non plus — suivez le principe du moindre privilège. - -**Sous-système d'environnement (cuisinière)** : Rendez l'état de l'environnement auto-descriptif. Utilisez `pyproject.toml` ou `package.json` pour verrouiller les dépendances, `.nvmrc` ou `.python-version` pour les versions d'exécution, Docker ou devcontainers pour la reproductibilité. - -**Sous-système d'état (plan de travail)** : Les tâches longues nécessitent un suivi de progression. Utilisez un simple fichier `PROGRESS.md` enregistrant : ce qui est terminé, ce qui est en cours, ce qui est bloqué. Mettez à jour avant la fin de chaque session, lisez au démarrage de la session suivante. - -**Sous-système de retour (fenêtre de contrôle qualité)** : C'est le sous-système au meilleur retour sur investissement. Listez explicitement les commandes de vérification dans `AGENTS.md` : -``` -Verification commands: -- Tests: pytest tests/ -x -- Type check: mypy src/ --strict -- Lint: ruff check src/ -- Full verification: make check (includes all above) -``` - -L'absence d'un sous-système est comme l'absence d'une zone fonctionnelle dans la cuisine — vous pouvez toujours cuisiner, mais c'est toujours laborieux. - -**Quantifier la valeur des composants du harness** : Utilisez le « contrôle isométrique du modèle ». Gardez le modèle fixe, retirez les sous-systèmes un par un et mesurez quel retrait provoque la plus forte baisse de performance. La plus forte baisse indique le composant à plus forte contribution marginale dans cette tâche, pas automatiquement le goulot d'étranglement. Pour localiser le vrai goulot, combinez cette expérience avec des journaux d'échec et une attribution : tâche floue, contexte insuffisant, environnement non reproductible, feedback de vérification manquant ou gestion d'état rompue. - -## L'histoire vraie d'une équipe - -Une équipe a utilisé GPT-4o sur une application frontend TypeScript + React (~20 000 lignes de code). Ils ont traversé quatre étapes — essentiellement ajouter des équipements de cuisine un par un : - -**Étape 1 — Cuisine vide** : Seulement une description basique du projet dans le README. 1 essai sur 5 a réussi (20 %). Principaux échecs : mauvais choix de gestionnaire de paquets (npm vs yarn), non-respect des conventions de nommage des composants, impossibilité de lancer les tests. - -**Étape 2 — Étagère à recettes installée** : Ajout de `AGENTS.md` avec les versions de la stack technique, les conventions de nommage, les décisions architecturales clés. Le taux de réussite est passé à 60 %. Les échecs restants concernaient principalement des problèmes d'environnement et l'absence de vérification. - -**Étape 3 — Fenêtre de contrôle qualité ouverte** : Liste des commandes de vérification dans `AGENTS.md` : `yarn test && yarn lint && yarn build`. Le taux de réussite est passé à 80 %. - -**Étape 4 — Plan de travail prêt** : Introduction de modèles de fichiers de progression où les agents enregistraient le travail terminé et non terminé à chaque exécution. Le taux de réussite s'est stabilisé entre 80 et 100 %. - -Quatre itérations, le modèle n'a pas du tout changé, le taux de réussite est passé de 20 % à près de 100 %. C'est la puissance du harness engineering. Vous n'avez pas acheté d'ingrédients plus chers — vous avez simplement organisé la cuisine correctement. - -## Points clés - -- Harness = Instructions + Outils + Environnement + État + Retour. Cinq sous-systèmes, comme les cinq zones fonctionnelles d'une cuisine — tous essentiels. -- Si ce ne sont pas les poids du modèle, c'est du harness. Votre harness détermine quelle part de la capacité du modèle est réellement exploitée. -- Parmi les cinq sous-systèmes, le sous-système de retour offre généralement l'investissement le plus faible et le rendement le plus élevé. Configurez d'abord vos commandes de vérification — la fenêtre de contrôle qualité est l'amélioration la plus rentable. -- Utilisez le « contrôle isométrique du modèle » pour quantifier la contribution marginale de chaque sous-système ; utilisez les journaux d'échec et l'attribution pour localiser le vrai goulot d'étranglement. -- Le harness se dégrade comme le code. Auditez régulièrement, remboursez la dette de harness comme vous remboursez la dette technique. - -## Pour aller plus loin - -- [OpenAI: Harness Engineering](https://openai.com/index/harness-engineering/) -- [Anthropic: Effective Harnesses for Long-Running Agents](https://www.anthropic.com/engineering/effective-harnesses-for-long-running-agents) -- [HumanLayer: Harness Engineering for Coding Agents](https://humanlayer.dev/articles/harness-engineering-for-coding-agents/) -- [SWE-agent: Agent-Computer Interfaces](https://github.com/princeton-nlp/SWE-agent) -- [Thoughtworks: Harness Engineering on Technology Radar](https://www.thoughtworks.com/radar) - -## Exercices - -1. **Audit harness en cinq tuples** : Prenez un projet où vous utilisez un agent IA et faites un audit complet en utilisant le cadre à cinq tuples. Notez chaque sous-système de 1 à 5. Trouvez le sous-système ayant la note la plus basse, passez 30 minutes à l'améliorer, puis observez le changement de performance de l'agent. - -2. **Expérience de contrôle isométrique du modèle** : Choisissez un modèle et une tâche difficile. Retirez séquentiellement les instructions (supprimez AGENTS.md), retirez le retour (ne fournissez pas de commandes de vérification), retirez l'état (pas de fichiers de progression) — ne retirez qu'un seul élément à la fois et mesurez la baisse de performance. En fonction des résultats, classez l'importance des sous-systèmes pour votre projet. - -3. **Analyse des affordances** : Trouvez un scénario où l'agent dans votre projet « veut faire quelque chose mais ne peut pas » (par ex., sait qu'il devrait utiliser des requêtes paramétrées mais ne connaît pas les patterns ORM de votre projet). Analysez s'il s'agit d'un Golfe d'Exécution (ne sait pas comment) ou d'un Golfe d'Évaluation (ne sait pas si c'est correct), puis concevez une amélioration du harness pour combler ce fossé. diff --git a/courses/learn-harness-engineering/docs/fr/lectures/lecture-03-why-the-repository-must-become-the-system-of-record/code/index.md b/courses/learn-harness-engineering/docs/fr/lectures/lecture-03-why-the-repository-must-become-the-system-of-record/code/index.md deleted file mode 100644 index cf626425..00000000 --- a/courses/learn-harness-engineering/docs/fr/lectures/lecture-03-why-the-repository-must-become-the-system-of-record/code/index.md +++ /dev/null @@ -1,7 +0,0 @@ -# Code du Cours 03 - -Utilisez ce dossier pour des exemples de : - -- structures de dépôt lisibles par les agents -- documentation comme système d'enregistrement (system of record) -- mauvais vs bon placement des connaissances diff --git a/courses/learn-harness-engineering/docs/fr/lectures/lecture-03-why-the-repository-must-become-the-system-of-record/code/repo-knowledge-layout.txt b/courses/learn-harness-engineering/docs/fr/lectures/lecture-03-why-the-repository-must-become-the-system-of-record/code/repo-knowledge-layout.txt deleted file mode 100644 index 45315dac..00000000 --- a/courses/learn-harness-engineering/docs/fr/lectures/lecture-03-why-the-repository-must-become-the-system-of-record/code/repo-knowledge-layout.txt +++ /dev/null @@ -1,13 +0,0 @@ -AGENTS.md -docs/ - ARCHITECTURE.md - PRODUCT.md - RELIABILITY.md - references/ - electron.md - sqlite.md - plans/ - active/ - completed/ -src/ -scripts/ diff --git a/courses/learn-harness-engineering/docs/fr/lectures/lecture-03-why-the-repository-must-become-the-system-of-record/code/repo-reader.ts b/courses/learn-harness-engineering/docs/fr/lectures/lecture-03-why-the-repository-must-become-the-system-of-record/code/repo-reader.ts deleted file mode 100644 index 46f6a62a..00000000 --- a/courses/learn-harness-engineering/docs/fr/lectures/lecture-03-why-the-repository-must-become-the-system-of-record/code/repo-reader.ts +++ /dev/null @@ -1,189 +0,0 @@ -/** - * repo-reader.ts - * - * Reads a directory structure and scores it on discoverability. - * Checks for: AGENTS.md, docs/, architecture docs, feature tracking, - * handoff files, and other signals of a repository that serves as the - * system of record. - * - * Usage: - * npx tsx docs/lectures/lecture-03.../code/repo-reader.ts [path] - * (defaults to current working directory if no path given) - * - * Run: npx tsx docs/lectures/lecture-03-why-the-repository-must-become-the-system-of-record/code/repo-reader.ts - */ - -import * as fs from "node:fs"; -import * as path from "node:path"; - -// --------------------------------------------------------------------------- -// Scoring criteria -// --------------------------------------------------------------------------- - -interface Check { - name: string; - description: string; - maxPoints: number; - check: (dir: string) => { points: number; found: string[]; missing: string[] }; -} - -const checks: Check[] = [ - { - name: "AGENTS.md / CLAUDE.md", - description: "Agent-readable instruction file at repo root", - maxPoints: 15, - check: (dir) => { - const candidates = ["AGENTS.md", "CLAUDE.md", ".claude/CLAUDE.md"]; - const found = candidates.filter((f) => fs.existsSync(path.join(dir, f))); - return { points: found.length > 0 ? 15 : 0, found, missing: found.length > 0 ? [] : candidates }; - }, - }, - { - name: "Documentation directory", - description: "Dedicated docs/ or documentation/ directory", - maxPoints: 10, - check: (dir) => { - const candidates = ["docs", "documentation", "doc"]; - const found = candidates.filter((f) => { - const p = path.join(dir, f); - return fs.existsSync(p) && fs.statSync(p).isDirectory(); - }); - return { points: found.length > 0 ? 10 : 0, found, missing: found.length > 0 ? [] : ["docs/"] }; - }, - }, - { - name: "Architecture documentation", - description: "Files describing system architecture", - maxPoints: 15, - check: (dir) => { - const patterns = [ - "architecture.md", "ARCHITECTURE.md", "docs/architecture.md", - "docs/architecture/", "design.md", "DESIGN.md", - ]; - const found = patterns.filter((f) => fs.existsSync(path.join(dir, f))); - return { points: found.length > 0 ? 15 : 0, found, missing: found.length > 0 ? [] : ["architecture.md or docs/architecture/"] }; - }, - }, - { - name: "Feature tracking", - description: "Feature list or task tracking file", - maxPoints: 15, - check: (dir) => { - const patterns = [ - "feature_list.json", "features.md", "FEATURES.md", - "docs/features.md", "tasks.json", "TODO.md", - ]; - const found = patterns.filter((f) => fs.existsSync(path.join(dir, f))); - return { points: found.length > 0 ? 15 : 0, found, missing: found.length > 0 ? [] : ["feature_list.json or features.md"] }; - }, - }, - { - name: "Handoff / session continuity", - description: "Files for multi-session continuity", - maxPoints: 15, - check: (dir) => { - const patterns = [ - "HANDOFF.md", "handoff.md", "SESSION_NOTES.md", - "docs/handoff.md", ".handoff", "PROGRESS.md", - ]; - const found = patterns.filter((f) => fs.existsSync(path.join(dir, f))); - return { points: found.length > 0 ? 15 : 0, found, missing: found.length > 0 ? [] : ["HANDOFF.md or PROGRESS.md"] }; - }, - }, - { - name: "Testing structure", - description: "Test directory or test configuration", - maxPoints: 10, - check: (dir) => { - const patterns = ["test", "tests", "__tests__", "spec"]; - const found = patterns.filter((f) => { - const p = path.join(dir, f); - return fs.existsSync(p) && fs.statSync(p).isDirectory(); - }); - return { points: found.length > 0 ? 10 : 0, found, missing: found.length > 0 ? [] : ["test/ or tests/"] }; - }, - }, - { - name: "Configuration files", - description: "Project config (package.json, tsconfig, etc.)", - maxPoints: 10, - check: (dir) => { - const patterns = ["package.json", "tsconfig.json", "pyproject.toml", "Cargo.toml", "go.mod"]; - const found = patterns.filter((f) => fs.existsSync(path.join(dir, f))); - return { points: found.length > 0 ? 10 : 0, found, missing: found.length > 0 ? [] : ["package.json or equivalent"] }; - }, - }, - { - name: "README", - description: "Root README file", - maxPoints: 10, - check: (dir) => { - const patterns = ["README.md", "README.rst", "README.txt", "README"]; - const found = patterns.filter((f) => fs.existsSync(path.join(dir, f))); - return { points: found.length > 0 ? 10 : 0, found, missing: found.length > 0 ? [] : ["README.md"] }; - }, - }, -]; - -// --------------------------------------------------------------------------- -// Report generation -// --------------------------------------------------------------------------- - -function pad(s: string, len: number): string { - return s.length >= len ? s : s + " ".repeat(len - s.length); -} - -function scoreRepo(targetDir: string): void { - const resolved = path.resolve(targetDir); - - if (!fs.existsSync(resolved)) { - console.error(` Error: Directory not found: ${resolved}`); - process.exit(1); - } - - console.log("\n" + "=".repeat(80)); - console.log(" REPOSITORY DISCOVERABILITY SCORE"); - console.log("=".repeat(80)); - console.log(` Target: ${resolved}\n`); - - const header = `| ${pad("Criterion", 30)}| ${pad("Points", 8)}| ${pad("Status", 10)}| Details`; - const sep = `|${"-".repeat(32)}|${"-".repeat(10)}|${"-".repeat(12)}|${"-".repeat(30)}`; - console.log(header); - console.log(sep); - - let totalScore = 0; - let maxScore = 0; - - for (const check of checks) { - const result = check.check(resolved); - totalScore += result.points; - maxScore += check.maxPoints; - const status = result.points > 0 ? "PASS" : "FAIL"; - const detail = result.found.length > 0 ? result.found.join(", ") : result.missing[0]; - - console.log(`| ${pad(check.name, 30)}| ${pad(result.points + "/" + check.maxPoints, 8)}| ${pad(status, 10)}| ${detail}`); - } - - // Final score - console.log("\n" + "-".repeat(80)); - console.log(` TOTAL SCORE: ${totalScore} / ${maxScore} (${Math.round((totalScore / maxScore) * 100)}%)`); - - // Grade - const pct = totalScore / maxScore; - let grade: string; - if (pct >= 0.9) grade = "A -- Repository is a strong system of record"; - else if (pct >= 0.7) grade = "B -- Good foundation, minor gaps"; - else if (pct >= 0.5) grade = "C -- Partial structure, significant gaps"; - else if (pct >= 0.3) grade = "D -- Minimal structure, hard for agents to navigate"; - else grade = "F -- Repository lacks discoverability signals"; - - console.log(` GRADE: ${grade}`); - console.log("=".repeat(80) + "\n"); -} - -// --------------------------------------------------------------------------- -// Main -// --------------------------------------------------------------------------- - -const target = process.argv[2] || process.cwd(); -scoreRepo(target); diff --git a/courses/learn-harness-engineering/docs/fr/lectures/lecture-03-why-the-repository-must-become-the-system-of-record/code/system-of-record-checklist.md b/courses/learn-harness-engineering/docs/fr/lectures/lecture-03-why-the-repository-must-become-the-system-of-record/code/system-of-record-checklist.md deleted file mode 100644 index dbb09251..00000000 --- a/courses/learn-harness-engineering/docs/fr/lectures/lecture-03-why-the-repository-must-become-the-system-of-record/code/system-of-record-checklist.md +++ /dev/null @@ -1,11 +0,0 @@ -# Liste de Contrôle du Système d'Enregistrement - -Un nouvel agent peut-il découvrir ce qui suit uniquement depuis le dépôt ? - -- Quel produit est en cours de construction ? -- Que doit faire l'application pour les utilisateurs ? -- Comment la base de code est-elle organisée ? -- Comment l'application démarre-t-elle ? -- Comment la santé est-elle vérifiée ? -- Quel travail est actuellement en cours ? -- Quelles normes de qualité sont importantes ? diff --git a/courses/learn-harness-engineering/docs/fr/lectures/lecture-03-why-the-repository-must-become-the-system-of-record/index.md b/courses/learn-harness-engineering/docs/fr/lectures/lecture-03-why-the-repository-must-become-the-system-of-record/index.md deleted file mode 100644 index a7a1b511..00000000 --- a/courses/learn-harness-engineering/docs/fr/lectures/lecture-03-why-the-repository-must-become-the-system-of-record/index.md +++ /dev/null @@ -1,137 +0,0 @@ -[中文版本 →](../../../zh/lectures/lecture-03-why-the-repository-must-become-the-system-of-record/) - -> Exemples de code : [code/](https://github.com/walkinglabs/learn-harness-engineering/blob/main/docs/fr/lectures/lecture-03-why-the-repository-must-become-the-system-of-record/code/) -> Projet pratique : [Projet 02. Espace de travail lisible par les agents](./../../projects/project-02-agent-readable-workspace/index.md) - -# Leçon 03. Faire du dépôt la source unique de vérité - -Les décisions d'architecture de votre équipe sont dispersées entre Confluence, Slack, Jira et la tête de quelques ingénieurs seniors. Pour les humains, cela fonctionne tout juste : vous pouvez demander à un collègue, chercher dans l'historique du chat, fouiller la documentation. En dernier recours, vous pouvez coincer quelqu'un en salle de pause. Mais pour un agent IA, l'information qui n'est pas dans le dépôt n'existe tout simplement pas. - -Ce n'est pas une exagération. Pensez à ce que sont réellement les entrées d'un agent : prompts système et descriptions de tâche, contenu des fichiers du dépôt, sorties d'outils. C'est tout. Votre historique Slack, vos tickets Jira, vos pages Confluence, et cette décision d'architecture discutée autour d'un café un vendredi après-midi : l'agent ne voit rien de tout cela. Il ne peut pas "aller demander à quelqu'un" ni "chercher dans l'historique du chat". C'est un ingénieur enfermé dans le dépôt ; de tout ce qui est dehors, il ne sait rien. - -La question devient donc : allez-vous donner une bonne carte à cet ingénieur ? - -## Ce qui doit figurer sur la carte - -OpenAI le formule clairement : **l'information qui n'existe pas dans le repo n'existe pas pour l'agent.** Ils appellent cela le principe "repo as spec" : le dépôt lui-même est le document de spécification ayant la plus haute autorité. - -La documentation d'Anthropic sur les agents de longue durée dit la même chose : l'état persistant est une condition nécessaire à la continuité des tâches longues. La capacité à récupérer les connaissances entre sessions détermine directement les taux de réussite. Et cet état doit exister dans le dépôt, car c'est le seul stockage stable et accessible dont dispose l'agent. - -Vous pourriez penser : "Notre équipe est petite, le savoir est dans la tête de tout le monde, et ça marche." Pour les humains, oui. Mais si vous utilisez un agent, acceptez ce fait : l'agent ne peut pas demander aux gens. Tout ce qu'il doit savoir doit être écrit et placé là où il peut le trouver. - -Il ne s'agit pas "d'écrire plus de documentation". Il s'agit de "mettre l'information de décision au bon endroit". Un `ARCHITECTURE.md` de 50 lignes dans le répertoire `src/api/` est dix mille fois plus utile qu'un document de conception de 500 pages dans Confluence que personne ne maintient. C'est comme une carte de bureau dessinée à la main et collée sur votre table, comparée à un magnifique plan d'architecte enfermé dans un classeur : la première est là quand vous en avez besoin ; le second est techniquement supérieur, mais inutile dans l'instant. - -## Visibilité du savoir - -```mermaid -flowchart LR - Slack["Règles dans Slack"] --> Write["Les écrire dans les fichiers du repo
AGENTS.md / ARCHITECTURE.md / PROGRESS.md"] - Confluence["Règles dans Confluence"] --> Write - Heads["Règles dans la tête des gens"] --> Write - Jira["Règles dans les tickets Jira"] --> Write - Write --> Repo["Fichiers du dépôt"] - Repo --> Agent["Nouvelle session d'agent
lit directement le repo"] - Warning["Si une règle n'est pas dans le repo,
l'agent ne peut pas la voir"] --> Agent -``` - -Comment tester si votre carte est suffisante ? Lancez un "test de démarrage à froid" : ouvrez une toute nouvelle session d'agent en utilisant uniquement le contenu du dépôt et voyez si elle peut répondre à cinq questions de base : - -```mermaid -flowchart TB - Q1["Qu'est-ce que ce système ?"] --> A1["AGENTS.md / README"] - Q2["Comment est-il organisé ?"] --> A2["ARCHITECTURE.md / docs de modules"] - Q3["Comment l'exécuter ?"] --> A3["Makefile / init.sh / scripts package"] - Q4["Comment le vérifier ?"] --> A4["Commandes test, lint et check"] - Q5["Où en sommes-nous ?"] --> A5["PROGRESS.md / feature list / historique git"] - - A1 --> Ready["Une nouvelle session peut commencer à travailler
sans demander à un humain"] - A2 --> Ready - A3 --> Ready - A4 --> Ready - A5 --> Ready -``` - -Si elle ne peut pas répondre, la carte a des zones blanches. Là où la carte est vide, l'agent devine : les mauvaises suppositions deviennent des bugs, et trop deviner gaspille du contexte. Et chaque nouvelle session recommence à deviner. Le coût de la supposition est toujours supérieur au coût de dessiner correctement la carte dès le départ. - -## Concepts clés - -- **Knowledge Visibility Gap** : proportion du savoir total du projet qui N'EST PAS dans le dépôt. Plus l'écart est grand, plus le taux d'échec de l'agent augmente. Combien de savoir implicite sur ce projet vit dans votre tête ? Comptez tout, puis voyez ce qui est arrivé dans le repo : la différence est votre écart de visibilité. -- **System of Record** : le dépôt de code comme source autorisée pour les décisions de projet, contraintes d'architecture, état d'exécution et standards de vérification. Le repo a le dernier mot ; rien d'autre ne compte. Comme une carte qui indique "route fermée" : vous n'y allez pas. Mais si cette information n'existe que dans la tête d'une personne, il faut lui demander à chaque fois. -- **Cold-Start Test** : les cinq questions ci-dessus. Le nombre de réponses indique le degré de complétude de votre carte. -- **Discovery Cost** : budget de contexte que l'agent brûle pour trouver une information clé dans le dépôt. Plus l'information est cachée, plus le coût de découverte est élevé, et moins il reste de budget pour la tâche réelle. Cacher une information critique dans un README dix niveaux plus bas, c'est comme enfermer l'extincteur dans un coffre au sous-sol : il existe, mais vous ne le trouvez pas quand vous en avez besoin. -- **Knowledge Decay Rate** : proportion d'entrées de connaissance qui deviennent obsolètes par unité de temps. Une documentation désynchronisée du code est le plus grand ennemi, pire que pas de documentation du tout. -- **ACID Analogy** : appliquer les principes de transaction de base de données (Atomicity, Consistency, Isolation, Durability) à la gestion d'état des agents. Nous détaillons cela ci-dessous. - -## Comment dessiner une bonne carte - -**Principe 1 : le savoir vit près du code.** Une règle sur l'authentification des endpoints API doit être à côté du code API, pas enterrée dans un énorme document global. Placez un court document dans chaque répertoire de module expliquant ses responsabilités, interfaces et contraintes particulières. Comme les étiquettes de rayons en bibliothèque : vous voulez des livres d'histoire, vous allez directement au rayon "Histoire". Pas besoin de fouiller toute la bibliothèque. - -**Principe 2 : utilisez un fichier d'entrée standardisé.** `AGENTS.md` (ou `CLAUDE.md`) est la page d'accueil de l'agent. Il n'a pas besoin de contenir toute l'information, mais il doit permettre à l'agent de répondre vite à trois questions : "Qu'est-ce que ce projet ?", "Comment l'exécuter ?" et "Comment le vérifier ?". 50-100 lignes suffisent. - -**Principe 3 : minimal mais complet.** Chaque élément de savoir doit avoir un cas d'usage clair. Si supprimer une règle n'affecte pas la qualité de décision de l'agent, cette règle ne devrait pas exister. Mais chaque question du test de démarrage à froid doit avoir une réponse. C'est un équilibre délicat : ni trop, ni trop peu, juste assez. - -**Principe 4 : mettez à jour avec le code.** Liez les mises à jour de connaissance aux changements de code. L'approche la plus simple : placer les documents d'architecture dans le répertoire du module correspondant. Quand vous modifiez le code, vous voyez naturellement le document. Après les changements de code, CI peut vous rappeler de vérifier si les docs doivent être mises à jour. - -**Structure concrète du repo** : - -``` -project/ -├── AGENTS.md # Entry: project overview, run commands, hard constraints -├── src/ -│ ├── api/ -│ │ ├── ARCHITECTURE.md # API layer architecture decisions -│ │ └── ... -│ ├── db/ -│ │ ├── CONSTRAINTS.md # Database operation hard constraints -│ │ └── ... -│ └── ... -├── PROGRESS.md # Current progress: done, in-progress, blocked -└── Makefile # Standardized commands: setup, test, lint, check -``` - -## Gérer l'état des agents avec les principes ACID - -Cette analogie vient de la gestion des transactions en base de données. Vous pouvez penser que cela complique les choses, mais elle fournit en réalité un cadre très pratique : - -- **Atomicity** : chaque "opération logique" (par exemple "ajouter un nouvel endpoint et mettre à jour les tests") correspond à un commit git. Si elle échoue en cours de route, `git stash` permet de revenir en arrière. Tout ou rien : pas de "à moitié fait". -- **Consistency** : définissez des prédicats de vérification d'un "état cohérent" : tous les tests passent, le lint ne rapporte aucune erreur. L'agent exécute la vérification après chaque opération ; les états intermédiaires incohérents ne sont pas commités. Comme un virement bancaire : on ne débite pas sans créditer. -- **Isolation** : quand plusieurs agents travaillent en parallèle, concevez les fichiers d'état pour éviter les conditions de course. Approche simple : chaque agent utilise son propre fichier de progrès, ou des branches git assurent l'isolation. Deux cuisiniers ne peuvent pas assaisonner la même marmite en même temps : qui est responsable si c'est trop salé ? -- **Durability** : le savoir critique du projet vit dans des fichiers suivis par git. L'état temporaire peut rester en mémoire de session, mais le savoir entre sessions doit être persisté dans des fichiers. Ce qui est dans votre tête ne compte pas ; seul ce qui est écrit compte. - -## Une vraie histoire de transformation - -Une équipe maintenait une plateforme e-commerce avec environ 30 microservices. Les décisions d'architecture (protocoles de communication interservices, stratégies de cohérence des données, règles de versioning API) étaient dispersées entre Confluence (partiellement obsolète), Slack (difficile à rechercher), la tête de quelques ingénieurs seniors (non scalable) et des commentaires de code sporadiques (pas systématiques). - -Après l'introduction d'agents IA, 70% des tâches nécessitaient une intervention humaine. Presque chaque échec impliquait que l'agent violait une contrainte implicite que "tout le monde connaît mais que personne n'a écrite". C'est comme un nouvel employé à qui personne n'a dit "tu dois poster ta commande de déjeuner dans le chat du groupe" : il devine mal, se fait reprendre, mais après la remarque personne n'écrit la règle. - -L'équipe a mené une transformation : -1. Création d'un `AGENTS.md` à la racine du repo avec aperçu du projet, versions du stack technique et contraintes globales dures -2. Ajout d'un `ARCHITECTURE.md` dans chaque répertoire de microservice décrivant responsabilités, interfaces et dépendances -3. Création d'un `CONSTRAINTS.md` centralisé avec les contraintes dures en langage explicite "MUST/MUST NOT" -4. Ajout d'un `PROGRESS.md` dans chaque répertoire de service pour suivre l'état de travail actuel - -Après transformation, le même agent pouvait répondre à toutes les questions clés du projet au démarrage à froid, et la qualité d'achèvement des tâches s'est nettement améliorée. - -## Points clés - -- Le savoir absent du repo n'existe pas pour l'agent. Mettre les décisions critiques dans le repo est l'investissement harness le plus fondamental : dessinez une bonne carte pour ne pas vous perdre. -- Utilisez le "test de démarrage à froid" pour évaluer la qualité du repo : une session fraîche peut-elle répondre à cinq questions de base avec le seul contenu du dépôt ? -- Le savoir doit être près du code, minimal mais complet, et mis à jour avec le code. Il ne s'agit pas d'écrire plus de docs, mais de mettre l'information au bon endroit. -- Utilisez les principes ACID pour l'état des agents : commits atomiques, vérification de cohérence, isolation de la concurrence, savoir critique durable. -- La dégradation du savoir est le plus grand ennemi. Une documentation désynchronisée du code est plus dangereuse qu'une absence de documentation : elle envoie l'agent dans la mauvaise direction alors qu'il pense avoir raison. - -## Pour aller plus loin - -- [OpenAI: Harness Engineering](https://openai.com/index/harness-engineering/) -- [Anthropic: Effective Harnesses for Long-Running Agents](https://www.anthropic.com/engineering/effective-harnesses-for-long-running-agents) -- [Infrastructure as Code — Martin Fowler](https://martinfowler.com/bliki/InfrastructureAsCode.html) -- [ADR: Architecture Decision Records](https://adr.github.io/) -- [The Twelve-Factor App](https://12factor.net/) - -## Exercices - -1. **Test de démarrage à froid** : ouvrez une session d'agent complètement fraîche dans votre projet (aucun contexte verbal, contenu du repo seulement). Posez cinq questions : Qu'est-ce que ce système ? Comment est-il organisé ? Comment l'exécuter ? Comment le vérifier ? Quel est l'avancement actuel ? Notez ce qu'il ne peut pas répondre, puis améliorez le repo jusqu'à ce qu'il puisse. - -2. **Quantification de l'externalisation du savoir** : listez toutes les décisions et contraintes importantes pour le travail de développement dans votre projet. Marquez chacune comme étant dans ou hors du repo. Calculez votre Knowledge Visibility Gap (proportion hors repo). Faites un plan pour le descendre sous 10%. - -3. **Évaluation ACID** : évaluez la gestion d'état de votre projet avec l'analogie ACID de cette leçon. Atomicity : les opérations d'agent peuvent-elles être annulées proprement ? Consistency : existe-t-il une vérification d'"état cohérent" ? Isolation : les agents concurrents se marchent-ils dessus ? Durability : tout le savoir intersessions est-il persisté ? diff --git a/courses/learn-harness-engineering/docs/fr/lectures/lecture-04-why-one-giant-instruction-file-fails/code/AGENTS-short.md b/courses/learn-harness-engineering/docs/fr/lectures/lecture-04-why-one-giant-instruction-file-fails/code/AGENTS-short.md deleted file mode 100644 index b7b98ac2..00000000 --- a/courses/learn-harness-engineering/docs/fr/lectures/lecture-04-why-one-giant-instruction-file-fails/code/AGENTS-short.md +++ /dev/null @@ -1,15 +0,0 @@ -# AGENTS.md - -## Commencez ici - -- Lisez `docs/ARCHITECTURE.md` -- Lisez `docs/PRODUCT.md` -- Utilisez `npm run dev` pour démarrer l'application -- Utilisez `npm run check` avant de marquer le travail comme terminé - -## Règles strictes - -- Ne modifiez pas les limites main/preload/renderer d'Electron sans avoir lu - `docs/ARCHITECTURE.md` -- Ne marquez pas une fonctionnalité comme terminée sans vérification -- Laissez un état propre (clean state) pour la prochaine session diff --git a/courses/learn-harness-engineering/docs/fr/lectures/lecture-04-why-one-giant-instruction-file-fails/code/anti-patterns.md b/courses/learn-harness-engineering/docs/fr/lectures/lecture-04-why-one-giant-instruction-file-fails/code/anti-patterns.md deleted file mode 100644 index 8994ed09..00000000 --- a/courses/learn-harness-engineering/docs/fr/lectures/lecture-04-why-one-giant-instruction-file-fails/code/anti-patterns.md +++ /dev/null @@ -1,7 +0,0 @@ -# Anti-Patrons des Fichiers d'Instructions - -- Mettre toutes les connaissances du dépôt dans un seul fichier -- Répéter la même règle à plusieurs endroits -- Encoder des règles obsolètes que personne n'audit -- Écrire des instructions conditionnelles si spécifiques qu'elles s'appliquent rarement -- Intégrer de longs manuels d'outils dans le contexte de démarrage diff --git a/courses/learn-harness-engineering/docs/fr/lectures/lecture-04-why-one-giant-instruction-file-fails/code/index.md b/courses/learn-harness-engineering/docs/fr/lectures/lecture-04-why-one-giant-instruction-file-fails/code/index.md deleted file mode 100644 index e90e2a5f..00000000 --- a/courses/learn-harness-engineering/docs/fr/lectures/lecture-04-why-one-giant-instruction-file-fails/code/index.md +++ /dev/null @@ -1,7 +0,0 @@ -# Code du Cours 04 - -Utilisez ce dossier pour des exemples de : - -- fichiers d'instructions monolithiques -- points d'entrée courts -- motifs de divulgation progressive (progressive disclosure) diff --git a/courses/learn-harness-engineering/docs/fr/lectures/lecture-04-why-one-giant-instruction-file-fails/code/split-vs-monolithic.ts b/courses/learn-harness-engineering/docs/fr/lectures/lecture-04-why-one-giant-instruction-file-fails/code/split-vs-monolithic.ts deleted file mode 100644 index 59a96328..00000000 --- a/courses/learn-harness-engineering/docs/fr/lectures/lecture-04-why-one-giant-instruction-file-fails/code/split-vs-monolithic.ts +++ /dev/null @@ -1,209 +0,0 @@ -/** - * split-vs-monolithic.ts - * - * Creates a monolithic instruction file (~200 lines) and then shows how - * splitting into 4 focused files dramatically reduces the context needed - * for any single query. Simulates an "agent" searching for a specific rule - * and measures how many lines it must read in each approach. - * - * Run: npx tsx docs/lectures/lecture-04-why-one-giant-instruction-file-fails/code/split-vs-monolithic.ts - */ - -// --------------------------------------------------------------------------- -// Simulated monolithic instruction file (200 lines of rules) -// --------------------------------------------------------------------------- - -const monolithicInstructions: { lineNumber: number; section: string; content: string }[] = []; - -// Section 1: Project Overview (lines 1-50) -for (let i = 1; i <= 50; i++) { - monolithicInstructions.push({ - lineNumber: i, - section: "Project Overview", - content: i === 25 ? "This project uses React 18 with TypeScript strict mode." : `Overview detail line ${i}`, - }); -} - -// Section 2: Code Style Rules (lines 51-100) -for (let i = 51; i <= 100; i++) { - monolithicInstructions.push({ - lineNumber: i, - section: "Code Style", - content: - i === 72 - ? "RULE: Always use explicit return types on exported functions." - : i === 78 - ? "RULE: Use const assertions for immutable arrays." - : `Style rule detail line ${i}`, - }); -} - -// Section 3: Testing Standards (lines 101-150) -for (let i = 101; i <= 150; i++) { - monolithicInstructions.push({ - lineNumber: i, - section: "Testing", - content: - i === 120 - ? "RULE: Every new endpoint must have integration tests." - : i === 135 - ? "RULE: Test files must mirror the source file structure." - : `Testing detail line ${i}`, - }); -} - -// Section 4: Deployment Rules (lines 151-200) -for (let i = 151; i <= 200; i++) { - monolithicInstructions.push({ - lineNumber: i, - section: "Deployment", - content: - i === 175 - ? "RULE: Never deploy on Fridays. Deploy window is Tue-Thu 10am-3pm." - : `Deployment detail line ${i}`, - }); -} - -// --------------------------------------------------------------------------- -// Split instruction files (4 focused files) -// --------------------------------------------------------------------------- - -const splitInstructions: Record = { - "01-project-overview.md": monolithicInstructions.filter((l) => l.section === "Project Overview"), - "02-code-style.md": monolithicInstructions.filter((l) => l.section === "Code Style"), - "03-testing.md": monolithicInstructions.filter((l) => l.section === "Testing"), - "04-deployment.md": monolithicInstructions.filter((l) => l.section === "Deployment"), -}; - -// --------------------------------------------------------------------------- -// Simulated queries -- the agent needs to find specific rules -// --------------------------------------------------------------------------- - -interface Query { - description: string; - targetRule: string; - relevantSection: string; -} - -const queries: Query[] = [ - { - description: "Find the rule about return types", - targetRule: "explicit return types", - relevantSection: "Code Style", - }, - { - description: "Find the deployment window rule", - targetRule: "deploy on Fridays", - relevantSection: "Deployment", - }, - { - description: "Find the integration test rule", - targetRule: "integration tests", - relevantSection: "Testing", - }, - { - description: "Find the test file structure rule", - targetRule: "mirror the source file", - relevantSection: "Testing", - }, -]; - -// --------------------------------------------------------------------------- -// Search simulation -// --------------------------------------------------------------------------- - -function searchMonolithic(query: Query): { linesRead: number; found: boolean } { - // Agent must scan from the top, reading each line until it finds the rule. - // In the worst case it reads all lines. - let linesRead = 0; - let found = false; - - for (const line of monolithicInstructions) { - linesRead++; - if (line.content.toLowerCase().includes(query.targetRule.toLowerCase())) { - found = true; - break; - } - } - - return { linesRead, found }; -} - -function searchSplit(query: Query): { linesRead: number; found: boolean; fileAccessed: string } { - // Agent knows which file to look in based on the section. - // It only reads lines from that one file. - const fileMap: Record = { - "Project Overview": "01-project-overview.md", - "Code Style": "02-code-style.md", - Testing: "03-testing.md", - Deployment: "04-deployment.md", - }; - - const targetFile = fileMap[query.relevantSection]; - const lines = splitInstructions[targetFile]; - let linesRead = 0; - let found = false; - - for (const line of lines) { - linesRead++; - if (line.content.toLowerCase().includes(query.targetRule.toLowerCase())) { - found = true; - break; - } - } - - return { linesRead, found, fileAccessed: targetFile }; -} - -// --------------------------------------------------------------------------- -// Reporting -// --------------------------------------------------------------------------- - -function pad(s: string, len: number): string { - return s.length >= len ? s : s + " ".repeat(len - s.length); -} - -function run(): void { - console.log("\n" + "=".repeat(90)); - console.log(" MONOLITHIC vs SPLIT INSTRUCTION FILES"); - console.log("=".repeat(90)); - - console.log("\n Monolithic file: 1 file, " + monolithicInstructions.length + " lines total"); - console.log(" Split files: 4 files, ~" + Math.round(monolithicInstructions.length / 4) + " lines each\n"); - - const header = `| ${pad("Query", 42)}| ${pad("Monolithic (lines)", 20)}| ${pad("Split (lines)", 15)}| ${pad("File Accessed", 22)}| Savings`; - const sep = `|${"-".repeat(44)}|${"-".repeat(22)}|${"-".repeat(17)}|${"-".repeat(24)}|${"-".repeat(10)}`; - console.log(header); - console.log(sep); - - let totalMono = 0; - let totalSplit = 0; - - for (const q of queries) { - const mono = searchMonolithic(q); - const split = searchSplit(q); - totalMono += mono.linesRead; - totalSplit += split.linesRead; - - const savings = Math.round(((mono.linesRead - split.linesRead) / mono.linesRead) * 100); - console.log( - `| ${pad(q.description, 42)}| ${pad(String(mono.linesRead), 20)}| ${pad(String(split.linesRead), 15)}| ${pad(split.fileAccessed, 22)}| ${savings}%` - ); - } - - console.log(sep); - const avgMono = Math.round(totalMono / queries.length); - const avgSplit = Math.round(totalSplit / queries.length); - console.log( - `| ${pad("AVERAGE", 42)}| ${pad(String(avgMono), 20)}| ${pad(String(avgSplit), 15)}| ${pad("(targeted file)", 22)}| ${Math.round(((avgMono - avgSplit) / avgMono) * 100)}%` - ); - - console.log("\n" + "=".repeat(90)); - console.log(" KEY INSIGHT"); - console.log("=".repeat(90)); - console.log(" With a monolithic file, the agent must scan up to " + monolithicInstructions.length + " lines for every query."); - console.log(" With split files, it reads only the relevant " + Math.round(monolithicInstructions.length / 4) + "-line file."); - console.log(" This means less context window usage, fewer hallucinations, and faster execution.\n"); -} - -run(); diff --git a/courses/learn-harness-engineering/docs/fr/lectures/lecture-04-why-one-giant-instruction-file-fails/index.md b/courses/learn-harness-engineering/docs/fr/lectures/lecture-04-why-one-giant-instruction-file-fails/index.md deleted file mode 100644 index 7102d890..00000000 --- a/courses/learn-harness-engineering/docs/fr/lectures/lecture-04-why-one-giant-instruction-file-fails/index.md +++ /dev/null @@ -1,132 +0,0 @@ -[中文版本 →](../../../zh/lectures/lecture-04-why-one-giant-instruction-file-fails/) - -> Exemples de code : [code/](https://github.com/walkinglabs/learn-harness-engineering/blob/main/docs/fr/lectures/lecture-04-why-one-giant-instruction-file-fails/code/) -> Projet pratique : [Projet 02. Espace de travail lisible par les agents](./../../projects/project-02-agent-readable-workspace/index.md) - -# Leçon 04. Répartir les instructions entre fichiers - -Vous avez pris le harness engineering au sérieux. Vous avez créé un `AGENTS.md` et vous y avez mis toutes les règles, contraintes et leçons apprises auxquelles vous pouviez penser. Un mois plus tard, le fichier faisait 300 lignes ; deux mois plus tard, 450 ; trois mois plus tard, 600. Puis vous remarquez que les performances de l'agent se dégradent : sur une simple correction de bug, il consomme beaucoup de contexte à traiter des instructions de déploiement sans rapport ; une contrainte de sécurité critique enfouie à la ligne 300 est ignorée ; trois règles de style contradictoires font que l'agent en choisit une au hasard à chaque fois. - -C'est le piège du "fichier d'instructions géant". C'est comme une valise trop remplie : tout semble utile, donc on tasse jusqu'à ce que la fermeture menace de céder. Pour trouver un sous-vêtement de rechange, il faut vider tout le sac. Vous transportez une valise pleine, mais vous n'utilisez peut-être qu'un tiers de ce qu'elle contient. - -## Le cycle vicieux à la racine - -Le cycle vicieux le plus courant ressemble à ceci : l'agent fait une erreur, vous dites "ajoutons une règle pour empêcher ça", vous l'ajoutez à `AGENTS.md`, cela fonctionne temporairement, l'agent fait une autre erreur, vous ajoutez une autre règle, vous recommencez, et le fichier enfle hors de contrôle. - -Ce n'est pas votre faute. C'est une réaction très naturelle : ajouter une règle chaque fois que quelque chose se passe mal paraît raisonnable, comme mettre un objet de plus dans son sac en sortant "au cas où". Mais l'effet cumulatif est désastreux. Regardons précisément ce qui se dégrade. - -**Le budget de contexte se fait dévorer.** La fenêtre de contexte de l'agent est finie. Supposons qu'il dispose d'une fenêtre de 200K tokens (un standard chez Claude). Un fichier d'instructions gonflé peut consommer 10-20K tokens. Il semble rester beaucoup de place ? Mais une tâche complexe peut exiger de lire des dizaines de fichiers source, les sorties d'outils prennent aussi du contexte, et l'historique de conversation s'accumule. Au moment où l'agent doit comprendre le code, le budget est déjà serré, comme une valise tellement pleine d'objets "au cas où" qu'il n'y a plus de place pour l'ordinateur portable. - -**Perdu au milieu.** L'article "Lost in the Middle" (Liu et al., 2023) a montré clairement que les LLM utilisent les informations situées au milieu de longs textes nettement moins efficacement que celles du début ou de la fin. Votre `AGENTS.md` fait 600 lignes, et la ligne 300 dit "toutes les requêtes de base de données doivent utiliser des requêtes paramétrées" : c'est une contrainte de sécurité forte. Mais elle est enfouie au milieu, et l'agent l'ignorera très probablement. Comme cette bouteille de crème solaire au fond d'une valise trop pleine : vous savez qu'elle est là, vous fouillez trois fois, vous ne la trouvez pas, et vous finissez par en racheter une. - -**Conflits de priorité.** Le fichier mélange des contraintes non négociables ("never use eval()"), des lignes directrices importantes ("prefer functional style") et une leçon historique spécifique ("une fuite mémoire WebSocket a été corrigée la semaine dernière, surveiller les motifs similaires"). Ces trois règles ont des niveaux d'importance complètement différents, mais elles se ressemblent dans le fichier. L'agent n'a aucun signal fiable pour les distinguer, comme si votre passeport et votre câble de charge étaient mélangés dans la valise sans moyen de savoir lequel est le plus urgent. - -**Déclin de maintenance.** Les gros fichiers sont intrinsèquement difficiles à maintenir. Les instructions obsolètes sont rarement supprimées, car les conséquences d'une suppression sont incertaines ("peut-être que quelque chose dépend de cette règle"), tandis que l'ajout de nouvelles instructions paraît gratuit. Résultat : le fichier ne fait que grandir, ne rétrécit jamais, et le rapport signal-bruit baisse en continu. C'est exactement l'accumulation de dette technique appliquée aux instructions. - -**Accumulation de contradictions.** Les instructions ajoutées à différents moments finissent par se contredire : l'une dit "utiliser TypeScript strict mode", l'autre dit "certains fichiers legacy autorisent les types any". L'agent choisit au hasard laquelle suivre à chaque fois. Comme si votre mère disait "couvre-toi" et votre père "ne mets pas trop de couches", et que vous restiez à la porte sans savoir qui écouter. - -## Concepts clés - -- **Instruction Bloat** : lorsqu'un fichier d'instructions occupe plus de 10-15% de la fenêtre de contexte, il commence à évincer le budget nécessaire à la lecture du code et au raisonnement sur la tâche. Un `AGENTS.md` de 600 lignes peut consommer 10 000-20 000 tokens, soit 8-15% d'une fenêtre de 128K avant même que l'agent commence. -- **Lost in the Middle Effect** : les travaux de Liu et al. en 2023 ont montré que les LLM utilisent beaucoup moins efficacement les informations au milieu de longs textes que celles du début ou de la fin. Une contrainte critique enfouie à la ligne 300 d'un fichier de 600 lignes a une forte probabilité d'être ignorée en pratique. -- **Instruction Signal-to-Noise Ratio (SNR)** : proportion des instructions d'un fichier qui sont pertinentes pour la tâche actuelle. Être obligé de lire 50 lignes de déploiement pendant une correction de bug, c'est un SNR faible. -- **Routing File** : fichier d'entrée court dont le rôle principal est d'orienter l'agent vers des documents plus détaillés, pas de tout contenir lui-même. 50-200 lignes suffisent. -- **Progressive Disclosure** : donner d'abord la vue d'ensemble, puis les détails quand ils deviennent nécessaires. Un bon design de harness ressemble à un bon design d'interface : ne pas déverser toutes les options d'un coup. -- **Priority Ambiguity** : lorsque toutes les instructions apparaissent au même format et au même endroit, l'agent ne peut pas distinguer les contraintes dures non négociables des simples recommandations. - -## Architecture des instructions - -```mermaid -flowchart LR - Mono["Un AGENTS.md de 600 lignes"] --> MonoLoad["Même un petit bug fix
doit lire règles de déploiement et vieilles notes"] - MonoLoad --> MonoRisk["Les règles importantes enfouies au milieu
sont faciles à manquer"] - - Router["AGENTS.md court"] --> Topics["Charger les docs API / DB / testing
seulement quand la tâche en a besoin"] - Topics --> RoutedResult["Plus de contexte restant pour lire le code
et vérifier"] -``` - -```mermaid -flowchart TB - File["Fichier d'instructions de 600 lignes"] --> Top["Section haute
quick start + contraintes dures"] - File --> Mid["Section centrale
règle de sécurité critique à la ligne 300"] - File --> Bot["Section basse
checklist explicite de fin"] - Top --> Seen["Forte chance d'être rappelé"] - Bot --> Seen - Mid --> Missed["Forte chance d'être dilué ou manqué"] -``` - -## Comment découper - -Principe central : gardez à portée de main les informations fréquemment nécessaires, rangez celles qui ne servent qu'occasionnellement, et supprimez ce que vous n'utiliserez jamais. - -Le fichier d'entrée `AGENTS.md` reste entre 50 et 200 lignes et ne contient que les éléments les plus souvent utilisés : aperçu du projet (une ou deux phrases), commandes de premier lancement (`make setup && make test`), contraintes globales dures (pas plus de 15 règles non négociables), et liens vers des documents thématiques (description en une ligne + condition d'applicabilité). - -```markdown -# AGENTS.md - -## Vue d'ensemble du projet -Python 3.11 FastAPI backend, PostgreSQL 15 database. - -## Quick Start -- Install: `make setup` -- Test: `make test` -- Full verification: `make check` - -## Hard Constraints -- All APIs must use OAuth 2.0 authentication -- All database queries must use SQLAlchemy 2.0 syntax -- All PRs must pass pytest + mypy --strict + ruff check - -## Topic Docs -- [API Design Patterns](docs/api-patterns.md) — Required reading when adding endpoints -- [Database Rules](docs/database-rules.md) — Required when modifying database operations -- [Testing Standards](docs/testing-standards.md) — Reference when writing tests -``` - -Chaque document thématique fait 50-150 lignes, organisé par sujet dans `docs/` ou près du module correspondant. L'agent ne les lit que lorsque c'est nécessaire. Comme des pochettes de rangement dans une valise : les sous-vêtements dans l'une, les produits de toilette dans une autre, les chargeurs dans une troisième. Trouver quelque chose n'exige pas de vider tout le sac. - -Certaines informations sont mieux placées directement dans le code : définitions de types, commentaires d'interface, explications dans les fichiers de configuration. L'agent les voit naturellement en lisant le code, inutile de les dupliquer dans les instructions. - -Chaque instruction devrait avoir une source ("pourquoi cette règle a-t-elle été ajoutée ?"), une condition d'applicabilité ("quand cette règle est-elle nécessaire ?") et une condition d'expiration ("dans quelles circonstances peut-on la supprimer ?"). Auditez régulièrement, retirez les entrées obsolètes, redondantes et contradictoires. Gérez vos instructions comme vos dépendances de code : les dépendances inutilisées doivent être supprimées, sinon elles ralentissent simplement le système. - -Si une instruction doit absolument rester dans le fichier d'entrée, mettez-la en haut ou en bas, jamais au milieu. L'effet "lost in the middle" nous dit que les LLM exploitent bien mieux les informations aux extrémités qu'au centre. Mais la meilleure approche reste de déplacer les instructions dans des documents thématiques chargés à la demande. - -OpenAI et Anthropic soutiennent implicitement cette approche de découpage. OpenAI dit que les fichiers d'entrée doivent être "courts et orientés routage", Anthropic dit que les informations de contrôle des agents de longue durée doivent être "concises et prioritaires". Ils disent la même chose : ne mettez pas tout dans un seul fichier. Une valise a besoin d'organisation, pas d'un bourrage en force. - -## Exemple réel - -Le `AGENTS.md` d'une équipe SaaS est passé de 50 à 600 lignes. Il mélangeait versions du stack technique, standards de code, notes historiques de correction de bugs, guides d'usage d'API, procédures de déploiement et préférences personnelles des membres de l'équipe : toute la valise était prête à éclater. - -Les performances de l'agent ont commencé à décliner nettement : pendant de simples corrections de bugs, il dépensait beaucoup de contexte sur des instructions de déploiement sans rapport ; la contrainte de sécurité "toutes les requêtes de base de données doivent utiliser des requêtes paramétrées" était enfouie à la ligne 300 et souvent ignorée ; trois règles de style contradictoires provoquaient un comportement aléatoire. - -L'équipe a effectué une "réorganisation de valise" : -1. `AGENTS.md` réduit à 80 lignes : seulement aperçu du projet, commandes d'exécution et 15 contraintes globales dures -2. Création de documents thématiques : `docs/api-patterns.md` (120 lignes), `docs/database-rules.md` (60 lignes), `docs/testing-standards.md` (80 lignes) -3. Ajout de liens vers ces documents dans le fichier de routage -4. Notes historiques converties en tests ou supprimées - -Après refactorisation, le taux de réussite du même ensemble de tâches est passé de 45% à 72%. Le respect de la contrainte de sécurité est passé de 60% à 95%, parce qu'elle a été déplacée du milieu du fichier vers le haut du fichier de routage, et n'était plus "perdue au milieu". - -## Points clés - -- "Ajouter une règle" soulage à court terme, mais empoisonne à long terme. Avant d'ajouter une règle, demandez : serait-elle mieux dans un document thématique ? Ne continuez pas à tasser la valise. -- Le fichier d'entrée est un routeur, pas une encyclopédie. 50-200 lignes avec aperçu, contraintes dures et liens seulement. -- Exploitez l'effet "lost in the middle" : les informations importantes vont en haut ou en bas ; les informations moins importantes vont dans des documents thématiques. -- Gérez le gonflement des instructions comme de la dette technique. Audits réguliers ; chaque instruction a besoin d'une source, d'une condition d'applicabilité et d'une condition d'expiration. -- Après le découpage, le SNR s'améliore et l'agent dépense plus de budget de contexte sur la tâche réelle au lieu de traiter des instructions sans rapport. - -## Pour aller plus loin - -- [OpenAI: Harness Engineering](https://openai.com/index/harness-engineering/) -- [Anthropic: Effective Harnesses for Long-Running Agents](https://www.anthropic.com/engineering/effective-harnesses-for-long-running-agents) -- [Lost in the Middle: How Language Models Use Long Contexts](https://arxiv.org/abs/2307.03172) -- [HumanLayer: Harness Engineering for Coding Agents](https://humanlayer.dev/articles/harness-engineering-for-coding-agents/) -- [Nielsen Norman Group: Progressive Disclosure](https://www.nngroup.com/articles/progressive-disclosure/) - -## Exercices - -1. **Audit SNR** : prenez votre fichier actuel d'instructions d'entrée et listez toutes les instructions. Choisissez 5 types de tâches courantes et marquez si chaque instruction est pertinente pour cette tâche. Calculez le SNR pour chaque type de tâche. Les instructions qui sont du bruit pour la plupart des tâches doivent aller dans des documents thématiques. - -2. **Refactorisation en progressive disclosure** : si vous avez un fichier d'instructions de plus de 300 lignes, découpez-le en : (a) un fichier de routage de moins de 100 lignes, (b) 3-5 documents thématiques. Exécutez le même ensemble de tâches (au moins 5) avant et après, puis comparez les taux de réussite. - -3. **Vérification lost in the middle** : dans un long fichier d'instructions, placez une contrainte critique respectivement au début, au milieu et à la fin, en exécutant le même ensemble de tâches à chaque fois (au moins 5 exécutions par position). Observez s'il existe une différence de taux de conformité. L'effet de position peut être étonnamment fort. diff --git a/courses/learn-harness-engineering/docs/fr/lectures/lecture-05-why-long-running-tasks-lose-continuity/code/continuity-checklist.md b/courses/learn-harness-engineering/docs/fr/lectures/lecture-05-why-long-running-tasks-lose-continuity/code/continuity-checklist.md deleted file mode 100644 index 7efc22d5..00000000 --- a/courses/learn-harness-engineering/docs/fr/lectures/lecture-05-why-long-running-tasks-lose-continuity/code/continuity-checklist.md +++ /dev/null @@ -1,6 +0,0 @@ -# Liste de Contrôle de Continuité - -- Un nouvel agent peut-il identifier le travail récent en moins de cinq minutes ? -- Le chemin de démarrage stable actuel est-il documenté ? -- Le travail inachevé est-il clairement identifié ? -- La prochaine meilleure tâche est-elle visible sans lire les anciens journaux de chat ? diff --git a/courses/learn-harness-engineering/docs/fr/lectures/lecture-05-why-long-running-tasks-lose-continuity/code/index.md b/courses/learn-harness-engineering/docs/fr/lectures/lecture-05-why-long-running-tasks-lose-continuity/code/index.md deleted file mode 100644 index e014ec4f..00000000 --- a/courses/learn-harness-engineering/docs/fr/lectures/lecture-05-why-long-running-tasks-lose-continuity/code/index.md +++ /dev/null @@ -1,7 +0,0 @@ -# Code du Cours 05 - -Utilisez ce dossier pour des exemples de : - -- tâches multi-sessions interrompues -- artefacts de continuité manquants -- motifs de récupération de continuité diff --git a/courses/learn-harness-engineering/docs/fr/lectures/lecture-05-why-long-running-tasks-lose-continuity/code/session-handoff.md b/courses/learn-harness-engineering/docs/fr/lectures/lecture-05-why-long-running-tasks-lose-continuity/code/session-handoff.md deleted file mode 100644 index e4768533..00000000 --- a/courses/learn-harness-engineering/docs/fr/lectures/lecture-05-why-long-running-tasks-lose-continuity/code/session-handoff.md +++ /dev/null @@ -1,17 +0,0 @@ -# Exemple de Passation de Session - -## Terminé - -- Ajout du support d'importation Markdown -- Ajout d'une liste de documents basique dans le renderer - -## Cassé ou Non Vérifié - -- L'importation réussit pour `.md` mais échoue pour les fichiers `.txt` volumineux -- L'application démarre, mais la vue détaillée n'est pas encore connectée - -## Prochaine Meilleure Étape - -- Corriger le chemin d'importation `.txt` -- Vérifier l'importation de bout en bout -- Puis ajouter le panneau de détail du document diff --git a/courses/learn-harness-engineering/docs/fr/lectures/lecture-05-why-long-running-tasks-lose-continuity/code/session-simulator.ts b/courses/learn-harness-engineering/docs/fr/lectures/lecture-05-why-long-running-tasks-lose-continuity/code/session-simulator.ts deleted file mode 100644 index 69f9c3e2..00000000 --- a/courses/learn-harness-engineering/docs/fr/lectures/lecture-05-why-long-running-tasks-lose-continuity/code/session-simulator.ts +++ /dev/null @@ -1,162 +0,0 @@ -/** - * session-simulator.ts - * - * Simulates two sessions working on a multi-step task. - * Run 1: No handoff file -- Session B starts from scratch and duplicates work. - * Run 2: With handoff file -- Session B picks up where Session A left off. - * - * Run: npx tsx docs/lectures/lecture-05-why-long-running-tasks-lose-continuity/code/session-simulator.ts - */ - -// --------------------------------------------------------------------------- -// Types -// --------------------------------------------------------------------------- - -interface TaskStep { - id: number; - name: string; - durationMs: number; -} - -interface SessionResult { - session: string; - stepsCompleted: number; - totalDurationMs: number; - duplicatedSteps: number; - output: string[]; -} - -// --------------------------------------------------------------------------- -// Task definition -// --------------------------------------------------------------------------- - -const taskSteps: TaskStep[] = [ - { id: 1, name: "Read project structure", durationMs: 50 }, - { id: 2, name: "Understand existing auth module", durationMs: 80 }, - { id: 3, name: "Design new search endpoint", durationMs: 60 }, - { id: 4, name: "Implement search endpoint", durationMs: 100 }, - { id: 5, name: "Write integration tests", durationMs: 70 }, - { id: 6, name: "Update documentation", durationMs: 40 }, -]; - -// --------------------------------------------------------------------------- -// Simulated session executor -// --------------------------------------------------------------------------- - -/** Simulate a session doing work. */ -function runSession( - sessionName: string, - startStep: number, - endStep: number -): SessionResult { - const output: string[] = []; - let totalDuration = 0; - - for (let i = startStep; i <= endStep; i++) { - const step = taskSteps.find((s) => s.id === i)!; - totalDuration += step.durationMs; - output.push(` [${sessionName}] Step ${step.id}: ${step.name} (${step.durationMs}ms)`); - } - - return { - session: sessionName, - stepsCompleted: endStep - startStep + 1, - totalDurationMs: totalDuration, - duplicatedSteps: 0, - output, - }; -} - -// --------------------------------------------------------------------------- -// Run 1: No handoff file -// --------------------------------------------------------------------------- - -function simulateNoHandoff(): { sessionA: SessionResult; sessionB: SessionResult } { - // Session A does steps 1-3 before timing out - const sessionA = runSession("Session A", 1, 3); - - // Session B has no context, starts from scratch - const sessionB = runSession("Session B", 1, 6); - sessionB.duplicatedSteps = 3; // Redoes steps 1-3 that A already did - - return { sessionA, sessionB }; -} - -// --------------------------------------------------------------------------- -// Run 2: With handoff file -// --------------------------------------------------------------------------- - -function simulateWithHandoff(): { sessionA: SessionResult; sessionB: SessionResult } { - // Session A does steps 1-3 and writes a handoff file - const sessionA = runSession("Session A", 1, 3); - - // Session B reads handoff, starts from step 4 - const sessionB = runSession("Session B", 4, 6); - sessionB.duplicatedSteps = 0; - - return { sessionA, sessionB }; -} - -// --------------------------------------------------------------------------- -// Reporting -// --------------------------------------------------------------------------- - -function pad(s: string, len: number): string { - return s.length >= len ? s : s + " ".repeat(len - s.length); -} - -function printRun(title: string, result: { sessionA: SessionResult; sessionB: SessionResult }): void { - console.log("\n" + "=".repeat(80)); - console.log(` ${title}`); - console.log("=".repeat(80)); - - console.log("\n Session A output:"); - for (const line of result.sessionA.output) { - console.log(line); - } - - console.log("\n Session B output:"); - for (const line of result.sessionB.output) { - console.log(line); - } - - console.log("\n Session B notes:"); - if (result.sessionB.duplicatedSteps > 0) { - console.log(` - Redid ${result.sessionB.duplicatedSteps} steps that Session A already completed`); - console.log(` - Wasted ${result.sessionB.totalDurationMs - (taskSteps.slice(3).reduce((s, t) => s + t.durationMs, 0))}ms on duplicate work`); - } else { - console.log(" - Read handoff file, continued from step 4"); - console.log(" - No duplicate work performed"); - } -} - -function printComparison(): void { - const noHandoff = simulateNoHandoff(); - const withHandoff = simulateWithHandoff(); - - printRun("RUN 1: NO HANDOFF FILE", noHandoff); - printRun("RUN 2: WITH HANDOFF FILE", withHandoff); - - // Comparison table - console.log("\n" + "=".repeat(80)); - console.log(" COMPARISON TABLE"); - console.log("=".repeat(80) + "\n"); - - const header = `| ${pad("Metric", 35)}| ${pad("No Handoff", 15)}| ${pad("With Handoff", 15)}|`; - const sep = `|${"-".repeat(37)}|${"-".repeat(17)}|${"-".repeat(17)}|`; - console.log(header); - console.log(sep); - - const noTotal = noHandoff.sessionA.totalDurationMs + noHandoff.sessionB.totalDurationMs; - const withTotal = withHandoff.sessionA.totalDurationMs + withHandoff.sessionB.totalDurationMs; - - console.log(`| ${pad("Session A steps completed", 35)}| ${pad(String(noHandoff.sessionA.stepsCompleted), 15)}| ${pad(String(withHandoff.sessionA.stepsCompleted), 15)}|`); - console.log(`| ${pad("Session B steps completed", 35)}| ${pad(String(noHandoff.sessionB.stepsCompleted), 15)}| ${pad(String(withHandoff.sessionB.stepsCompleted), 15)}|`); - console.log(`| ${pad("Duplicated steps", 35)}| ${pad(String(noHandoff.sessionB.duplicatedSteps), 15)}| ${pad(String(withHandoff.sessionB.duplicatedSteps), 15)}|`); - console.log(`| ${pad("Total work (ms)", 35)}| ${pad(String(noTotal), 15)}| ${pad(String(withTotal), 15)}|`); - console.log(`| ${pad("Time saved by handoff", 35)}| ${pad("-", 15)}| ${pad(String(noTotal - withTotal) + "ms", 15)}|`); - - console.log("\n A handoff file eliminates duplicate work and ensures continuity across sessions.\n"); -} - -printComparison(); diff --git a/courses/learn-harness-engineering/docs/fr/lectures/lecture-05-why-long-running-tasks-lose-continuity/index.md b/courses/learn-harness-engineering/docs/fr/lectures/lecture-05-why-long-running-tasks-lose-continuity/index.md deleted file mode 100644 index 42094811..00000000 --- a/courses/learn-harness-engineering/docs/fr/lectures/lecture-05-why-long-running-tasks-lose-continuity/index.md +++ /dev/null @@ -1,180 +0,0 @@ -[中文版本 →](../../../zh/lectures/lecture-05-why-long-running-tasks-lose-continuity/) - -> Exemples de code : [code/](https://github.com/walkinglabs/learn-harness-engineering/blob/main/docs/fr/lectures/lecture-05-why-long-running-tasks-lose-continuity/code/) -> Projet pratique : [Project 03. Multi-session continuity](./../../projects/project-03-multi-session-continuity/index.md) - -# Leçon 05. Garder le contexte vivant entre les sessions - -Vous demandez à Claude Code d'implémenter une fonctionnalité complète. Il tourne pendant 30 minutes, fait la majeure partie du travail, mais le contexte commence à manquer. Vous lancez une nouvelle session pour continuer — et découvrez qu'il ne se souvient pas des décisions prises la dernière fois, pourquoi l'option A a été choisie plutôt que l'option B, quels fichiers ont déjà été modifiés, ou dans quel état se trouvent les tests. Il passe 15 minutes à réexplorer le projet, et risque d'être en contradiction avec l'approche précédente. - -Imaginez que vous êtes un artisan qui oublie tout chaque matin en vous réveillant. Vous devriez vous refamiliariser avec l'ensemble du chantier — quel mur est à moitié construit, pourquoi des briques rouges ont été choisies plutôt que des bleues, où en sont les passages de plomberie. Pire, vous pourriez arracher une fenêtre qui a déjà été installée hier, simplement parce que vous ne vous souveniez pas que c'était fait. - -C'est exactement la situation dans laquelle se trouvent les agents de codage IA lors de tâches multi-sessions. Cette leçon explique pourquoi les agents « perdent le fil » pendant les tâches longues, et comment la persistance structurée de l'état peut les transformer en un artisan qui tient un journal fiable chaque jour — toujours amnésique, mais le journal se souvient de tout. - -## Fenêtres de contexte : pas infinies - -Les fenêtres de contexte sont finies. Ce n'est pas résolu par les mises à niveau de modèles — même si les tailles de fenêtre atteignent 1M de tokens, les tâches complexes les épuiseront toujours. Parce que les agents ne se contentent pas de générer du code ; ils comprennent les codebases, suivent leur propre historique de décisions, traitent les sorties des outils et maintiennent le contexte de conversation. Toutes ces informations croissent plus vite que l'expansion des fenêtres. - -Un problème plus profond : les informations que l'agent produit ne sont pas uniformément importantes. Les étapes de raisonnement intermédiaires contiennent le « pourquoi » des décisions — pourquoi l'option B a été choisie plutôt que A, pourquoi cette bibliothèque plutôt qu'une autre, pourquoi une optimisation particulière a été écartée. La sortie finale ne contient que le « quoi » — le code lui-même. Les stratégies de compaction préservent généralement ce dernier mais perdent le premier. La session suivante voit le code mais ne sait pas pourquoi il est écrit de cette façon, et pourrait « optimiser » à tort une décision de conception délibérée. - -Anthropic a découvert quelque chose de fascinant dans sa recherche sur les agents longue durée : quand les agents sentent que le contexte diminue, ils présentent un comportement de « convergence prématurée » — se précipitant pour terminer le travail en cours, sautant les étapes de vérification, ou choisissant une solution simple plutôt qu'optimale. C'est comme réaliser que le temps est presque écoulé à un examen et deviner rapidement les questions à choix multiples restantes. Anthropic appelle cela l'« anxiété de contexte ». - -## Flux de continuité entre sessions - -Sans artefacts de continuité, chaque nouvelle session est un désastre : - -```mermaid -flowchart LR - S1["Session 1
feature is half done"] --> End1["Context is nearly full
session ends"] - End1 --> S2["Session 2 starts fresh"] - S2 --> Guess["Re-read folders, rerun tests,
guess why the code was written this way"] - Guess --> Drift["Work gets repeated
and recovery is slow"] -``` - -Avec des artefacts de continuité, les nouvelles sessions peuvent reprendre rapidement : - -```mermaid -flowchart LR - Work["Session 1 work"] --> Progress["PROGRESS.md
done / in progress / next step"] - Work --> Decisions["DECISIONS.md
why this approach was chosen"] - Work --> Verify["Verification notes
which tests pass and fail"] - Work --> Commit["Git checkpoint
exact repo state"] - - Progress --> Rebuild["Session 2 rebuild"] - Decisions --> Rebuild - Verify --> Rebuild - Commit --> Rebuild - - Rebuild --> Resume["New session picks up quickly"] -``` - -## Concepts clés - -- **Les fenêtres de contexte sont finies** : Quelle que soit la taille de fenêtre annoncée (128K, 200K, 1M), les tâches longues finiront par les épuiser. Après épuisement, soit la compaction (perte d'informations) soit la réinitialisation (nouvelle session) est nécessaire. Les deux perdent quelque chose. -- **Artefacts de continuité** : Fichiers d'état persistés qui permettent à une nouvelle session de reprendre sans ambiguïté là où la précédente s'est arrêtée. La forme de base : journal de progression + enregistrement de vérification + actions suivantes. Le journal de l'artisan. -- **Coût de reconstruction** : Le temps nécessaire à une nouvelle session pour atteindre un état exécutable. Les bons harness peuvent comprimer le coût de reconstruction de 15 minutes à 3 minutes. -- **Dérive** : L'écart entre la compréhension de l'agent et l'état réel du dépôt de code. Chaque frontière de session introduit de la dérive ; sans contrôle, elle se cumule. -- **Anxiété de contexte** : Un phénomène observé par Anthropic — les agents présentent un comportement de convergence prématurée à l'approche des limites de contexte perçues, terminant les tâches prématurément pour éviter la perte d'informations. C'est une anxiété irrationnelle face aux ressources. -- **Compaction vs réinitialisation** : La compaction résume le contexte au sein de la même session (conserve le « quoi », peut perdre le « pourquoi ») ; la réinitialisation ouvre une nouvelle session reconstruisant à partir de l'état persisté (propre mais dépend de l'exhaustivité des artefacts). - -## Ce qui se passe quand la continuité se brise - -La session précédente a dépensé un budget de contexte significatif à analyser trois approches et choisir l'option B. L'agent de cette session ne connaît pas cette analyse et pourrait redécider sur la base d'informations incomplètes — choisissant potentiellement l'option A. Comme l'artisan amnésique qui ne se souvient pas pourquoi des briques rouges ont été choisies, regarde les bleues aujourd'hui et les trouve plus jolies, et démolit le mur d'hier pour reconstruire. - -Pire encore est le travail en double. L'agent n'est pas sûr que certains travaux aient déjà été complétés et les refait. Ou pire — en fait la moitié, découvre un conflit avec l'implémentation existante, et doit tout reprendre. Sur un chantier de construction, deux équipes ne peuvent pas construire le même mur simultanément — mais sans registre de progression, la nouvelle équipe ne sait pas que quelqu'un travaille déjà dessus. - -Au fil de plusieurs sessions, la direction d'implémentation peut avoir silencieusement dévié des exigences initiales. Chaque nouvelle session a une compréhension légèrement différente des objectifs du projet. Comme un jeu de téléphone arabe — après que dix personnes ont transmis le message, « passe me prendre un café » peut devenir « achète-moi une machine à café ». - -Il y a aussi le fossé de vérification. Les résultats de vérification de la session précédente (quels tests passent, lesquels échouent, pourquoi ils échouent) n'ont pas été enregistrés. La nouvelle session doit relancer toutes les vérifications pour comprendre l'état actuel. Chaque session rediagnostique à partir de zéro, gaspillant chaque fois un contexte précieux. - -OpenAI et Anthropic soulignent tous deux la persistance structurée de l'état dans leur documentation. L'article sur le harness engineering d'OpenAI traite le dépôt comme un « registre opérationnel » — chaque résultat d'opération devrait laisser une trace vérifiable dans le repo. La documentation d'Anthropic sur les agents longue durée recommande spécifiquement des « fichiers de passation » — des documents structurés contenant l'état actuel, les problèmes connus et les actions suivantes. - -## Un journal pour l'artisan amnésique - -Approche fondamentale : **Traitez l'agent comme un brillant ingénieur atteint d'amnésie.** Avant qu'il ne « termine sa journée », il doit noter les informations critiques pour que l'agent du « poste » suivant puisse prendre le relais rapidement. - -**Outil 1 : Fichier de progression (PROGRESS.md).** L'artefact de continuité le plus basique — le cœur du journal : - -```markdown -# Project Progress - -## Current State -- Latest commit: abc1234 (feat: add user preferences endpoint) -- Test status: 42/43 passing (test_pagination_edge_case failing) -- Lint: passing - -## Completed -- [x] User model and database migration -- [x] Basic CRUD endpoints -- [x] Auth middleware integration - -## In Progress -- [ ] Pagination feature (90% - edge case test failing) - -## Known Issues -- test_pagination_edge_case returns 500 on empty result sets -- Need to confirm whether deleted users should appear in listings - -## Next Steps -1. Fix pagination edge case bug -2. Add "include deleted users" query parameter -3. Update API documentation -``` - -**Outil 2 : Journal des décisions (DECISIONS.md).** Enregistrez les décisions de conception importantes et leurs raisons. Pas besoin de documents de conception détaillés — juste « quelle décision, pourquoi, quand » — les mémos du journal : - -```markdown -# Design Decisions - -## 2024-01-15: Use Redis for user preferences caching -- Reason: High read frequency (every API call), small data size -- Rejected alternative: PostgreSQL materialized view (high change frequency makes maintenance cost not worthwhile) -- Constraint: Cache TTL of 5 minutes, active invalidation on write -``` - -**Outil 3 : Commits git comme points de contrôle.** Commitez après avoir terminé chaque unité de travail atomique. Les messages de commit devraient expliquer ce qui a été fait et pourquoi. Ce sont des instantanés d'état gratuits et automatiquement versionnés. - -**Outil 4 : init.sh ou flux d'initialisation du harness.** Spécifiez dans `AGENTS.md` les routines de « prise de poste » et de « fin de service » : - -```markdown -## At session start (clock in) -1. Read PROGRESS.md for current state -2. Read DECISIONS.md for important decisions -3. Run make check to confirm repo is in consistent state -4. Continue from PROGRESS.md "Next Steps" section - -## Before session end (clock out) -1. Update PROGRESS.md -2. Run make check to confirm consistent state -3. Commit all completed work -``` - -**Stratégie mixte** : Toutes les tâches n'ont pas besoin d'une réinitialisation de contexte. Les tâches courtes (moins de 30 minutes) peuvent se terminer en une seule session. Les tâches longues (s'étalant sur plusieurs sessions) doivent utiliser des fichiers de progression et des journaux de décisions pour la continuité. Critère de décision : si une tâche nécessite plus de 60 % de la fenêtre, commencez à préparer la passation. - -### Plongée approfondie sur l'anxiété de contexte - -La recherche de mars 2026 d'Anthropic a révélé plus en détail les manifestations spécifiques de l'anxiété de contexte : sur Sonnet 4.5, lorsque le contexte approche de la limite de la fenêtre, l'agent montre un fort comportement de « convergence prématurée ». C'est comme réaliser que le temps est presque écoulé à un examen et remplir rapidement des réponses au hasard. - -Deux stratégies y remédient : - -**Compaction** : Résumer la conversation antérieure au sein de la même session. Avantage : maintient la continuité, l'agent peut voir le « quoi ». Inconvénient : le « pourquoi » est souvent perdu dans les résumés — pourquoi l'option B a été choisie plutôt que A, pourquoi une optimisation particulière a été écartée. Plus critiques encore, la compaction n'élimine pas l'anxiété de contexte — l'agent sait que le contexte a déjà été volumineux, et psychologiquement a toujours tendance à se précipiter vers la clôture. - -**Réinitialisation de contexte** : Effacement complet du contexte, ouverture d'une nouvelle session, reconstruction à partir des artefacts persistés. Avantage : état mental propre — la nouvelle session n'a pas d'anxiété de « je manque de temps ». Inconvénient : dépend de l'exhaustivité des artefacts de passation. Si le journal manque d'informations critiques, la nouvelle session peut perdre du temps dans une mauvaise direction. - -Les données réelles d'Anthropic : pour Sonnet 4.5, l'anxiété de contexte est suffisamment sévère pour que la compaction seule ne suffise pas — la réinitialisation de contexte devient un composant critique de la conception du harness. Mais pour Opus 4.5, ce comportement est grandement réduit, et la compaction peut gérer le contexte sans recourir aux réinitialisations. Cela signifie : **la conception du harness nécessite une compréhension spécifique du modèle cible, pas un modèle universel unique.** - -> Source : [Anthropic: Harness design for long-running application development](https://www.anthropic.com/engineering/harness-design-long-running-apps) - -## Exemple concret - -Un agent a été chargé d'implémenter un système de blog avec authentification utilisateur — 12 points de fonctionnalité, estimation de 5 sessions nécessaires. - -**Baseline sans le journal** : La session 1 a implémenté le modèle utilisateur et les routes de base. La session 2 a démarré sans que l'agent se souvienne du contrat d'interface du middleware d'authentification, passant ~15 minutes à déduire l'intention de conception précédente. Dès la session 3, la dérive accumulée a poussé l'agent à réimplémenter des fonctionnalités déjà complétées. À la session 5, le repo contenait beaucoup de code redondant mais la fonctionnalité d'authentification principale n'avait toujours pas passé les tests de bout en bout. Seulement 7 des 12 points de fonctionnalité complétés, dont 3 avec des problèmes de correction cachés. Comme l'artisan qui n'écrit jamais dans son journal — au cinquième jour, le chantier est chaotique, certains murs construits deux fois, d'autres qui auraient dû l'être ne sont même pas commencés. - -**Avec le journal** : Utilisation de fichiers de progression, journaux de décisions, enregistrements de vérification et points de contrôle git. Le rapport d'état mis à jour automatiquement à la fin de chaque session. Le coût de reconstruction de la session 2 est tombé à ~3 minutes. À la session 5, les 12 points de fonctionnalité étaient complétés et vérifiés. - -Comparaison quantitative : temps de reconstruction réduit de ~78 %, taux de complétion des fonctionnalités de 58 % à 100 %, taux de défauts cachés de 43 % à 8 %. L'artisan est toujours amnésique, mais avec le journal, chaque jour commence là où celui d'hier s'est arrêté, pas à zéro. - -## Points clés - -- Les fenêtres de contexte sont une ressource finie. Les tâches longues s'étaleront sur plusieurs sessions, et les sessions perdront de l'information — comme l'artisan qui oublie chaque jour, c'est une réalité objective. -- La solution n'est pas des fenêtres plus grandes — c'est une meilleure persistance de l'état. Fichiers de progression + journaux de décisions + points de contrôle git — donnez à l'artisan amnésique un journal fiable. -- Traitez l'agent comme un ingénieur atteint d'amnésie : avant de « terminer sa journée », notez ce qui a été fait, pourquoi, et ce qui reste à faire. -- Le coût de reconstruction est la métrique clé. Les bons harness devraient amener les nouvelles sessions à un état exécutable en moins de 3 minutes. -- Stratégie mixte : tâches courtes en session, tâches longues avec des artefacts structurés pour la continuité. - -## Pour aller plus loin - -- [Anthropic: Effective Harnesses for Long-Running Agents](https://www.anthropic.com/engineering/effective-harnesses-for-long-running-agents) -- [OpenAI: Harness Engineering](https://openai.com/index/harness-engineering/) -- [Lost in the Middle: How Language Models Use Long Contexts](https://arxiv.org/abs/2307.03172) -- [Claude Code Documentation](https://docs.anthropic.com/fr/docs/claude-code) -- [HumanLayer: Harness Engineering for Coding Agents](https://humanlayer.dev/articles/harness-engineering-for-coding-agents/) - -## Exercices - -1. **Mesure de la perte de continuité** : Choisissez une tâche de développement nécessitant au moins 3 sessions. Sans fournir aucun artefact de continuité, enregistrez au début de chaque session combien de contexte l'agent dépense à « comprendre ce qui s'est passé la dernière fois ». Après chaque session, créez un fichier de progression et laissez la session suivante démarrer à partir de celui-ci. Comparez les coûts de reconstruction avec et sans fichiers de progression. - -2. **Conception d'un modèle de passation** : Concevez un modèle de passation minimal avec quatre champs : état du repo (hash de commit), état d'exécution (taux de réussite des tests), bloqueurs, actions suivantes. Laissez une toute nouvelle session d'agent restaurer l'état du projet en utilisant uniquement ce modèle. Enregistrez les ambiguïtés rencontrées lors de la restauration, itérez pour améliorer le modèle. - -3. **Expérience de stratégie mixte** : Dans une tâche de développement en 5 sessions, comparez trois stratégies : (a) toujours démarrer de nouvelles sessions + fichiers de progression, (b) faire le maximum possible en une seule session (compaction de contexte), (c) stratégie mixte (tâches courtes en session, tâches longues sur plusieurs sessions + fichiers de progression). Comparez le temps de reconstruction, le taux de complétion des fonctionnalités et la cohérence des décisions. diff --git a/courses/learn-harness-engineering/docs/fr/lectures/lecture-06-why-initialization-needs-its-own-phase/code/index.md b/courses/learn-harness-engineering/docs/fr/lectures/lecture-06-why-initialization-needs-its-own-phase/code/index.md deleted file mode 100644 index 3721f551..00000000 --- a/courses/learn-harness-engineering/docs/fr/lectures/lecture-06-why-initialization-needs-its-own-phase/code/index.md +++ /dev/null @@ -1,8 +0,0 @@ -# Code du Cours 06 - -Utilisez ce dossier pour des exemples de : - -- sorties de l'initialiseur -- scripts d'initialisation -- fichiers de progression -- échafaudage de première exécution diff --git a/courses/learn-harness-engineering/docs/fr/lectures/lecture-06-why-initialization-needs-its-own-phase/code/init-check.ts b/courses/learn-harness-engineering/docs/fr/lectures/lecture-06-why-initialization-needs-its-own-phase/code/init-check.ts deleted file mode 100644 index 52dcd985..00000000 --- a/courses/learn-harness-engineering/docs/fr/lectures/lecture-06-why-initialization-needs-its-own-phase/code/init-check.ts +++ /dev/null @@ -1,296 +0,0 @@ -/** - * init-check.ts - * - * Programmatically checks initialization prerequisites: - * - Node.js version - * - Dependencies installed (node_modules exists) - * - Data directory exists - * - Config files present - * - * Simulates running with and without an explicit init phase, - * showing how missing prerequisites cause silent failures later. - * - * Run: npx tsx docs/lectures/lecture-06-why-initialization-needs-its-own-phase/code/init-check.ts - */ - -import * as fs from "node:fs"; -import * as path from "node:path"; -import * as childProcess from "node:child_process"; - -// --------------------------------------------------------------------------- -// Types -// --------------------------------------------------------------------------- - -interface CheckItem { - name: string; - category: string; - check: () => { pass: boolean; detail: string }; - impactIfMissing: string; -} - -interface CheckResult { - name: string; - category: string; - passed: boolean; - detail: string; - impactIfMissing: string; -} - -// --------------------------------------------------------------------------- -// Checks -// --------------------------------------------------------------------------- - -function createChecks(targetDir: string): CheckItem[] { - return [ - { - name: "Node.js version >= 18", - category: "Runtime", - check: () => { - try { - const version = process.version; // e.g. "v20.11.0" - const major = parseInt(version.replace("v", "").split(".")[0], 10); - return { - pass: major >= 18, - detail: `Detected: ${version} (need >= v18)`, - }; - } catch { - return { pass: false, detail: "Could not detect Node.js version" }; - } - }, - impactIfMissing: "TypeScript features and built-in APIs unavailable", - }, - { - name: "package.json exists", - category: "Config", - check: () => { - const p = path.join(targetDir, "package.json"); - const exists = fs.existsSync(p); - return { - pass: exists, - detail: exists ? `Found: ${p}` : "Not found in target directory", - }; - }, - impactIfMissing: "Cannot install dependencies or run scripts", - }, - { - name: "Dependencies installed (node_modules)", - category: "Dependencies", - check: () => { - const p = path.join(targetDir, "node_modules"); - const exists = fs.existsSync(p); - return { - pass: exists, - detail: exists ? `Found: ${p}` : "node_modules/ not found -- run npm install", - }; - }, - impactIfMissing: "All imports fail at runtime", - }, - { - name: "TypeScript available", - category: "Toolchain", - check: () => { - try { - const result = childProcess.execSync("npx tsc --version", { - encoding: "utf-8", - timeout: 10000, - stdio: ["pipe", "pipe", "pipe"], - }).trim(); - return { pass: true, detail: `Detected: ${result}` }; - } catch { - return { pass: false, detail: "TypeScript not available via npx" }; - } - }, - impactIfMissing: "Cannot compile TypeScript files", - }, - { - name: "tsconfig.json exists", - category: "Config", - check: () => { - const p = path.join(targetDir, "tsconfig.json"); - const exists = fs.existsSync(p); - return { - pass: exists, - detail: exists ? `Found: ${p}` : "Not found", - }; - }, - impactIfMissing: "TypeScript compiler uses defaults, may not match project needs", - }, - { - name: "Source directory exists", - category: "Structure", - check: () => { - const candidates = ["src", "lib", "app"]; - const found = candidates.filter((d) => { - const p = path.join(targetDir, d); - return fs.existsSync(p) && fs.statSync(p).isDirectory(); - }); - return { - pass: found.length > 0, - detail: found.length > 0 ? `Found: ${found.join(", ")}` : "No src/ lib/ or app/ directory found", - }; - }, - impactIfMissing: "Agent cannot locate source files to modify", - }, - { - name: "Test directory exists", - category: "Structure", - check: () => { - const candidates = ["test", "tests", "__tests__", "spec"]; - const found = candidates.filter((d) => { - const p = path.join(targetDir, d); - return fs.existsSync(p) && fs.statSync(p).isDirectory(); - }); - return { - pass: found.length > 0, - detail: found.length > 0 ? `Found: ${found.join(", ")}` : "No test directory found", - }; - }, - impactIfMissing: "Agent cannot find or run existing tests", - }, - { - name: "Git repository initialized", - category: "Version Control", - check: () => { - const gitDir = path.join(targetDir, ".git"); - const exists = fs.existsSync(gitDir); - return { - pass: exists, - detail: exists ? "Git repository detected" : "No .git directory found", - }; - }, - impactIfMissing: "No rollback capability, no change history", - }, - ]; -} - -// --------------------------------------------------------------------------- -// Simulation: with and without init phase -// --------------------------------------------------------------------------- - -interface SimResult { - scenario: string; - checksRun: boolean; - failuresBeforeWork: number; - workAttempted: boolean; - workSucceeded: boolean; - timeWastedMs: number; -} - -function simulateWithoutInit(): SimResult { - // Agent skips init, goes straight to work. - // Encounters failures one at a time as it hits missing prerequisites. - const checks = createChecks(process.cwd()); - let failures = 0; - let timeWasted = 0; - - for (const check of checks) { - const result = check.check(); - if (!result.pass) { - failures++; - timeWasted += 200; // Agent spends time discovering each missing piece - } - } - - return { - scenario: "NO INIT PHASE", - checksRun: false, - failuresBeforeWork: 0, // Didn't check upfront - workAttempted: true, - workSucceeded: failures === 0, - timeWastedMs: timeWasted, - }; -} - -function simulateWithInit(): SimResult { - // Agent runs init phase first, discovers all issues upfront. - const checks = createChecks(process.cwd()); - let failures = 0; - - for (const check of checks) { - if (!check.check().pass) { - failures++; - } - } - - return { - scenario: "WITH INIT PHASE", - checksRun: true, - failuresBeforeWork: failures, - workAttempted: failures === 0, - workSucceeded: failures === 0, - timeWastedMs: 0, - }; -} - -// --------------------------------------------------------------------------- -// Reporting -// --------------------------------------------------------------------------- - -function pad(s: string, len: number): string { - return s.length >= len ? s : s + " ".repeat(len - s.length); -} - -function run(): void { - const targetDir = process.cwd(); - console.log("\n" + "=".repeat(80)); - console.log(" INITIALIZATION PREREQUISITE CHECK"); - console.log("=".repeat(80)); - console.log(` Target: ${targetDir}\n`); - - const checks = createChecks(targetDir); - const results: CheckResult[] = checks.map((c) => { - const r = c.check(); - return { - name: c.name, - category: c.category, - passed: r.pass, - detail: r.detail, - impactIfMissing: c.impactIfMissing, - }; - }); - - // Detailed report - const header = `| ${pad("Check", 35)}| ${pad("Category", 12)}| ${pad("Status", 6)}| Detail`; - const sep = `|${"-".repeat(37)}|${"-".repeat(14)}|${"-".repeat(8)}|${"-".repeat(40)}`; - console.log(header); - console.log(sep); - - for (const r of results) { - const status = r.passed ? "PASS" : "FAIL"; - console.log(`| ${pad(r.name, 35)}| ${pad(r.category, 12)}| ${pad(status, 6)}| ${r.detail}`); - } - - const passCount = results.filter((r) => r.passed).length; - const failCount = results.filter((r) => !r.passed).length; - - console.log("\n" + "-".repeat(80)); - console.log(` Results: ${passCount} passed, ${failCount} failed out of ${results.length} checks`); - - if (failCount > 0) { - console.log("\n FAILED CHECKS -- Impact if not resolved:"); - for (const r of results.filter((r) => !r.passed)) { - console.log(` - ${r.name}: ${r.impactIfMissing}`); - } - } - - // Comparison: with vs without init - const noInit = simulateWithoutInit(); - const withInit = simulateWithInit(); - - console.log("\n" + "=".repeat(80)); - console.log(" INIT PHASE COMPARISON"); - console.log("=".repeat(80) + "\n"); - - const cHeader = `| ${pad("Metric", 35)}| ${pad("No Init Phase", 18)}| ${pad("With Init Phase", 18)}|`; - const cSep = `|${"-".repeat(37)}|${"-".repeat(20)}|${"-".repeat(20)}|`; - console.log(cHeader); - console.log(cSep); - console.log(`| ${pad("Prerequisites checked upfront", 35)}| ${pad("No", 18)}| ${pad("Yes", 18)}|`); - console.log(`| ${pad("Work attempted despite issues", 35)}| ${pad(String(noInit.workAttempted), 18)}| ${pad(String(withInit.workAttempted), 18)}|`); - console.log(`| ${pad("Time wasted discovering issues late", 35)}| ${pad(noInit.timeWastedMs + "ms", 18)}| ${pad(withInit.timeWastedMs + "ms", 18)}|`); - console.log(`| ${pad("Work succeeded", 35)}| ${pad(String(noInit.workSucceeded), 18)}| ${pad(String(withInit.workSucceeded), 18)}|`); - - console.log("\n An explicit init phase catches problems before they waste time.\n"); -} - -run(); diff --git a/courses/learn-harness-engineering/docs/fr/lectures/lecture-06-why-initialization-needs-its-own-phase/code/init.sh b/courses/learn-harness-engineering/docs/fr/lectures/lecture-06-why-initialization-needs-its-own-phase/code/init.sh deleted file mode 100644 index 2c6e1b0c..00000000 --- a/courses/learn-harness-engineering/docs/fr/lectures/lecture-06-why-initialization-needs-its-own-phase/code/init.sh +++ /dev/null @@ -1,10 +0,0 @@ -#!/usr/bin/env bash -set -euo pipefail - -echo "[init] installing dependencies" -npm install - -echo "[init] starting the docs site is optional" -echo "[init] use npm run docs:dev for course docs" - -echo "[init] project-specific startup would go here" diff --git a/courses/learn-harness-engineering/docs/fr/lectures/lecture-06-why-initialization-needs-its-own-phase/code/initializer-output-checklist.md b/courses/learn-harness-engineering/docs/fr/lectures/lecture-06-why-initialization-needs-its-own-phase/code/initializer-output-checklist.md deleted file mode 100644 index 82d4a3b7..00000000 --- a/courses/learn-harness-engineering/docs/fr/lectures/lecture-06-why-initialization-needs-its-own-phase/code/initializer-output-checklist.md +++ /dev/null @@ -1,7 +0,0 @@ -# Liste de Contrôle des Sorties de l'Initialiseur - -- Y a-t-il une commande de démarrage canonique ? -- Y a-t-il une commande de vérification canonique ? -- Y a-t-il un premier artefact de progression ? -- Y a-t-il un premier commit stable ? -- Y a-t-il une surface de fonctionnalité visible pour les sessions ultérieures ? diff --git a/courses/learn-harness-engineering/docs/fr/lectures/lecture-06-why-initialization-needs-its-own-phase/index.md b/courses/learn-harness-engineering/docs/fr/lectures/lecture-06-why-initialization-needs-its-own-phase/index.md deleted file mode 100644 index 2a26a70c..00000000 --- a/courses/learn-harness-engineering/docs/fr/lectures/lecture-06-why-initialization-needs-its-own-phase/index.md +++ /dev/null @@ -1,157 +0,0 @@ -[中文版本 →](../../../zh/lectures/lecture-06-why-initialization-needs-its-own-phase/) - -> Exemples de code : [code/](https://github.com/walkinglabs/learn-harness-engineering/blob/main/docs/fr/lectures/lecture-06-why-initialization-needs-its-own-phase/code/) -> Projet pratique : [Project 03. Multi-session continuity](./../../projects/project-03-multi-session-continuity/index.md) - -# Leçon 06. Initialiser avant chaque session d'agent - -Vous démarrez une nouvelle session d'agent et dites « ajoute une fonctionnalité de recherche ». Il se lance directement dans le code — un bel enthousiasme. Après 20 minutes, il découvre que le framework de test n'est pas correctement configuré, passe encore 10 minutes à le corriger, puis le format du script de migration de base de données est incorrect, encore des ajustements. La fonctionnalité de recherche finit par être ajoutée, mais toute la session a été inefficace — la majeure partie du temps a été consacrée à « comprendre comment fonctionne ce projet » plutôt qu'à écrire la fonctionnalité de recherche. - -La meilleure approche : avant de laisser l'agent commencer à travailler, utiliser une phase séparée pour préparer l'environnement de base, faire passer les commandes de vérification, et faire comprendre la structure du projet. C'est comme construire une maison — vous ne coulez pas les fondations et n'érigez pas les murs simultanément. Si vous le faites, les murs montent avant que les fondations n'aient séché, et tout le bâtiment doit être démoli et recommencé. Coulez d'abord les fondations, laissez-les sécher, puis construisez les murs — propre et efficace. - -Cette leçon explique pourquoi l'initialisation doit être une phase séparée, pas mélangée avec l'implémentation. - -## Fondations et murs : deux travaux fondamentalement différents - -L'initialisation et l'implémentation ont des cibles d'optimisation complètement différentes. La phase d'implémentation optimise pour : maximiser la quantité et la qualité des fonctionnalités vérifiées. La phase d'initialisation optimise pour : maximiser la fiabilité et l'efficacité de toutes les implémentations ultérieures. - -Quand vous mélangez initialisation et implémentation, l'agent fait face à un problème d'optimisation multi-objectifs — construire simultanément l'infrastructure et écrire du code de fonctionnalité. Sans priorité explicite, l'agent se tourne naturellement vers l'écriture de code (parce que c'est une sortie directement visible) au détriment de l'infrastructure (parce que sa valeur ne se manifeste que dans les sessions suivantes). C'est comme demander à une équipe de construction de couler simultanément les fondations et de monter les murs — ils vont probablement se précipiter pour construire les murs parce que les murs sont visibles et démontrables. Mais une maison avec de mauvaises fondations aura des problèmes systémiques par la suite. - -## Cycle de vie de l'initialisation - -```mermaid -flowchart TB - subgraph Wrong["Mixed session (wrong)"] - W1["Start feature work immediately"] --> W2["Discover env and test gaps mid-task"] - W2 --> W3["Accumulate unverified code"] - W3 --> W4["Next session must rediscover project state"] - end - - subgraph Right["Dedicated initialization (right)"] - R1["Session 1: environment runnable"] --> R2["Example test passing"] - R2 --> R3["Bootstrap contract + task list written"] - R3 --> R4["Clean checkpoint committed"] - R4 --> R5["Later sessions start directly on verified tasks"] - end -``` - -## Ce qui se passe quand on les mélange - -Le problème le plus direct : les fondations ne sèchent pas correctement. L'agent passe 80 % de ses efforts sur le code de fonctionnalité et 20 % à configurer vaguement de l'infrastructure. Le framework de test est configuré mais jamais vérifié, les règles lint sont définies mais trop laxistes, aucun fichier de progression créé. Ces défauts ne sont pas évidents lors de la première session (parce que l'agent se souvient encore de ce qu'il a fait), mais ils surgissent lors de la deuxième session — le nouvel agent ne sait pas comment lancer, tester, ou où en sont les choses. Fondations bâclées, bâtiment instable. - -Un coût plus caché est l'« accumulation non vérifiée » — le code de fonctionnalité écrit avant que le framework de test ne soit configuré est du code sans vérification. Quand vous revenez enfin pour ajouter des tests à ce code, vous pourriez découvrir que la conception était fausse dès le départ — si vous l'aviez su, vous l'auriez implémenté différemment. Comme carreler sur du béton encore humide — quand vous découvrez que le sol n'est pas de niveau, toutes les tuiles doivent être arrachées et refaites. - -Le budget de session est également gaspillé. Le travail d'initialisation (configurer les environnements, mettre en place les tests, comprendre la structure du projet) consomme un budget significatif, laissant moins pour l'implémentation réelle des fonctionnalités. Résultat : la première session ne complète que la moitié des fonctionnalités, et la deuxième session doit recommencer à comprendre le projet. Budget dépensé pour les fondations, mais les fondations ne sont pas solides non plus — aucun objectif atteint. - -Le problème le plus facilement négligé est celui des mines d'hypothèses implicites. Les décisions que l'agent prend pendant l'initialisation (quel framework de test, comment organiser les répertoires, gestion des dépendances) — si elles ne sont pas explicitement enregistrées, les sessions suivantes ne peuvent pas comprendre ces choix. Pire, les sessions suivantes pourraient prendre des décisions contradictoires. La première équipe de construction a utilisé des fondations en béton, la deuxième ne le sait pas et y a enfoncé des pilotis en bois — les fondations se fissurent. - -La recherche d'Anthropic sur le développement d'applications longue durée recommande explicitement de séparer l'initialisation de l'implémentation. Leurs données expérimentales : les projets utilisant une phase d'initialisation dédiée ont montré des taux de complétion de fonctionnalités 31 % plus élevés dans les scénarios multi-sessions par rapport aux approches mixtes. L'insight clé — le temps investi dans la phase d'initialisation est entièrement récupéré dans les 3-4 sessions suivantes. Plus les fondations sont solides, plus vite les murs montent. - -Le guide de harness engineering d'OpenAI Codex souligne également le principe du « dépôt comme registre opérationnel » — établir une structure opérationnelle claire dès la première exécution, ou chaque nouvelle session doit ré-inférer les conventions du projet. - -## Concepts clés - -- **Phase d'initialisation** : La première phase du cycle de vie de l'agent — pas d'implémentation de fonctionnalité, seulement l'établissement des prérequis pour toutes les phases d'implémentation suivantes. La sortie n'est pas du code, c'est de l'infrastructure. -- **Contrat d'amorçage (Bootstrap Contract)** : Les conditions dans lesquelles un projet peut être exploité sans ambiguïté par une nouvelle session d'agent — peut démarrer, peut tester, peut voir la progression, peut reprendre les étapes suivantes. Quatre conditions, toutes obligatoires. -- **Démarrage à froid vs démarrage à chaud** : Le démarrage à froid part d'un répertoire vide où l'agent doit deviner la structure du projet ; le démarrage à chaud part d'un modèle ou d'un projet existant où l'infrastructure est déjà en place. Le démarrage à chaud surpasse largement le démarrage à froid — comme commencer à travailler sur un chantier avec eau courante et électricité versus partir d'un terrain nu. -- **Préparation à la passation (Handoff Readiness)** : Le projet est dans un état où, à tout moment, un agent neuf peut prendre le relais. Pas d'explication verbale nécessaire — seulement le contenu du repo. -- **Temps jusqu'à la première vérification (Time to First Verification)** : Le temps entre le début du projet et le moment où le premier point de fonctionnalité passe la vérification. C'est la métrique clé pour mesurer l'efficacité de l'initialisation. -- **Utilisabilité en aval** : La meilleure mesure de la qualité de l'initialisation — la proportion de sessions suivantes qui peuvent exécuter des tâches avec succès sans s'appuyer sur des connaissances implicites. - -## Comment bien faire l'initialisation - -**Traitez l'initialisation comme une phase dédiée.** La première session ne fait que de l'initialisation — aucun code de fonctionnalité métier. L'initialisation produit : - -**1. Environnement exécutable.** Le projet démarre, les dépendances sont installées, aucun problème d'environnement. Fondations coulées, sans fissures. - -**2. Framework de test vérifiable.** Au moins un test exemple passe. Cela prouve que le framework de test lui-même est correctement configuré — comme dresser un pilier sur les fondations pour prouver qu'il peut supporter du poids. - -**3. Document de contrat d'amorçage.** Un document clair indiquant aux sessions suivantes : -```markdown -# Initialization Contract - -## Start Commands -- Install dependencies: `make setup` -- Start dev server: `make dev` -- Run tests: `make test` -- Full verification: `make check` - -## Current State -- All dependencies installed and locked -- Test framework configured (Vitest + React Testing Library) -- Example test passing (1/1) -- Lint rules configured (ESLint + Prettier) - -## Project Structure -- src/ — Source code -- src/components/ — React components -- src/api/ — API client -- tests/ — Test files -``` - -**4. Découpage des tâches.** Divisez tout le projet en une liste ordonnée de tâches, chacune avec des critères d'acceptation clairs : -```markdown -# Task Breakdown - -## Task 1: User Authentication Basics -- Implement JWT auth middleware -- Add login/register endpoints -- Acceptance: pytest tests/test_auth.py all passing - -## Task 2: User Profile Page -- Implement user profile CRUD -- Add profile edit form -- Acceptance: pytest tests/test_profile.py all passing - -## Task 3: Search Feature -- ... -``` - -**5. Commit git comme point de contrôle.** Après la complétion de l'initialisation, commitez un point de contrôle propre. Tout le travail ultérieur part de ce point de contrôle. - -**Stratégie de démarrage à chaud** : Ne partez pas d'un répertoire vide. Utilisez un modèle de projet (create-react-app, fastapi-template, etc.) pour prérégler la structure de répertoires standard, la configuration des dépendances et le framework de test. Intégrez les étapes d'initialisation courantes dans le modèle, ne laissant que le travail d'initialisation spécifique au projet. Comme commencer à travailler sur un chantier avec eau courante et électricité — mille fois mieux que de partir d'un terrain nu. - -**Critères de complétion de l'initialisation** : Pas « combien de code a été écrit », mais si les quatre conditions du contrat d'amorçage sont remplies — peut démarrer, peut tester, peut voir la progression, peut reprendre les étapes suivantes. Utilisez cette liste de contrôle pour valider l'initialisation : - -```markdown -## Initialization Acceptance Checklist -- [ ] `make setup` succeeds from scratch -- [ ] `make test` has at least one passing test -- [ ] A new agent session can answer "how to run" and "how to test" from repo contents alone -- [ ] Task breakdown file exists with at least 3 tasks -- [ ] Everything committed to git -``` - -## Exemple concret - -Deux approches d'initialisation pour un projet frontend React : - -**Approche mixte (couler les fondations et monter les murs simultanément)** : L'agent a simultanément créé l'échafaudage du projet et implémenté la première fonctionnalité lors de la session 1. À la fin de la session, le repo avait du code exécutable mais : pas de documentation explicite des commandes start/test, pas de fichier de suivi de progression, pas de découpage des tâches. La session 2 a passé ~20 minutes à déduire la structure du projet, le framework de test et le processus de build — comme une nouvelle équipe de construction arrivant sur un chantier, ne sachant pas où en sont les fondations ni où passent les canalisations, devant creuser des trous un par un pour le découvrir. - -**Initialisation dédiée (fondations d'abord)** : La session 1 n'a fait que de l'initialisation — créé la structure de répertoires à partir d'un modèle, configuré le framework de test (Vitest + React Testing Library), écrit et vérifié un test exemple, créé le document de contrat d'amorçage et le fichier de découpage des tâches, committé le point de contrôle initial. Le temps de reconstruction de la session 2 était inférieur à 3 minutes, et elle a commencé à travailler directement à partir de la liste de tâches — l'équipe arrive, jette un coup d'oeil au plan, et sait exactement où reprendre. - -Comparaison sur le cycle complet du projet : le temps de reconstruction total de l'approche mixte (sur toutes les sessions) était ~60 % supérieur à celui de l'approche d'initialisation dédiée. Les 20 minutes supplémentaires passées à l'initialisation ont été récupérées plusieurs fois lors des sessions suivantes. Comme des fondations solides qui font monter les murs plus vite — lent c'est vite. - -## Points clés - -- L'initialisation et l'implémentation ont des cibles d'optimisation différentes — les mélanger tire les deux vers le bas. Coulez d'abord les fondations, puis construisez les murs. -- La sortie de l'initialisation n'est pas du code, c'est de l'infrastructure : environnement exécutable, tests vérifiables, contrat d'amorçage, découpage des tâches. -- Validez l'initialisation avec les quatre conditions du contrat d'amorçage : peut démarrer, peut tester, peut voir la progression, peut reprendre les étapes suivantes. -- Le démarrage à chaud bat le démarrage à froid. Utilisez des modèles de projet pour prérégler l'infrastructure standardisée. -- Le temps investi dans l'initialisation est entièrement récupéré dans les 3-4 sessions suivantes. Ce n'est pas un coût supplémentaire — c'est un investissement initial. Plus les fondations sont solides, plus vite le bâtiment monte. - -## Pour aller plus loin - -- [Anthropic: Effective Harnesses for Long-Running Agents](https://www.anthropic.com/engineering/effective-harnesses-for-long-running-agents) -- [OpenAI: Harness Engineering](https://openai.com/index/harness-engineering/) -- [HumanLayer: Harness Engineering for Coding Agents](https://humanlayer.dev/articles/harness-engineering-for-coding-agents/) -- [Infrastructure as Code — Martin Fowler](https://martinfowler.com/bliki/InfrastructureAsCode.html) -- [SWE-agent: Agent-Computer Interfaces](https://github.com/princeton-nlp/SWE-agent) - -## Exercices - -1. **Conception du contrat d'amorçage** : Écrivez un contrat d'amorçage complet pour un projet que vous développez. Ouvrez ensuite une toute nouvelle session d'agent, montrez-lui uniquement le contenu du repo (aucun contexte verbal), et demandez-lui d'essayer de lancer le projet, d'exécuter les tests et de comprendre la progression actuelle. Enregistrez chaque problème rencontré — chacun correspond à une clause manquante dans votre contrat d'amorçage. - -2. **Expérience comparative** : Choisissez un nouveau projet de complexité modérée. Approche A : laissez l'agent initialiser et faire la première implémentation simultanément. Approche B : consacrez une session à l'initialisation dédiée, commencez l'implémentation en session 2. Après 4 sessions, comparez : le temps jusqu'à la première vérification, le coût de reconstruction, le taux de complétion des fonctionnalités. - -3. **Liste de contrôle d'acceptation de l'initialisation** : Concevez une liste de contrôle d'acceptation de l'initialisation pour votre projet. Demandez à une nouvelle session d'agent d'exécuter chaque élément de la liste de contrôle et d'enregistrer lesquels passent et lesquels échouent. Les éléments qui échouent indiquent les points où votre harness a besoin d'être renforcé. diff --git a/courses/learn-harness-engineering/docs/fr/lectures/lecture-07-why-agents-overreach-and-under-finish/code/index.md b/courses/learn-harness-engineering/docs/fr/lectures/lecture-07-why-agents-overreach-and-under-finish/code/index.md deleted file mode 100644 index 0b27ef4e..00000000 --- a/courses/learn-harness-engineering/docs/fr/lectures/lecture-07-why-agents-overreach-and-under-finish/code/index.md +++ /dev/null @@ -1,8 +0,0 @@ -# Code du Cours 07 - -Utilisez ce dossier pour des exemples de : - -- échecs en un seul essai -- prompts de tâches surdimensionnés -- formation incrémentale des tâches -- surfaces de fonctionnalités structurées diff --git a/courses/learn-harness-engineering/docs/fr/lectures/lecture-07-why-agents-overreach-and-under-finish/code/next-task-template.md b/courses/learn-harness-engineering/docs/fr/lectures/lecture-07-why-agents-overreach-and-under-finish/code/next-task-template.md deleted file mode 100644 index 6587a276..00000000 --- a/courses/learn-harness-engineering/docs/fr/lectures/lecture-07-why-agents-overreach-and-under-finish/code/next-task-template.md +++ /dev/null @@ -1,6 +0,0 @@ -# Modèle de Prochaine Tâche - -- Fonctionnalité actuelle de plus haute priorité : -- Pourquoi cette fonctionnalité est la prochaine : -- Ce qui compte comme réussi : -- Ce qui ne doit pas être modifié pendant cette étape : diff --git a/courses/learn-harness-engineering/docs/fr/lectures/lecture-07-why-agents-overreach-and-under-finish/code/scope-surface-example.md b/courses/learn-harness-engineering/docs/fr/lectures/lecture-07-why-agents-overreach-and-under-finish/code/scope-surface-example.md deleted file mode 100644 index ffb16d64..00000000 --- a/courses/learn-harness-engineering/docs/fr/lectures/lecture-07-why-agents-overreach-and-under-finish/code/scope-surface-example.md +++ /dev/null @@ -1,17 +0,0 @@ -# Exemple de Surface de Portée - -Tâche : - -- Ajouter l'indexation à l'application de connaissance Electron - -Mauvaise forme de portée : - -- "Implémenter l'indexation" - -Meilleure forme de portée : - -- Analyser les documents importés -- Diviser les documents en morceaux -- Persister les métadonnées des morceaux -- Afficher le statut d'indexation dans l'UI -- Ajouter une action de réindexation diff --git a/courses/learn-harness-engineering/docs/fr/lectures/lecture-07-why-agents-overreach-and-under-finish/code/scope-tracker.ts b/courses/learn-harness-engineering/docs/fr/lectures/lecture-07-why-agents-overreach-and-under-finish/code/scope-tracker.ts deleted file mode 100644 index c587a1dc..00000000 --- a/courses/learn-harness-engineering/docs/fr/lectures/lecture-07-why-agents-overreach-and-under-finish/code/scope-tracker.ts +++ /dev/null @@ -1,151 +0,0 @@ -/** - * scope-tracker.ts - * - * Reads a feature list and a change log. Enforces single-active-feature - * policy. Given a log of changes, flags any changes outside the active - * feature scope. Demonstrates how scope drift happens and how the tracker - * catches it. - * - * Run: npx tsx docs/lectures/lecture-07-why-agents-overreach-and-under-finish/code/scope-tracker.ts - */ - -// --------------------------------------------------------------------------- -// Types -// --------------------------------------------------------------------------- - -interface Feature { - id: string; - name: string; - status: "active" | "pending" | "done"; -} - -interface ChangeLogEntry { - step: number; - file: string; - description: string; - featureId: string; // The feature this change claims to belong to -} - -// --------------------------------------------------------------------------- -// Sample data -// --------------------------------------------------------------------------- - -const features: Feature[] = [ - { id: "F-001", name: "Search endpoint", status: "active" }, - { id: "F-002", name: "Delete endpoint", status: "pending" }, - { id: "F-003", name: "Rate limiting", status: "pending" }, - { id: "F-004", name: "User dashboard", status: "pending" }, -]; - -// A realistic change log where the agent gradually drifts from the active feature -const changeLog: ChangeLogEntry[] = [ - { step: 1, file: "src/routes/search.ts", description: "Add search route handler", featureId: "F-001" }, - { step: 2, file: "src/routes/search.ts", description: "Add query parameter validation", featureId: "F-001" }, - { step: 3, file: "src/routes/search.ts", description: "Add search results pagination", featureId: "F-001" }, - { step: 4, file: "src/routes/delete.ts", description: "Add delete route handler", featureId: "F-002" }, // DRIFT - { step: 5, file: "src/middleware/rate-limit.ts", description: "Add rate limiter middleware", featureId: "F-003" }, // DRIFT - { step: 6, file: "src/routes/search.ts", description: "Integrate rate limiter into search", featureId: "F-001" }, - { step: 7, file: "src/dashboard/ui.tsx", description: "Create dashboard layout component", featureId: "F-004" }, // DRIFT - { step: 8, file: "src/routes/search.ts", description: "Add search response formatting", featureId: "F-001" }, - { step: 9, file: "src/routes/delete.ts", description: "Add delete confirmation logic", featureId: "F-002" }, // DRIFT - { step: 10, file: "src/routes/search.ts", description: "Add search tests", featureId: "F-001" }, -]; - -// --------------------------------------------------------------------------- -// Scope tracker -// --------------------------------------------------------------------------- - -interface ScopeCheckResult { - step: number; - file: string; - description: string; - featureId: string; - inScope: boolean; - activeFeature: string; -} - -function trackScope( - featureList: Feature[], - changes: ChangeLogEntry[] -): ScopeCheckResult[] { - const activeFeatures = featureList.filter((f) => f.status === "active"); - const activeIds = new Set(activeFeatures.map((f) => f.id)); - const activeNames = activeFeatures.map((f) => f.name).join(", "); - - return changes.map((change) => ({ - step: change.step, - file: change.file, - description: change.description, - featureId: change.featureId, - inScope: activeIds.has(change.featureId), - activeFeature: activeNames, - })); -} - -// --------------------------------------------------------------------------- -// Reporting -// --------------------------------------------------------------------------- - -function pad(s: string, len: number): string { - return s.length >= len ? s : s + " ".repeat(len - s.length); -} - -function run(): void { - const results = trackScope(features, changeLog); - - console.log("\n" + "=".repeat(100)); - console.log(" SCOPE TRACKER -- Single Active Feature Enforcement"); - console.log("=".repeat(100)); - - console.log("\n Active feature: " + features.filter((f) => f.status === "active").map((f) => `${f.id} (${f.name})`).join(", ")); - console.log(" Pending features: " + features.filter((f) => f.status === "pending").map((f) => `${f.id} (${f.name})`).join(", ")); - - // Detailed change log - console.log("\n" + "-".repeat(100)); - const header = `| ${pad("Step", 5)}| ${pad("File", 35)}| ${pad("Description", 40)}| ${pad("Feature", 8)}| ${pad("In Scope", 10)}|`; - const sep = `|${"-".repeat(7)}|${"-".repeat(37)}|${"-".repeat(42)}|${"-".repeat(10)}|${"-".repeat(12)}|`; - console.log(header); - console.log(sep); - - let inScopeCount = 0; - let driftCount = 0; - - for (const r of results) { - const scopeLabel = r.inScope ? "OK" : "DRIFT"; - if (r.inScope) inScopeCount++; - else driftCount++; - - const marker = r.inScope ? " " : ">>"; - console.log(`${marker}| ${pad(String(r.step), 5)}| ${pad(r.file, 35)}| ${pad(r.description, 40)}| ${pad(r.featureId, 8)}| ${pad(scopeLabel, 10)}|`); - } - - // Summary - console.log("\n" + "=".repeat(100)); - console.log(" SCOPE DRIFT SUMMARY"); - console.log("=".repeat(100) + "\n"); - - const sHeader = `| ${pad("Metric", 40)}| ${pad("Value", 15)}|`; - const sSep = `|${"-".repeat(42)}|${"-".repeat(17)}|`; - console.log(sHeader); - console.log(sSep); - console.log(`| ${pad("Total changes", 40)}| ${pad(String(results.length), 15)}|`); - console.log(`| ${pad("Changes within active scope (F-001)", 40)}| ${pad(String(inScopeCount), 15)}|`); - console.log(`| ${pad("Changes outside active scope (DRIFT)", 40)}| ${pad(String(driftCount), 15)}|`); - console.log(`| ${pad("Features touched (total)", 40)}| ${pad(String(new Set(results.map((r) => r.featureId)).size), 15)}|`); - - // Drift detail - const driftFeatures = [...new Set(results.filter((r) => !r.inScope).map((r) => r.featureId))]; - if (driftFeatures.length > 0) { - console.log("\n DRIFTED FEATURES:"); - for (const fid of driftFeatures) { - const feat = features.find((f) => f.id === fid); - const driftChanges = results.filter((r) => r.featureId === fid); - console.log(` ${fid} (${feat?.name}): ${driftChanges.length} unauthorized changes`); - } - } - - console.log("\n Without a scope tracker, the agent silently worked on " + driftFeatures.length + " unrelated features."); - console.log(" The tracker catches this drift and enforces the single-active-feature policy.\n"); -} - -run(); diff --git a/courses/learn-harness-engineering/docs/fr/lectures/lecture-07-why-agents-overreach-and-under-finish/index.md b/courses/learn-harness-engineering/docs/fr/lectures/lecture-07-why-agents-overreach-and-under-finish/index.md deleted file mode 100644 index 36538939..00000000 --- a/courses/learn-harness-engineering/docs/fr/lectures/lecture-07-why-agents-overreach-and-under-finish/index.md +++ /dev/null @@ -1,134 +0,0 @@ -[中文版本 →](../../../zh/lectures/lecture-07-why-agents-overreach-and-under-finish/) - -> Exemples de code : [code/](https://github.com/walkinglabs/learn-harness-engineering/blob/main/docs/fr/lectures/lecture-07-why-agents-overreach-and-under-finish/code/) -> Projet pratique : [Project 04. Runtime feedback and scope control](./../../projects/project-04-incremental-indexing/index.md) - -# Leçon 07. Définir des limites de tâche claires - -Vous demandez à Claude Code d'« ajouter l'authentification utilisateur à ce projet », et il commence à modifier le schéma de base de données, à écrire des routes, à changer des composants frontend, et — tant qu'à faire — à refactorer le middleware de gestion d'erreurs. Deux heures plus tard, vous vérifiez : 12 fichiers modifiés, 800 lignes de nouveau code, et pas une seule fonctionnalité qui fonctionne de bout en bout. - -Mordre plus qu'on ne peut mâcher — ce dicton s'applique particulièrement bien aux agents IA. Les agents ont un instinct inné pour « faire un peu plus » — ils voient des choses liées et les traitent au passage, comme quelqu'un qui va au supermarché pour une bouteille de sauce soja et ressort en poussant un caddy plein. Le problème, c'est que les humains qui achètent trop gaspillent juste de l'argent ; les agents qui font trop de choses simultanément n'en terminent aucune correctement. - -Le blog d'ingénierie d'Anthropic « Effective harnesses for long-running agents » le dit clairement : quand les prompts sont trop larges, les agents ont tendance à « commencer plusieurs choses à la fois » plutôt qu'à « terminer une chose d'abord ». Les pratiques d'ingénierie Codex d'OpenAI ont constaté la même chose — les tâches sans contrôles de portée explicites voient leurs taux de complétion chuter. Ce n'est pas un problème de modèle — c'est un problème de harness. Vous n'avez pas tracé la limite. - -## L'attention est une ressource finie - -Ce n'est pas une métaphore — ce sont des mathématiques. Supposez que la capacité de contexte de l'agent est C et qu'il active k tâches simultanément. Chaque tâche reçoit en moyenne C/k ressources de raisonnement. Quand C/k descend en dessous du seuil minimum nécessaire pour terminer une seule tâche, aucune n'est achevée. Votre estomac n'est que d'une certaine taille — enfoncez dix raviolis d'un coup et vous ne les digérerez pas tous, vous aurez juste dix cas d'indigestion. - -Le comportement réel de Claude Code est révélateur. Demandez-lui d'« ajouter l'inscription utilisateur » et il pourrait : - -1. Créer un modèle User -2. Écrire la route d'inscription -3. Remarquer qu'il a besoin de vérification email, donc ajouter un service mail -4. Voir que les mots de passe doivent être hachés, donc intégrer bcrypt -5. Remarquer que la gestion des erreurs est incohérente, donc refactorer le middleware d'erreurs global -6. Voir que la structure des fichiers de test est désordonnée, donc réorganiser le répertoire - -Six étapes plus tard, chacune est à moitié faite. Pas de vérification de bout en bout, couplage complexe entre le code à moitié cuit, et la session suivante qui devra ramasser les morceaux sera complètement perdue. Comme quelqu'un qui cuisine six plats simultanément — chaque plat est dans la poêle mais aucun n'est dressé. Ils brûlent tous. - -Les données expérimentales d'Anthropic le confirment directement : les agents utilisant une stratégie de « petit pas suivant » (équivalent à WIP=1) affichent un taux de complétion de tâches 37 % plus élevé que les agents utilisant des prompts larges. Plus intéressant encore, le nombre de lignes de code générées par les agents est faiblement corrélé négativement avec la complétion réelle des fonctionnalités — plus de code écrit, moins de fonctionnalités complétées. Prendre plus qu'on ne peut mâcher, prouvé par les données. - -## Flux de travail WIP=1 - -```mermaid -flowchart LR - Queue["Feature queue"] --> Pick["Pick exactly one task"] - Pick --> Active["Only one active item"] - Active --> Verify["Run end-to-end verification"] - Verify -->|pass| Commit["Commit and unlock next task"] - Verify -->|fail| Active - Commit --> Queue -``` - -```mermaid -flowchart TB - Budget["Available reasoning budget = C"] --> One["WIP = 1
C / 1 per task"] - Budget --> Many["WIP = 5
C / 5 per task"] - - One --> Finish["One feature reaches passing state"] - Many --> Partial["Five partial implementations"] - Partial --> VCR["Low verified completion rate
high rework next session"] -``` - -## Concepts clés - -- **Surportée (Overreach)** : L'agent active plus de tâches en une seule session que ce qui est optimal. C'est quantifiable — faire 5 fonctionnalités avec 0 passage de bout en bout est une surportée. -- **Sous-achèvement (Under-finish)** : Le ratio de tâches passant la vérification de bout en bout, parmi toutes les tâches activées, tombe en dessous du seuil. Du code écrit mais des tests qui ne passent pas est du sous-achèvement. -- **Limite WIP (Work-in-Progress Limit)** : Issu de la méthodologie Kanban. Idée fondamentale : limiter le nombre de tâches en cours simultanément. Pour les agents, WIP=1 est le défaut le plus sûr — terminez-en un avant de commencer le suivant. Comme au buffet — n'empilez pas votre assiette, terminez-en une puis retournez en chercher une autre. -- **Preuve de complétion (Completion Evidence)** : La condition vérifiable qu'une tâche doit satisfaire pour passer de « en cours » à « terminée ». Sans cela, les agents substituent « le code a l'air correct » à « le comportement passe les tests ». -- **Surface de portée (Scope Surface)** : Une structure DAG où chaque nœud est une unité de travail et les arêtes sont des dépendances. Les états sont limités à quatre : not_started, active, blocked, passing. -- **Pression d'achèvement (Completion Pressure)** : La force contraignante que le harness exerce via les limites WIP et les exigences de preuve de complétion, forçant l'agent à terminer la tâche en cours avant d'en commencer une nouvelle. - -## Surportée et sous-achèvement sont symbiotiques - -Ces deux problèmes ne sont pas indépendants — ils s'amplifient mutuellement. La surportée dilue l'attention, l'attention diluée cause du sous-achèvement, et le code à moitié terminé qui en résulte augmente la complexité du système, ce qui alimente davantage la surportée dans la tâche suivante. Un cercle vicieux. - -En termes Kanban : la loi de Little nous dit L = lambda * W. Si le travail en cours L est trop élevé (faire trop de choses à la fois), le temps de traitement W de chaque tâche augmente inévitablement. Pour les agents, cela signifie que chaque fonctionnalité prend plus de temps du début à la vérification finale, et la probabilité d'échec croît. - -C'est aussi un vieux problème dans le monde humain — Steve McConnell a documenté dans *Rapid Development* que la dérive du périmètre est la principale cause d'échec de projet. Mais les humains ont au moins l'intuition d'en avoir fait assez. Les agents n'en ont aucune. Générer l'idée suivante ne coûte presque rien en tokens supplémentaires au modèle — écrire « laissez-moi aussi corriger ça tant que j'y suis » ne compte presque pas — mais chaque modification supplémentaire dilue l'attention de l'agent. Comme un buffet où chaque assiette supplémentaire a un coût marginal quasi nul, mais votre estomac n'a qu'une certaine capacité. - -## Comment bien le faire - -### 1. Imposer WIP=1 - -C'est la méthode la plus directe et la plus efficace. Dans votre harness, dites explicitement à l'agent : **une seule tâche est autorisée en statut « active » à tout moment.** Dans le CLAUDE.md de Claude Code ou l'AGENTS.md de Codex, écrivez : - -``` -## Work Rules -- Work on one feature at a time -- Only start the next feature after the current one passes end-to-end verification -- Don't "also refactor" feature B while implementing feature A -``` - -Comme manger au buffet — une assiette à la fois, terminez-la avant de retourner en chercher une autre. - -### 2. Définir des preuves de complétion explicites pour chaque tâche - -Terminé n'est pas « le code est écrit » — c'est « la vérification du comportement passe ». Dans votre feature list, chaque entrée a besoin d'une commande de vérification : - -``` -F01: User Registration - Verification: curl -X POST /api/register -d '{"email":"test@example.com","password":"123456"}' | jq .status == 201 - State: passing -``` - -### 3. Externaliser la surface de portée - -Utilisez un fichier lisible par machine (JSON ou Markdown) pour enregistrer tous les états des tâches. Toute nouvelle session peut lire ce fichier et savoir immédiatement : quelle tâche est active ? Quel comportement compte comme terminé ? Quelles vérifications ont passé ? - -### 4. Surveiller le taux de complétion vérifié - -Le harness devrait suivre en continu le VCR (Verified Completion Rate) = tâches vérifiées / tâches activées. Bloquer les activations de nouvelles tâches quand VCR < 1.0. - -## Cas concret - -Un projet d'API REST avec 8 fonctionnalités, deux stratégies comparées : - -**Mode buffet (sans contrainte)** : L'agent active 5 fonctionnalités simultanément lors de la session 1. Produit ~800 lignes sur 12 fichiers. Taux de réussite des tests de bout en bout : 20 % — seule l'inscription utilisateur fonctionne. Les 4 autres fonctionnalités : schéma de base de données créé mais logique de validation manquante, routes définies mais retournant de mauvais formats de réponse. Comme quelqu'un qui cuisine six plats à la fois, un seul est à peine comestible. À la fin de la session 3, seulement 3 fonctionnalités sur 8 sont complétées. - -**Mode assiette unique (WIP=1)** : L'agent ne travaille que sur l'inscription utilisateur lors de la session 1. Produit ~200 lignes sur 4 fichiers. Tests de bout en bout : 100 % de réussite. Commite une implémentation propre et vérifiée. À la fin de la session 4, 7 fonctionnalités sur 8 sont complétées (la 8e bloquée par une dépendance externe). - -Résultat : moins de code total (800 vs 1200 lignes) mais du code plus efficace. Taux de complétion : 87,5 % vs 37,5 %. Prenez une bouchée à la fois, et vous mangez finalement plus. - -## Points clés - -- **WIP=1 est le réglage de sécurité par défaut pour les harness d'agents** — terminez-en un, puis commencez le suivant ; n'essayez pas de paralléliser. On ne devient pas gros en une bouchée. -- **La preuve de complétion doit être exécutable** — « le code a l'air correct » ne compte pas ; « curl retourne 201 » compte. -- **La surface de portée doit être externalisée dans un fichier** — pas juste mentionnée en conversation, mais enregistrée dans un format lisible par machine dans le repo. -- **La surportée et le sous-achèvement sont symbiotiques** — résoudre l'un résout l'autre. -- **« Faire moins mais terminer » bat toujours « faire plus mais laisser à moitié »** — les lignes de code de l'agent et le taux de complétion des fonctionnalités sont corrélés négativement. La qualité bat toujours la quantité. - -## Pour aller plus loin - -- [Effective harnesses for long-running agents - Anthropic](https://www.anthropic.com/engineering/effective-harnesses-for-long-running-agents) — Blog d'ingénierie d'Anthropic, discussion détaillée de la stratégie « petit pas suivant » -- [Harness Engineering - OpenAI](https://openai.com/index/harness-engineering/) — Traitement complet du harness engineering par OpenAI -- [Kanban: Successful Evolutionary Change - David Anderson](https://www.goodreads.com/book/show/1070822.Kanban) — La source classique sur les limites WIP -- [Rapid Development - Steve McConnell](https://www.goodreads.com/book/show/125171.Rapid_Development) — Données empiriques sur la dérive du périmètre comme cause principale d'échec de projet - -## Exercices - -1. **Atomisation des tâches** : Prenez une exigence large (par ex., « implémenter un système de gestion des utilisateurs ») et décomposez-la en au moins 5 unités de travail atomiques. Pour chaque unité, spécifiez : (a) une description de comportement unique, (b) une commande de vérification exécutable, (c) les dépendances. Vérifiez si la décomposition satisfait la contrainte WIP=1. - -2. **Expérience comparative** : Exécutez le même projet deux fois — une fois sans contraintes, une fois avec WIP=1 imposé. Comparez : le taux de complétion vérifié, le nombre total de lignes de code, le ratio de code effectif. - -3. **Audit des preuves de complétion** : Examinez la sortie d'une exécution récente d'agent, en classant chaque modification de code comme « comportement complété », « comportement incomplet » ou « échafaudage ». Ajoutez les commandes de vérification manquantes pour chaque comportement incomplet. diff --git a/courses/learn-harness-engineering/docs/fr/lectures/lecture-08-why-feature-lists-are-harness-primitives/code/feature-list-validator.ts b/courses/learn-harness-engineering/docs/fr/lectures/lecture-08-why-feature-lists-are-harness-primitives/code/feature-list-validator.ts deleted file mode 100644 index 52d6a2b9..00000000 --- a/courses/learn-harness-engineering/docs/fr/lectures/lecture-08-why-feature-lists-are-harness-primitives/code/feature-list-validator.ts +++ /dev/null @@ -1,218 +0,0 @@ -/** - * feature-list-validator.ts - * - * Reads a feature_list.json, validates its schema, and checks for features - * marked "pass" without verification evidence. Outputs a structured report. - * Can run against any project directory that has a feature_list.json. - * - * Usage: - * npx tsx docs/lectures/lecture-08.../code/feature-list-validator.ts [path-to-dir] - * (defaults to the directory containing this script) - * - * Run: npx tsx docs/lectures/lecture-08-why-feature-lists-are-harness-primitives/code/feature-list-validator.ts - */ - -import * as fs from "node:fs"; -import * as path from "node:path"; - -// --------------------------------------------------------------------------- -// Types -// --------------------------------------------------------------------------- - -interface FeatureEntry { - id?: string; - category?: string; - description?: string; - verification?: string[]; - passes?: boolean; - // Allow unknown fields for flexibility - [key: string]: unknown; -} - -interface ValidationResult { - featureId: string; - schemaValid: boolean; - schemaErrors: string[]; - hasVerification: boolean; - markedPassWithoutEvidence: boolean; - passes: boolean; - verificationCount: number; -} - -// --------------------------------------------------------------------------- -// Schema validation -// --------------------------------------------------------------------------- - -function validateSchema(entry: FeatureEntry, index: number): string[] { - const errors: string[] = []; - const label = entry.id ?? `entry ${index + 1}`; - - if (!entry.id || typeof entry.id !== "string") { - errors.push(`[${label}] Missing or invalid 'id' field`); - } - if (!entry.category || typeof entry.category !== "string") { - errors.push(`[${label}] Missing or invalid 'category' field`); - } - if (!entry.description || typeof entry.description !== "string") { - errors.push(`[${label}] Missing or invalid 'description' field`); - } - if (entry.verification !== undefined && !Array.isArray(entry.verification)) { - errors.push(`[${label}] 'verification' must be an array if present`); - } - if (entry.passes !== undefined && typeof entry.passes !== "boolean") { - errors.push(`[${label}] 'passes' must be a boolean if present`); - } - - return errors; -} - -// --------------------------------------------------------------------------- -// Evidence validation -// --------------------------------------------------------------------------- - -function checkEvidence(entry: FeatureEntry): { - hasVerification: boolean; - markedPassWithoutEvidence: boolean; -} { - const hasVerification = Array.isArray(entry.verification) && entry.verification.length > 0; - const markedPassWithoutEvidence = entry.passes === true && !hasVerification; - - return { hasVerification, markedPassWithoutEvidence }; -} - -// --------------------------------------------------------------------------- -// Process feature list -// --------------------------------------------------------------------------- - -function processFeatureList(entries: FeatureEntry[]): ValidationResult[] { - return entries.map((entry, index) => { - const schemaErrors = validateSchema(entry, index); - const evidence = checkEvidence(entry); - - return { - featureId: (entry.id as string) ?? `entry-${index + 1}`, - schemaValid: schemaErrors.length === 0, - schemaErrors, - hasVerification: evidence.hasVerification, - markedPassWithoutEvidence: evidence.markedPassWithoutEvidence, - passes: entry.passes === true, - verificationCount: Array.isArray(entry.verification) ? entry.verification.length : 0, - }; - }); -} - -// --------------------------------------------------------------------------- -// Reporting -// --------------------------------------------------------------------------- - -function pad(s: string, len: number): string { - return s.length >= len ? s : s + " ".repeat(len - s.length); -} - -function run(): void { - // Resolve target directory - const scriptDir = path.dirname(new URL(import.meta.url).pathname); - const targetDir = process.argv[2] - ? path.resolve(process.argv[2]) - : scriptDir; - - const filePath = path.join(targetDir, "feature_list.json"); - - console.log("\n" + "=".repeat(90)); - console.log(" FEATURE LIST VALIDATOR"); - console.log("=".repeat(90)); - console.log(` Reading: ${filePath}\n`); - - if (!fs.existsSync(filePath)) { - console.error(` ERROR: feature_list.json not found at ${filePath}`); - console.error(" Usage: npx tsx feature-list-validator.ts [path-to-directory-containing-feature_list.json]\n"); - process.exit(1); - } - - let entries: FeatureEntry[]; - try { - const raw = fs.readFileSync(filePath, "utf-8"); - entries = JSON.parse(raw); - } catch (err) { - console.error(` ERROR: Could not parse feature_list.json: ${err}`); - process.exit(1); - } - - if (!Array.isArray(entries)) { - console.error(" ERROR: feature_list.json must contain a JSON array at the top level."); - process.exit(1); - } - - // For demo purposes, also validate an extended test set - const demoEntries: FeatureEntry[] = [ - ...entries, - { - id: "qna-002", - category: "import", - description: "User can import a PDF document.", - verification: ["Upload a PDF file", "Verify it appears in the document list"], - passes: true, - }, - { - id: "qna-003", - category: "grounded_qa", - description: "System hallucination rate is below 5%.", - verification: [], // Empty -- no evidence - passes: true, // Marked as pass WITHOUT evidence - }, - { - id: "missing-fields", - // Missing 'category' and 'description' - passes: true, - } as FeatureEntry, - ]; - - const results = processFeatureList(demoEntries); - - // Print report - const header = `| ${pad("Feature ID", 14)}| ${pad("Schema", 8)}| ${pad("Passes", 7)}| ${pad("Verifications", 14)}| ${pad("Evidence?", 12)}| Notes`; - const sep = `|${"-".repeat(16)}|${"-".repeat(10)}|${"-".repeat(9)}|${"-".repeat(16)}|${"-".repeat(14)}|${"-".repeat(30)}`; - console.log(header); - console.log(sep); - - for (const r of results) { - const schemaLabel = r.schemaValid ? "OK" : "INVALID"; - const passesLabel = r.passes ? "PASS" : "FAIL"; - const evidenceLabel = r.hasVerification ? "Present" : "MISSING"; - const notes = r.markedPassWithoutEvidence - ? "FLAGGED: passes without evidence!" - : r.schemaErrors.length > 0 - ? r.schemaErrors[0] - : ""; - - const marker = r.markedPassWithoutEvidence ? ">>" : r.schemaValid ? " " : "!!"; - console.log( - `${marker}| ${pad(r.featureId, 14)}| ${pad(schemaLabel, 8)}| ${pad(passesLabel, 7)}| ${pad(String(r.verificationCount), 14)}| ${pad(evidenceLabel, 12)}| ${notes}` - ); - } - - // Summary - const total = results.length; - const schemaOk = results.filter((r) => r.schemaValid).length; - const passing = results.filter((r) => r.passes).length; - const flagged = results.filter((r) => r.markedPassWithoutEvidence).length; - const withEvidence = results.filter((r) => r.hasVerification).length; - - console.log("\n" + "-".repeat(90)); - console.log(" SUMMARY"); - console.log("-".repeat(90)); - console.log(` Total features: ${total}`); - console.log(` Schema valid: ${schemaOk}/${total}`); - console.log(` Marked as passing: ${passing}/${total}`); - console.log(` With verification evidence: ${withEvidence}/${total}`); - console.log(` Flagged (pass without evidence): ${flagged}`); - - if (flagged > 0) { - console.log(`\n WARNING: ${flagged} feature(s) marked as "pass" without any verification evidence.`); - console.log(" These features need verification before they can be trusted.\n"); - } else { - console.log("\n All passing features have verification evidence. Feature list is healthy.\n"); - } -} - -run(); diff --git a/courses/learn-harness-engineering/docs/fr/lectures/lecture-08-why-feature-lists-are-harness-primitives/code/feature_list.json b/courses/learn-harness-engineering/docs/fr/lectures/lecture-08-why-feature-lists-are-harness-primitives/code/feature_list.json deleted file mode 100644 index 8b973e31..00000000 --- a/courses/learn-harness-engineering/docs/fr/lectures/lecture-08-why-feature-lists-are-harness-primitives/code/feature_list.json +++ /dev/null @@ -1,15 +0,0 @@ -[ - { - "id": "qna-001", - "category": "grounded_qa", - "description": "User can ask a question about an imported document and receive an answer with visible citations.", - "verification": [ - "Import a markdown document", - "Open the Q&A panel", - "Ask a question about known content", - "Verify the answer is returned", - "Verify one or more citations are displayed" - ], - "passes": false - } -] diff --git a/courses/learn-harness-engineering/docs/fr/lectures/lecture-08-why-feature-lists-are-harness-primitives/code/index.md b/courses/learn-harness-engineering/docs/fr/lectures/lecture-08-why-feature-lists-are-harness-primitives/code/index.md deleted file mode 100644 index 3ad5066c..00000000 --- a/courses/learn-harness-engineering/docs/fr/lectures/lecture-08-why-feature-lists-are-harness-primitives/code/index.md +++ /dev/null @@ -1,8 +0,0 @@ -# Code du Cours 08 - -Utilisez ce dossier pour des exemples de : - -- gating d'état de réussite -- vérification de bout en bout -- critères d'achèvement faibles vs forts -- exemples de boucle d'évaluation diff --git a/courses/learn-harness-engineering/docs/fr/lectures/lecture-08-why-feature-lists-are-harness-primitives/code/pass-gate-policy.md b/courses/learn-harness-engineering/docs/fr/lectures/lecture-08-why-feature-lists-are-harness-primitives/code/pass-gate-policy.md deleted file mode 100644 index 297fa4c6..00000000 --- a/courses/learn-harness-engineering/docs/fr/lectures/lecture-08-why-feature-lists-are-harness-primitives/code/pass-gate-policy.md +++ /dev/null @@ -1,8 +0,0 @@ -# Politique de Porte de Réussite - -Une fonctionnalité ne peut passer de `passes: false` à `passes: true` que lorsque : - -- le flux de travail attendu a été exercé -- la preuve de succès est enregistrée -- aucune erreur bloquante n'est présente dans le chemin testé -- l'implémentation ne laisse pas l'application dans un état cassé ou ambigu diff --git a/courses/learn-harness-engineering/docs/fr/lectures/lecture-08-why-feature-lists-are-harness-primitives/index.md b/courses/learn-harness-engineering/docs/fr/lectures/lecture-08-why-feature-lists-are-harness-primitives/index.md deleted file mode 100644 index fe3da9c4..00000000 --- a/courses/learn-harness-engineering/docs/fr/lectures/lecture-08-why-feature-lists-are-harness-primitives/index.md +++ /dev/null @@ -1,138 +0,0 @@ -[中文版本 →](../../../zh/lectures/lecture-08-why-feature-lists-are-harness-primitives/) - -> Exemples de code : [code/](https://github.com/walkinglabs/learn-harness-engineering/blob/main/docs/fr/lectures/lecture-08-why-feature-lists-are-harness-primitives/code/) -> Projet pratique : [Projet 04. Feedback runtime et contrôle de portée](./../../projects/project-04-incremental-indexing/index.md) - -# Leçon 08. Utiliser les listes de fonctionnalités pour contraindre l'agent - -Vous demandez à un agent de construire un site e-commerce. Une fois terminé, il vous dit "done". Vous regardez le code : l'authentification utilisateur fonctionne, mais le bouton de checkout du panier ne fait rien, et le flux de paiement n'est pas connecté. Le problème : vous ne lui avez jamais dit ce que "done" signifie, donc il a utilisé son propre standard : "j'ai écrit beaucoup de code et ça a l'air assez complet". - -Aux yeux de beaucoup de gens, les listes de fonctionnalités ne sont qu'un mémo : on note les choses pour ne pas oublier, puis on les met de côté. Mais dans le monde du harness, une liste de fonctionnalités n'est pas un mémo pour humains : c'est la colonne vertébrale de tout le harness. Le scheduler s'en sert pour choisir les tâches, le verifier pour juger l'achèvement, le handoff reporter pour générer les résumés. Si la colonne vertébrale casse, tout le corps est paralysé. - -Anthropic et OpenAI insistent tous deux : **les artefacts doivent être externalisés.** L'état des fonctionnalités doit vivre dans un fichier lisible par machine dans le repo, pas dans du texte de conversation non structuré. - -## Les agents ne savent pas ce que "done" signifie - -Ni Claude Code ni Codex ne savent automatiquement ce que vous voulez dire par "done". Vous dites "ajoute une fonctionnalité de panier", et le modèle peut interpréter cela comme "écrire un composant Cart et une méthode addToCart". Mais vous vouliez dire "l'utilisateur peut parcourir les produits, les ajouter au panier et terminer le checkout de bout en bout". Sans liste de fonctionnalités, cet écart de compréhension persiste. L'agent utilise son propre standard implicite, généralement "le code n'a pas d'erreur de syntaxe évidente". Ce qu'il vous faut, c'est une vérification comportementale end-to-end. C'est comme demander à un ami d'acheter des fruits : vous dites "prends des fruits" et il revient avec des citrons. Ses fruits et vos fruits ne sont pas les mêmes fruits. - -Regardez cette note de progression courante : - -``` -Did user auth, shopping cart mostly done, still need payments -``` - -Une nouvelle session d'agent peut-elle répondre à ces questions à partir de cette note ? Que signifie "mostly done" ? Quels tests le panier a-t-il réussis ? Qu'est-ce qui bloque les paiements ? La réponse à tout cela est "personne ne sait". Comme dire à votre médecin "j'ai mal au ventre, ça va à peu près ces derniers temps" : quel médicament peut-il prescrire ? - -Résultat : la nouvelle session passe 20 minutes à inférer l'état du projet et peut réimplémenter des fonctionnalités déjà terminées. Les données d'ingénierie d'Anthropic montrent que de bons enregistrements de progression réduisent le temps de diagnostic au démarrage d'une session de 60 à 80%. - -## Machine d'état des fonctionnalités - -```mermaid -flowchart LR - Feature["Une ligne de fonctionnalité"] --> Behavior["Comportement
par ex. : POST /cart/items returns 201"] - Feature --> Check["Commande de vérification
le check exact à exécuter"] - Feature --> State["État
not_started / active / blocked / passing"] - - Behavior --> Complete["Les trois champs sont nécessaires
pour que la ligne soit utilisable"] - Check --> Complete - State --> Complete -``` - -```mermaid -flowchart LR - List["feature_list.json / features.md"] --> Scheduler["Choisir le prochain item not_started"] - Scheduler --> Agent["L'agent travaille sur cet item"] - Agent --> Verifier["Exécuter la commande de vérification de l'item"] - Verifier -->|pass| Passing["Le marquer passing
et écrire la preuve"] - Verifier -->|fail| Active["Le garder active"] - Verifier -->|dependency issue| Blocked["Le marquer blocked"] - Passing --> Handoff["Mettre à jour la note de handoff
et la progression actuelle"] - Active --> Agent -``` - -## Concepts clés - -- **Les listes de fonctionnalités sont des primitives de harness** : pas des "outils de planification optionnels", mais des structures de données fondatrices dont dépendent tous les autres composants du harness. Comme les structures de tables en base de données : on ne peut pas dire "sautons les clés primaires". -- **Structure triple** : chaque item de fonctionnalité est un triplet `(description de comportement, commande de vérification, état actuel)`. Si un élément manque, l'item est incomplet. -- **Modèle de machine d'état** : chaque item a quatre états : `not_started`, `active`, `blocked`, `passing`. Les transitions d'état sont contrôlées par le harness, pas librement modifiées par l'agent. -- **Pass-state gating** : le seul moyen de passer de `active` à `passing` est que la commande de vérification réussisse. C'est irréversible : une fois `passing`, on ne revient pas en arrière. Comme un examen réussi : vous l'avez réussi, on ne change pas rétroactivement la note. -- **Single source of truth** : toute information sur "ce qu'il faut faire" doit dériver d'une seule liste de fonctionnalités. Pas de contradictions entre la liste et l'historique de conversation. -- **Back-pressure** : le nombre de fonctionnalités qui ne sont pas encore passées est la pression que le harness exerce sur l'agent. Pression zéro = projet terminé. - -## Pourquoi les listes de fonctionnalités doivent être des "primitives" - -Les documents sont faits pour être lus par des humains ; les primitives sont faites pour être exécutées par des systèmes. Les documents peuvent être ignorés ; les primitives ne peuvent pas être contournées. - -Pensez à la différence entre les contraintes de trigger en base de données et les checks côté application : les premières sont imposées par le moteur de base de données, aucun SQL ne peut les éviter ; les secondes dépendent de la correction du code applicatif et peuvent être contournées par accident. Comme primitive du harness, la liste de fonctionnalités sert quatre composants : - -1. **Scheduler** : lit les états et choisit la prochaine fonctionnalité `not_started`. Comme un système de planification de production d'usine. -2. **Verifier** : exécute les commandes de vérification et décide d'autoriser ou non les transitions d'état. Comme une inspection qualité. -3. **Handoff reporter** : génère automatiquement des résumés de passation de session depuis la liste de fonctionnalités. Comme un rapport automatique de changement d'équipe. -4. **Progress tracker** : agrège la distribution des états et fournit des métriques de santé du projet. Comme un tableau de bord. - -## Comment bien faire - -### 1. Définir un format minimal de liste de fonctionnalités - -Pas besoin d'un système complexe : un fichier Markdown structuré ou JSON suffit. L'essentiel est que chaque entrée contienne le triplet : - -```json -{ - "id": "F03", - "behavior": "POST /cart/items with {product_id, quantity} returns 201", - "verification": "curl -X POST http://localhost:3000/api/cart/items -H 'Content-Type: application/json' -d '{\"product_id\":1,\"quantity\":2}' | jq .status == 201", - "state": "passing", - "evidence": "commit abc123, test output log" -} -``` - -### 2. Laisser le harness contrôler les transitions d'état - -L'agent ne peut pas modifier directement l'état d'une fonctionnalité en `passing`. Il peut seulement soumettre une demande de vérification ; le harness exécute la commande de vérification et décide d'autoriser ou non la transition. C'est le pass-state gating. - -### 3. Écrire les règles dans CLAUDE.md - -``` -## Feature List Rules -- Feature list file: /docs/features.md -- Only one feature active at a time -- Verification command must pass before marking as passing -- Don't modify feature list states yourself — the verification script updates them automatically -``` - -### 4. Calibrer la granularité - -Chaque item de fonctionnalité doit être dimensionné pour être "terminable en une session". Trop large, il ne se terminera pas ; trop étroit, le coût de gestion augmente. "L'utilisateur peut ajouter des articles au panier" est une bonne granularité. "Implémenter le panier" est trop large. "Créer le champ name sur le modèle Cart" est trop étroit. Comme découper un steak : ni la pièce entière, ni de la viande hachée. - -## Cas réel - -Une plateforme e-commerce avec 10 fonctionnalités. Deux approches de suivi ont été comparées : - -**Mode mémo** : l'agent utilise des notes non structurées. Après 3 sessions, les notes deviennent "user auth et product list faits, shopping cart presque terminé mais avec bugs, payments pas commencé". La nouvelle session a besoin de 20 minutes pour inférer l'état et finit par réimplémenter des fonctionnalités déjà terminées. Comme une liste de courses qui dit "lait, pain, et ce truc" : au magasin, vous ne savez toujours pas quoi acheter. - -**Mode colonne vertébrale** : chaque fonctionnalité a un état clair et une commande de vérification. La nouvelle session lit la liste et sait en 3 minutes : F01-F05 sont `passing`, F06 est `active`, F07-F10 sont `not_started`. Elle reprend directement à F06, sans retravail. - -Résultat quantifié : les projets utilisant des listes de fonctionnalités structurées affichent un taux d'achèvement 45% supérieur au suivi libre, avec zéro implémentation dupliquée. - -## Points clés - -- **Les listes de fonctionnalités sont la colonne vertébrale du harness**, pas des mémos pour humains. Scheduler, verifier et handoff reporter en dépendent tous. -- **Chaque item doit avoir le triplet** : description de comportement + commande de vérification + état actuel. S'il manque un élément, il est incomplet, comme un tabouret à trois pieds auquel il manque un pied. -- **Les transitions d'état sont contrôlées par le harness** : l'agent ne peut pas changer les états seul. Réussir la vérification est le seul chemin de promotion. -- **La liste de fonctionnalités est la single source of truth du projet** : toutes les informations "quoi faire" dérivent d'une seule liste. -- **Calibrez la granularité sur "terminable en une session".** - -## Pour aller plus loin - -- [Building Effective Agents - Anthropic](https://www.anthropic.com/research/building-effective-agents) — Identifie explicitement la liste de fonctionnalités comme la "core data structure" pour contrôler la portée de l'agent -- [Harness Engineering - OpenAI](https://openai.com/index/harness-engineering/) — Met l'accent sur le principe d'"externalisation des artefacts" -- [Design by Contract - Bertrand Meyer](https://www.goodreads.com/book/show/130439.Object_Oriented_Software_Construction) — Principes de design par contrat, fondation théorique des listes de fonctionnalités -- [How Google Tests Software](https://www.goodreads.com/book/show/13563030-how-google-tests-software) — Pyramide de tests et pratiques d'ingénierie de spécification comportementale - -## Exercices - -1. **Design de liste de fonctionnalités** : définissez un schéma JSON minimal. Incluez : id, description de comportement, commande de vérification, état actuel, référence de preuve. Utilisez-le pour décrire un vrai projet avec 5 fonctionnalités. - -2. **Comparaison de rigueur de vérification** : choisissez 3 fonctionnalités et concevez une vérification "souple" (par exemple "le code n'a pas d'erreurs de syntaxe") et une vérification "stricte" (par exemple "le test end-to-end passe"). Comparez le taux de faux positifs. - -3. **Audit du principe single source** : examinez un projet agentique existant et cherchez les informations de portée qui contredisent la liste de fonctionnalités (exigences implicites dans les conversations, commentaires TODO dans le code, etc.). Concevez un plan pour unifier toute l'information dans la liste. diff --git a/courses/learn-harness-engineering/docs/fr/lectures/lecture-09-why-agents-declare-victory-too-early/code/clean-state-checklist.md b/courses/learn-harness-engineering/docs/fr/lectures/lecture-09-why-agents-declare-victory-too-early/code/clean-state-checklist.md deleted file mode 100644 index 5666124b..00000000 --- a/courses/learn-harness-engineering/docs/fr/lectures/lecture-09-why-agents-declare-victory-too-early/code/clean-state-checklist.md +++ /dev/null @@ -1,6 +0,0 @@ -# Liste de Contrôle d'État Propre - -- L'application démarre sans réparation manuelle -- La progression actuelle est enregistrée -- Aucune étape d'importation ou d'indexation à moitié terminée ne reste non documentée -- Le prochain agent peut exécuter immédiatement le chemin standard de démarrage et de vérification diff --git a/courses/learn-harness-engineering/docs/fr/lectures/lecture-09-why-agents-declare-victory-too-early/code/index.md b/courses/learn-harness-engineering/docs/fr/lectures/lecture-09-why-agents-declare-victory-too-early/code/index.md deleted file mode 100644 index 92e8806b..00000000 --- a/courses/learn-harness-engineering/docs/fr/lectures/lecture-09-why-agents-declare-victory-too-early/code/index.md +++ /dev/null @@ -1,8 +0,0 @@ -# Code du Cours 09 - -Utilisez ce dossier pour des exemples de : - -- logs comme retours -- visibilité de l'état d'exécution -- vérifications d'état propre -- exemples de récupération diff --git a/courses/learn-harness-engineering/docs/fr/lectures/lecture-09-why-agents-declare-victory-too-early/code/victory-detector.ts b/courses/learn-harness-engineering/docs/fr/lectures/lecture-09-why-agents-declare-victory-too-early/code/victory-detector.ts deleted file mode 100644 index fad1a3ba..00000000 --- a/courses/learn-harness-engineering/docs/fr/lectures/lecture-09-why-agents-declare-victory-too-early/code/victory-detector.ts +++ /dev/null @@ -1,241 +0,0 @@ -/** - * victory-detector.ts - * - * Simulates an agent that claims task completion, then checks the claimed - * state against actual verification. Outputs: claimed vs actual, highlighting - * gaps between what the agent said and what's really true. - * - * Run: npx tsx docs/lectures/lecture-09-why-agents-declare-victory-too-early/code/victory-detector.ts - */ - -// --------------------------------------------------------------------------- -// Types -// --------------------------------------------------------------------------- - -interface Task { - name: string; - claimedComplete: boolean; - checks: VerificationCheck[]; -} - -interface VerificationCheck { - description: string; - claimed: boolean; // What the agent says - actual: boolean; // What is actually true - severity: "critical" | "warning"; -} - -// --------------------------------------------------------------------------- -// Simulated tasks with claimed vs actual states -// --------------------------------------------------------------------------- - -const tasks: Task[] = [ - { - name: "Add search endpoint", - claimedComplete: true, - checks: [ - { - description: "Route handler exists", - claimed: true, - actual: true, - severity: "critical", - }, - { - description: "Authentication middleware applied", - claimed: true, - actual: false, // Agent forgot auth - severity: "critical", - }, - { - description: "Input validation added", - claimed: true, - actual: true, - severity: "critical", - }, - { - description: "Unit tests written", - claimed: true, - actual: false, // Agent skipped tests - severity: "critical", - }, - { - description: "Integration tests pass", - claimed: true, - actual: false, // No tests to run - severity: "critical", - }, - { - description: "Documentation updated", - claimed: true, - actual: false, // Agent skipped docs - severity: "warning", - }, - ], - }, - { - name: "Fix pagination bug", - claimedComplete: true, - checks: [ - { - description: "Bug reproduction case confirmed", - claimed: true, - actual: true, - severity: "critical", - }, - { - description: "Root cause identified", - claimed: true, - actual: true, - severity: "critical", - }, - { - description: "Fix applied", - claimed: true, - actual: true, - severity: "critical", - }, - { - description: "Edge cases tested", - claimed: true, - actual: false, // Only happy path tested - severity: "warning", - }, - { - description: "Regression tests added", - claimed: true, - actual: false, - severity: "critical", - }, - ], - }, - { - name: "Refactor auth module", - claimedComplete: true, - checks: [ - { - description: "Old code removed", - claimed: true, - actual: false, // Old code still present - severity: "warning", - }, - { - description: "All existing tests still pass", - claimed: true, - actual: false, // Two tests broke - severity: "critical", - }, - { - description: "New auth flow works", - claimed: true, - actual: true, - severity: "critical", - }, - { - description: "Migration guide written", - claimed: true, - actual: false, - severity: "warning", - }, - ], - }, -]; - -// --------------------------------------------------------------------------- -// Verification -// --------------------------------------------------------------------------- - -interface TaskVerification { - taskName: string; - claimedComplete: boolean; - actuallyComplete: boolean; - totalChecks: number; - claimedPassing: number; - actualPassing: number; - gaps: { description: string; severity: string }[]; -} - -function verifyTask(task: Task): TaskVerification { - const claimedPassing = task.checks.filter((c) => c.claimed).length; - const actualPassing = task.checks.filter((c) => c.actual).length; - const gaps = task.checks.filter((c) => c.claimed && !c.actual); - - return { - taskName: task.name, - claimedComplete: task.claimedComplete, - actuallyComplete: gaps.length === 0, - totalChecks: task.checks.length, - claimedPassing, - actualPassing, - gaps: gaps.map((g) => ({ description: g.description, severity: g.severity })), - }; -} - -// --------------------------------------------------------------------------- -// Reporting -// --------------------------------------------------------------------------- - -function pad(s: string, len: number): string { - return s.length >= len ? s : s + " ".repeat(len - s.length); -} - -function run(): void { - console.log("\n" + "=".repeat(90)); - console.log(" VICTORY DETECTOR -- Claimed vs Actual Verification"); - console.log("=".repeat(90)); - - const verifications = tasks.map(verifyTask); - - for (const v of verifications) { - console.log("\n Task: " + v.taskName); - console.log(" " + "-".repeat(70)); - console.log(` Agent claimed: ${v.claimedComplete ? "COMPLETE" : "NOT COMPLETE"}`); - console.log(` Actually is: ${v.actuallyComplete ? "COMPLETE" : "INCOMPLETE"}`); - - if (!v.actuallyComplete) { - console.log(` MISMATCH: Agent declared victory but ${v.gaps.length} check(s) failed!`); - } - - // Detailed checks for this task - const task = tasks.find((t) => t.name === v.taskName)!; - console.log("\n Check detail:"); - const header = ` | ${pad("Check", 40)}| ${pad("Claimed", 9)}| ${pad("Actual", 9)}| ${pad("Gap?", 6)}|`; - const sep = ` |${"-".repeat(42)}|${"-".repeat(11)}|${"-".repeat(11)}|${"-".repeat(8)}|`; - console.log(header); - console.log(sep); - - for (const check of task.checks) { - const claimedLabel = check.claimed ? "PASS" : "FAIL"; - const actualLabel = check.actual ? "PASS" : "FAIL"; - const gapLabel = check.claimed && !check.actual ? "GAP" : ""; - const marker = gapLabel ? ">>" : " "; - console.log(`${marker}| ${pad(check.description, 40)}| ${pad(claimedLabel, 9)}| ${pad(actualLabel, 9)}| ${pad(gapLabel, 6)}|`); - } - } - - // Overall summary - console.log("\n" + "=".repeat(90)); - console.log(" OVERALL SUMMARY"); - console.log("=".repeat(90) + "\n"); - - const sHeader = `| ${pad("Task", 28)}| ${pad("Claimed", 10)}| ${pad("Actual", 10)}| ${pad("Gaps", 6)}| ${pad("Accuracy", 10)}|`; - const sSep = `|${"-".repeat(30)}|${"-".repeat(12)}|${"-".repeat(12)}|${"-".repeat(8)}|${"-".repeat(12)}|`; - console.log(sHeader); - console.log(sSep); - - for (const v of verifications) { - const accuracy = Math.round((v.actualPassing / v.totalChecks) * 100); - const cLabel = v.claimedComplete ? "Done" : "Pending"; - const aLabel = v.actuallyComplete ? "Done" : "Incomplete"; - console.log(`| ${pad(v.taskName, 28)}| ${pad(cLabel, 10)}| ${pad(aLabel, 10)}| ${pad(String(v.gaps.length), 6)}| ${pad(accuracy + "%", 10)}|`); - } - - const totalGaps = verifications.reduce((s, v) => s + v.gaps.length, 0); - const falseVictories = verifications.filter((v) => v.claimedComplete && !v.actuallyComplete).length; - - console.log("\n False victories: " + falseVictories + " of " + verifications.length + " tasks"); - console.log(" Total undetected gaps: " + totalGaps); - console.log("\n The agent declared 'done' on every task, but verification reveals " + totalGaps + " unmet criteria."); - console.log(" Without explicit verification, premature victory declarations go unchecked.\n"); -} - -run(); diff --git a/courses/learn-harness-engineering/docs/fr/lectures/lecture-09-why-agents-declare-victory-too-early/index.md b/courses/learn-harness-engineering/docs/fr/lectures/lecture-09-why-agents-declare-victory-too-early/index.md deleted file mode 100644 index ba9e7e3a..00000000 --- a/courses/learn-harness-engineering/docs/fr/lectures/lecture-09-why-agents-declare-victory-too-early/index.md +++ /dev/null @@ -1,145 +0,0 @@ -[中文版本 →](../../../zh/lectures/lecture-09-why-agents-declare-victory-too-early/) - -> Code examples for this lecture: [code/](https://github.com/walkinglabs/learn-harness-engineering/blob/main/docs/fr/lectures/lecture-09-why-agents-declare-victory-too-early/code/) -> Hands-on practice: [Project 05. Let the agent verify its own work](./../../projects/project-05-grounded-qa-verification/index.md) - -# Leçon 09. Empêcher les agents de déclarer victoire trop tôt - -Vous demandez à un agent d'implémenter une fonctionnalité de « réinitialisation de mot de passe ». Il modifie le schéma de la base de données, écrit l'endpoint API, ajoute le template d'email, lance les tests unitaires (tous réussis), puis vous annonce avec assurance « c'est fait ». Quand vous essayez réellement de l'exécuter — le lien de réinitialisation ne peut pas être envoyé (configuration du service email manquante), la migration de la base de données échoue à mi-chemin (incohérence du schéma), et le flux de bout en bout n'a jamais été exécuté une seule fois. - -Ce sentiment ne devrait pas vous être étranger — c'est comme remplir entièrement sa copie d'examen, être le premier à la remettre en toute confiance, pour finalement échouer quand les notes arrivent. Ce n'est pas parce que la copie est pleine que les réponses sont justes. - -Ce n'est pas un incident isolé. L'article classique de 2017 à l'ICML de Guo et al. a prouvé : **les réseaux de neurones modernes sont systématiquement trop confiants** — la confiance rapportée par les modèles est significativement plus élevée que leur précision réelle. Il en va de même pour les agents de codage IA : ils « sentent » qu'ils ont terminé, mais en réalité, ils en sont loin. Votre harness doit remplacer les « sentiments » de l'agent par une vérification externalisée, basée sur l'exécution. - -## La pente glissante - -Les déclarations prématurées de complétion suivent presque toujours le même schéma : le code semble correct — la syntaxe est bonne, la logique paraît raisonnable, et l'analyse statique ne montre aucune erreur évidente. Mais le harness n'impose pas de vérification d'exécution complète, donc l'agent skip l'exécution réelle ou ne lance que des tests partiels. Il exécute les tests unitaires mais ignore les tests d'intégration ; il lance les tests mais ne vérifie pas la couverture. Finalement, « le code semble bon » est pris comme preuve que « la fonctionnalité est complète ». Et la copie d'examen est remise. - -De l'information est perdue à chaque étape. Des spécifications de la tâche à l'implémentation du code jusqu'au comportement à l'exécution, chaque transformation peut introduire un biais, et chaque vérification ignorée aggrave l'asymétrie d'information. - -## Vérification d'achèvement en trois couches - -```mermaid -flowchart LR - Claim["Agent says: done"] --> L1["First run
lint / typecheck"] - L1 --> L2["Then run
tests and startup checks"] - L2 --> L3["Finally run
complete user flow"] - L3 --> Done["Pass all three to be done"] -``` - -```mermaid -flowchart LR - A["Code is written
unit tests are green"] --> B["But app didn't really start
full flow never ran"] - B --> C["Config, DB, external service issues
all remain hidden"] - C --> D["So agent declares victory too early"] -``` - -## Concepts clés - -- **Déclaration prématurée de complétion** : L'agent affirme que la tâche est terminée, mais des spécifications de correction non satisfaites existent encore. Le problème central : l'agent juge sur la base d'une confiance locale au niveau du code, alors que la correction au niveau système nécessite une vérification globale. -- **Biais de calibration de confiance** : L'écart systématique entre la confiance auto-rapportée de l'agent quant à l'achèvement et la qualité réelle de cet achèvement. Pour les tâches complexes multi-fichiers, ce biais est significativement positif — l'agent est toujours plus confiant qu'il ne performe réellement. Comme un étudiant qui surestime toujours sa note après un examen. -- **Critères d'achèvement** : Un ensemble clair et exécutable de conditions de jugement définies dans le harness. L'agent doit satisfaire toutes les conditions avant de déclarer l'achèvement. « Terminé » passe d'un jugement subjectif à une détermination objective. -- **Double porte vérification-validation** : La première couche de vérification contrôle « le code a-t-il correctement implémenté le comportement spécifié » ; la seconde couche de validation contrôle « le comportement au niveau système répond-il aux exigences de bout en bout ». Les deux doivent réussir pour que la tâche soit considérée comme complète. -- **Signaux de retour à l'exécution** : Logs, états de processus et vérifications de santé provenant de l'exécution du programme. C'est la base objective sur laquelle le harness juge la qualité de l'achèvement. -- **Contrainte de priorité d'achèvement** : D'abord vérifier la correction fonctionnelle, puis traiter la performance, et enfin s'occuper du style. Le refactoring est interdit tant que la fonctionnalité principale n'est pas vérifiée. - -## Les tests unitaires passent ≠ La tâche est terminée - -C'est le piège le plus courant, et le plus dangereux. L'agent a écrit le code, lancé les tests unitaires, tout est au vert, et dit « terminé ». Mais la philosophie de conception des tests unitaires — isoler l'unité testée et mocker les dépendances — est précisément ce qui les rend incapables de détecter les problèmes inter-composants : - -**Inadéquation d'interface** : Le chemin de fichier passé par le processus de rendu au script preload est un chemin relatif, mais le script preload attend un chemin absolu. Leurs tests unitaires respectifs utilisaient tous des mocks et passaient. Le problème n'est découvert que lors des tests de bout en bout. Comme chaque musicien d'un groupe qui répète parfaitement de son côté, pour se rendre compte qu'ils sont dans des tonalités différentes en jouant ensemble. - -**Erreurs de propagation d'état** : Une migration de base de données modifie le schéma de la table, mais la couche de cache ORM conserve encore des entrées de cache pour l'ancien schéma. Les tests unitaires fournissent un environnement mock frais à chaque fois, ce qui n'exposera pas cette incohérence d'état inter-couches. - -**Dépendance à l'environnement** : Le code se comporte correctement dans l'environnement de test (où tout est mocké) mais échoue dans l'environnement réel en raison de différences de configuration, de latence réseau ou d'indisponibilité de service. Comme chanter parfaitement dans la salle de répétition, mais rencontrer des problèmes d'équipement audio sur scène. - -### « Refactorer pendant qu'on y est » est un poison pour le jugement d'achèvement - -Claude Code présente un schéma de comportement courant : il commence à refactorer le code, optimiser la performance et améliorer le style avant que la fonctionnalité principale n'ait passé la vérification. La citation de Knuth, « L'optimisation prématurée est la racine de tous les maux », prend un nouveau sens dans le scénario de l'agent — le refactoring modifie la frontière entre le code vérifié et non vérifié, risquant de casser des chemins de code précédemment corrects de manière implicite. C'est comme recopier ses réponses à choix multiples pour une meilleure mise en forme avant d'avoir fini les questions de dissertation en mathématiques — non seulement cela perd du temps, mais vous pourriez mal les recopier. - -### Biais systématique dans l'auto-évaluation - -Anthropic a découvert un schéma d'échec plus profond dans ses recherches de 2026 : **quand on demande à un agent d'évaluer son propre travail, il fournit systématiquement des évaluations excessivement positives — même quand un observateur humain considérerait la qualité comme clairement insuffisante.** C'est comme demander à un étudiant de noter sa propre copie — il sera toujours particulièrement indulgent envers ses propres réponses. - -Ce problème est particulièrement sévère dans les tâches subjectives (comme l'esthétique de design) — juger si une « mise en page est exquise » est une question d'appréciation, et l'agent penche systématiquement vers le positif. Même sur des tâches à résultats vérifiables, la performance de l'agent peut être entravée par un mauvais jugement. - -La solution n'est pas de rendre l'agent « plus objectif » — le même modèle générant et évaluant est intrinsèquement enclin à être généreux envers lui-même. **La solution est de séparer le « travailleur » du « vérificateur ».** Comme un étudiant ne devrait pas noter sa propre copie — il faut un correcteur indépendant. - -Un agent d'évaluation indépendant, spécifiquement réglé pour être « pointilleux », est bien plus efficace que de faire s'auto-évaluer l'agent générateur. Données expérimentales d'Anthropic : - -| Architecture | Durée d'exécution | Coût | Fonctionnalités principales fonctionnelles ? | -|--------------|---------|------|------------------------| -| Agent unique (exécution nue) | 20 min | 9 $ | Non (entités du jeu ne réagissent pas aux entrées) | -| Trois agents (planificateur + générateur + évaluateur) | 6 heures | 200 $ | Oui (le jeu est entièrement jouable) | - -C'est exactement le même modèle (Opus 4.5) avec exactement le même prompt (« construire un éditeur de jeu rétro 2D »). La seule différence est le harness — passer d'une « exécution nue » à « planificateur détaille les exigences → générateur implémente fonctionnalité par fonctionnalité → évaluateur effectue des tests de clic réels avec Playwright ». - -> Source : [Anthropic : Harness design for long-running application development](https://www.anthropic.com/engineering/harness-design-long-running-apps) - -## Comment empêcher les remises prématurées - -### 1. Externaliser le jugement d'achèvement - -Le jugement de complétion ne devrait pas être fait par l'agent lui-même. Le harness doit exécuter indépendamment la validation d'achèvement, en utilisant les signaux à l'exécution comme entrée, pas la confiance de l'agent. Écrivez ceci clairement dans `CLAUDE.md` : - -``` -## Definition of Done -- Feature complete = end-to-end verification passed, not "code is written" -- Required verification levels: - 1. Unit tests pass - 2. Integration tests pass - 3. End-to-end flow verification passes -- Do not proceed to level 2 if level 1 fails -- Do not proceed to level 3 if level 2 fails -``` - -### 2. Construire une validation d'achèvement en trois couches - -- **Couche 1 : Syntaxe et analyse statique**. Coût le plus faible, le moins d'information, mais doit passer. C'est la vérification minimale — il faut d'abord bien orthographier les mots avant d'examiner autre chose. -- **Couche 2 : Vérification du comportement à l'exécution**. Exécution des tests, vérifications de démarrage de l'application, validation des chemins critiques. C'est la preuve centrale de l'achèvement. Il ne suffit pas de l'écrire ; il faut que ça tourne. -- **Couche 3 : Confirmation au niveau système**. Tests de bout en bout, validation d'intégration, simulation de scénarios utilisateur. La dernière ligne de défense contre les déclarations prématurées. Il ne suffit pas que ça tourne ; il faut que ça tourne correctement. - -### 3. Concevoir de bonnes « annotations au stylo rouge » pour les agents - -OpenAI a introduit un schéma particulièrement efficace lors de sa pratique Codex : **les messages d'erreur pour les agents doivent inclure des instructions de correction**. Ne vous contentez pas de tracer une grande croix rouge comme un correcteur paresseux ; soyez comme un bon professeur et écrivez en marge « voici comment vous devriez changer cela ». N'utilisez pas `"Test failed"`, mais utilisez `"Test failed: POST /api/reset-password returned 500. Check that the email service config exists in environment variables. The template file should be at templates/reset-email.html."` Ce retour spécifique et actionnable permet à l'agent de s'autocorriger sans intervention humaine. - -### 4. Capturer les signaux à l'exécution - -Les signaux à l'exécution efficaces incluent : -- L'application a-t-elle démarré avec succès et atteint un état prêt ? -- Les chemins de fonctionnalités critiques se sont-ils exécutés avec succès à l'exécution ? -- Les écritures en base de données, opérations sur fichiers et autres effets de bord étaient-ils corrects ? -- Les ressources temporaires ont-elles été nettoyées ? - -## Cas concret - -**Tâche** : Implémenter une fonctionnalité de réinitialisation de mot de passe utilisateur. Implique des opérations en base de données, l'envoi d'emails et des modifications d'endpoints API. - -**Parcours de remise prématurée** : L'agent modifie le schéma de la base de données, écrit l'endpoint API, ajoute le template d'email, lance les tests unitaires (réussis), et déclare la complétion. La copie d'examen est entièrement remplie. - -**Déductions de points réelles** : (1) Le flux de bout en bout n'a pas été testé — l'envoi et la vérification réels du lien de réinitialisation n'ont jamais été confirmés. (2) La migration de la base de données a échoué après une exécution partielle, causant une incohérence du schéma. (3) La configuration du service email manquait dans l'environnement cible. - -**Intervention du harness** : Validation d'achèvement imposée — (1) Démarrer l'application complète pour vérifier l'accessibilité de l'endpoint de réinitialisation ; (2) Exécuter le flux complet de réinitialisation ; (3) Vérifier la cohérence de l'état de la base de données. Tous les défauts ont été trouvés dans la session, permettant d'économiser 5 à 10 fois le coût des corrections ultérieures. Le correcteur indépendant a trouvé les vrais problèmes. - -## Points clés - -- **Les agents sont systématiquement trop confiants** — le biais de calibration de confiance est une réalité objective. Remplir la copie d'examen ne signifie pas avoir les bonnes réponses. -- **Le jugement d'achèvement doit être externalisé** — le harness vérifie indépendamment ; ne vous fiez pas aux « sentiments » de l'agent. Les étudiants ne peuvent pas noter leurs propres copies. -- **Les trois couches de validation sont toutes indispensables** — syntaxe validée, comportement validé, système validé, progressant couche par couche. -- **Les messages d'erreur devraient ressembler aux annotations au stylo rouge d'un bon professeur** — inclure des étapes de correction spécifiques pour que l'agent puisse s'autocorriger. -- **Pas de refactoring tant que la fonctionnalité principale n'est pas vérifiée** — la contrainte de priorité d'achèvement est la clé pour prévenir l'optimisation prématurée. - -## Pour aller plus loin - -- [On Calibration of Modern Neural Networks - Guo et al.](https://arxiv.org/abs/1706.04599) — Prouve que les réseaux profonds modernes sont systématiquement trop confiants -- [Building Effective Agents - Anthropic](https://www.anthropic.com/research/building-effective-agents) — Le rôle critique des preuves à l'exécution dans le jugement d'achèvement -- [Harness Engineering - OpenAI](https://openai.com/index/harness-engineering/) — La déclaration prématurée de complétion est l'un des principaux modes d'échec des agents -- [The Art of Software Testing - Myers](https://www.goodreads.com/book/show/137543.The_Art_of_Software_Testing) — Référence classique sur les hiérarchies de méthodes de test et leur efficacité - -## Exercices - -1. **Conception d'une fonction de validation d'achèvement** : Concevez une validation d'achèvement complète pour une tâche impliquant une migration de base de données et une modification d'API. Listez les signaux à l'exécution requis et les critères de réussite/échec pour chaque signal. Exécutez-la sur une tâche réelle et enregistrez les problèmes cachés qu'elle découvre. - -2. **Mesure du biais de calibration** : Choisissez 10 types différents de tâches de codage, et enregistrez la confiance d'achèvement auto-rapportée par l'agent par rapport à la qualité réelle de l'achèvement. Calculez la valeur du biais et analysez sa relation avec la complexité de la tâche. - -3. **Expérience de défense multi-couches** : Exécutez trois configurations sur le même ensemble de tâches — (a) analyse statique uniquement, (b) ajout des tests unitaires, (c) validation complète en trois couches. Comparez la proportion de déclarations prématurées de complétion et le nombre de défauts non détectés. diff --git a/courses/learn-harness-engineering/docs/fr/lectures/lecture-10-why-end-to-end-testing-changes-results/code/architecture-rules.md b/courses/learn-harness-engineering/docs/fr/lectures/lecture-10-why-end-to-end-testing-changes-results/code/architecture-rules.md deleted file mode 100644 index 5cf6d52d..00000000 --- a/courses/learn-harness-engineering/docs/fr/lectures/lecture-10-why-end-to-end-testing-changes-results/code/architecture-rules.md +++ /dev/null @@ -1,6 +0,0 @@ -# Règles d'Architecture Electron - -- Le code du renderer ne peut pas accéder directement au système de fichiers. -- Le preload est le seul pont entre le renderer et Electron main. -- La logique de récupération et d'indexation vit dans les modules de service, pas dans les composants UI. -- La journalisation doit être structurée et émise depuis les frontières de service. diff --git a/courses/learn-harness-engineering/docs/fr/lectures/lecture-10-why-end-to-end-testing-changes-results/code/e2e-runner.ts b/courses/learn-harness-engineering/docs/fr/lectures/lecture-10-why-end-to-end-testing-changes-results/code/e2e-runner.ts deleted file mode 100644 index 24284165..00000000 --- a/courses/learn-harness-engineering/docs/fr/lectures/lecture-10-why-end-to-end-testing-changes-results/code/e2e-runner.ts +++ /dev/null @@ -1,253 +0,0 @@ -/** - * e2e-runner.ts - * - * A minimal E2E test harness. Defines test cases as user action sequences - * (import doc -> index -> ask question -> verify citation). Simulates - * running them and shows the difference between "unit tests pass" and - * "full pipeline works". - * - * Run: npx tsx docs/lectures/lecture-10-why-end-to-end-testing-changes-results/code/e2e-runner.ts - */ - -// --------------------------------------------------------------------------- -// Types -// --------------------------------------------------------------------------- - -interface PipelineStep { - name: string; - unitTestPasses: boolean; - // Simulated actual behavior in the pipeline - actualBehavior: "works" | "fails" | "partial"; - failureReason?: string; -} - -interface TestCase { - name: string; - steps: PipelineStep[]; -} - -interface TestResult { - testCase: string; - unitTestsPassed: number; - unitTestsTotal: number; - unitTestResult: "PASS" | "FAIL"; - e2eResult: "PASS" | "FAIL"; - e2eFailureStep?: string; - e2eFailureReason?: string; -} - -// --------------------------------------------------------------------------- -// Test cases -- realistic scenarios where unit tests pass but E2E fails -// --------------------------------------------------------------------------- - -const testCases: TestCase[] = [ - { - name: "Import document and ask question", - steps: [ - { - name: "Parse uploaded document", - unitTestPasses: true, - actualBehavior: "works", - }, - { - name: "Store document chunks", - unitTestPasses: true, - actualBehavior: "works", - }, - { - name: "Index chunks for retrieval", - unitTestPasses: true, - actualBehavior: "partial", // Indexes but with wrong embedding dimensions - failureReason: "Embedding dimension mismatch between indexer and retriever", - }, - { - name: "Retrieve relevant chunks", - unitTestPasses: true, // Unit test uses mock data with correct dimensions - actualBehavior: "fails", - failureReason: "Empty results due to dimension mismatch from previous step", - }, - { - name: "Generate answer with citations", - unitTestPasses: true, // Unit test provides pre-retrieved chunks - actualBehavior: "fails", - failureReason: "No chunks retrieved, so answer has no citations", - }, - ], - }, - { - name: "Delete document and verify removal", - steps: [ - { - name: "Find document by ID", - unitTestPasses: true, - actualBehavior: "works", - }, - { - name: "Delete document record", - unitTestPasses: true, - actualBehavior: "works", - }, - { - name: "Remove indexed chunks", - unitTestPasses: true, - actualBehavior: "fails", // Orphaned chunks remain in the index - failureReason: "Index cleanup query timed out, chunks remain orphaned", - }, - { - name: "Verify document not in search results", - unitTestPasses: true, // Unit test mocks the search - actualBehavior: "fails", - failureReason: "Orphaned chunks from previous step still appear in results", - }, - ], - }, - { - name: "Multi-user concurrent access", - steps: [ - { - name: "User A imports document", - unitTestPasses: true, - actualBehavior: "works", - }, - { - name: "User B imports document", - unitTestPasses: true, - actualBehavior: "works", - }, - { - name: "User A queries their document", - unitTestPasses: true, - actualBehavior: "partial", // Cross-contamination of results - failureReason: "No user-scoping on retrieval, returns chunks from User B's doc", - }, - { - name: "Verify only User A's results returned", - unitTestPasses: true, - actualBehavior: "fails", - failureReason: "Results include documents from other users", - }, - ], - }, -]; - -// --------------------------------------------------------------------------- -// Run tests -// --------------------------------------------------------------------------- - -function runUnitTests(tc: TestCase): { passed: number; total: number } { - const passed = tc.steps.filter((s) => s.unitTestPasses).length; - return { passed, total: tc.steps.length }; -} - -function runE2ETest(tc: TestCase): { pass: boolean; failureStep?: string; failureReason?: string } { - // Pipeline: if any step actually fails, the whole E2E fails - for (const step of tc.steps) { - if (step.actualBehavior === "fails") { - return { - pass: false, - failureStep: step.name, - failureReason: step.failureReason ?? "Unknown failure", - }; - } - // "partial" means the step technically completes but creates problems downstream - // We let it continue but track it - } - // Check if any step was "partial" (which may cause downstream issues) - const partialSteps = tc.steps.filter((s) => s.actualBehavior === "partial"); - if (partialSteps.length > 0) { - // The partial steps may or may not cause overall failure - // In our simulation, partial steps always lead to failure downstream - // unless there's an explicit "fails" step that already caught it - // This case means all steps were either "works" or "partial" - return { - pass: false, - failureStep: partialSteps[partialSteps.length - 1].name, - failureReason: partialSteps[partialSteps.length - 1].failureReason ?? "Partial completion", - }; - } - - return { pass: true }; -} - -// --------------------------------------------------------------------------- -// Reporting -// --------------------------------------------------------------------------- - -function pad(s: string, len: number): string { - return s.length >= len ? s : s + " ".repeat(len - s.length); -} - -function run(): void { - console.log("\n" + "=".repeat(95)); - console.log(" E2E TEST RUNNER -- Unit Tests vs Full Pipeline"); - console.log("=".repeat(95)); - - const results: TestResult[] = testCases.map((tc) => { - const unit = runUnitTests(tc); - const e2e = runE2ETest(tc); - - return { - testCase: tc.name, - unitTestsPassed: unit.passed, - unitTestsTotal: unit.total, - unitTestResult: unit.passed === unit.total ? "PASS" : "FAIL", - e2eResult: e2e.pass ? "PASS" : "FAIL", - e2eFailureStep: e2e.failureStep, - e2eFailureReason: e2e.failureReason, - }; - }); - - // Per-test-case detail - for (let i = 0; i < testCases.length; i++) { - const tc = testCases[i]; - const r = results[i]; - - console.log("\n Test Case: " + tc.name); - console.log(" " + "-".repeat(70)); - - const stepHeader = ` | ${pad("Step", 40)}| ${pad("Unit Test", 11)}| ${pad("E2E Actual", 12)}|`; - const stepSep = ` |${"-".repeat(42)}|${"-".repeat(13)}|${"-".repeat(14)}|`; - console.log(stepHeader); - console.log(stepSep); - - for (const step of tc.steps) { - const utLabel = step.unitTestPasses ? "PASS" : "FAIL"; - let e2eLabel: string; - if (step.actualBehavior === "works") e2eLabel = "PASS"; - else if (step.actualBehavior === "partial") e2eLabel = "PARTIAL*"; - else e2eLabel = "FAIL"; - - const marker = step.actualBehavior !== "works" ? ">>" : " "; - console.log(`${marker}| ${pad(step.name, 40)}| ${pad(utLabel, 11)}| ${pad(e2eLabel, 12)}|`); - } - - if (r.e2eFailureStep) { - console.log(`\n E2E Failure at: ${r.e2eFailureStep}`); - console.log(` Reason: ${r.e2eFailureReason}`); - } - } - - // Summary comparison - console.log("\n" + "=".repeat(95)); - console.log(" COMPARISON: Unit Tests vs E2E Tests"); - console.log("=".repeat(95) + "\n"); - - const header = `| ${pad("Test Case", 35)}| ${pad("Unit Tests", 15)}| ${pad("E2E Result", 12)}| Discrepancy`; - const sep = `|${"-".repeat(37)}|${"-".repeat(17)}|${"-".repeat(14)}|${"-".repeat(30)}`; - console.log(header); - console.log(sep); - - for (const r of results) { - const utLabel = `${r.unitTestsPassed}/${r.unitTestsTotal} ${r.unitTestResult}`; - const discrepancy = r.unitTestResult === "PASS" && r.e2eResult === "FAIL"; - const discLabel = discrepancy ? "UNIT PASS BUT E2E FAIL" : "Consistent"; - - console.log(`| ${pad(r.testCase, 35)}| ${pad(utLabel, 15)}| ${pad(r.e2eResult, 12)}| ${discLabel}`); - } - - const falseConfidence = results.filter((r) => r.unitTestResult === "PASS" && r.e2eResult === "FAIL").length; - console.log("\n False confidence count: " + falseConfidence + " of " + results.length); - console.log(" Unit tests pass but E2E reveals integration failures that units cannot catch.\n"); -} - -run(); diff --git a/courses/learn-harness-engineering/docs/fr/lectures/lecture-10-why-end-to-end-testing-changes-results/code/index.md b/courses/learn-harness-engineering/docs/fr/lectures/lecture-10-why-end-to-end-testing-changes-results/code/index.md deleted file mode 100644 index b832238c..00000000 --- a/courses/learn-harness-engineering/docs/fr/lectures/lecture-10-why-end-to-end-testing-changes-results/code/index.md +++ /dev/null @@ -1,8 +0,0 @@ -# Code du Cours 10 - -Utilisez ce dossier pour des exemples de : - -- contraintes d'architecture -- tests structurels -- invariants de goût (taste invariants) -- messages lint orientés remédiation diff --git a/courses/learn-harness-engineering/docs/fr/lectures/lecture-10-why-end-to-end-testing-changes-results/code/review-feedback-to-rule.md b/courses/learn-harness-engineering/docs/fr/lectures/lecture-10-why-end-to-end-testing-changes-results/code/review-feedback-to-rule.md deleted file mode 100644 index 2b29b9ae..00000000 --- a/courses/learn-harness-engineering/docs/fr/lectures/lecture-10-why-end-to-end-testing-changes-results/code/review-feedback-to-rule.md +++ /dev/null @@ -1,10 +0,0 @@ -# Exemple : Transformer un Retour de Review en Règle - -Commentaire de review répété : - -> N'utilisez pas les utilitaires du système de fichiers depuis le renderer. Utilisez le pont preload. - -Règle de harness promue : - -- ajouter une règle lint ou d'import empêchant l'utilisation de `fs` dans le code du renderer -- ajouter un texte de remédiation expliquant la frontière du preload diff --git a/courses/learn-harness-engineering/docs/fr/lectures/lecture-10-why-end-to-end-testing-changes-results/index.md b/courses/learn-harness-engineering/docs/fr/lectures/lecture-10-why-end-to-end-testing-changes-results/index.md deleted file mode 100644 index bb546a2d..00000000 --- a/courses/learn-harness-engineering/docs/fr/lectures/lecture-10-why-end-to-end-testing-changes-results/index.md +++ /dev/null @@ -1,158 +0,0 @@ -[中文版本 →](../../../zh/lectures/lecture-10-why-end-to-end-testing-changes-results/) - -> Code examples for this lecture: [code/](https://github.com/walkinglabs/learn-harness-engineering/blob/main/docs/fr/lectures/lecture-10-why-end-to-end-testing-changes-results/code/) -> Hands-on practice: [Project 05. Let the agent verify its own work](./../../projects/project-05-grounded-qa-verification/index.md) - -# Leçon 10. Seul le test end-to-end est une vraie vérification - -Vous demandez à l'agent d'ajouter une fonctionnalité d'export de fichier à une application Electron. Il écrit le composant du processus de rendu, le script preload et la logique de la couche service. Les tests unitaires de chaque composant passent parfaitement. L'agent dit : « C'est fait. » Quand vous cliquez réellement sur le bouton d'export — le format du chemin de fichier est incorrect, la barre de progression ne se met pas à jour, et l'export de gros fichiers provoque une fuite de mémoire. Cinq défauts aux frontières des composants, et les tests unitaires n'en ont attrapé aucun. - -C'est comme une répétition de chœur — chaque pupitre sonne parfaitement chanté individuellement, mais quand ils chantent ensemble, les sopranes sont un demi-temps plus rapides que les basses, et l'accompagnement est un demi-ton décalé par rapport à la mélodie principale. Chaque partie est « correcte » individuellement, mais l'ensemble est faux. - -La pyramide de tests de Google nous dit : un grand nombre de tests unitaires constituent la base, mais si vous vous arrêtez là, vous allez systématiquement manquer les problèmes d'interaction entre composants. Pour les agents de codage IA, ce problème est encore plus grave — les agents ont tendance à ne lancer que les tests les plus rapides puis à déclarer la complétion. **Seul le test de bout en bout peut prouver que les défauts au niveau système n'existent pas.** - -## Les angles morts des tests unitaires - -La philosophie de conception des tests unitaires est l'isolation — mocker les dépendances et se concentrer uniquement sur l'unité testée. Cette philosophie rend les tests unitaires rapides et précis, mais crée aussi des angles morts systématiques. C'est comme si chaque pupitre répétait avec un casque lors d'une répétition de chœur — chacun s'entend très bien, mais les problèmes n'apparaissent que lorsqu'ils se rassemblent : - -**Inadéquation d'interface** : Le chemin de fichier passé par le processus de rendu au script preload est un chemin relatif, mais le script preload attend un chemin absolu. Leurs tests unitaires respectifs utilisaient tous des mocks et passaient. Le problème n'est découvert que lorsque le flux de bout en bout est exécuté — comme deux pupitres répétaient indépendamment et se sentaient bien, pour se rendre compte à l'ensemble que l'un chantait en 4/4 et l'autre en 3/4. - -**Erreurs de propagation d'état** : Une migration de base de données modifie le schéma de la table, mais la couche de cache ORM conserve encore des entrées de cache pour l'ancien schéma. Les tests unitaires fournissent un environnement mock entièrement nouveau à chaque fois, ce qui n'exposera pas cette incohérence d'état inter-couches. C'est comme changer les paroles d'une chanson, mais quelqu'un chante encore l'ancienne version. - -**Problèmes de cycle de vie des ressources** : L'acquisition et la libération des handles de fichiers, des connexions à la base de données et des sockets réseau s'étendent sur plusieurs composants. Les tests unitaires créent et détruisent des ressources indépendantes pour chaque test, ne parvenant pas à exposer les contentions ou les fuites de ressources. C'est comme chaque pupitre utilisant les micros à tour de rôle pendant la répétition, mais quand tout le monde monte sur scène ensemble, il n'y a pas assez de micros. - -**Dépendance à l'environnement** : Le code se comporte correctement dans l'environnement de test (où tout est mocké) mais échoue dans l'environnement réel en raison de différences de configuration, de latence réseau ou d'indisponibilité de service. Comme chanter parfaitement dans la salle de répétition, mais rencontrer des larsens et des interférences de vent lors d'un festival en plein air. - -## Le test end-to-end ne change pas seulement les résultats, il change le comportement - -C'est quelque chose que beaucoup de gens ne réalisent pas : quand un agent sait que son travail sera soumis à des tests de bout en bout, son comportement de codage change. - -1. **Prise en compte des interactions entre composants** : En écrivant le code, il pensera à « comment cette interface se connecte avec l'amont », plutôt que de se concentrer uniquement sur une seule fonction. Comme savoir qu'on chantera finalement ensemble, on fera attention aux autres pupitres pendant la répétition. -2. **Respect des frontières architecturales** : Dans les systèmes avec des contraintes architecturales, le test de bout en bout force l'agent à respecter les règles de frontière. Comme une partition marquée « crescendo ici », il faut la suivre. -3. **Gestion des chemins d'erreur** : Les tests de bout en bout incluent généralement des scénarios d'échec, forçant l'agent à considérer le traitement des exceptions. C'est comme simuler « et si le micro tombe en panne soudainement » pendant la répétition, pour savoir quoi faire. - -## Pyramide de tests et promotion du retour de revue - -```mermaid -flowchart TB - subgraph Unit["Unit tests only check isolated parts"] - U1["Renderer tests"] - U2["Preload tests"] - U3["Service tests"] - end - - subgraph E2E["E2E runs through the real system"] - R["Click renderer button"] --> P["Preload bridge"] - P --> S["Service layer"] - S --> F["File System / OS"] - F --> Result["Actual exported file"] - end -``` - -```mermaid -flowchart LR - Review["Review feedback:
renderer cannot import fs directly"] --> Rule["Add a direct fs import check"] - Rule --> Message["Tell agent in error message
to move file access to preload"] - Message --> Harness["Add this check to harness"] - Harness --> Stronger["Will fail immediately next time"] -``` - -Dans les pratiques d'ingénierie Codex, OpenAI souligne : **les messages d'erreur écrits pour les agents doivent inclure des instructions de correction.** Ne vous contentez pas d'écrire `"Direct filesystem access in renderer"` ; écrivez `"Direct filesystem access in renderer. All file operations must go through the preload bridge. Move this call to preload/file-ops.ts and invoke it via window.api."` Cela transforme les règles architecturales en une boucle d'auto-correction. Comme un chef de chœur qui ne dit pas juste « tu as mal chanté ça », mais dit plutôt « tu étais un demi-temps trop rapide ici, écoute le rythme des altis, et entre à la mesure 32. » - -## Concepts clés - -- **Défauts aux frontières des composants** : Les composants A et B passent tous deux leurs tests unitaires, mais leur interaction produit un comportement incorrect. C'est le type de problème que le test de bout en bout est le meilleur pour détecter — comme des pupitres de chœur qui sont individuellement corrects mais faux ensemble. -- **Gradient d'adéquation des tests** : Défauts détectés par les tests unitaires <= défauts détectés par les tests d'intégration <= défauts détectés par les tests de bout en bout. Chaque couche supérieure augmente la capacité de détection. -- **Règles d'application des frontières architecturales** : Transformer les règles des documents d'architecture (comme « le processus de rendu ne peut pas accéder directement au système de fichiers ») en vérifications exécutables et automatisées. Passer d'« écrit sur papier » à « exécuté dans le CI ». -- **Promotion du retour de revue** : Convertir les commentaires récurrents de revue de code en tests automatisés. Chaque fois qu'un problème récurrent est découvert, ajoutez une règle, et le harness se renforce automatiquement. Comme un chef de chœur qui transforme les erreurs courantes de répétition en exercices d'échauffement — la prochaine fois que la même erreur est commise, l'exercice lui-même l'expose sans que le chef ait besoin de dire quoi que ce soit. -- **Messages d'erreur orientés agent** : Les messages d'échec ne devraient pas seulement indiquer « ce qui n'a pas fonctionné », mais aussi dire à l'agent exactement comment le corriger. Cela transforme les échecs de test en boucles de retour auto-correctives. - -## Comment le faire - -### 0. Définir les frontières architecturales d'abord, puis écrire les tests E2E - -Le prérequis pour le test de bout en bout est d'avoir des frontières système claires. Si l'architecture est une assiette de spaghettis, le test de bout en bout ne prouvera que « cette assiette de spaghettis fonctionne », il ne vous dira pas où les intentions de design ont été violées. C'est comme un chœur qui n'a même pas été divisé en pupitres — aucune quantité de répétition ne le fera sonner correctement. - -L'expérience d'OpenAI : **pour les bases de code générées par des agents, les contraintes architecturales doivent être des prérequis précoces établis dès le premier jour, et non quelque chose à considérer quand l'équipe grandit.** La raison est simple — les agents copient les motifs existants dans le dépôt, même si ces motifs sont inégaux ou sous-optimaux. Sans contraintes architecturales, l'agent introduira plus de déviations à chaque session. - -OpenAI a adopté une « architecture en couches par domaine » — chaque domaine métier est divisé en couches fixes : Types → Config → Repo → Service → Runtime → UI. Les dépendances circulent strictement vers l'avant, et les préoccupations inter-domaines entrent par des interfaces Providers explicites. Toute autre dépendance est interdite et imposée mécaniquement via du linting personnalisé. - -Principe clé : **Appliquer les invariants, ne pas micro-gérer l'implémentation.** Par exemple, exiger « les données sont analysées à la frontière », mais ne pas dicter quelle bibliothèque utiliser. Les messages d'erreur doivent inclure des instructions de correction — ne pas juste dire « violation », mais dire à l'agent exactement comment le changer. - -> Source : [OpenAI : Harness engineering : leveraging Codex in an agent-first world](https://openai.com/index/harness-engineering/) - -### 1. Le harness doit inclure une couche end-to-end - -Rendez-le explicite dans votre flux de validation : pour les tâches impliquant des modifications inter-composants, réussir les tests de bout en bout est un prérequis à la complétion : - -``` -## Validation Hierarchy -- Level 1: Unit tests (Must pass) -- Level 2: Integration tests (Must pass) -- Level 3: End-to-end tests (Must pass when cross-component changes are involved) -- Skipping any required level = Not Complete -``` - -### 2. Transformer les règles architecturales en vérifications exécutables - -Chaque contrainte architecturale devrait avoir un test ou une règle de lint correspondante : - -```bash -# Check if the render process directly calls Node.js APIs -grep -r "require('fs')" src/renderer/ && exit 1 || echo "OK: no direct fs access in renderer" -``` - -### 3. Concevoir des messages d'erreur orientés agent - -Les messages d'échec devraient contenir trois éléments : ce qui n'a pas fonctionné, pourquoi, et comment le corriger : - -``` -ERROR: Found direct import of 'fs' in src/renderer/App.tsx:12 -WHY: Renderer process has no access to Node.js APIs for security -FIX: Move file operations to src/preload/file-ops.ts and call via window.api.readFile() -``` - -### 4. Établir un processus de promotion du retour de revue - -Chaque fois qu'un nouveau type d'erreur d'agent est découvert lors d'une revue de code, transformez-le en vérification automatisée. Un mois plus tard, votre harness sera significativement plus fort qu'au début du mois. C'est comme les notes de répétition d'un chœur — enregistrer les problèmes trouvés à chaque répétition pour pouvoir les vérifier avant la suivante. Avec le temps, les erreurs courantes diminuent, et la musique devient plus harmonieuse. - -## Cas concret - -**Tâche** : Implémenter une fonctionnalité d'export de fichier dans une application Electron. Implique l'UI du processus de rendu, le proxy de système de fichiers du script preload et la transformation de données de la couche service. - -**Chanter les parties individuellement (tests unitaires réussis)** : Tests du composant de rendu (réussis, opérations sur fichiers mockées), tests du script preload (réussis, système de fichiers mocké), tests de la couche service (réussis, source de données mockée). L'agent déclare la complétion. - -**Chanter ensemble (défauts révélés par les tests end-to-end)** : - -| Défaut | Description | Test unitaire | E2E | -|--------|-------------|---------------|-----| -| Inadéquation d'interface | Format de chemin de fichier incohérent | Manqué | Détecté | -| Propagation d'état | La progression de l'export n'est pas renvoyée à l'UI via IPC | Manqué | Détecté | -| Fuite de ressources | Handles d'export de gros fichiers non libérés | Manqué | Détecté | -| Problème de permissions | Permissions différentes dans l'environnement packaging | Manqué | Détecté | -| Propagation d'erreur | Les exceptions de la couche service n'atteignaient pas la couche UI | Manqué | Détecté | - -Les 5 défauts ont été détectés par les tests de bout en bout, tandis que les tests unitaires n'en ont détecté aucun. Le coût a été une augmentation du temps de test de 2 secondes à 15 secondes — tout à fait acceptable dans un flux de travail d'agent. Aussi bien que chaque partie chante individuellement, ça ne remplace pas une répétition générale complète. - -## Points clés - -- **Les tests unitaires sont systématiquement aveugles aux défauts aux frontières des composants** — leur conception par isolation est précisément ce qui les empêche de détecter les problèmes d'interaction. Que chacun chante correctement ne signifie pas que le chœur n'est pas faux. -- **Le test end-to-end ne détecte pas seulement les défauts, il change le comportement de codage de l'agent** — le faisant se concentrer davantage sur l'intégration et les frontières. -- **Les règles architecturales doivent être exécutables** — non écrites dans un document en attente d'être lu, mais automatiquement vérifiées à chaque commit. -- **Les messages d'erreur doivent être conçus pour les agents** — incluant des étapes spécifiques sur « comment le corriger » pour former une boucle auto-corrective. -- **La promotion du retour de revue renforce automatiquement le harness** — chaque catégorie de défaut capturée devient une ligne de défense permanente. - -## Pour aller plus loin - -- [How Google Tests Software - Whittaker et al.](https://www.goodreads.com/book/show/13563030-how-google-tests-software) — La source classique du modèle de pyramide de tests -- [Harness Engineering - OpenAI](https://openai.com/index/harness-engineering/) — Pratiques d'ingénierie pour l'exécution automatisée des contraintes architecturales -- [Chaos Engineering - Netflix (Basiri et al.)](https://ieeexplore.ieee.org/document/7466237) — Injection proactive de pannes pour vérifier la résilience du système -- [QuickCheck - Claessen & Hughes](https://www.cs.tufts.edu/~nr/cs257/archive/john-hughes/quick.pdf) — Méthodologie de test par propriétés, entre le test par l'exemple et la vérification formelle - -## Exercices - -1. **Détection de défauts inter-composants** : Choisissez une tâche de modification impliquant au moins trois composants. D'abord, lancez uniquement les tests unitaires et enregistrez les résultats, puis lancez les tests de bout en bout. Analysez à quel type de problème d'interaction inter-couches appartient chaque défaut supplémentaire découvert. - -2. **Automatisation d'une règle architecturale** : Choisissez une contrainte architecturale de votre projet et transformez-la en vérification exécutable (avec un message d'erreur orienté agent). Intégrez-la dans le harness et vérifiez son efficacité avec une tâche de référence. - -3. **Promotion du retour de revue** : Trouvez un type de commentaire récurrent dans votre historique de revue de code et convertissez-le en vérification automatisée en utilisant le processus en cinq étapes. Comparez la fréquence du problème avant et après la promotion. diff --git a/courses/learn-harness-engineering/docs/fr/lectures/lecture-11-why-observability-belongs-inside-the-harness/code/evaluator-rubric.md b/courses/learn-harness-engineering/docs/fr/lectures/lecture-11-why-observability-belongs-inside-the-harness/code/evaluator-rubric.md deleted file mode 100644 index 45864a58..00000000 --- a/courses/learn-harness-engineering/docs/fr/lectures/lecture-11-why-observability-belongs-inside-the-harness/code/evaluator-rubric.md +++ /dev/null @@ -1,8 +0,0 @@ -# Exemple de Rubrique d'Évaluation - -Utilisez une notation de 1 à 5 pour chaque dimension : - -- Fondement : les réponses sont-elles clairement liées aux sources importées ? -- Qualité de citation : les références aux sources sont-elles visibles et spécifiques ? -- Fonctionnalité : l'utilisateur peut-il compléter le flux question-réponse ? -- Cohérence produit : le flux de travail semble-t-il intégré ? diff --git a/courses/learn-harness-engineering/docs/fr/lectures/lecture-11-why-observability-belongs-inside-the-harness/code/index.md b/courses/learn-harness-engineering/docs/fr/lectures/lecture-11-why-observability-belongs-inside-the-harness/code/index.md deleted file mode 100644 index 10b79cce..00000000 --- a/courses/learn-harness-engineering/docs/fr/lectures/lecture-11-why-observability-belongs-inside-the-harness/code/index.md +++ /dev/null @@ -1,8 +0,0 @@ -# Code du Cours 11 - -Utilisez ce dossier pour des exemples de : - -- sorties du planificateur -- rubriques d'évaluation -- boucles générateur/évaluateur -- comparaisons agent unique vs multi-rôles diff --git a/courses/learn-harness-engineering/docs/fr/lectures/lecture-11-why-observability-belongs-inside-the-harness/code/runtime-logger.ts b/courses/learn-harness-engineering/docs/fr/lectures/lecture-11-why-observability-belongs-inside-the-harness/code/runtime-logger.ts deleted file mode 100644 index 213dc198..00000000 --- a/courses/learn-harness-engineering/docs/fr/lectures/lecture-11-why-observability-belongs-inside-the-harness/code/runtime-logger.ts +++ /dev/null @@ -1,260 +0,0 @@ -/** - * runtime-logger.ts - * - * A structured logging module demo. Shows ad-hoc console.log output vs - * structured JSON log output when diagnosing a failure. Includes a seeded - * failure scenario and demonstrates how structured logs pinpoint the issue - * faster. - * - * Run: npx tsx docs/lectures/lecture-11-why-observability-belongs-inside-the-harness/code/runtime-logger.ts - */ - -// --------------------------------------------------------------------------- -// Types -// --------------------------------------------------------------------------- - -interface StructuredLogEntry { - timestamp: string; - level: "info" | "warn" | "error" | "debug"; - component: string; - action: string; - durationMs?: number; - input?: unknown; - output?: unknown; - error?: string; - correlationId: string; -} - -// --------------------------------------------------------------------------- -// Simulated pipeline with a seeded failure -// --------------------------------------------------------------------------- - -const CORRELATION_ID = "req-" + Math.random().toString(36).slice(2, 8); - -// Simulated stages of a document Q&A pipeline -interface PipelineStage { - component: string; - action: string; - durationMs: number; - success: boolean; - errorMessage?: string; - input?: unknown; - output?: unknown; -} - -function runPipeline(): PipelineStage[] { - return [ - { - component: "DocumentLoader", - action: "parse_upload", - durationMs: 45, - success: true, - input: { filename: "report.pdf", size: "2.3MB" }, - output: { chunks: 47 }, - }, - { - component: "ChunkIndexer", - action: "embed_and_store", - durationMs: 230, - success: true, - input: { chunks: 47 }, - output: { indexed: 47, embeddingDim: 1536 }, - }, - { - component: "QueryRouter", - action: "route_query", - durationMs: 12, - success: true, - input: { query: "What is the revenue target?" }, - output: { routedTo: "RetrievalEngine" }, - }, - { - // SEEDED FAILURE: Retrieval returns 0 results due to dimension mismatch - component: "RetrievalEngine", - action: "semantic_search", - durationMs: 180, - success: false, - errorMessage: "Vector dimension mismatch: query embedding dim=768, index embedding dim=1536", - input: { query: "What is the revenue target?", topK: 5 }, - output: { results: 0 }, - }, - { - component: "AnswerGenerator", - action: "generate_with_citations", - durationMs: 1500, - success: true, // Doesn't crash, but produces a bad answer - input: { context: [], query: "What is the revenue target?" }, - output: { answer: "I could not find relevant information.", citations: 0 }, - }, - ]; -} - -// --------------------------------------------------------------------------- -// Ad-hoc logging (console.log style) -// --------------------------------------------------------------------------- - -function printAdHocLog(stages: PipelineStage[]): void { - console.log("Starting document Q&A pipeline..."); - console.log("User uploaded report.pdf"); - - for (const stage of stages) { - if (stage.success) { - console.log(`${stage.component}: ${stage.action} done (${stage.durationMs}ms)`); - } else { - console.log(`${stage.component}: something went wrong`); - } - } - - console.log("Pipeline finished. Answer: I could not find relevant information."); -} - -// --------------------------------------------------------------------------- -// Structured logging (JSON) -// --------------------------------------------------------------------------- - -function printStructuredLog(stages: PipelineStage[]): StructuredLogEntry[] { - const entries: StructuredLogEntry[] = []; - - for (const stage of stages) { - entries.push({ - timestamp: new Date().toISOString(), - level: stage.success ? "info" : "error", - component: stage.component, - action: stage.action, - durationMs: stage.durationMs, - input: stage.input, - output: stage.output, - error: stage.errorMessage, - correlationId: CORRELATION_ID, - }); - } - - return entries; -} - -// --------------------------------------------------------------------------- -// Diagnose failure from structured logs -// --------------------------------------------------------------------------- - -function diagnoseFromStructured(entries: StructuredLogEntry[]): string[] { - const diagnosis: string[] = []; - - // Find errors - const errors = entries.filter((e) => e.level === "error"); - if (errors.length > 0) { - diagnosis.push("ERRORS FOUND:"); - for (const err of errors) { - diagnosis.push(` - ${err.component}.${err.action}: ${err.error}`); - } - } - - // Check for latency spikes - const slowStages = entries.filter((e) => (e.durationMs ?? 0) > 1000); - if (slowStages.length > 0) { - diagnosis.push("LATENCY SPIKES:"); - for (const s of slowStages) { - diagnosis.push(` - ${s.component}.${s.action}: ${s.durationMs}ms`); - } - } - - // Check for cascading failures - const emptyOutputs = entries.filter((e) => { - const out = e.output as Record | undefined; - return out && typeof out === "object" && "results" in out && (out.results as number) === 0; - }); - if (emptyOutputs.length > 0) { - diagnosis.push("EMPTY OUTPUTS (possible cascade):"); - for (const e of emptyOutputs) { - diagnosis.push(` - ${e.component}.${e.action}: returned 0 results`); - } - } - - return diagnosis; -} - -// --------------------------------------------------------------------------- -// Reporting -// --------------------------------------------------------------------------- - -function pad(s: string, len: number): string { - return s.length >= len ? s : s + " ".repeat(len - s.length); -} - -function run(): void { - const pipeline = runPipeline(); - - console.log("\n" + "=".repeat(90)); - console.log(" OBSERVABILITY DEMO -- Ad-hoc vs Structured Logging"); - console.log("=".repeat(90)); - - // --- Ad-hoc output --- - console.log("\n" + "-".repeat(90)); - console.log(" SCENARIO A: Ad-hoc console.log output"); - console.log("-".repeat(90) + "\n"); - printAdHocLog(pipeline); - console.log("\n Diagnosis from ad-hoc logs: ??? Hard to tell what went wrong."); - console.log(" The failure message is vague: 'something went wrong'."); - console.log(" No dimensions, no input/output data, no correlation ID."); - - // --- Structured output --- - console.log("\n" + "-".repeat(90)); - console.log(" SCENARIO B: Structured JSON log output"); - console.log("-".repeat(90) + "\n"); - - const structuredEntries = printStructuredLog(pipeline); - - const header = `| ${pad("Timestamp", 26)}| ${pad("Level", 6)}| ${pad("Component", 20)}| ${pad("Action", 25)}| ${pad("Duration", 9)}| Error?`; - const sep = `|${"-".repeat(28)}|${"-".repeat(8)}|${"-".repeat(22)}|${"-".repeat(27)}|${"-".repeat(11)}|${"-".repeat(30)}`; - console.log(header); - console.log(sep); - - for (const entry of structuredEntries) { - const hasError = entry.error ? entry.error.slice(0, 30) : ""; - const marker = entry.level === "error" ? ">>" : " "; - console.log( - `${marker}| ${pad(entry.timestamp, 26)}| ${pad(entry.level, 6)}| ${pad(entry.component, 20)}| ${pad(entry.action, 25)}| ${pad(String(entry.durationMs) + "ms", 9)}| ${hasError}` - ); - } - - // --- Diagnosis --- - console.log("\n" + "-".repeat(90)); - console.log(" AUTOMATED DIAGNOSIS FROM STRUCTURED LOGS"); - console.log("-".repeat(90) + "\n"); - - const errors = structuredEntries.filter((e) => e.level === "error"); - for (const err of errors) { - console.log(` ROOT CAUSE: ${err.component}.${err.action}`); - console.log(` Error: ${err.error}`); - console.log(` Input: ${JSON.stringify(err.input)}`); - console.log(` Output: ${JSON.stringify(err.output)}`); - console.log(` Correlation ID: ${err.correlationId}`); - } - - // Downstream impact - console.log("\n DOWNSTREAM IMPACT:"); - const answerGen = structuredEntries.find((e) => e.component === "AnswerGenerator"); - if (answerGen) { - const out = answerGen.output as Record; - console.log(` AnswerGenerator received empty context (${JSON.stringify(answerGen.input)})`); - console.log(` Produced answer: "${out.answer}" with ${out.citations} citations`); - } - - // Comparison summary - console.log("\n" + "=".repeat(90)); - console.log(" COMPARISON"); - console.log("=".repeat(90) + "\n"); - - const cHeader = `| ${pad("Metric", 35)}| ${pad("Ad-hoc Logs", 18)}| ${pad("Structured Logs", 18)}|`; - const cSep = `|${"-".repeat(37)}|${"-".repeat(20)}|${"-".repeat(20)}|`; - console.log(cHeader); - console.log(cSep); - console.log(`| ${pad("Root cause identifiable", 35)}| ${pad("No", 18)}| ${pad("Yes", 18)}|`); - console.log(`| ${pad("Input/output traceable", 35)}| ${pad("No", 18)}| ${pad("Yes", 18)}|`); - console.log(`| ${pad("Correlation across steps", 35)}| ${pad("No", 18)}| ${pad("Yes", 18)}|`); - console.log(`| ${pad("Machine-parseable", 35)}| ${pad("No", 18)}| ${pad("Yes", 18)}|`); - console.log(`| ${pad("Time to diagnose", 35)}| ${pad("Minutes (manual)", 18)}| ${pad("Seconds (auto)", 18)}|`); - - console.log("\n Structured logging transforms debugging from guesswork into a deterministic lookup.\n"); -} - -run(); diff --git a/courses/learn-harness-engineering/docs/fr/lectures/lecture-11-why-observability-belongs-inside-the-harness/code/sprint-contract.md b/courses/learn-harness-engineering/docs/fr/lectures/lecture-11-why-observability-belongs-inside-the-harness/code/sprint-contract.md deleted file mode 100644 index 5d7d73fa..00000000 --- a/courses/learn-harness-engineering/docs/fr/lectures/lecture-11-why-observability-belongs-inside-the-harness/code/sprint-contract.md +++ /dev/null @@ -1,12 +0,0 @@ -# Exemple de Contrat de Sprint - -Objectif du sprint : - -- Ajouter des citations visibles aux résultats Q&A fondés - -"Terminé" signifie : - -- L'utilisateur pose une question -- L'application renvoie une réponse -- Au moins une citation est affichée -- Cliquer sur une citation ouvre l'emplacement source dans la vue document diff --git a/courses/learn-harness-engineering/docs/fr/lectures/lecture-11-why-observability-belongs-inside-the-harness/index.md b/courses/learn-harness-engineering/docs/fr/lectures/lecture-11-why-observability-belongs-inside-the-harness/index.md deleted file mode 100644 index 9d2ddaa8..00000000 --- a/courses/learn-harness-engineering/docs/fr/lectures/lecture-11-why-observability-belongs-inside-the-harness/index.md +++ /dev/null @@ -1,154 +0,0 @@ -[中文版本 →](../../../zh/lectures/lecture-11-why-observability-belongs-inside-the-harness/) - -> Exemples de code : [code/](https://github.com/walkinglabs/learn-harness-engineering/blob/main/docs/fr/lectures/lecture-11-why-observability-belongs-inside-the-harness/code/) -> Projet pratique : [Projet 06. Harness complet (capstone)](./../../projects/project-06-runtime-observability-and-debugging/index.md) - -# Leçon 11. Rendre le runtime de l'agent observable - -## Quel problème cette leçon résout-elle ? - -Vous demandez à un agent d'implémenter une fonctionnalité. Il travaille pendant 20 minutes, modifie beaucoup de fichiers, puis annonce : « terminé, mais deux tests échouent ». Vous demandez pourquoi ils échouent : « pas sûr, peut-être un problème de timing ». Vous demandez quelles routes critiques il a modifiées : « laissez-moi regarder le code... ». - -Ce n'est pas un manque de capacité de l'agent. C'est votre harness qui ne fournit pas assez d'observabilité. **Sans observabilité, les agents décident dans l'incertitude, les évaluations deviennent subjectives et les nouvelles tentatives deviennent de l'errance à l'aveugle.** OpenAI et Anthropic définissent tous deux la fiabilité comme un problème de preuves : le harness doit exposer le comportement runtime et les signaux d'évaluation sous une forme qui guide la décision suivante. - -## Concepts clés - -- **Observabilité runtime** : signaux système — logs, traces, événements de processus, health checks. Répond à « qu'a fait le système ? ». -- **Observabilité de processus** : visibilité sur les artefacts de décision du harness — plans, grilles de score, critères d'acceptation. Répond à « pourquoi ce changement devrait-il être accepté ? ». -- **Trace de tâche** : enregistrement complet du chemin de décision, du début à la fin de la tâche, analogue au tracing de requêtes dans les systèmes distribués. -- **Sprint contract** : accord court négocié avant le codage, spécifiant scope, critères de vérification et exclusions. C'est l'outil central de l'observabilité de processus. -- **Grille d'évaluation** : transforme l'évaluation de qualité en score structuré fondé sur des preuves. -- **Observabilité en couches** : observabilité système et observabilité de processus conçues ensemble. Les signaux runtime expliquent le comportement ; les artefacts de processus expliquent l'intention. - -## Observabilité en couches - -```mermaid -flowchart LR - Contract["Écrire d'abord la tâche
quoi changer / quoi ne pas changer / critères de réussite"] --> Generator["Generator"] - Generator --> Signals["Collecter logs, traces
et health checks pendant l'exécution"] - Contract --> Review["Vérifier le résultat point par point
comportement / tests / limites"] - Signals --> Review - Review --> Verdict["Pointer le contrôle échoué
et l'endroit à corriger"] - Verdict --> Generator -``` - -## Pourquoi cela arrive - -### Le vrai coût du manque d'observabilité - -Quand un harness manque d'observabilité, quatre problèmes apparaissent systématiquement : - -**Impossible de distinguer « correct » de « semble correct »** : une fonction peut paraître parfaite en revue de code — syntaxe correcte, logique solide. Mais au runtime, une erreur d'edge case produit de mauvais résultats pour certaines entrées. Seules les traces runtime révèlent que le chemin réel d'exécution a divergé. - -**L'évaluation devient mystique** : sans grille de score et critères d'acceptation, les évaluateurs, humains ou agents, s'appuient sur des hypothèses implicites. Le même résultat peut recevoir des évaluations très différentes. La qualité devient non reproductible. - -**Les retries deviennent des paris aveugles** : quand l'agent ne sait pas pourquoi quelque chose a échoué, la direction du retry est aléatoire. Il peut corriger des chemins de code sans rapport en ignorant la vraie cause racine. Chaque retry aveugle coûte des tokens et du temps. - -**Falaise d'information au handoff** : quand un travail incomplet passe à la session suivante, l'absence d'observabilité oblige la nouvelle session à diagnostiquer l'état du système depuis zéro. Les observations d'Anthropic sur les agents longue durée montrent que ce diagnostic redondant peut consommer 30-50 % du temps total de session. - -### Un scénario réaliste avec Claude Code - -Imaginez un harness avec workflow à trois rôles, planner-generator-evaluator, exécutant « ajouter le dark mode à l'application ». - -**Sans observabilité** : le planner produit une description vague. Le generator implémente le dark mode à partir de cette description, mais ne correspond pas aux attentes implicites du planner. L'evaluator rejette selon ses propres standards implicites sans pouvoir expliquer précisément ce qui ne va pas. Le generator réessaie à l'aveugle. Le cycle se répète 3-4 fois, prend environ 45 minutes et produit un résultat à peine acceptable. - -**Avec observabilité complète** : le planner produit un sprint contract listant les composants à modifier, les critères de vérification et les exclusions. Le generator implémente selon le contrat. L'observabilité runtime enregistre le chargement et l'application des styles de chaque composant. L'evaluator utilise une grille de score, dimension par dimension, avec citations de preuves. Une itération produit un résultat de haute qualité en environ 15 minutes. - -La différence d'efficacité est de 3x. Le seul changement est l'observabilité. - -### Pourquoi les agents ne peuvent pas résoudre cela seuls - -Vous pourriez penser : « l'agent ne peut-il pas simplement imprimer ses propres logs ? » Les problèmes sont : - -1. L'agent ne sait pas ce qu'il ne sait pas ; il n'enregistrera pas spontanément des signaux dont il ignore avoir besoin. -2. Les formats de logs sont incohérents ; différentes sessions utilisent différents formats, rendant l'analyse systématique impossible. -3. L'observabilité de processus ne se résout pas par des logs ; sprint contracts et grilles de score sont des artefacts structurés nécessitant un support au niveau du harness. - -## Comment bien le faire - -### 1. Intégrer la collecte de signaux runtime dans le harness - -Ne comptez pas sur l'agent pour imprimer ses propres logs. Le harness doit collecter automatiquement ces signaux : - -- **Cycle de vie de l'application** : états startup, ready, running et shutdown -- **Exécution du chemin fonctionnel** : enregistrements des chemins critiques, avec points d'entrée, checkpoints et sorties -- **Flux de données** : données circulant entre composants -- **Utilisation des ressources** : motifs anormaux, par exemple mémoire qui augmente continuellement -- **Erreurs et exceptions** : contexte complet, pas seulement messages d'erreur - -### 2. Implémenter des sprint contracts - -Avant chaque tâche, generator et evaluator, éventuellement deux invocations du même agent, négocient un contrat : - -```markdown -# Sprint Contract: Dark Mode Support - -## Scope -- Modify the theme toggle component -- Update global CSS variables -- Add dark mode tests - -## Verification Standards -- Visual regression tests pass for each component -- Main flow end-to-end tests pass -- No flash of unstyled content (FOUC) - -## Exclusions -- Not handling print styles -- Not handling third-party component dark mode -``` - -### 3. Établir une grille d'évaluation - -Transformez « est-ce bon ou non » en score quantifiable : - -```markdown -# Scoring Rubric - -| Dimension | A | B | C | D | -|-----------|---|---|---|---| -| Code correctness | All tests pass | Main flow passes | Partial pass | Build fails | -| Architecture compliance | Fully compliant | Minor deviations | Obvious deviations | Serious violations | -| Test coverage | Main + edge cases | Main flow only | Only skeleton | No tests | -``` - -### 4. Standardiser avec OpenTelemetry - -Créez une trace pour chaque session de harness, un span pour chaque tâche et des sous-spans pour chaque étape de vérification. Utilisez des attributs standards pour annoter les informations clés. Les données d'observabilité peuvent ainsi s'intégrer à des outils comme Jaeger ou Zipkin. - -## Cas réel - -Un harness avec workflow planner-generator-evaluator exécute « ajouter le support dark mode » : - -**Version non observable** : 3-4 tours de retries aveugles, 45 minutes, résultat à peine acceptable. L'evaluator dit « ça ne semble pas juste » sans pouvoir préciser. Le generator gaspille beaucoup de temps dans de mauvaises directions. - -**Version entièrement observable** : -- Le sprint contract clarifie scope, standards et exclusions -- Les traces runtime enregistrent le chargement des styles de chaque composant -- La grille fournit une évaluation structurée dimension par dimension -- Une itération produit des résultats de haute qualité en 15 minutes - -Amélioration d'efficacité de 3x, qualité plus stable, évaluations reproductibles. - -## Points clés - -- **L'observabilité est une propriété d'architecture du harness**, pas une fonctionnalité ajoutée après coup. -- **Les deux couches d'observabilité sont essentielles** : les signaux runtime expliquent « ce qui s'est passé », les artefacts de processus expliquent « pourquoi cela a été fait ainsi ». -- **Les sprint contracts alignent en amont**, évitant qu'un generator construise quelque chose que l'evaluator rejette immédiatement pour des raisons prévisibles. -- **Les grilles rendent l'évaluation reproductible**, différents évaluateurs produisant des scores proches pour le même résultat. -- **Le manque d'observabilité gaspille 30-50 % du temps de session en diagnostic redondant.** - -## Pour aller plus loin - -- [Observability Engineering - Charity Majors](https://www.honeycomb.io/blog/observability-engineering-book) — cadre théorique et pratique pour l'observability engineering moderne -- [Dapper - Google (Sigelman et al.)](https://research.google/pubs/pub36356/) — pratique fondatrice du tracing distribué à grande échelle -- [Harness Design - Anthropic](https://www.anthropic.com/engineering/harness-design-long-running-apps) — introduction des sprint contracts et grilles d'évaluation -- [Site Reliability Engineering - Google](https://sre.google/sre-book/table-of-contents/) — application systématique de l'observabilité en production - -## Exercices - -1. **Analyse du déficit d'observabilité** : auditez votre harness actuel sur les couches système et processus. Trouvez les états impossibles à distinguer avec les signaux existants et proposez des ajouts. - -2. **Pratique du sprint contract** : rédigez un sprint contract pour une tâche réelle. Faites exécuter l'agent selon le contrat et comparez efficacité et qualité avec et sans contrat. - -3. **Construction de trace de tâche** : enregistrez chaque étape des opérations d'un agent pendant une tâche complète. Annotez avec les conventions sémantiques OpenTelemetry et analysez les goulets d'information. diff --git a/courses/learn-harness-engineering/docs/fr/lectures/lecture-12-why-every-session-must-leave-a-clean-state/code/benchmark-comparison-template.md b/courses/learn-harness-engineering/docs/fr/lectures/lecture-12-why-every-session-must-leave-a-clean-state/code/benchmark-comparison-template.md deleted file mode 100644 index 957db26a..00000000 --- a/courses/learn-harness-engineering/docs/fr/lectures/lecture-12-why-every-session-must-leave-a-clean-state/code/benchmark-comparison-template.md +++ /dev/null @@ -1,18 +0,0 @@ -# Modèle de Comparaison de Benchmark - -Harness A : - -- taux d'achèvement -- tentatives moyennes -- bugs détectés avant revue humaine - -Harness B : - -- taux d'achèvement -- tentatives moyennes -- bugs détectés avant revue humaine - -Interprétation : - -- Quel harness a changé le résultat ? -- Quel harness a changé le coût pour obtenir le résultat ? diff --git a/courses/learn-harness-engineering/docs/fr/lectures/lecture-12-why-every-session-must-leave-a-clean-state/code/benchmark-runner.ts b/courses/learn-harness-engineering/docs/fr/lectures/lecture-12-why-every-session-must-leave-a-clean-state/code/benchmark-runner.ts deleted file mode 100644 index 0388cad7..00000000 --- a/courses/learn-harness-engineering/docs/fr/lectures/lecture-12-why-every-session-must-leave-a-clean-state/code/benchmark-runner.ts +++ /dev/null @@ -1,216 +0,0 @@ -/** - * benchmark-runner.ts - * - * Reads a benchmark task definition (JSON array of tasks with pass criteria), - * "executes" each task, records timing and pass/fail, outputs a comparison - * report showing which tasks pass and which fail. - * - * Run: npx tsx docs/lectures/lecture-12-why-every-session-must-leave-a-clean-state/code/benchmark-runner.ts - */ - -// --------------------------------------------------------------------------- -// Types -// --------------------------------------------------------------------------- - -interface BenchmarkTask { - id: string; - name: string; - category: string; - passCriteria: string[]; - expectedDurationMs: number; - // Simulated actual results - actualDurationMs: number; - actualPass: boolean; - failureReason?: string; -} - -interface BenchmarkResult { - id: string; - name: string; - category: string; - criteriaTotal: number; - criteriaPassed: number; - pass: boolean; - expectedMs: number; - actualMs: number; - durationDelta: number; - failureReason?: string; -} - -// --------------------------------------------------------------------------- -// Benchmark task definitions -// --------------------------------------------------------------------------- - -const benchmarkTasks: BenchmarkTask[] = [ - { - id: "bench-001", - name: "Import markdown document", - category: "Document Pipeline", - passCriteria: ["File accepted", "Chunks created", "Metadata stored"], - expectedDurationMs: 100, - actualDurationMs: 95, - actualPass: true, - }, - { - id: "bench-002", - name: "Import PDF document", - category: "Document Pipeline", - passCriteria: ["File accepted", "Text extracted", "Chunks created", "Metadata stored"], - expectedDurationMs: 200, - actualDurationMs: 310, - actualPass: false, - failureReason: "PDF text extraction failed on page 3 -- encoding issue", - }, - { - id: "bench-003", - name: "Ask grounded question", - category: "Q&A Pipeline", - passCriteria: ["Query received", "Relevant chunks retrieved", "Answer generated", "Citations present"], - expectedDurationMs: 500, - actualDurationMs: 480, - actualPass: true, - }, - { - id: "bench-004", - name: "Ask question with no relevant docs", - category: "Q&A Pipeline", - passCriteria: ["Query received", "Graceful 'no results' message", "No hallucinated citations"], - expectedDurationMs: 400, - actualDurationMs: 420, - actualPass: false, - failureReason: "Model hallucinated a citation instead of saying 'no results'", - }, - { - id: "bench-005", - name: "Delete imported document", - category: "Document Pipeline", - passCriteria: ["Document removed from list", "Chunks removed from index", "No orphan data"], - expectedDurationMs: 150, - actualDurationMs: 145, - actualPass: true, - }, - { - id: "bench-006", - name: "Concurrent user access", - category: "Security", - passCriteria: ["Users isolated", "No cross-user data leak", "Performance within SLA"], - expectedDurationMs: 300, - actualDurationMs: 850, - actualPass: false, - failureReason: "Cross-user data leak detected + response time exceeded SLA (850ms > 500ms)", - }, - { - id: "bench-007", - name: "Session continuity after restart", - category: "Reliability", - passCriteria: ["State persisted", "Session recovers", "No data loss"], - expectedDurationMs: 200, - actualDurationMs: 180, - actualPass: true, - }, - { - id: "bench-008", - name: "API rate limiting", - category: "Security", - passCriteria: ["Rate limit enforced", "429 response after limit", "Legitimate traffic unaffected"], - expectedDurationMs: 100, - actualDurationMs: 100, - actualPass: true, - }, -]; - -// --------------------------------------------------------------------------- -// Execution simulation -// --------------------------------------------------------------------------- - -function executeBenchmark(tasks: BenchmarkTask[]): BenchmarkResult[] { - return tasks.map((task) => ({ - id: task.id, - name: task.name, - category: task.category, - criteriaTotal: task.passCriteria.length, - criteriaPassed: task.actualPass ? task.passCriteria.length : Math.floor(task.passCriteria.length * 0.5), - pass: task.actualPass, - expectedMs: task.expectedDurationMs, - actualMs: task.actualDurationMs, - durationDelta: task.actualDurationMs - task.expectedDurationMs, - failureReason: task.failureReason, - })); -} - -// --------------------------------------------------------------------------- -// Reporting -// --------------------------------------------------------------------------- - -function pad(s: string, len: number): string { - return s.length >= len ? s : s + " ".repeat(len - s.length); -} - -function run(): void { - console.log("\n" + "=".repeat(100)); - console.log(" BENCHMARK RUNNER -- Task Execution Report"); - console.log("=".repeat(100)); - - const results = executeBenchmark(benchmarkTasks); - - // Detailed results - const header = `| ${pad("ID", 10)}| ${pad("Task", 35)}| ${pad("Category", 18)}| ${pad("Pass?", 6)}| ${pad("Criteria", 10)}| ${pad("Expected", 10)}| ${pad("Actual", 10)}| ${pad("Delta", 8)}|`; - const sep = `|${"-".repeat(12)}|${"-".repeat(37)}|${"-".repeat(20)}|${"-".repeat(8)}|${"-".repeat(12)}|${"-".repeat(12)}|${"-".repeat(12)}|${"-".repeat(10)}|`; - console.log("\n" + header); - console.log(sep); - - for (const r of results) { - const passLabel = r.pass ? "PASS" : "FAIL"; - const criteriaLabel = `${r.criteriaPassed}/${r.criteriaTotal}`; - const deltaLabel = r.durationDelta >= 0 ? `+${r.durationDelta}ms` : `${r.durationDelta}ms`; - const marker = r.pass ? " " : ">>"; - - console.log( - `${marker}| ${pad(r.id, 10)}| ${pad(r.name, 35)}| ${pad(r.category, 18)}| ${pad(passLabel, 6)}| ${pad(criteriaLabel, 10)}| ${pad(r.expectedMs + "ms", 10)}| ${pad(r.actualMs + "ms", 10)}| ${pad(deltaLabel, 8)}|` - ); - } - - // Failure details - const failures = results.filter((r) => !r.pass); - if (failures.length > 0) { - console.log("\n" + "-".repeat(100)); - console.log(" FAILURE DETAILS"); - console.log("-".repeat(100)); - for (const f of failures) { - console.log(`\n [${f.id}] ${f.name}`); - console.log(` Category: ${f.category}`); - console.log(` Criteria: ${f.criteriaPassed}/${f.criteriaTotal} passed`); - console.log(` Reason: ${f.failureReason ?? "Unknown"}`); - console.log(` Timing: Expected ${f.expectedMs}ms, actual ${f.actualMs}ms (delta: ${f.durationDelta >= 0 ? "+" : ""}${f.durationDelta}ms)`); - } - } - - // Summary - console.log("\n" + "=".repeat(100)); - console.log(" SUMMARY BY CATEGORY"); - console.log("=".repeat(100) + "\n"); - - const categories = [...new Set(results.map((r) => r.category))]; - const catHeader = `| ${pad("Category", 20)}| ${pad("Total", 8)}| ${pad("Passed", 8)}| ${pad("Failed", 8)}| ${pad("Pass Rate", 12)}|`; - const catSep = `|${"-".repeat(22)}|${"-".repeat(10)}|${"-".repeat(10)}|${"-".repeat(10)}|${"-".repeat(14)}|`; - console.log(catHeader); - console.log(catSep); - - for (const cat of categories) { - const catResults = results.filter((r) => r.category === cat); - const catPassed = catResults.filter((r) => r.pass).length; - const catFailed = catResults.filter((r) => !r.pass).length; - const rate = Math.round((catPassed / catResults.length) * 100); - - console.log(`| ${pad(cat, 20)}| ${pad(String(catResults.length), 8)}| ${pad(String(catPassed), 8)}| ${pad(String(catFailed), 8)}| ${pad(rate + "%", 12)}|`); - } - - // Overall - const totalPassed = results.filter((r) => r.pass).length; - const totalFailed = results.filter((r) => !r.pass).length; - console.log("\n" + "-".repeat(100)); - console.log(` OVERALL: ${totalPassed}/${results.length} passed (${Math.round((totalPassed / results.length) * 100)}%), ${totalFailed} failures`); - console.log("=".repeat(100) + "\n"); -} - -run(); diff --git a/courses/learn-harness-engineering/docs/fr/lectures/lecture-12-why-every-session-must-leave-a-clean-state/code/cleanup-loop.md b/courses/learn-harness-engineering/docs/fr/lectures/lecture-12-why-every-session-must-leave-a-clean-state/code/cleanup-loop.md deleted file mode 100644 index a3ababea..00000000 --- a/courses/learn-harness-engineering/docs/fr/lectures/lecture-12-why-every-session-must-leave-a-clean-state/code/cleanup-loop.md +++ /dev/null @@ -1,9 +0,0 @@ -# Exemple de Boucle de Nettoyage - -Tâches de nettoyage récurrentes : - -- scanner les docs obsolètes -- scanner les violations structurelles -- mettre à jour les notes de qualité -- ouvrir des PR de nettoyage ciblées -- réexécuter une tranche de benchmark fixe après le nettoyage diff --git a/courses/learn-harness-engineering/docs/fr/lectures/lecture-12-why-every-session-must-leave-a-clean-state/code/cleanup-scanner.ts b/courses/learn-harness-engineering/docs/fr/lectures/lecture-12-why-every-session-must-leave-a-clean-state/code/cleanup-scanner.ts deleted file mode 100644 index 6b35905c..00000000 --- a/courses/learn-harness-engineering/docs/fr/lectures/lecture-12-why-every-session-must-leave-a-clean-state/code/cleanup-scanner.ts +++ /dev/null @@ -1,336 +0,0 @@ -/** - * cleanup-scanner.ts - * - * Scans a project directory for stale artifacts, dead code, structural - * violations, and outputs a cleanup report. Helps enforce the "clean state - * at end of every session" principle. - * - * Usage: - * npx tsx docs/lectures/lecture-12.../code/cleanup-scanner.ts [path] - * (defaults to current working directory) - * - * Run: npx tsx docs/lectures/lecture-12-why-every-session-must-leave-a-clean-state/code/cleanup-scanner.ts - */ - -import * as fs from "node:fs"; -import * as path from "node:path"; - -// --------------------------------------------------------------------------- -// Types -// --------------------------------------------------------------------------- - -interface ScanResult { - category: string; - check: string; - severity: "critical" | "warning" | "info"; - found: string[]; - description: string; -} - -// --------------------------------------------------------------------------- -// Scanner checks -// --------------------------------------------------------------------------- - -interface ScannerCheck { - category: string; - name: string; - severity: "critical" | "warning" | "info"; - description: string; - scan: (dir: string) => string[]; -} - -function createChecks(): ScannerCheck[] { - return [ - { - category: "Stale Artifacts", - name: "Temporary files (*.tmp, *.bak, *.swp)", - severity: "warning", - description: "Leftover temp files from editing or processing", - scan: (dir) => findFiles(dir, [".tmp", ".bak", ".swp", "~"]), - }, - { - category: "Stale Artifacts", - name: "Debug/log files in source", - severity: "warning", - description: "Log files that should not be in the source tree", - scan: (dir) => { - const found: string[] = []; - const logPatterns = [".log"]; - for (const p of findFiles(dir, logPatterns)) { - // Only flag log files in source directories - if (p.includes("/src/") || p.includes("/lib/") || p.includes("/app/")) { - found.push(p); - } - } - return found; - }, - }, - { - category: "Dead Code", - name: "Unused imports (placeholder detection)", - severity: "info", - description: "Files that may contain unused code (heuristic scan)", - scan: (dir) => { - const found: string[] = []; - const srcDirs = ["src", "lib", "app"]; - for (const srcDir of srcDirs) { - const srcPath = path.join(dir, srcDir); - if (fs.existsSync(srcPath)) { - scanForPatterns(srcPath, ["TODO:", "FIXME:", "HACK:", "XXX:"], found, dir); - } - } - return found; - }, - }, - { - category: "Structural Violations", - name: "Missing .gitignore", - severity: "warning", - description: "No .gitignore file found -- risk of committing build artifacts", - scan: (dir) => fs.existsSync(path.join(dir, ".gitignore")) ? [] : [".gitignore (MISSING)"], - }, - { - category: "Structural Violations", - name: "Build artifacts in source", - severity: "critical", - description: "Compiled output (dist/, build/) mixed with source", - scan: (dir) => { - const found: string[] = []; - // Check if dist/ or build/ exists inside src/ - const dirs = ["src/dist", "src/build", "lib/dist", "lib/build"]; - for (const d of dirs) { - if (fs.existsSync(path.join(dir, d))) { - found.push(d + "/"); - } - } - return found; - }, - }, - { - category: "Structural Violations", - name: "node_modules in source tree", - severity: "critical", - description: "node_modules directory found outside root (nested dependency)", - scan: (dir) => { - const found: string[] = []; - const srcDirs = ["src", "lib", "app", "test", "tests"]; - for (const srcDir of srcDirs) { - const nmPath = path.join(dir, srcDir, "node_modules"); - if (fs.existsSync(nmPath)) { - found.push(srcDir + "/node_modules/"); - } - } - return found; - }, - }, - { - category: "Session Cleanliness", - name: "Uncommitted changes indicator", - severity: "info", - description: "Checks for common artifacts of incomplete sessions", - scan: (dir) => { - const found: string[] = []; - const indicators = [ - "WIP.md", "IN_PROGRESS.md", "scratch.ts", "scratch.js", - "debug.ts", "test-manual.ts", "temp.ts", - ]; - for (const f of indicators) { - if (fs.existsSync(path.join(dir, f))) { - found.push(f); - } - } - return found; - }, - }, - { - category: "Session Cleanliness", - name: "Empty directories", - severity: "info", - description: "Empty directories that may be leftover from incomplete work", - scan: (dir) => { - const found: string[] = []; - const commonDirs = ["src", "lib", "app", "test", "tests", "docs"]; - for (const d of commonDirs) { - const dp = path.join(dir, d); - if (fs.existsSync(dp) && fs.statSync(dp).isDirectory()) { - try { - const contents = fs.readdirSync(dp); - if (contents.length === 0) { - found.push(d + "/"); - } - } catch { - // Skip directories we can't read - } - } - } - return found; - }, - }, - { - category: "Configuration", - name: "Environment files in source", - severity: "critical", - description: ".env files that may contain secrets", - scan: (dir) => { - const found: string[] = []; - const envFiles = [".env", ".env.local", ".env.production", ".env.staging"]; - for (const f of envFiles) { - if (fs.existsSync(path.join(dir, f))) { - found.push(f); - } - } - return found; - }, - }, - ]; -} - -// --------------------------------------------------------------------------- -// Helper functions -// --------------------------------------------------------------------------- - -function findFiles(dir: string, extensions: string[]): string[] { - const found: string[] = []; - - function walk(current: string, depth: number): void { - if (depth > 4) return; // Limit recursion depth - try { - const entries = fs.readdirSync(current, { withFileTypes: true }); - for (const entry of entries) { - // Skip common non-source directories - if (entry.isDirectory() && ["node_modules", ".git", "dist", "build", ".next", "coverage"].includes(entry.name)) { - continue; - } - - const fullPath = path.join(current, entry.name); - const relativePath = path.relative(dir, fullPath); - - if (entry.isDirectory()) { - walk(fullPath, depth + 1); - } else if (entry.isFile()) { - if (extensions.some((ext) => entry.name.endsWith(ext))) { - found.push(relativePath); - } - } - } - } catch { - // Skip directories we can't read - } - } - - walk(dir, 0); - return found; -} - -function scanForPatterns(dir: string, patterns: string[], results: string[], baseDir: string): void { - try { - const entries = fs.readdirSync(dir, { withFileTypes: true }); - for (const entry of entries) { - if (entry.isDirectory()) { - if (!["node_modules", ".git", "dist", "build"].includes(entry.name)) { - scanForPatterns(path.join(dir, entry.name), patterns, results, baseDir); - } - continue; - } - - const fullPath = path.join(dir, entry.name); - if (!entry.name.endsWith(".ts") && !entry.name.endsWith(".tsx") && !entry.name.endsWith(".js")) { - continue; - } - - try { - const content = fs.readFileSync(fullPath, "utf-8"); - const lines = content.split("\n"); - for (let i = 0; i < Math.min(lines.length, 50); i++) { - for (const pattern of patterns) { - if (lines[i].includes(pattern)) { - const relative = path.relative(baseDir, fullPath); - results.push(`${relative}:${i + 1} contains ${pattern}`); - } - } - } - } catch { - // Skip files we can't read - } - } - } catch { - // Skip directories we can't read - } -} - -// --------------------------------------------------------------------------- -// Reporting -// --------------------------------------------------------------------------- - -function pad(s: string, len: number): string { - return s.length >= len ? s : s + " ".repeat(len - s.length); -} - -function run(): void { - const targetDir = process.argv[2] ? path.resolve(process.argv[2]) : process.cwd(); - - console.log("\n" + "=".repeat(90)); - console.log(" CLEANUP SCANNER -- Stale Artifact & Violation Report"); - console.log("=".repeat(90)); - console.log(` Scanning: ${targetDir}\n`); - - const checks = createChecks(); - const results: ScanResult[] = []; - - for (const check of checks) { - const found = check.scan(targetDir); - results.push({ - category: check.category, - check: check.name, - severity: check.severity, - found, - description: check.description, - }); - } - - // Report - const header = `| ${pad("Category", 20)}| ${pad("Check", 40)}| ${pad("Severity", 10)}| ${pad("Count", 6)}| Details`; - const sep = `|${"-".repeat(22)}|${"-".repeat(42)}|${"-".repeat(12)}|${"-".repeat(8)}|${"-".repeat(30)}`; - console.log(header); - console.log(sep); - - for (const r of results) { - const count = r.found.length; - const detail = count > 0 ? r.found.slice(0, 2).join(", ") : "(clean)"; - const marker = count > 0 && r.severity === "critical" ? ">>" : count > 0 ? " * " : " "; - - console.log(`${marker}| ${pad(r.category, 20)}| ${pad(r.check, 40)}| ${pad(r.severity, 10)}| ${pad(String(count), 6)}| ${detail}`); - } - - // Summary - const criticals = results.filter((r) => r.severity === "critical" && r.found.length > 0); - const warnings = results.filter((r) => r.severity === "warning" && r.found.length > 0); - const infos = results.filter((r) => r.severity === "info" && r.found.length > 0); - const clean = results.filter((r) => r.found.length === 0); - - console.log("\n" + "=".repeat(90)); - console.log(" CLEANUP SUMMARY"); - console.log("=".repeat(90) + "\n"); - - console.log(` Critical issues: ${criticals.length}`); - console.log(` Warnings: ${warnings.length}`); - console.log(` Info items: ${infos.length}`); - console.log(` Clean checks: ${clean.length}/${results.length}`); - - if (criticals.length > 0) { - console.log("\n ACTION REQUIRED (Critical):"); - for (const c of criticals) { - console.log(` - ${c.check}: ${c.found.length} item(s) found`); - console.log(` ${c.description}`); - } - } - - const totalIssues = results.reduce((s, r) => s + r.found.length, 0); - if (totalIssues === 0) { - console.log("\n Project is in a clean state. No issues found.\n"); - } else { - console.log(`\n Total issues found: ${totalIssues}. Run this scanner at the end of every session to maintain clean state.\n`); - } -} - -run(); diff --git a/courses/learn-harness-engineering/docs/fr/lectures/lecture-12-why-every-session-must-leave-a-clean-state/code/index.md b/courses/learn-harness-engineering/docs/fr/lectures/lecture-12-why-every-session-must-leave-a-clean-state/code/index.md deleted file mode 100644 index 4aa86796..00000000 --- a/courses/learn-harness-engineering/docs/fr/lectures/lecture-12-why-every-session-must-leave-a-clean-state/code/index.md +++ /dev/null @@ -1,8 +0,0 @@ -# Code du Cours 12 - -Utilisez ce dossier pour des exemples de : - -- tranches de benchmark -- tâches de nettoyage -- exemples de réduction d'entropie -- exécutions de harness reproductibles diff --git a/courses/learn-harness-engineering/docs/fr/lectures/lecture-12-why-every-session-must-leave-a-clean-state/index.md b/courses/learn-harness-engineering/docs/fr/lectures/lecture-12-why-every-session-must-leave-a-clean-state/index.md deleted file mode 100644 index 0106296d..00000000 --- a/courses/learn-harness-engineering/docs/fr/lectures/lecture-12-why-every-session-must-leave-a-clean-state/index.md +++ /dev/null @@ -1,186 +0,0 @@ -[中文版本 →](../../../zh/lectures/lecture-12-why-every-session-must-leave-a-clean-state/) - -> Exemples de code : [code/](https://github.com/walkinglabs/learn-harness-engineering/blob/main/docs/fr/lectures/lecture-12-why-every-session-must-leave-a-clean-state/code/) -> Projet pratique : [Project 06. Complete harness (Capstone)](./../../projects/project-06-runtime-observability-and-debugging/index.md) - -# Leçon 12. Laisser un handoff propre à la fin de chaque session - -## Quel problème cette leçon résout-elle ? - -Votre agent travaille tout l'après-midi, modifie 20 fichiers, commite le code, la session se termine. La prochaine session d'agent démarre et découvre immédiatement : le build est cassé, les tests sont rouges, des fichiers de debug temporaires sont partout, la feature list n'a pas été mise à jour, et l'avancement est totalement flou. La nouvelle session passe ses 30 premières minutes juste à comprendre « ce que la session précédente a réellement fait. » - -OpenAI et Anthropic le disent clairement : **la fiabilité à long terme dépend de la discipline opérationnelle, pas seulement du succès d'une exécution unique.** La qualité de l'état à la sortie de la session détermine directement l'efficacité de la session suivante. Pensez aux bonnes pratiques Git — chaque commit devrait être un changement atomique et compilable, pas un tas de code à moitié fini. - -## Concepts clés - -- **État propre (clean state)** : Le système satisfait cinq conditions à la fin de la session — le build passe, les tests passent, l'avancement est enregistré, aucun artefact périmé, le chemin de démarrage est disponible. S'il en manque une seule, la session n'est pas « terminée ». -- **Intégrité de session** : Analogue aux transactions de base de données — soit on commit entièrement et on laisse un état propre, soit on revient au dernier état cohérent. Pas de terrain d'entente. -- **Document de qualité** : Un artefact actif qui enregistre en continu les notes de qualité de chaque module. Pas une évaluation ponctuelle, mais un suivi montrant si la base de code se renforce ou s'affaiblit au fil du temps. -- **Boucle de nettoyage** : Une session de maintenance régulière visant à réduire systématiquement l'entropie dans la base de code. Pas un correctif d'urgence, mais des opérations de routine. -- **Simplification du harness** : À mesure que les capacités du modèle s'améliorent, retirer périodiquement les composants du harness qui ne sont plus nécessaires. Une contrainte essentielle aujourd'hui peut être une surcharge inutile dans trois mois. -- **Nettoyage idempotent** : Les opérations de nettoyage produisent le même résultat quel que soit le nombre de fois où elles sont exécutées. Garantit que le nettoyage reste sûr même dans les scénarios de tentative après échec. - -## Cinq dimensions de l'état propre - -```mermaid -flowchart LR - Work["Feature work complete"] --> Build{"Build passes?"} - Build -->|yes| Test{"Tests pass?"} - Build -->|no| Fix["Fix before exit"] - Test -->|yes| Record["Update feature list + progress"] - Test -->|no| Fix - Record --> Cleanup["Remove temp artifacts / debug code"] - Cleanup --> Startup{"Standard startup path works?"} - Startup -->|yes| Clean["Clean handoff"] - Startup -->|no| Fix - Fix --> Build -``` - -```mermaid -flowchart LR - Dirty["Session ends with
red tests / temp files / no progress update"] --> Diagnose["Next session first has to
figure out what happened"] - Diagnose --> Fragile["New work starts on a messy repo"] - Fragile --> More["More debug files, more broken checks,
more unclear progress"] - More --> Dirty - - Clean["Session ends with
green tests / updated progress / temp files removed"] --> Fast["Next session can start coding immediately"] - Fast --> Stable["No need to rescue the repo first"] - Stable --> Clean -``` - -## Pourquoi cela arrive - -### La croissance de l'entropie est l'état par défaut - -Les lois de Lehman sur l'évolution logicielle nous disent : les systèmes soumis à des changements continus augmenteront inévitablement en complexité s'ils ne sont pas activement gérés. C'est particulièrement vrai pour les agents de codage IA — chaque session introduit des changements, et sans nettoyage à la sortie, la dette technique s'accumule de manière exponentielle. - -Les données réelles sont éloquentes. Un projet développé avec des agents pendant 12 semaines, sans stratégie de nettoyage : - -- Semaine 1 : Taux de réussite du build 100 %, taux de réussite des tests 100 %, démarrage de nouvelle session 5 min -- Semaine 4 : Build 95 %, tests 92 %, démarrage 15 min -- Semaine 8 : Build 82 %, tests 78 %, démarrage 35 min -- Semaine 12 : Build 68 %, tests 61 %, démarrage 60+ min - -Le même projet avec une stratégie de nettoyage : - -- Semaine 1 : 100 %, 100 %, 5 min -- Semaine 12 : 97 %, 95 %, 9 min - -Après 12 semaines : le taux de réussite du build diffère de 29 points de pourcentage, le temps de démarrage d'une nouvelle session diffère de 85 %. Ce n'est pas théorique — c'est une différence observée. - -### Les cinq dimensions de l'état propre - -L'état propre, ce n'est pas juste « le code compile ». C'est cinq dimensions évaluées ensemble : - -**Dimension build** : Le code se compile-t-il sans erreurs ? C'est le minimum — la prochaine session ne devrait pas avoir à corriger des erreurs de build en premier. - -**Dimension tests** : Tous les tests passent-ils ? Y compris les tests qui existaient avant la session — la session est responsable de ne pas casser les fonctionnalités existantes. Et cela devrait être vérifié dans le CI, pas juste « ça marche chez moi ». - -**Dimension avancement** : L'avancement actuel est-il enregistré dans un artefact lisible par machine ? Sous-tâches terminées avec leurs critères de réussite, sous-tâches en cours mais incomplètes avec leur état actuel, sous-tâches non encore commencées. De bons enregistrements d'avancement réduisent de 60 à 80 % le temps de diagnostic au démarrage d'une session. - -**Dimension artefacts** : Y a-t-il des artefacts temporaires périmés ou ambigus ? Logs de debug, fichiers temporaires, code commenté, marqueurs TODO — tous ces éléments augmentent la charge cognitive pour la prochaine session. - -**Dimension démarrage** : Le chemin de démarrage standard est-il disponible ? La prochaine session peut-elle commencer à travailler sans intervention manuelle ? Initialisation de l'environnement, chargement de la base de code, acquisition du contexte, sélection de la tâche — ces chemins ne doivent pas être cassés. - -### « Nettoyer plus tard » signifie ne jamais nettoyer - -Le piège mental le plus courant est « pas le temps de nettoyer cette session, je le ferai la prochaine fois ». Mais la prochaine session d'agent ne sait pas ce que vous avez laissé derrière — elle voit un code en désordre et un état incertain. Elle passera un temps significatif à déduire « quelles parties de ce code sont intentionnelles et lesquelles sont temporaires ». - -Pire, chaque session a ses propres objectifs de tâche. La nouvelle session est là pour faire du nouveau travail, pas nettoyer le désordre de la session précédente. Elle ignorera le chaos et commencera un nouveau travail par-dessus, introduisant plus de chaos sur du chaos. C'est la boucle de retour positif de l'entropie. - -## Comment bien le faire - -### 1. L'état propre comme condition d'achèvement - -Définissez explicitement dans le harness : **achèvement de la session = la tâche passe la vérification ET la vérification d'état propre passe.** S'il manque l'un ou l'autre, la session n'est pas terminée. Écrivez dans CLAUDE.md : - -``` -## Session Exit Checklist -- [ ] Build passes (npm run build) -- [ ] All tests pass (npm test) -- [ ] Feature list updated -- [ ] No debug code remaining (console.log, debugger, TODO) -- [ ] Standard startup path available (npm run dev) -``` - -### 2. Stratégie de nettoyage en double mode - -Combinez deux modes de nettoyage : - -**Nettoyage immédiat (à la fin de chaque session)** : Nettoyer les artefacts temporaires créés pendant la session, mettre à jour l'état de la feature list, s'assurer que le build et les tests passent. C'est le nettoyage par « comptage de références ». - -**Nettoyage périodique (hebdomadaire)** : Scan complet du système — traiter les problèmes structurels accumulés, mettre à jour les documents de qualité, lancer des tests de référence pour dététecter les dérives. C'est le nettoyage par « traçage ». - -### 3. Maintenir un document de qualité - -Un document de qualité est un artefact actif qui note en continu chaque module : - -```markdown -# Quality Document - -## User Authentication Module (Quality: A) -- Verification passing: Yes -- Agent understandable: Yes -- Test stability: Stable -- Architecture boundaries: Compliant -- Code conventions: Followed - -## Payment Module (Quality: C) -- Verification passing: Partial (payment callback untested) -- Agent understandable: Difficult (logic spread across 3 files) -- Test stability: Unstable (2 flaky tests) -- Architecture boundaries: Violations present -- Code conventions: Partially followed -``` - -Les nouvelles sessions lisent ce document et savent immédiatement où prioriser. Corriger d'abord le module avec la note la plus basse. - -### 4. Simplifier périodiquement le harness - -Une insight importante d'Anthropic : **chaque composant du harness existe parce que le modèle ne peut pas faire quelque chose de manière fiable par lui-même. Mais à mesure que les modèles s'améliorent, ces hypothèses deviennent obsolètes.** Une contrainte essentielle il y a trois mois peut être une surcharge inutile aujourd'hui. - -Pratique recommandée : Chaque mois, choisissez un composant du harness, désactivez-le temporairement, et lancez des tâches de référence. Si les résultats ne se dégradent pas, retirez-le définitivement. S'ils se dégradent, restaurez-le ou remplacez-le par une alternative plus légère. - -### 5. Les opérations de nettoyage doivent être idempotentes - -Les scripts de nettoyage devraient être sûrs à exécuter de manière répétée : - -```bash -# Idempotent cleanup operations -rm -f /tmp/debug-*.log # -f ensures no error when files don't exist -git checkout -- .env.local # Restore to known state -npm run test # Verify cleanup didn't break anything -``` - -## Cas concret - -Une application Electron développée avec des agents sur 12 semaines, comparant deux approches : - -**Sans stratégie de nettoyage** (groupe de contrôle) : Semaine 12, taux de réussite du build 68 %, taux de réussite des tests 61 %, démarrage de nouvelle session 60+ min, artefacts périmés 103. - -**Avec stratégie de nettoyage** (groupe expérimental) : Vérification complète d'état propre à chaque fin de session + boucle de nettoyage hebdomadaire. Semaine 12, taux de réussite du build 97 %, taux de réussite des tests 95 %, démarrage de nouvelle session 9 min, artefacts périmés 11. - -À la semaine 12, le taux de réussite du build du groupe expérimental est de 29 points de pourcentage plus élevé, le taux de réussite des tests de 34 points plus élevé, et le temps de démarrage d'une nouvelle session réduit de 85 %. - -## Points clés - -- **L'état propre est une condition nécessaire à l'achèvement de la session** — pas du ménage optionnel, mais partie intégrante de la « définition du fini ». -- **Les cinq dimensions sont toutes requises** — build, tests, avancement, artefacts, démarrage — chacune doit être vérifiée explicitement. -- **Les documents de qualité rendent la santé de la base de code traçable** — on ne peut corriger que ce qu'on sait se dégrader. -- **Simplifier périodiquement le harness** — à mesure que les capacités du modèle s'améliorent, retirer les contraintes devenues inutiles. -- **« Nettoyer plus tard » équivaut à ne jamais nettoyer** — la croissance de l'entropie est la norme ; seul un nettoyage actif la contrecarre. - -## Pour aller plus loin - -- [Clean Code - Robert C. Martin](https://www.goodreads.com/book/show/3735293-clean-code) — Principes systématiques de propreté du code -- [Harness Engineering - OpenAI](https://openai.com/index/harness-engineering/) — La reproductibilité comme exigence fondamentale de la conception du harness -- [Effective Harnesses - Anthropic](https://www.anthropic.com/engineering/effective-harnesses-for-long-running-agents) — Le rôle critique des sorties de session propres pour la fiabilité à long terme -- [Programs, Life Cycles, and Laws of Software Evolution - Lehman](https://ieeexplore.ieee.org/document/1702314) — Les lois de l'évolution logicielle prouvant que la complexité du système croît inévitablement sans maintenance active - -## Exercices - -1. **Liste de contrôle d'état propre** : Concevez une liste de contrôle de sortie de session pour votre base de code couvrant les cinq dimensions. Appliquez-la sur 5 sessions consécutives et enregistrez les violations par dimension. - -2. **Comparaison de référence** : Utilisez un ensemble de tâches fixe avec deux variantes de harness (avec/sans exigences d'état propre). Comparez le taux de complétion, le nombre de tentatives et le taux de fuite de défauts. - -3. **Pratique de simplification du harness** : Choisissez un composant du harness, désactivez-le temporairement, et lancez des tâches de référence. Comparez les résultats avec et sans celui-ci. Décidez de le conserver, le retirer ou le remplacer. diff --git a/courses/learn-harness-engineering/docs/fr/projects/index.md b/courses/learn-harness-engineering/docs/fr/projects/index.md deleted file mode 100644 index 9777ba69..00000000 --- a/courses/learn-harness-engineering/docs/fr/projects/index.md +++ /dev/null @@ -1,23 +0,0 @@ -# Bienvenue dans les projets - -Voici la section pratique de Learn Harness Engineering. Lire les leçons ne suffit pas : vous devez construire vous-même les environnements et observer comment Codex, Claude Code ou d'autres agents IA se comportent sous différentes règles. - -## Vue d'ensemble - -Le cours propose 6 projets pratiques et progressifs qui vous apprennent à construire de zéro un environnement agentique fiable : - -1. **Prompt seul vs règles d'abord** : comparer les résultats d'un agent avec un simple prompt et avec un harness de base. -2. **Workspace lisible par l'agent** : structurer le dépôt pour l'IA et établir des mécanismes de handoff. -3. **Continuité multi-session** : concevoir des fichiers d'état et des scripts d'initialisation pour reprendre le travail entre sessions. -4. **Feedback runtime et contrôle du scope** : introduire des outils permettant à l'agent de tester son code et de corriger les erreurs pendant l'exécution. -5. **Auto-vérification et séparation des rôles** : construire un mécanisme de revue indépendant pour éviter les hallucinations et les déclarations de réussite trop précoces. -6. **Harness complet (capstone)** : assembler un environnement final, observable et end-to-end pour le travail agentique. - -## Comment avancer - -Chaque dossier de projet contient généralement : -- `starter/` : votre workspace de départ. -- `solution/` : une implémentation de référence si vous êtes bloqué. -- Des consignes décrivant le contexte et les objectifs précis. - -Utilisez votre AI Coding Agent préféré, par exemple Claude Code, Cursor ou Trae, pour réaliser les tâches dans le répertoire `starter/`. diff --git a/courses/learn-harness-engineering/docs/fr/projects/project-01-baseline-vs-minimal-harness/index.md b/courses/learn-harness-engineering/docs/fr/projects/project-01-baseline-vs-minimal-harness/index.md deleted file mode 100644 index e8bf0813..00000000 --- a/courses/learn-harness-engineering/docs/fr/projects/project-01-baseline-vs-minimal-harness/index.md +++ /dev/null @@ -1,25 +0,0 @@ -[中文版本 →](../../../zh/projects/project-01-baseline-vs-minimal-harness/) - -> Leçons liées : [Leçon 01. Les modèles forts ne garantissent pas une exécution fiable](./../../lectures/lecture-01-why-capable-agents-still-fail/index.md) · [Leçon 02. Ce que signifie vraiment harness](./../../lectures/lecture-02-what-a-harness-actually-is/index.md) -> Fichiers modèles : [templates/](https://github.com/walkinglabs/learn-harness-engineering/blob/main/docs/fr/resources/templates/) - -# Projet 01. Prompt seul vs règles d'abord : quelle différence cela fait-il ? - -## Ce que vous faites - -Construisez le squelette minimal d'une application Electron de base de connaissances : une fenêtre avec une liste de documents à gauche, un panneau Q&A à droite et un dossier de données local. La tâche elle-même n'est pas complexe. Ce qui est complexe, c'est la manière de faire en sorte que l'agent la termine. - -Vous l'exécutez deux fois. Première fois : seulement un prompt, sans préparation. Deuxième fois : `AGENTS.md`, `init.sh` et `feature_list.json` déjà placés dans le dépôt. Ensuite, comparez. - -Le cœur de ce projet n'est pas d'écrire du code, mais de mesurer l'écart entre "passer 15 minutes à préparer les règles" et "laisser simplement l'agent partir". - -## Outils - -- Claude Code ou Codex (choisissez-en un et gardez-le pour les deux exécutions) -- Git (gérer les branches et comparer) -- Node.js + Electron (stack du projet) -- Un minuteur (noter la durée de chaque exécution) - -## Mécanisme de harness - -Harness minimal : `AGENTS.md` + `init.sh` + `feature_list.json` diff --git a/courses/learn-harness-engineering/docs/fr/projects/project-02-agent-readable-workspace/index.md b/courses/learn-harness-engineering/docs/fr/projects/project-02-agent-readable-workspace/index.md deleted file mode 100644 index b5903df9..00000000 --- a/courses/learn-harness-engineering/docs/fr/projects/project-02-agent-readable-workspace/index.md +++ /dev/null @@ -1,22 +0,0 @@ -[中文版本 →](../../../zh/projects/project-02-agent-readable-workspace/) - -> Leçons liées : [Leçon 03. Faire du dépôt votre source de vérité unique](./../../lectures/lecture-03-why-the-repository-must-become-the-system-of-record/index.md) · [Leçon 04. Répartir les instructions entre plusieurs fichiers](./../../lectures/lecture-04-why-one-giant-instruction-file-fails/index.md) -> Fichiers modèles : [templates/](https://github.com/walkinglabs/learn-harness-engineering/blob/main/docs/fr/resources/templates/) - -# Projet 02. Rendre le projet lisible et reprendre là où vous vous êtes arrêté - -## Ce que vous faites - -Ajoutez de la "lisibilité" au dépôt pour qu'un nouvel agent comprenne rapidement la structure du projet, connaisse l'avancement actuel et reprenne le travail. Concrètement : implémentez l'import de documents, la vue de détail et la persistance locale, en deux sessions. - -Vous l'exécutez deux fois : d'abord sans aide, puis avec `ARCHITECTURE.md`, `PRODUCT.md` et `session-handoff.md` déjà placés dans le dépôt. - -## Outils - -- Claude Code ou Codex -- Git -- Node.js + Electron - -## Mécanisme de harness - -Workspace lisible par l'agent + fichiers d'état persistants diff --git a/courses/learn-harness-engineering/docs/fr/projects/project-03-multi-session-continuity/index.md b/courses/learn-harness-engineering/docs/fr/projects/project-03-multi-session-continuity/index.md deleted file mode 100644 index 1c0cfd57..00000000 --- a/courses/learn-harness-engineering/docs/fr/projects/project-03-multi-session-continuity/index.md +++ /dev/null @@ -1,22 +0,0 @@ -[中文版本 →](../../../zh/projects/project-03-multi-session-continuity/) - -> Leçons liées : [Leçon 05. Garder le contexte vivant entre les sessions](./../../lectures/lecture-05-why-long-running-tasks-lose-continuity/index.md) · [Leçon 06. Initialiser avant chaque session d'agent](./../../lectures/lecture-06-why-initialization-needs-its-own-phase/index.md) -> Fichiers modèles : [templates/](https://github.com/walkinglabs/learn-harness-engineering/blob/main/docs/fr/resources/templates/) - -# Projet 03. Garder l'agent au travail après les redémarrages de session - -## Ce que vous faites - -Ajoutez un contrôle de scope et des portes de vérification à l'agent. Implémentez le découpage de documents, l'extraction de métadonnées, l'affichage de progression d'indexation et un flux Q&A avec citations. Utilisez `feature_list.json` pour suivre l'état des fonctionnalités : une fonctionnalité à la fois, sans marquer `pass` sans preuve de vérification. - -Vous l'exécutez deux fois : d'abord sans contraintes, puis avec application stricte. - -## Outils - -- Claude Code ou Codex -- Git -- Node.js + Electron - -## Mécanisme de harness - -Journal de progression + handoff de session + continuité multi-session diff --git a/courses/learn-harness-engineering/docs/fr/projects/project-04-incremental-indexing/index.md b/courses/learn-harness-engineering/docs/fr/projects/project-04-incremental-indexing/index.md deleted file mode 100644 index fa79ba7f..00000000 --- a/courses/learn-harness-engineering/docs/fr/projects/project-04-incremental-indexing/index.md +++ /dev/null @@ -1,22 +0,0 @@ -[中文版本 →](../../../zh/projects/project-04-incremental-indexing/) - -> Leçons liées : [Leçon 07. Définir des limites de tâche claires pour les agents](./../../lectures/lecture-07-why-agents-overreach-and-under-finish/index.md) · [Leçon 08. Utiliser les listes de fonctionnalités pour contraindre ce que fait l'agent](./../../lectures/lecture-08-why-feature-lists-are-harness-primitives/index.md) -> Fichiers modèles : [templates/](https://github.com/walkinglabs/learn-harness-engineering/blob/main/docs/fr/resources/templates/) - -# Projet 04. Utiliser le feedback runtime pour corriger le comportement de l'agent - -## Ce que vous faites - -Ajoutez de l'observabilité runtime, comme les logs de démarrage, les logs d'import/indexation et les états d'erreur, ainsi que des contraintes d'architecture pour éviter les violations entre couches. Introduisez un bug runtime que l'agent devra corriger. - -Vous l'exécutez deux fois : d'abord sans logs ni contraintes, puis avec les bons outils et règles. - -## Outils - -- Claude Code ou Codex -- Git -- Node.js + Electron - -## Mécanisme de harness - -Feedback runtime + contrôle du scope + indexation incrémentale diff --git a/courses/learn-harness-engineering/docs/fr/projects/project-05-grounded-qa-verification/index.md b/courses/learn-harness-engineering/docs/fr/projects/project-05-grounded-qa-verification/index.md deleted file mode 100644 index ecba5163..00000000 --- a/courses/learn-harness-engineering/docs/fr/projects/project-05-grounded-qa-verification/index.md +++ /dev/null @@ -1,22 +0,0 @@ -[中文版本 →](../../../zh/projects/project-05-grounded-qa-verification/) - -> Leçons liées : [Leçon 09. Empêcher les agents de déclarer la victoire trop tôt](./../../lectures/lecture-09-why-agents-declare-victory-too-early/index.md) · [Leçon 10. Seule une exécution de pipeline complet compte comme vraie vérification](./../../lectures/lecture-10-why-end-to-end-testing-changes-results/index.md) -> Fichiers modèles : [templates/](https://github.com/walkinglabs/learn-harness-engineering/blob/main/docs/fr/resources/templates/) - -# Projet 05. Faire vérifier son propre travail par l'agent - -## Ce que vous faites - -Implémentez la séparation des rôles : un generator qui implémente, un evaluator qui relit, et éventuellement un planner. Exécutez trois fois pour mesurer l'effet de chaque rôle ajouté. - -Choisissez une amélioration substantielle de fonctionnalité, comme une conversation multi-tour, une refonte du panneau de citations ou un filtrage de documents, et gardez-la identique dans toutes les exécutions. - -## Outils - -- Claude Code ou Codex -- Git -- Node.js + Electron - -## Mécanisme de harness - -Auto-vérification + Q&A fondé sur des sources + finalisation basée sur des preuves diff --git a/courses/learn-harness-engineering/docs/fr/projects/project-06-runtime-observability-and-debugging/index.md b/courses/learn-harness-engineering/docs/fr/projects/project-06-runtime-observability-and-debugging/index.md deleted file mode 100644 index d6955ce2..00000000 --- a/courses/learn-harness-engineering/docs/fr/projects/project-06-runtime-observability-and-debugging/index.md +++ /dev/null @@ -1,25 +0,0 @@ -[中文版本 →](../../../zh/projects/project-06-runtime-observability-and-debugging/) - -> Leçons liées : [Leçon 11. Rendre le runtime de l'agent observable](./../../lectures/lecture-11-why-observability-belongs-inside-the-harness/index.md) · [Leçon 12. Handoff propre à la fin de chaque session](./../../lectures/lecture-12-why-every-session-must-leave-a-clean-state/index.md) -> Fichiers modèles : [templates/](https://github.com/walkinglabs/learn-harness-engineering/blob/main/docs/fr/resources/templates/) - -# Projet 06. Construire un harness complet pour agents (capstone) - -## Ce que vous faites - -C'est le projet final. Assemblez tout ce qui a été appris dans les cinq premiers projets, exécutez un benchmark complet, puis faites une passe de nettoyage pour vérifier que la qualité reste maintenable. - -Utilisez un ensemble fixe de tâches multi-fonctionnalités couvrant une tranche complète du produit : import de documents, indexation, Q&A avec citations, observabilité runtime et état de dépôt lisible et redémarrable. Lancez d'abord avec un baseline de harness faible, puis avec votre harness le plus fort, puis nettoyez et relancez. Enfin, réalisez une expérience d'ablation du harness : retirez un composant à la fois et voyez lesquels comptent vraiment. - -## Outils - -- Claude Code ou Codex -- Git -- Node.js + Electron -- Modèle de document qualité -- Grille d'évaluation -- Tous les composants de harness accumulés dans les cinq premiers projets - -## Mécanisme de harness - -Harness complet : tous les mécanismes + observabilité + étude d'ablation diff --git a/courses/learn-harness-engineering/docs/fr/resources/index.md b/courses/learn-harness-engineering/docs/fr/resources/index.md deleted file mode 100644 index 4662b4a8..00000000 --- a/courses/learn-harness-engineering/docs/fr/resources/index.md +++ /dev/null @@ -1,50 +0,0 @@ -# Bibliothèque de ressources en français - -Ce dossier transforme les méthodes du cours en modèles prêts à copier et en références compactes utilisables dans un vrai dépôt. - -## Quand l'utiliser - -Commencez ici lorsque vous voulez que Codex, Claude Code ou un autre agent de codage travaille sur plusieurs sessions sans redécouvrir en permanence le setup, l'état et le scope. - -C'est particulièrement utile lorsque : - -- le travail s'étend sur plusieurs sessions -- les fonctionnalités sont nombreuses et faciles à laisser à moitié terminées -- les agents ont tendance à déclarer la victoire trop tôt -- les étapes de démarrage sont redécouvertes à chaque fois - -## Commencer ici - -Pour une configuration minimale, commencez avec : - -- instructions racine : [`templates/AGENTS.md`](./templates/AGENTS.md) ou [`templates/CLAUDE.md`](./templates/CLAUDE.md) -- état des fonctionnalités : [`templates/feature_list.json`](./templates/feature_list.json) -- journal de progression : [`templates/claude-progress.md`](./templates/claude-progress.md) -- référence du script de bootstrap : `docs/fr/resources/templates/init.sh` - -Ajoutez ensuite : - -- handoff de session : [`templates/session-handoff.md`](./templates/session-handoff.md) -- checklist de sortie propre : [`templates/clean-state-checklist.md`](./templates/clean-state-checklist.md) -- grille d'évaluation : [`templates/evaluator-rubric.md`](./templates/evaluator-rubric.md) - -Si vous voulez une structure de dépôt plus complète, proche de l'article OpenAI "Harness engineering", utilisez le pack avancé : - -- [`openai-advanced/index.md`](./openai-advanced/index.md) - -## Structure de la bibliothèque - -- [`templates/`](./templates/index.md) : modèles à copier dans un vrai dépôt -- [`reference/`](./reference/index.md) : notes de méthode, flux de démarrage et cartes des modes de défaillance -- [`openai-advanced/`](./openai-advanced/index.md) : squelette de dépôt avancé, documents system-of-record et modèles de gouvernance agent-first - -## Pack minimal recommandé - -- `AGENTS.md` ou `CLAUDE.md` -- `feature_list.json` -- `claude-progress.md` -- `init.sh` - -Ces quatre fichiers suffisent à rendre la plupart des workflows d'agents nettement plus stables. - -Lorsque le dépôt devient un système durable avec plusieurs domaines, des plans actifs, une notation qualité et des politiques de fiabilité, passez au pack [`openai-advanced/`](./openai-advanced/index.md) au lieu d'étirer trop loin le pack minimal. diff --git a/courses/learn-harness-engineering/docs/fr/resources/openai-advanced/index.md b/courses/learn-harness-engineering/docs/fr/resources/openai-advanced/index.md deleted file mode 100644 index 9e1bf76c..00000000 --- a/courses/learn-harness-engineering/docs/fr/resources/openai-advanced/index.md +++ /dev/null @@ -1,79 +0,0 @@ -# Pack Avancé OpenAI - -Ce dossier empaquette la structure de dépôt plus opiniâtre décrite dans -l'article d'OpenAI "Harness engineering: leveraging Codex in an agent-first world" -en fichiers de démarrage prêts à copier. - -Utilisez ce pack lorsque le harness minimal ne suffit plus et que votre dépôt -a maintenant besoin de : - -- un `AGENTS.md` court de style routeur -- des docs durables de système d'enregistrement dans le dépôt -- des plans d'exécution actifs et terminés -- des fichiers de politique explicites pour produit, fiabilité, sécurité et frontend -- un scoring qualité par domaine produit et couche architecturale -- des dossiers de matériel de référence adapté aux modèles -- des procédures opérationnelles standard pour l'architecture, la capture de connaissances et la validation d'exécution - -## Mise en Page de Démarrage Incluse - -Le pack de démarrage dans [`repo-template/`](./repo-template/index.md) reflète -la structure suivante : - -```text -AGENTS.md -ARCHITECTURE.md -docs/ -├── design-docs/ -│ ├── index.md -│ └── core-beliefs.md -├── exec-plans/ -│ ├── active/ -│ ├── completed/ -│ └── tech-debt-tracker.md -├── generated/ -│ └── db-schema.md -├── product-specs/ -│ ├── index.md -│ └── new-user-onboarding.md -├── references/ -│ ├── design-system-reference-llms.txt -│ ├── nixpacks-llms.txt -│ └── uv-llms.txt -├── DESIGN.md -├── FRONTEND.md -├── PLANS.md -├── PRODUCT_SENSE.md -├── QUALITY_SCORE.md -├── RELIABILITY.md -└── SECURITY.md -``` - -## Comment l'Adopter - -1. Commencez avec le pack minimal si votre dépôt est encore petit. -2. Copiez les fichiers dans [`repo-template/`](./repo-template/index.md) dans - votre propre dépôt lorsque vous avez besoin d'une structure plus forte. -3. Gardez `AGENTS.md` court. Traitez-le comme un routeur vers les docs plus profonds, pas comme une encyclopédie. -4. Mettez à jour les docs de qualité, fiabilité et plans dans le cadre du travail normal, pas comme un jour de nettoyage séparé. -5. Gardez les artefacts générés et les références externes explicites pour que les agents puissent les trouver sans dépendre de l'historique de chat. - -## Bibliothèque de SOPs - -Le dossier [`sops/`](./sops/index.md) transforme les diagrammes de l'article en -procédures opérationnelles étape par étape : - -- configuration d'architecture de domaine en couches -- encodage de connaissances inédites dans le dépôt -- stack d'observabilité locale et flux de travail de boucle de retour -- boucle de validation Chrome DevTools pour le travail UI - -## Principes de Conception - -- Point d'entrée court, docs plus profonds liés -- Dépôt comme système d'enregistrement -- Les vérifications mécaniques优于 les règles mémorisées -- Les plans et l'historique qualité vivent à côté du code -- Le nettoyage et la simplification sont des responsabilités de premier plan - -Ce pack est intentionnellement opiné, mais doit être adapté à votre projet plutôt que copié aveuglément. diff --git a/courses/learn-harness-engineering/docs/fr/resources/openai-advanced/repo-template/AGENTS.md b/courses/learn-harness-engineering/docs/fr/resources/openai-advanced/repo-template/AGENTS.md deleted file mode 100644 index 17c0e430..00000000 --- a/courses/learn-harness-engineering/docs/fr/resources/openai-advanced/repo-template/AGENTS.md +++ /dev/null @@ -1,61 +0,0 @@ -# AGENTS.md - -Ce dépôt est optimisé pour le travail des agents de codage longue durée. Gardez ce fichier -court. Utilisez-le comme couche de routage vers les documents du système de référence, -et non comme un dump géant d'instructions. - -## Flux de démarrage - -Avant de modifier le code : - -1. Confirmez la racine du dépôt avec `pwd`. -2. Lisez `ARCHITECTURE.md` pour la carte actuelle du système et les règles strictes de dépendance. -3. Lisez `docs/QUALITY_SCORE.md` pour voir quels domaines ou couches sont les plus faibles. -4. Lisez `docs/PLANS.md`, puis ouvrez le plan actif sur lequel vous travaillez. -5. Lisez la spécification produit pertinente dans `docs/product-specs/`. -6. Exécutez le chemin standard d'amorçage et de vérification pour ce dépôt. -7. Si la vérification de base échoue, réparez la ligne de base avant d'ajouter du périmètre. - -## Carte de routage - -- `ARCHITECTURE.md` : carte des domaines, modèle de couches, règles de dépendance -- `docs/design-docs/index.md` : décisions de conception et croyances fondamentales -- `docs/product-specs/index.md` : comportements actuels du produit et critères d'acceptation -- `docs/PLANS.md` : cycle de vie des plans et politique des plans d'exécution -- `docs/QUALITY_SCORE.md` : santé des domaines produit et des couches -- `docs/RELIABILITY.md` : signaux d'exécution, benchmarks et attentes de redémarrage -- `docs/SECURITY.md` : secrets, sandbox, données et règles d'actions externes -- `docs/FRONTEND.md` : contraintes UI, règles du système de design, vérifications d'accessibilité - -## Contrat de travail - -- Travaillez à partir d'un plan délimité ou d'une tranche de fonctionnalité à la fois. -- Ne marquez pas le travail comme terminé par simple inspection du code ; une preuve - exécutable est requise. -- Si vous modifiez un comportement, mettez à jour les documents produit, plan ou fiabilité - correspondants dans la même session. -- Si vous observez des retours de revue répétés, promouvez-les en une règle mécanique, - un contrôle ou un linter plutôt que de les réexpliquer dans le chat. -- Gardez le contenu généré dans `docs/generated/` et les références sources dans - `docs/references/`. -- Préférez ajouter de petits documents à jour plutôt que de faire grossir ce fichier. - -## Définition de « terminé » - -Une modification n'est terminée que lorsque toutes les conditions suivantes sont remplies : - -- le comportement cible est implémenté -- la vérification requise a réellement été exécutée -- la preuve est liée depuis le plan ou le document de qualité pertinent -- les documents affectés restent à jour -- le dépôt peut redémarrer proprement depuis le chemin de démarrage standard - -## Fin de session - -Avant de terminer une session : - -1. Mettez à jour le plan d'exécution actif. -2. Mettez à jour `docs/QUALITY_SCORE.md` si un domaine ou une couche a changé de manière significative. -3. Enregistrez la nouvelle dette dans `docs/exec-plans/tech-debt-tracker.md` si vous l'avez différée. -4. Déplacez les plans terminés vers `docs/exec-plans/completed/` si approprié. -5. Laissez le dépôt dans un état redémarrable avec une prochaine action claire. diff --git a/courses/learn-harness-engineering/docs/fr/resources/openai-advanced/repo-template/ARCHITECTURE.md b/courses/learn-harness-engineering/docs/fr/resources/openai-advanced/repo-template/ARCHITECTURE.md deleted file mode 100644 index a4f6618a..00000000 --- a/courses/learn-harness-engineering/docs/fr/resources/openai-advanced/repo-template/ARCHITECTURE.md +++ /dev/null @@ -1,63 +0,0 @@ -# ARCHITECTURE.md - -Ce fichier est la carte de niveau supérieur du système. Il doit rester concis et -renvoyer vers des documents plus détaillés si nécessaire. - -## Forme du système - -- Produit : `[replace with product name]` -- Flux utilisateur principal : `[replace with main workflow]` -- Surfaces d'exécution : `[desktop / web / cli / services / workers]` -- Source de vérité pour le comportement produit : `docs/product-specs/` - -## Carte des domaines - -| Domaine | Objectif | Points d'entrée principaux | Spécification associée | -|---------|----------|---------------------------|----------------------| -| `[domain-a]` | `[what it owns]` | `[modules / routes / commands]` | `[spec path]` | -| `[domain-b]` | `[what it owns]` | `[modules / routes / commands]` | `[spec path]` | - -## Modèle de couches - -Utilisez un modèle directionnel fixe pour que les agents n'inventent pas une architecture -ad hoc : - -`Types -> Config -> Repo -> Service -> Runtime -> UI` - -Les préoccupations transversales doivent entrer par des frontières explicites de -fournisseur ou d'adaptateur plutôt que d'atteindre directement à travers les couches. - -## Règles strictes de dépendance - -- Les couches inférieures ne doivent pas dépendre des couches supérieures. -- L'UI ne doit pas contourner les contrats d'exécution ou de service. -- L'accès aux données doit passer par des dépôts ou des adaptateurs équivalents. -- Les utilitaires partagés doivent rester génériques et ne doivent pas accumuler - de logique métier. -- Les nouvelles dépendances doivent être justifiées dans le plan ou le document - de conception correspondant. - -## Interfaces transversales - -| Préoccupation | Frontière approuvée | Notes | -|--------------|--------------------| -----| -| Journalisation et traçage | `[provider / utility path]` | `[structured only, no ad hoc console use]` | -| Authentification | `[provider path]` | `[token/session rules]` | -| APIs externes | `[client or provider path]` | `[rate limit / retry guidance]` | -| Feature flags | `[flag boundary]` | `[ownership]` | - -## Points chauds actuels - -- `[zone la plus difficile à modifier en toute sécurité pour les agents]` -- `[zone avec des limites faibles ou des tests fragiles]` - -## Liste de contrôle des modifications - -Lorsque vous modifiez du code pertinent pour l'architecture : - -1. Mettez à jour ce fichier si la carte des domaines ou les frontières autorisées - ont changé. -2. Mettez à jour le document de conception associé dans `docs/design-docs/` si le - raisonnement a changé. -3. Ajoutez ou mettez à jour un contrôle exécutable si la règle doit être appliquée - mécaniquement. diff --git a/courses/learn-harness-engineering/docs/fr/resources/openai-advanced/repo-template/docs/DESIGN.md b/courses/learn-harness-engineering/docs/fr/resources/openai-advanced/repo-template/docs/DESIGN.md deleted file mode 100644 index 68ca18cf..00000000 --- a/courses/learn-harness-engineering/docs/fr/resources/openai-advanced/repo-template/docs/DESIGN.md +++ /dev/null @@ -1,28 +0,0 @@ -# DESIGN.md - -Ce fichier est le point d'entrée de la conception. Gardez-le bref et utilisez-le pour -router vers les fichiers plus détaillés sous `docs/design-docs/`. - -## Objectif - -Enregistrer les décisions de conception produit et système durables qui doivent survivre -au-delà d'une seule conversation, sprint ou mémoire de relecteur. - -## Lisez ceci quand - -- vous avez besoin de la philosophie de conception actuelle -- vous êtes sur le point d'introduire un nouveau motif -- vous devez savoir quelles décisions de conception sont tranchées versus encore ouvertes - -## Documents de conception canoniques - -- `docs/design-docs/index.md` : index des documents acceptés, proposés et dépréciés -- `docs/design-docs/core-beliefs.md` : croyances agent-first à l'échelle du projet - -## Règles de conception - -- Gardez les documents de conception petits et à jour. -- Préférez un document par domaine de décision. -- Liez les documents de conception depuis les plans et spécifications quand un changement en dépend. -- Si une règle de conception devient critique opérationnellement, promouvez-la en un contrôle - automatisé ou mettez à jour `ARCHITECTURE.md`. diff --git a/courses/learn-harness-engineering/docs/fr/resources/openai-advanced/repo-template/docs/FRONTEND.md b/courses/learn-harness-engineering/docs/fr/resources/openai-advanced/repo-template/docs/FRONTEND.md deleted file mode 100644 index 9e4a2958..00000000 --- a/courses/learn-harness-engineering/docs/fr/resources/openai-advanced/repo-template/docs/FRONTEND.md +++ /dev/null @@ -1,28 +0,0 @@ -# FRONTEND.md - -Ce fichier définit les attentes stables du frontend pour que les agents n'inventent pas -des motifs d'interface de manière imprévisible. - -## Principes d'UI - -- Optimisez pour la clarté avant la nouveauté. -- Gardez les flux d'interaction découvrables et redémarrables. -- Préférez un petit nombre de composants réutilisables plutôt que des variantes à usage unique. -- Les vérifications d'accessibilité font partie de la vérification normale, pas du travail - de finition. - -## Garde-fous - -- Documentez le système de design ou la bibliothèque de composants dans `docs/references/`. -- Enregistrez les états clés visibles par l'utilisateur : vide, chargement, succès, erreur, - nouvelle tentative. -- Gardez le texte, le comportement clavier et la hiérarchie visuelle cohérents entre les flux. -- Lorsqu'un bug d'interface est corrigé, ajoutez ou mettez à jour l'étape de validation - correspondante. - -## Attentes de vérification - -- Capturez des preuves pour les parcours utilisateur critiques. -- Enregistrez les étapes de validation navigateur ou d'exécution dans le plan pertinent. -- Si les régressions visuelles sont fréquentes, standardisez les vérifications par captures - d'écran ou contrôles du DOM. diff --git a/courses/learn-harness-engineering/docs/fr/resources/openai-advanced/repo-template/docs/PLANS.md b/courses/learn-harness-engineering/docs/fr/resources/openai-advanced/repo-template/docs/PLANS.md deleted file mode 100644 index c33dede4..00000000 --- a/courses/learn-harness-engineering/docs/fr/resources/openai-advanced/repo-template/docs/PLANS.md +++ /dev/null @@ -1,35 +0,0 @@ -# PLANS.md - -Ce fichier définit la manière dont les plans d'exécution sont créés, mis à jour, complétés -et archivés. - -## Quand un plan est nécessaire - -Créez un plan d'exécution lorsque le travail : - -- s'étend sur plus d'une session -- modifie plus d'un sous-système -- comporte des risques de vérification ou de déploiement non négligeables -- dépend de décisions en cours qui doivent être consignées - -## Emplacements des plans - -- `docs/exec-plans/active/` : plans pilotant actuellement le travail -- `docs/exec-plans/completed/` : plans terminés conservés pour le contexte futur des agents -- `docs/exec-plans/tech-debt-tracker.md` : travail différé et suivis - -## Sections minimales d'un plan - -- objectif -- périmètre et hors périmètre -- chemin de vérification -- risques et obstacles -- journal de progression -- décisions en cours - -## Règles de fonctionnement - -- Un plan actif doit avoir une étape actuelle clairement attribuée. -- Mettez à jour le plan au fil de l'avancement ; ne le traitez pas comme un texte statique. -- Si une décision modifie la direction de l'implémentation, consignez-la dans le plan. -- Déplacez les plans terminés vers `completed/` afin que les agents puissent encore retrouver le contexte antérieur. diff --git a/courses/learn-harness-engineering/docs/fr/resources/openai-advanced/repo-template/docs/PRODUCT_SENSE.md b/courses/learn-harness-engineering/docs/fr/resources/openai-advanced/repo-template/docs/PRODUCT_SENSE.md deleted file mode 100644 index 02b16006..00000000 --- a/courses/learn-harness-engineering/docs/fr/resources/openai-advanced/repo-template/docs/PRODUCT_SENSE.md +++ /dev/null @@ -1,25 +0,0 @@ -# PRODUCT_SENSE.md - -Ce fichier capture le jugement produit durable que les agents ne peuvent pas -inférer de manière fiable uniquement à partir du code. - -## Cœur du Produit - -- Utilisateur principal : `[remplacer]` -- Travail à accomplir : `[remplacer]` -- Principale frustration à éliminer : `[remplacer]` -- Niveau de qualité pour l'acceptation : `[remplacer]` - -## Règles Produit - -- Favoriser la fiabilité visible par l'utilisateur plutôt que le nombre de fonctionnalités. -- Traiter le comportement ambigu comme une lacune de spécification, pas comme une permission de deviner. -- Si l'implémentation change ce que les utilisateurs voient ou en qui ils ont confiance, mettre à jour la spécification correspondante. -- Utiliser les spécifications produit pour les flux concrets, et utiliser ce fichier pour les priorités produit transversales. - -## Motifs à Éviter - -- Actions destructrices cachées -- Échec silencieux sans retour utilisateur -- Source de vérité peu claire pour l'état visible -- Fonctionnalités qui ne peuvent pas être expliquées en une seule phrase diff --git a/courses/learn-harness-engineering/docs/fr/resources/openai-advanced/repo-template/docs/QUALITY_SCORE.md b/courses/learn-harness-engineering/docs/fr/resources/openai-advanced/repo-template/docs/QUALITY_SCORE.md deleted file mode 100644 index 8bfdd2cc..00000000 --- a/courses/learn-harness-engineering/docs/fr/resources/openai-advanced/repo-template/docs/QUALITY_SCORE.md +++ /dev/null @@ -1,37 +0,0 @@ -# QUALITY_SCORE.md - -Ce document permet de suivre si le dépôt s'améliore ou se dégrade au fil du temps. - -## Échelle de notation - -- `A` : vérifié, lisible, stable, limites appliquées -- `B` : fonctionnel avec des lacunes mineures -- `C` : partiellement fonctionnel, confusion ou instabilité notable -- `D` : cassé, non sûr ou structurellement peu clair - -## Domaines produit - -| Domaine | Note | Vérification | Lisibilité agent | Stabilité des tests | Lacunes principales | Dernière mise à jour | -|---------|------|-------------|-----------------|--------------------|--------------------|---------------------| -| `[domain-a]` | - | - | - | - | - | - | -| `[domain-b]` | - | - | - | - | - | - | -| `[domain-c]` | - | - | - | - | - | - | - -## Couches architecturales - -| Couche | Note | Application des limites | Lisibilité agent | Lacunes principales | Dernière mise à jour | -|--------|------|------------------------|-----------------|--------------------|---------------------| -| Types | - | - | - | - | - | -| Services | - | - | - | - | - | -| Runtime | - | - | - | - | - | -| UI | - | - | - | - | - | - -| Date | Variante de harnais | Taux de complétion | Tentatives | Défauts avant revue | Notes | -|------|---------------------|--------------------|------------|--------------------|-------| -| YYYY-MM-DD | `[baseline / improved / simplified]` | - | - | - | - | - -## Journal de simplification - -| Date | Composant retiré | Résultat | Décision | -|------|-----------------|----------|----------| -| YYYY-MM-DD | `[component]` | `[degraded / unchanged]` | `[restore / keep removed]` | diff --git a/courses/learn-harness-engineering/docs/fr/resources/openai-advanced/repo-template/docs/RELIABILITY.md b/courses/learn-harness-engineering/docs/fr/resources/openai-advanced/repo-template/docs/RELIABILITY.md deleted file mode 100644 index b0463f1f..00000000 --- a/courses/learn-harness-engineering/docs/fr/resources/openai-advanced/repo-template/docs/RELIABILITY.md +++ /dev/null @@ -1,36 +0,0 @@ -# RELIABILITY.md - -Ce fichier définit comment le système prouve qu'il est sain et redémarrable. - -## Chemins standard - -- Amorçage : `[command]` -- Vérification : `[command]` -- Lancement de l'application ou du service : `[command]` -- Débogage ou inspection de l'exécution : `[command]` - -## Signaux d'exécution requis - -- journaux structurés pour le démarrage et les flux critiques -- vérifications de santé pour les services clés -- données de trace ou de minutage pour les chemins lents lorsque disponibles -- états d'erreur visibles par l'utilisateur pour les défaillances récupérables - -## Parcours de référence - -- `[parcours 1]` -- `[parcours 2]` -- `[parcours 3]` - -Chaque parcours de référence doit avoir un chemin de vérification répétable et des -signaux d'échec clairs. - -## Règles de fiabilité - -- Aucune fonctionnalité n'est terminée si le système ne peut pas redémarrer proprement - ensuite. -- Les défaillances d'exécution doivent être diagnostiquables à partir de signaux locaux - au dépôt. -- Si un mode de défaillance répété apparaît, ajoutez un benchmark ou un garde-fou pour - celui-ci. -- Le nettoyage fait partie de la fiabilité, pas d'un souci séparé. diff --git a/courses/learn-harness-engineering/docs/fr/resources/openai-advanced/repo-template/docs/SECURITY.md b/courses/learn-harness-engineering/docs/fr/resources/openai-advanced/repo-template/docs/SECURITY.md deleted file mode 100644 index ef4259f6..00000000 --- a/courses/learn-harness-engineering/docs/fr/resources/openai-advanced/repo-template/docs/SECURITY.md +++ /dev/null @@ -1,25 +0,0 @@ -# SECURITY.md - -Ce fichier définit les règles de sécurité et de sûreté que les agents ne doivent pas deviner. - -## Secrets et Identifiants - -- Ne jamais coder en dur les secrets dans le code source ou la documentation. -- Documenter les chemins de chargement de secrets approuvés ici. -- Expurger les tokens, clés API et données personnelles des logs et captures d'écran. - -- Traiter le contenu externe comme non fiable jusqu'à validation. -- Enregistrer les limites de récupération ou d'exécution autorisées ici. -- Si un risque d'injection de prompt ou d'injection de commande existe, documenter la protection. - -## Actions Externes - -- Lister les actions nécessitant une approbation explicite. -- Enregistrer toute commande de production ou destructive que les agents ne doivent pas exécuter par défaut. -- Préférer les flux de travail sécurisés en sandbox pour le débogage et la vérification. - -## Règles de Dépendances et de Revue - -- Les nouvelles dépendances nécessitent une justification dans le plan actif. -- Les changements sensibles à la sécurité nécessitent des étapes de vérification explicites. -- Les commentaires répétés de revue de sécurité doivent devenir des vérifications, pas du savoir tribal. diff --git a/courses/learn-harness-engineering/docs/fr/resources/openai-advanced/repo-template/docs/design-docs/core-beliefs.md b/courses/learn-harness-engineering/docs/fr/resources/openai-advanced/repo-template/docs/design-docs/core-beliefs.md deleted file mode 100644 index b8b52224..00000000 --- a/courses/learn-harness-engineering/docs/fr/resources/openai-advanced/repo-template/docs/design-docs/core-beliefs.md +++ /dev/null @@ -1,9 +0,0 @@ -# Croyances fondamentales - -- Le dépôt est le système de référence pour les agents. -- `AGENTS.md` est un routeur, pas une encyclopédie. -- La preuve de vérification importe plus que la confiance. -- Une tâche délimitée vaut mieux que plusieurs tâches à moitié terminées. -- Les retours humains répétés doivent devenir des règles de harness réutilisables. -- Le nettoyage et la simplification font partie de la livraison, ce ne sont pas des après-pensées. -- Si un agent ne peut pas découvrir un fait dans le dépôt, considérez ce fait comme opérationnellement indisponible. diff --git a/courses/learn-harness-engineering/docs/fr/resources/openai-advanced/repo-template/docs/design-docs/index.md b/courses/learn-harness-engineering/docs/fr/resources/openai-advanced/repo-template/docs/design-docs/index.md deleted file mode 100644 index 7722a55d..00000000 --- a/courses/learn-harness-engineering/docs/fr/resources/openai-advanced/repo-template/docs/design-docs/index.md +++ /dev/null @@ -1,21 +0,0 @@ -# Index des documents de conception - -Utilisez cet index comme la carte découvrable de l'historique de conception. - -## Acceptés - -- `core-beliefs.md` : croyances opérationnelles agent-first et normes durables du projet - -## Proposés - -- `[add new design doc paths here]` - -## Dépréciés - -- `[move old or superseded design docs here with replacement links]` - -## Règles de maintenance - -- Chaque document de conception devrait avoir un propriétaire ou un déclencheur de mise à jour. -- Supprimez les documents obsolètes ou marquez-les comme dépréciés plutôt que de les laisser dériver. -- Liez les plans d'exécution actifs aux documents de conception dont ils dépendent. diff --git a/courses/learn-harness-engineering/docs/fr/resources/openai-advanced/repo-template/docs/exec-plans/active/index.md b/courses/learn-harness-engineering/docs/fr/resources/openai-advanced/repo-template/docs/exec-plans/active/index.md deleted file mode 100644 index 128422a2..00000000 --- a/courses/learn-harness-engineering/docs/fr/resources/openai-advanced/repo-template/docs/exec-plans/active/index.md +++ /dev/null @@ -1,10 +0,0 @@ -# Plans actifs - -Gardez un fichier markdown par plan d'exécution actif dans ce dossier. - -Modèle de nommage suggéré : - -- `YYYY-MM-DD-short-topic.md` - -Chaque plan actif devrait être suffisamment à jour pour qu'une nouvelle session d'agent -puisse reprendre le travail à partir du seul dépôt. diff --git a/courses/learn-harness-engineering/docs/fr/resources/openai-advanced/repo-template/docs/exec-plans/completed/index.md b/courses/learn-harness-engineering/docs/fr/resources/openai-advanced/repo-template/docs/exec-plans/completed/index.md deleted file mode 100644 index 43792519..00000000 --- a/courses/learn-harness-engineering/docs/fr/resources/openai-advanced/repo-template/docs/exec-plans/completed/index.md +++ /dev/null @@ -1,5 +0,0 @@ -# Plans terminés - -Déplacez les plans terminés ici au lieu de les supprimer. Les plans terminés font partie -de la surface de mémoire du dépôt et aident les exécutions ultérieures des agents à -comprendre pourquoi le code se présente tel qu'il est. diff --git a/courses/learn-harness-engineering/docs/fr/resources/openai-advanced/repo-template/docs/exec-plans/tech-debt-tracker.md b/courses/learn-harness-engineering/docs/fr/resources/openai-advanced/repo-template/docs/exec-plans/tech-debt-tracker.md deleted file mode 100644 index 3180e24e..00000000 --- a/courses/learn-harness-engineering/docs/fr/resources/openai-advanced/repo-template/docs/exec-plans/tech-debt-tracker.md +++ /dev/null @@ -1,7 +0,0 @@ -# Suivi de la dette technique - -Utilisez ce fichier pour la dette qui est réelle, reconnue et intentionnellement différée. - -| Date | Domaine | Dette | Pourquoi différée | Risque | Prochain déclencheur | -|------|---------|-------|-------------------|--------|----------------------| -| YYYY-MM-DD | `[area]` | `[debt]` | `[reason]` | `[risk]` | `[when to revisit]` | diff --git a/courses/learn-harness-engineering/docs/fr/resources/openai-advanced/repo-template/docs/generated/db-schema.md b/courses/learn-harness-engineering/docs/fr/resources/openai-advanced/repo-template/docs/generated/db-schema.md deleted file mode 100644 index bbfeb955..00000000 --- a/courses/learn-harness-engineering/docs/fr/resources/openai-advanced/repo-template/docs/generated/db-schema.md +++ /dev/null @@ -1,14 +0,0 @@ -# Schéma de base de données - -Utilisez ce dossier pour les artefacts générés ou dérivés que les agents doivent pouvoir -inspecter sans avoir à les reconstituer à partir du code. - -## Source - -- Généré depuis : `[command or source path]` -- Dernière actualisation : `YYYY-MM-DD` - -## Notes - -- Ne modifiez pas manuellement les sections générées. -- Régénérez ce fichier lorsque le schéma sous-jacent change. diff --git a/courses/learn-harness-engineering/docs/fr/resources/openai-advanced/repo-template/docs/product-specs/index.md b/courses/learn-harness-engineering/docs/fr/resources/openai-advanced/repo-template/docs/product-specs/index.md deleted file mode 100644 index 7307936b..00000000 --- a/courses/learn-harness-engineering/docs/fr/resources/openai-advanced/repo-template/docs/product-specs/index.md +++ /dev/null @@ -1,14 +0,0 @@ -# Index des spécifications produit - -Utilisez ce dossier pour les spécifications actuelles du comportement visible par l'utilisateur. - -## Spécifications actives - -- `new-user-onboarding.md` - -## Règles - -- Les spécifications doivent décrire le comportement visible par l'utilisateur et les critères d'acceptation. -- Si l'implémentation diverge de la spécification, mettez à jour l'une d'entre elles au cours de la même - session. -- Maintenez cet index à jour pour qu'un nouvel agent puisse découvrir rapidement le périmètre produit. diff --git a/courses/learn-harness-engineering/docs/fr/resources/openai-advanced/repo-template/docs/product-specs/new-user-onboarding.md b/courses/learn-harness-engineering/docs/fr/resources/openai-advanced/repo-template/docs/product-specs/new-user-onboarding.md deleted file mode 100644 index 0c866ee9..00000000 --- a/courses/learn-harness-engineering/docs/fr/resources/openai-advanced/repo-template/docs/product-specs/new-user-onboarding.md +++ /dev/null @@ -1,26 +0,0 @@ -# Accueil des nouveaux utilisateurs - -## Objectif - -Décrire l'expérience de première utilisation qu'un nouvel utilisateur devrait avoir. - -## Conditions d'entrée - -- `[état avant le début du parcours]` - -## Parcours utilisateur - -1. `[étape une]` -2. `[étape deux]` -3. `[étape trois]` - -## Critères d'acceptation - -- `[résultat observable]` -- `[résultat observable]` -- `[résultat observable]` - -## États d'échec - -- `[erreur récupérable et retour utilisateur]` -- `[état bloqué et solution de repli]` diff --git a/courses/learn-harness-engineering/docs/fr/resources/openai-advanced/repo-template/docs/references/design-system-reference-llms.txt b/courses/learn-harness-engineering/docs/fr/resources/openai-advanced/repo-template/docs/references/design-system-reference-llms.txt deleted file mode 100644 index 6f1c612c..00000000 --- a/courses/learn-harness-engineering/docs/fr/resources/openai-advanced/repo-template/docs/references/design-system-reference-llms.txt +++ /dev/null @@ -1,9 +0,0 @@ -Purpose: store model-friendly reference material for the design system. - -Suggested contents: -- component naming rules -- spacing and typography tokens -- state variants -- accessibility expectations - -Keep this file concise and refresh it when the upstream design system changes. diff --git a/courses/learn-harness-engineering/docs/fr/resources/openai-advanced/repo-template/docs/references/nixpacks-llms.txt b/courses/learn-harness-engineering/docs/fr/resources/openai-advanced/repo-template/docs/references/nixpacks-llms.txt deleted file mode 100644 index c30cc9c2..00000000 --- a/courses/learn-harness-engineering/docs/fr/resources/openai-advanced/repo-template/docs/references/nixpacks-llms.txt +++ /dev/null @@ -1,8 +0,0 @@ -Purpose: store a clean agent-readable extract of the deployment or packaging -rules your repository depends on. - -Suggested contents: -- build entrypoints -- runtime assumptions -- environment variable expectations -- common failure signatures diff --git a/courses/learn-harness-engineering/docs/fr/resources/openai-advanced/repo-template/docs/references/uv-llms.txt b/courses/learn-harness-engineering/docs/fr/resources/openai-advanced/repo-template/docs/references/uv-llms.txt deleted file mode 100644 index f2ceba9a..00000000 --- a/courses/learn-harness-engineering/docs/fr/resources/openai-advanced/repo-template/docs/references/uv-llms.txt +++ /dev/null @@ -1,8 +0,0 @@ -Purpose: store a compact reference for your Python package and environment -workflow when `uv` or similar tooling matters to the repo. - -Suggested contents: -- install and sync commands -- lockfile policy -- virtualenv expectations -- verification commands diff --git a/courses/learn-harness-engineering/docs/fr/resources/openai-advanced/repo-template/index.md b/courses/learn-harness-engineering/docs/fr/resources/openai-advanced/repo-template/index.md deleted file mode 100644 index 8840fd41..00000000 --- a/courses/learn-harness-engineering/docs/fr/resources/openai-advanced/repo-template/index.md +++ /dev/null @@ -1,25 +0,0 @@ -# Modèle de dépôt avancé - -Copiez ce modèle de départ dans un véritable dépôt lorsque vous souhaitez une surface -de documentation agent-first de style OpenAI au lieu d'un simple harnais minimal. - -## Ordre de copie - -1. Copiez `AGENTS.md` et `ARCHITECTURE.md` à la racine du dépôt. -2. Copiez l'arborescence complète `docs/`. -3. Remplissez d'abord `docs/PRODUCT_SENSE.md`, `docs/QUALITY_SCORE.md` et - `docs/RELIABILITY.md`. -4. Ajoutez votre premier plan actif sous `docs/exec-plans/active/`. -5. Gardez les fichiers d'entrée courts et acheminez les détails vers les documents liés. - -## Ce pour quoi ce modèle est optimisé - -- contexte durable local au dépôt -- divulgation progressive au lieu d'un seul fichier d'instructions géant -- cycle de vie explicite des plans -- suivi de la qualité au fil du temps -- limites lisibles pour les agents et les humains - -Considérez chaque fichier ici comme un point de départ. Remplacez les substituts, -exemples et commandes d'exemple par les spécifications réelles de votre projet avant -de vous y fier. diff --git a/courses/learn-harness-engineering/docs/fr/resources/openai-advanced/sops/chrome-devtools-validation-loop.md b/courses/learn-harness-engineering/docs/fr/resources/openai-advanced/sops/chrome-devtools-validation-loop.md deleted file mode 100644 index df7f0609..00000000 --- a/courses/learn-harness-engineering/docs/fr/resources/openai-advanced/sops/chrome-devtools-validation-loop.md +++ /dev/null @@ -1,52 +0,0 @@ -# SOP : Boucle de validation Chrome DevTools - -Utilisez cette SOP lorsque le travail sur l'interface utilisateur dépend d'interactions -réelles à l'exécution et que les captures d'écran, l'état du DOM et la sortie console -sont plus importants que la simple inspection du code. - -## Objectif - -Transformer la validation de l'interface utilisateur en une boucle d'interaction -répétable que l'agent peut exécuter jusqu'à ce que le parcours soit propre. - -## Boucle principale - -1. Sélectionner la page cible ou l'instance de l'application. -2. Effacer le bruit de console obsolète. -3. Capturer l'état AVANT. -4. Déclencher le parcours dans l'interface. -5. Observer les événements d'exécution pendant l'interaction. -6. Capturer l'état APRÈS. -7. Appliquer le correctif et redémarrer l'application si nécessaire. -8. Relancer la validation jusqu'à ce que le parcours soit propre. - -## Entrées requises - -- une commande de démarrage stable -- un parcours d'interface reproductible -- un moyen de capturer le DOM, la console ou des captures d'écran -- une règle définissant ce qui est considéré comme « propre » - -## SOP d'exécution - -1. Écrire le parcours cible dans le plan actif. -2. Définir le succès en termes observables : texte présent, bouton activé, - erreur disparue, console propre, requête réussie. -3. Capturer l'état initial avant l'interaction. -4. Déclencher exactement un seul parcours à la fois. -5. Enregistrer les événements d'exécution, les changements du DOM et la sortie visible. -6. Si le parcours échoue, corriger la couche responsable la plus petite et redémarrer. -7. Relancer le même parcours et comparer les preuves AVANT/APRÈS. - -## Critères de propreté - -- l'état visible attendu est présent -- les erreurs inattendues sont absentes -- le bruit de console est compris ou effacé -- relancer le même parcours donne le même résultat - -## Artefacts du dépôt à mettre à jour - -- plan d'exécution actif -- `docs/RELIABILITY.md` si le parcours devient un chemin de référence -- spécification produit si le comportement visible a changé diff --git a/courses/learn-harness-engineering/docs/fr/resources/openai-advanced/sops/encode-knowledge-into-repo.md b/courses/learn-harness-engineering/docs/fr/resources/openai-advanced/sops/encode-knowledge-into-repo.md deleted file mode 100644 index 4ad8e559..00000000 --- a/courses/learn-harness-engineering/docs/fr/resources/openai-advanced/sops/encode-knowledge-into-repo.md +++ /dev/null @@ -1,48 +0,0 @@ -# SOP : Encoder les connaissances invisibles dans le dépôt - -Utilisez cette SOP lorsque du contexte important se trouve encore dans Google Docs, -les fils de discussion, les tickets ou dans la tête des gens. - -## Objectif - -Rendre les connaissances invisibles pour l'agent découvrables dans le codebase -pour qu'une nouvelle session puisse agir dessus sans s'appuyer sur une conversation antérieure. - -## Signaux déclencheurs - -- L'agent demande constamment comment fonctionne le système. -- Des humains disent « on a décidé ça sur Slack » ou « suis ce que X a dit la semaine dernière. » -- Les revues font référence à des règles produit ou de sécurité qui ne sont pas écrites dans le dépôt. -- Les nouvelles sessions répètent un travail de découverte qui devrait déjà être établi. - -## SOP d'exécution - -1. Lister les sources de connaissances invisibles : docs, chats, règles d'équipe implicites, - décisions verbales. -2. Pour chaque source, se demander : s'agit-il d'architecture, de comportement produit, - de politique de sécurité, d'attente de fiabilité, de contexte de planification ou de - matériel de référence ? -3. L'encoder dans l'artefact de dépôt correspondant : - - architecture -> `ARCHITECTURE.md` - - comportement produit -> `docs/product-specs/` - - justification de conception -> `docs/design-docs/` - - état d'exécution -> `docs/exec-plans/` - - références externes répétées -> `docs/references/` - - attentes de qualité ou de fiabilité -> `docs/QUALITY_SCORE.md` ou `docs/RELIABILITY.md` -4. Remplacer les formulations vagues par un langage opérationnellement utile. -5. Supprimer ou déprécier les copies obsolètes pour que le dépôt conserve - une seule vérité découvrable. - -## Règles de bon encodage - -- Écrire pour la découvabilité, pas pour l'exhaustivité littéraire. -- Préférer des documents courts avec des noms de fichiers clairs. -- Lier les artefacts connexes entre eux. -- Stocker des règles durables, pas des comptes rendus de réunion. -- Mettre à jour le dépôt dans la même session où la décision est prise. - -## Définition du « fini » - -- Un nouvel agent peut découvrir la règle concernée sans demander à un humain. -- Le même fait n'est pas dispersé dans plusieurs fichiers contradictoires. -- Le nouvel artefact se trouve à proximité du code ou du flux de travail qu'il régit. diff --git a/courses/learn-harness-engineering/docs/fr/resources/openai-advanced/sops/index.md b/courses/learn-harness-engineering/docs/fr/resources/openai-advanced/sops/index.md deleted file mode 100644 index 934fabba..00000000 --- a/courses/learn-harness-engineering/docs/fr/resources/openai-advanced/sops/index.md +++ /dev/null @@ -1,27 +0,0 @@ -# SOP OpenAI avancées - -Ces SOP traduisent les modèles de fonctionnement de l'article en playbooks -d'exécution concrets que vous pouvez suivre ou adapter. - -## SOP incluses - -- [`layered-domain-architecture.md`](./layered-domain-architecture.md) : - établir des couches explicites et des limites transversales -- [`encode-knowledge-into-repo.md`](./encode-knowledge-into-repo.md) : - déplacer les connaissances invisibles depuis les chats, docs et mémoires - vers des fichiers locaux au dépôt -- [`observability-feedback-loop.md`](./observability-feedback-loop.md) : - donner aux agents des journaux, métriques, traces et une boucle de débogage répétable -- [`chrome-devtools-validation-loop.md`](./chrome-devtools-validation-loop.md) : - utiliser l'automatisation de navigateur et des captures pour valider le comportement - de l'interface utilisateur jusqu'à ce qu'il soit propre - -## Comment les utiliser - -1. Choisissez la SOP qui correspond à votre goulot d'étranglement actuel. -2. Utilisez la liste de contrôle pour mettre en place les artefacts ou outils manquants. -3. Encodez les règles résultantes dans les docs de votre copie de `repo-template/`. -4. Convertissez les commentaires de revue répétés en vérifications, scripts ou garde-fous. - -Ces SOP ne sont pas destinées à être suivies aveuglément. Elles ont pour but de rendre -le harnais plus lisible, applicable et répétable. diff --git a/courses/learn-harness-engineering/docs/fr/resources/openai-advanced/sops/layered-domain-architecture.md b/courses/learn-harness-engineering/docs/fr/resources/openai-advanced/sops/layered-domain-architecture.md deleted file mode 100644 index ff7ae735..00000000 --- a/courses/learn-harness-engineering/docs/fr/resources/openai-advanced/sops/layered-domain-architecture.md +++ /dev/null @@ -1,55 +0,0 @@ -# SOP : Architecture en couches par domaine - -Utilisez cette SOP lorsque l'agent enfreint constamment les limites, duplique -la logique entre les couches, ou produit du code qui devient difficile à réviser -après quelques sessions. - -## Objectif - -Rendre les limites de domaine suffisamment explicites pour que les agents puissent -avancer rapidement sans dégrader silencieusement la structure. - -## Modèle cible - -Au sein d'un domaine métier, privilégiez ce flux directionnel : - -`Types -> Config -> Repo -> Service -> Runtime -> UI` - -Les préoccupations transversales doivent entrer par des fournisseurs ou adaptateurs -explicites. Les utilitaires partagés restent en dehors du domaine et ne doivent pas -accumuler de logique métier. - -## Liste de contrôle de configuration - -- Définir les domaines actuels dans `ARCHITECTURE.md`. -- Écrire les directions de dépendance autorisées dans `ARCHITECTURE.md`. -- Enregistrer les interfaces transversales telles que l'authentification, - la télémétrie et les API externes. -- Ajouter une note courte pour la violation de limite la plus difficile actuellement. -- Décider ce qui doit être appliqué mécaniquement par lint, tests ou scripts. - -## SOP d'exécution - -1. Cartographier le codebase en domaines avant de toucher au style d'implémentation. -2. Pour chaque domaine, identifier la séquence de couches autorisée. -3. Identifier toutes les préoccupations transversales et les router via des fournisseurs - ou adaptateurs. -4. Déplacer la logique partagée ambiguë soit dans le domaine propriétaire, - soit dans des utilitaires véritablement génériques. -5. Documenter les règles dans `ARCHITECTURE.md`. -6. Ajouter un garde-fou exécutable pour la violation au coût le plus élevé. -7. Mettre à jour la notation de qualité après le changement. - -## Définition du « fini » - -- Un nouvel agent peut dire quelle couche est propriétaire d'un changement. -- Le code de l'interface n'accède plus directement au dépôt ou aux effets de bord externes. -- Les préoccupations transversales ont des points d'entrée nommés. -- Au moins une limite importante est appliquée mécaniquement. - -## Artefacts du dépôt à mettre à jour - -- `ARCHITECTURE.md` -- `docs/QUALITY_SCORE.md` -- `docs/design-docs/` lorsque la justification a changé -- `docs/PLANS.md` ou le plan d'exécution actif diff --git a/courses/learn-harness-engineering/docs/fr/resources/openai-advanced/sops/observability-feedback-loop.md b/courses/learn-harness-engineering/docs/fr/resources/openai-advanced/sops/observability-feedback-loop.md deleted file mode 100644 index e276b113..00000000 --- a/courses/learn-harness-engineering/docs/fr/resources/openai-advanced/sops/observability-feedback-loop.md +++ /dev/null @@ -1,47 +0,0 @@ -# SOP : Boucle de rétroaction d'observabilité - -Utilisez cette SOP lorsque le débogage est lent, que les agents affirment leur succès -sans preuve, ou que le comportement à l'exécution est plus difficile à inspecter -que le code lui-même. - -## Objectif - -Donner à l'agent une boucle de rétroaction locale sur les journaux, métriques, -traces et charges de travail exécutables pour qu'il puisse raisonner à partir -de l'exécution, et non seulement de l'inspection du code. - -## Pile minimale - -- l'application émet des journaux structurés -- l'application émet des métriques et des traces lorsque c'est possible -- couche locale de distribution ou de collecte -- interfaces de requête pour les journaux, métriques et traces -- charge de travail ou parcours utilisateur répétable à relancer après chaque changement - -## SOP d'exécution - -1. Définir les parcours d'exécution de référence les plus importants. -2. Ajouter des journaux structurés au démarrage et sur le chemin critique. -3. Ajouter des métriques pour la latence, les compteurs d'échecs ou la profondeur - de file d'attente lorsque c'est utile. -4. Ajouter des traces ou des marqueurs de temps pour les flux lents ou multi-étapes. -5. Rendre les signaux interrogeables depuis l'environnement de développement local. -6. Donner à l'agent une charge de travail ou un scénario répétable à relancer. -7. Exiger la boucle : interroger -> corréler -> raisonner -> implémenter -> - redémarrer -> relancer -> vérifier. - -## Liste de contrôle de session de débogage - -- Qu'est-ce qui a échoué ? -- Quel signal prouve l'échec ? -- Quelle couche est propriétaire de l'échec ? -- Qu'est-ce qui a changé après le correctif ? -- L'application a-t-elle redémarré correctement ? -- La même charge de travail a-t-elle réussi après relance ? - -## Définition du « fini » - -- L'agent peut expliquer un mode d'échec à partir de preuves d'exécution. -- La même charge de travail peut être relancée après chaque changement. -- Le redémarrage et la relance font partie de la boucle de tâches normale. -- Les signaux de fiabilité sont documentés dans `docs/RELIABILITY.md`. diff --git a/courses/learn-harness-engineering/docs/fr/resources/reference/coding-agent-startup-flow.md b/courses/learn-harness-engineering/docs/fr/resources/reference/coding-agent-startup-flow.md deleted file mode 100644 index b091dd99..00000000 --- a/courses/learn-harness-engineering/docs/fr/resources/reference/coding-agent-startup-flow.md +++ /dev/null @@ -1,36 +0,0 @@ -# Flux de démarrage de l'agent de codage - -Utilisez ceci au début de chaque session une fois l'initialisation terminée. - -## Modèle de démarrage fixe - -1. Exécuter `pwd` et confirmer la racine du dépôt. -2. Lire `claude-progress.md`. -3. Lire `feature_list.json`. -4. Examiner les commits récents avec `git log --oneline -5`. -5. Exécuter `./init.sh`. -6. Exécuter un parcours de test de fumée ou de bout en bout de référence. -7. Si la référence est cassée, corriger cela en premier. -8. Sélectionner la fonctionnalité inachevée la plus prioritaire. -9. Travailler uniquement sur cette fonctionnalité jusqu'à ce qu'elle soit - vérifiée ou explicitement bloquée. - -## Pourquoi cet ordre est important - -- `pwd` évite un travail accidentel dans le mauvais répertoire. -- Les fichiers de progression et de fonctionnalités restaurent l'état durable - avant que de nouvelles modifications ne commencent. -- Les commits récents expliquent ce qui a changé le plus récemment. -- `init.sh` standardise le démarrage au lieu de s'appuyer sur la mémoire. -- La vérification de référence intercepte les états de départ cassés avant - que le nouveau travail ne les cache. - -## Miroir de fin de session - -La même session devrait se terminer par : - -1. l'enregistrement de la progression -2. la mise à jour de l'état des fonctionnalités -3. la rédaction d'une passation si nécessaire -4. la validation du travail sûr -5. le maintien d'un chemin de redémarrage propre diff --git a/courses/learn-harness-engineering/docs/fr/resources/reference/index.md b/courses/learn-harness-engineering/docs/fr/resources/reference/index.md deleted file mode 100644 index 1760b839..00000000 --- a/courses/learn-harness-engineering/docs/fr/resources/reference/index.md +++ /dev/null @@ -1,89 +0,0 @@ -# Référence - -Ces notes expliquent comment utiliser les modèles comme un harnais fonctionnel -plutôt que comme un simple ensemble de fichiers épars. - -## Notes de référence internes - -- [`method-map.md`](./method-map.md) : faire correspondre les modes d'échec - courants des exécutions longues à l'artefact ou à la politique qui les - traite en premier -- [`initializer-agent-playbook.md`](./initializer-agent-playbook.md) : ce que - l'initialiseur doit laisser en place avant que le travail sur les fonctionnalités - ne commence -- [`coding-agent-startup-flow.md`](./coding-agent-startup-flow.md) : flux fixe - de début de session pour les sessions de codage ultérieures -- [`prompt-calibration.md`](./prompt-calibration.md) : comment garder les - instructions racines précises sans les rendre gonflées et fragiles - -## Articles principaux - -Cette liste est intentionnellement restreinte. Un harnais désigne le système -d'exécution autour du modèle : la boucle de l'agent, l'exécution des outils, -le sandboxing, l'état, le contexte, la vérification, la terminaison, -l'orchestration et l'observabilité. Les articles généraux d'ingénierie de prompt -ou de cadres d'agents larges n'appartiennent pas à la liste principale. - -Les trois articles originaux restent la colonne vertébrale du cours : - -- [OpenAI : Harness engineering : leveraging Codex in an agent-first world](https://openai.com/index/harness-engineering/) (2026-02-11) : dépôts agent-first, contexte local au dépôt, linting personnalisé et garde-fous structurels. -- [Anthropic : Effective harnesses for long-running agents](https://www.anthropic.com/engineering/effective-harnesses-for-long-running-agents) (2025-11-26) : agent initialiseur, agent de codage, liste de fonctionnalités, journal de progression et passation à travers les fenêtres de contexte. -- [Anthropic : Harness design for long-running application development](https://www.anthropic.com/engineering/harness-design-long-running-apps) (2026-03-24) : rôles de planificateur / générateur / évaluateur, réinitialisations de contexte, simplification du harnais et hypothèses obsolètes. - -Quelques articles 2026 très pertinents sont ajoutés : - -- [OpenAI : Unrolling the Codex agent loop](https://openai.com/index/unrolling-the-codex-agent-loop/) (2026-01-23) : le harnais d'exécution Codex, les appels d'outils, la croissance du contexte et la terminaison de boucle. -- [Anthropic : Demystifying evals for AI agents](https://www.anthropic.com/engineering/demystifying-evals-for-ai-agents) (2026-01-09) : évaluer le modèle et le harnais ensemble, et distinguer les harnais d'évaluation des harnais d'agents. -- [LangChain : Improving Deep Agents with harness engineering](https://www.langchain.com/blog/improving-deep-agents-with-harness-engineering) (2026-02-17) : maintenir le modèle fixe tout en améliorant les prompts système, les outils, le middleware, le traçage et l'auto-vérification pour faire passer un agent de codage du Top 30 au Top 5 sur Terminal Bench 2.0. -- [Thoughtworks / Martin Fowler : Harness engineering for coding agent users](https://martinfowler.com/articles/harness-engineering.html) (2026-04-02) : harnais utilisateurs d'agents de codage comme guides anticipatifs et capteurs de rétroaction, avec des contrôles déterministes et inférentiels. -- [Cursor : Continually improving our agent harness](https://cursor.com/blog/continually-improving-agent-harness) (2026-04-30) : traiter le harnais comme un système produit en amélioration continue avec évaluations hors ligne, métriques en ligne, taxonomie des erreurs d'outils, réglage spécifique au modèle et changement de modèle en cours de conversation. - -## Références étendues 2026 - -Ce ne sont pas des sources principales du cours, mais elles sont utiles lors de la -conception de modules de harnais spécifiques. Cette section ne conserve que les sources -dont le contenu couvre directement la boucle de l'agent, l'exécution des outils, la -gestion du contexte, la vérification, le sandboxing, les couches de contrôle ou la -gouvernance des régressions. Les produits d'agents purs, les annonces de plateforme, -les études de cas d'équipe et les benchmarks sont exclus. - -- [OpenAI : Unlocking the Codex harness: how we built the App Server](https://openai.com/index/unlocking-the-codex-harness/) (2026-02-04) : le harnais comme protocole réutilisable d'App Server avec cycle de vie des threads, reprise, fork, diffs et intégrations clientes. -- [OpenAI Developers : Run long horizon tasks with Codex](https://developers.openai.com/blog/run-long-horizon-tasks-with-codex) (2026-02-23) : mémoire de projet durable, validation des jalons et exemples de critères de complétion pour les tâches longues. -- [OpenAI : The next evolution of the Agents SDK](https://openai.com/index/the-next-evolution-of-the-agents-sdk/) (2026-04-15) : harnais natifs au modèle, exécution en sandbox et exécution de fichiers/commandes. -- [OpenAI : An open-source spec for Codex orchestration: Symphony](https://openai.com/index/open-source-codex-orchestration-symphony/) (2026-04-27) : transformer un traqueur de tickets ou un tableau Linear en plan de contrôle multi-agents. -- [Anthropic : Building a C compiler with a team of parallel Claudes](https://www.anthropic.com/engineering/building-c-compiler) (2026-02-05) : équipes d'agents parallèles, verrous de tâches, synchronisation git, isolation par conteneurs et boucles autonomes. -- [Anthropic : Scaling Managed Agents: Decoupling the brain from the hands](https://www.anthropic.com/engineering/managed-agents) (2026-04-08) : une vue méta-harnais qui sépare session, harnais et sandbox en tant qu'interfaces interchangeables. -- [Anthropic : An update on recent Claude Code quality reports](https://www.anthropic.com/engineering/april-23-postmortem) (2026-04-23) : niveau de raisonnement, élagage du contexte et prompts système comme changements de harnais nécessitant une gouvernance des régressions. -- [LangChain : Context Management for Deep Agents](https://www.langchain.com/blog/context-management-for-deepagents) (2026-01-28) : déchargement sur système de fichiers, troncature des appels d'outils, résumé et évaluations ciblées pour les harnais de gestion du contexte. -- [LangChain : Tuning Deep Agents to Work Well with Different Models](https://www.langchain.com/blog/tuning-deep-agents-different-models) (2026-04-29) : profils de harnais spécifiques au modèle pour les prompts, noms d'outils, middleware et configuration de sous-agents. -- [LangChain : Continual learning for AI agents](https://www.langchain.com/blog/continual-learning-for-ai-agents) (2026-04-05) : diviser l'amélioration des agents en couches de modèle, de harnais et de contexte, alimentée par les traces. -- [Microsoft : Agent Harness in Agent Framework](https://devblogs.microsoft.com/agent-framework/agent-harness-in-agent-framework/) (2026-03-12) : harnais shell/système de fichiers, flux d'approbation, exécution shell hébergée et compactage du contexte. -- [Google : Announcing ADK for Java 1.0.0](https://developers.googleblog.com/announcing-adk-for-java-100-building-the-future-of-ai-agents-in-java/) (2026-03-30) : plugins, compactage d'événements, HITL, services de session/mémoire et A2A comme primitives de harnais réutilisables. -- [GitHub : Automate repository tasks with GitHub Agentic Workflows](https://github.blog/ai-and-ml/automate-repository-tasks-with-github-agentic-workflows/) (2026-02-13) : GitHub Actions comme exécuteur de flux de travail agentique avec sorties sûres, sandboxing, permissions et revue. -- [AWS : AI agents in enterprises: Best practices with Amazon Bedrock AgentCore](https://aws.amazon.com/blogs/machine-learning/ai-agents-in-enterprises-best-practices-with-amazon-bedrock-agentcore/) (2026-02-03) : couches de harnais d'entreprise couvrant Runtime, Memory, Gateway, Identity/Policy, Observability et Evaluations. -- [Stripe : Minions: Stripe's one-shot, end-to-end coding agents](https://stripe.dev/blog/minions-stripes-one-shot-end-to-end-coding-agents) (2026-02-09) et [Partie 2](https://stripe.dev/blog/minions-stripes-one-shot-end-to-end-coding-agents-part-2) (2026-02-19) : isolation devbox, harnais d'agents personnalisés, machines à états blueprint, fichiers de règles, curation d'outils MCP, contrôles de sécurité et boucles de rétroaction pre-push/CI. -- [Cognition : What We Learned Building Cloud Agents](https://cognition.ai/blog/what-we-learned-building-cloud-agents) (2026-04-23) : isolation par VM, capture/reprise de session, orchestration, gouvernance, journalisation d'audit et intégrations pour les runtimes d'agents cloud. -- [Cognition : Multi-Agents: What's Actually Working](https://cognition.ai/blog/multi-agents-working) (2026-04-22) : boucles générateur-vérificateur, réviseurs à contexte propre, routage smart-friend, coordination manager-enfant et limites de communication inter-agents. -- [Replit : Decision-Time Guidance: Keeping Replit Agent Reliable](https://blog.replit.com/decision-time-guidance) (2026-01-20, mis à jour 2026-01-23) : un classificateur léger injecte des conseils situationnels courts au point de décision au lieu de fourrer toutes les règles dans le prompt système. -- [Vercel : How we made v0 an effective coding agent](https://vercel.com/blog/how-we-made-v0-an-effective-coding-agent) (2026-01-07) : prompts système dynamiques, une couche de réécriture en streaming et des autocorrections déterministes/basées sur le modèle. -- [Vercel : Introducing deepsec](https://vercel.com/blog/introducing-deepsec-find-and-fix-vulnerabilities-in-your-code-base) (2026-05-04) : un harnais d'agent de codage axé sur la sécurité avec étapes de scan, investigation, revalidation, enrichissement, export, plugin et vérificateur de refus. -- [Sourcegraph : CodeScaleBench](https://sourcegraph.com/blog/codescalebench-testing-coding-agents-on-large-codebases-and-multi-repo-software-engineering-tasks) (2026-03-03) : une référence de harnais d'évaluation/outillage couvrant l'adoption d'outils MCP, les transcriptions d'utilisation d'outils, l'assurance qualité de benchmarks, les portes de vérificateur/reproductibilité et l'itération prompt/préambule. - -Les références générales strictement limitées à 2025 sont exclues de la liste principale. -L'article original Anthropic de 2025 sur les harnais reste car c'est une source -fondamentale du cours. - -## Ordre de lecture suggéré - -1. `method-map.md` -2. `initializer-agent-playbook.md` -3. `coding-agent-startup-flow.md` -4. `prompt-calibration.md` -5. OpenAI Harness engineering -6. Anthropic Effective harnesses -7. Anthropic Harness design for long-running application development -8. OpenAI Codex agent loop -9. Anthropic agent evals -10. LangChain Improving Deep Agents -11. Thoughtworks / Martin Fowler Harness engineering for coding agent users -12. Cursor Continually improving our agent harness diff --git a/courses/learn-harness-engineering/docs/fr/resources/reference/initializer-agent-playbook.md b/courses/learn-harness-engineering/docs/fr/resources/reference/initializer-agent-playbook.md deleted file mode 100644 index 4734dd88..00000000 --- a/courses/learn-harness-engineering/docs/fr/resources/reference/initializer-agent-playbook.md +++ /dev/null @@ -1,38 +0,0 @@ -# Playbook de l'agent initialiseur - -Utilisez ce playbook pour la première session sérieuse dans un dépôt, avant que -le travail incrémental sur les fonctionnalités ne commence. - -## Objectif - -Créer une surface de fonctionnement stable pour que les sessions ultérieures puissent -implémenter le comportement sans avoir à redériver les commandes de démarrage, -l'état actuel ou les limites des tâches. - -## Sorties requises - -L'initialiseur doit laisser au moins ces artefacts : - -- un fichier d'instructions racine tel que `AGENTS.md` ou `CLAUDE.md` -- une surface de fonctionnalités lisible par machine telle que `feature_list.json` -- un artefact de progression durable tel que `claude-progress.md` -- un assistant de démarrage standard tel que `init.sh` -- un commit initial sûr capturant l'échafaudage de référence - -## Liste de contrôle - -1. Définir le chemin de démarrage standard. -2. Définir le chemin de vérification standard. -3. Créer le journal de progression et enregistrer l'état de départ. -4. Décomposer le travail en fonctionnalités explicites avec des statuts. -5. Créer le premier commit de référence propre. - -## Test de réussite - -Une nouvelle session sans contexte de conversation antérieur doit pouvoir répondre : - -- ce que fait ce dépôt -- comment le démarrer -- comment le vérifier -- ce qui est inachevé -- quelle est la meilleure prochaine étape diff --git a/courses/learn-harness-engineering/docs/fr/resources/reference/method-map.md b/courses/learn-harness-engineering/docs/fr/resources/reference/method-map.md deleted file mode 100644 index f130aa42..00000000 --- a/courses/learn-harness-engineering/docs/fr/resources/reference/method-map.md +++ /dev/null @@ -1,20 +0,0 @@ -# Carte des méthodes - -Ce tableau fait correspondre les modes d'échec les plus courants des agents de codage -en exécution longue à l'artefact ou à la règle de fonctionnement qui les corrige -généralement en premier. - -| Mode d'échec | À quoi cela ressemble en pratique | Correctif principal | Artefact de support | -| --- | --- | --- | --- | -| Confusion au démarrage à froid | Une nouvelle session passe la majeure partie de son temps à redécouvrir la configuration et l'état | Faire du dépôt le système de référence | `claude-progress.md` | -| Dérapage du périmètre | L'agent démarre plusieurs fonctionnalités et n'en termine aucune correctement | Restreindre le périmètre actif | `feature_list.json` | -| Achèvement prématuré | L'agent déclare avoir terminé après des modifications de code mais avant une preuve exécutable | Lier l'achèvement à des preuves | `clean-state-checklist.md` | -| Démarrage fragile | Chaque session réapprend comment démarrer le projet | Standardiser la configuration et la vérification | `init.sh` | -| Passation faible | La session suivante ne peut pas dire ce qui est vérifié, cassé ou à venir | Terminer par une passation explicite | `session-handoff.md` | -| Revue subjective | La qualité de la revue dépend du goût ou de la mémoire | Noter la sortie avec des catégories fixes | `evaluator-rubric.md` | - -## Principe de fonctionnement - -Ajouter le plus petit artefact qui traite directement le mode d'échec observé. -Éviter de résoudre chaque problème de fiabilité en déversant plus de texte -dans un seul fichier d'instructions global. diff --git a/courses/learn-harness-engineering/docs/fr/resources/reference/prompt-calibration.md b/courses/learn-harness-engineering/docs/fr/resources/reference/prompt-calibration.md deleted file mode 100644 index ba30ed9e..00000000 --- a/courses/learn-harness-engineering/docs/fr/resources/reference/prompt-calibration.md +++ /dev/null @@ -1,26 +0,0 @@ -# Calibration des prompts - -Les instructions racines doivent définir le cadre de fonctionnement, pas chaque -mouvement possible. - -## Garder dans le fichier racine - -- l'objectif et le périmètre du dépôt -- le chemin de démarrage -- le chemin de vérification -- les contraintes non négociables -- les artefacts d'état requis -- les règles de fin de session - -## Sortir du fichier racine - -- les cas limites historiques longs -- les détails d'implémentation spécifiques à un sujet -- les notes d'architecture locale qui doivent se trouver près du code -- les exemples qui ne s'appliquent qu'à un seul sous-système - -## Règle de travail - -Le fichier racine doit aider une nouvelle session à s'orienter rapidement. -Si le fichier devient une poubelle pour chaque échec passé, diviser les détails -en documents plus petits et y faire un lien à la place. diff --git a/courses/learn-harness-engineering/docs/fr/resources/templates/AGENTS.md b/courses/learn-harness-engineering/docs/fr/resources/templates/AGENTS.md deleted file mode 100644 index a874dde0..00000000 --- a/courses/learn-harness-engineering/docs/fr/resources/templates/AGENTS.md +++ /dev/null @@ -1,57 +0,0 @@ -# AGENTS.md - -Ce dépôt est conçu pour le travail d'agents de codage en exécution longue. L'objectif -n'est pas de maximiser la production brute de code. L'objectif est de laisser le dépôt -dans un état où la prochaine session peut continuer sans avoir à deviner. - -## Flux de démarrage - -Avant d'écrire du code : - -1. Confirmer le répertoire de travail avec `pwd`. -2. Lire `claude-progress.md` pour connaître le dernier état vérifié et la prochaine étape. -3. Lire `feature_list.json` et choisir la fonctionnalité inachevée la plus prioritaire. -4. Examiner les commits récents avec `git log --oneline -5`. -5. Exécuter `./init.sh`. -6. Exécuter la vérification de fumée ou de bout en bout requise avant de commencer - un nouveau travail. - -Si la vérification de référence échoue déjà, corriger cela en premier. Ne pas empiler -de nouveau travail sur un état de départ cassé. - -## Règles de travail - -- Travailler sur une seule fonctionnalité à la fois. -- Ne pas marquer une fonctionnalité comme terminée uniquement parce que du code a été ajouté. -- Garder les modifications dans le périmètre de la fonctionnalité sélectionnée, sauf si - un obstacle impose un correctif de support restreint. -- Ne pas modifier silencieusement les règles de vérification pendant l'implémentation. -- Préférer les artefacts durables du dépôt aux résumés de conversation. - -## Artefacts requis - -- `feature_list.json` : source de vérité pour l'état des fonctionnalités -- `claude-progress.md` : journal de session et état vérifié actuel -- `init.sh` : chemin standard de démarrage et de vérification -- `session-handoff.md` : passation compacte optionnelle pour les sessions plus importantes - -## Définition du « fini » - -Une fonctionnalité est terminée uniquement lorsque toutes les conditions suivantes sont -remplies : - -- le comportement cible est implémenté -- la vérification requise a réellement été exécutée -- les preuves sont enregistrées dans `feature_list.json` ou `claude-progress.md` -- le dépôt reste redémarrable depuis le chemin de démarrage standard - -## Fin de session - -Avant de terminer une session : - -1. Mettre à jour `claude-progress.md`. -2. Mettre à jour `feature_list.json`. -3. Enregistrer tout risque ou obstacle non résolu. -4. Valider avec un message descriptif une fois le travail dans un état sûr. -5. Laisser le dépôt suffisamment propre pour que la prochaine session puisse exécuter - `./init.sh` immédiatement. diff --git a/courses/learn-harness-engineering/docs/fr/resources/templates/CLAUDE.md b/courses/learn-harness-engineering/docs/fr/resources/templates/CLAUDE.md deleted file mode 100644 index 60a28ccc..00000000 --- a/courses/learn-harness-engineering/docs/fr/resources/templates/CLAUDE.md +++ /dev/null @@ -1,48 +0,0 @@ -# CLAUDE.md - -Vous travaillez dans un dépôt conçu pour un travail d'implémentation en exécution longue. -Priorisez l'achèvement fiable, la continuité entre les sessions et la vérification -explicite plutôt que la vitesse. - -## Boucle de fonctionnement - -Au début de chaque session : - -1. Exécuter `pwd` et confirmer que vous êtes à la racine du dépôt attendu. -2. Lire `claude-progress.md`. -3. Lire `feature_list.json`. -4. Examiner les commits récents avec `git log --oneline -5`. -5. Exécuter `./init.sh`. -6. Vérifier si le parcours de fumée ou de bout en bout de référence est déjà cassé. - -Ensuite, sélectionner exactement une fonctionnalité inachevée et travailler uniquement -sur cette fonctionnalité jusqu'à ce que vous la vérifiiez ou que vous documentiez -pourquoi elle est bloquée. - -## Règles - -- Une seule fonctionnalité active à la fois. -- Ne pas déclarer l'achèvement sans preuve exécutable. -- Ne pas réécrire la liste des fonctionnalités pour masquer le travail inachevé. -- Ne pas supprimer ou affaiblir les tests uniquement pour faire paraître la tâche terminée. -- Utiliser les artefacts du dépôt comme système de référence. - -## Fichiers requis - -- `feature_list.json` -- `claude-progress.md` -- `init.sh` -- `session-handoff.md` lorsqu'une passation compacte est utile - -## Porte d'achèvement - -Une fonctionnalité ne peut passer à `passing` qu'après que la vérification requise -a réussi et que le résultat est enregistré. - -## Avant de vous arrêter - -1. Mettre à jour le journal de progression. -2. Mettre à jour l'état des fonctionnalités. -3. Enregistrer ce qui est encore cassé ou non vérifié. -4. Valider une fois que le dépôt est sûr à reprendre. -5. Laisser un chemin de redémarrage propre pour la prochaine session. diff --git a/courses/learn-harness-engineering/docs/fr/resources/templates/claude-progress.md b/courses/learn-harness-engineering/docs/fr/resources/templates/claude-progress.md deleted file mode 100644 index 6da153d3..00000000 --- a/courses/learn-harness-engineering/docs/fr/resources/templates/claude-progress.md +++ /dev/null @@ -1,35 +0,0 @@ -# Journal de progression - -## État vérifié actuel - -- Racine du dépôt : -- Chemin de démarrage standard : -- Chemin de vérification standard : -- Fonctionnalité inachevée la plus prioritaire actuelle : -- Obstacle actuel : - -## Journal des sessions - -### Session 001 - -- Date : -- Objectif : -- Terminé : -- Vérification exécutée : -- Preuves capturées : -- Commits : -- Fichiers ou artefacts mis à jour : -- Risque connu ou problème non résolu : -- Meilleure prochaine étape : - -### Session 002 - -- Date : -- Objectif : -- Terminé : -- Vérification exécutée : -- Preuves capturées : -- Commits : -- Fichiers ou artefacts mis à jour : -- Risque connu ou problème non résolu : -- Meilleure prochaine étape : diff --git a/courses/learn-harness-engineering/docs/fr/resources/templates/clean-state-checklist.md b/courses/learn-harness-engineering/docs/fr/resources/templates/clean-state-checklist.md deleted file mode 100644 index e73c9e7e..00000000 --- a/courses/learn-harness-engineering/docs/fr/resources/templates/clean-state-checklist.md +++ /dev/null @@ -1,8 +0,0 @@ -# Liste de contrôle d'état propre - -- [ ] Le chemin de démarrage standard fonctionne toujours. -- [ ] Le chemin de vérification standard s'exécute toujours. -- [ ] La progression actuelle est enregistrée dans le journal de progression. -- [ ] L'état des fonctionnalités reflète ce qui réussit réellement par rapport à ce qui n'est pas vérifié. -- [ ] Aucune étape à moitié terminée n'est laissée non documentée. -- [ ] La prochaine session peut continuer sans réparation manuelle. diff --git a/courses/learn-harness-engineering/docs/fr/resources/templates/evaluator-rubric.md b/courses/learn-harness-engineering/docs/fr/resources/templates/evaluator-rubric.md deleted file mode 100644 index 9358c9d2..00000000 --- a/courses/learn-harness-engineering/docs/fr/resources/templates/evaluator-rubric.md +++ /dev/null @@ -1,24 +0,0 @@ -# Grille d'évaluation - -Utilisez cette grille après l'implémentation et avant l'acceptation finale. - -| Catégorie | Question | Score (0-2) | Notes | -| --- | --- | --- | --- | -| Correction | Le comportement implémenté correspond-il à la fonctionnalité demandée ? | | | -| Vérification | Les vérifications requises ont-elles réellement été exécutées, avec des preuves ? | | | -| Discipline de périmètre | La session est-elle restée dans le périmètre de la fonctionnalité choisie ? | | | -| Fiabilité | Le résultat survit-il au redémarrage ou à la relance sans réparation ? | | | -| Maintenabilité | Le code et la documentation sont-ils suffisamment clairs pour la prochaine session ? | | | -| Préparation à la passation | Une nouvelle session peut-elle continuer le travail à partir des seuls artefacts du dépôt ? | | | - -## Verdict - -- Accepter -- Réviser -- Bloquer - -## Suivi requis - -- Preuves manquantes : -- Correctifs requis : -- Prochain déclencheur de revue : diff --git a/courses/learn-harness-engineering/docs/fr/resources/templates/feature_list.json b/courses/learn-harness-engineering/docs/fr/resources/templates/feature_list.json deleted file mode 100644 index 138adc6a..00000000 --- a/courses/learn-harness-engineering/docs/fr/resources/templates/feature_list.json +++ /dev/null @@ -1,64 +0,0 @@ -{ - "project": "replace-with-project-name", - "last_updated": "YYYY-MM-DD", - "rules": { - "single_active_feature": true, - "passing_requires_evidence": true, - "do_not_skip_verification": true - }, - "status_legend": { - "not_started": "Work has not begun.", - "in_progress": "The feature is the current active task.", - "blocked": "Work cannot continue until a documented blocker is resolved.", - "passing": "Required verification has passed and evidence is recorded." - }, - "features": [ - { - "id": "chat-001", - "priority": 1, - "area": "chat", - "title": "Create a new conversation", - "user_visible_behavior": "A user can click New Chat and see a fresh empty conversation.", - "status": "not_started", - "verification": [ - "Open the app.", - "Click New Chat.", - "Verify a new conversation appears in the sidebar.", - "Verify the main panel shows an empty conversation state." - ], - "evidence": [], - "notes": "" - }, - { - "id": "chat-002", - "priority": 2, - "area": "chat", - "title": "Send a message in the current conversation", - "user_visible_behavior": "A user can submit a message and see it appear in the active thread.", - "status": "not_started", - "verification": [ - "Open an existing conversation.", - "Type a message into the input.", - "Submit the message.", - "Verify the new message appears in the thread." - ], - "evidence": [], - "notes": "" - }, - { - "id": "chat-003", - "priority": 3, - "area": "chat", - "title": "Persist the active conversation list", - "user_visible_behavior": "A user sees previously created conversations after restarting the app.", - "status": "not_started", - "verification": [ - "Create two conversations.", - "Restart the app.", - "Verify both conversations still appear in the sidebar." - ], - "evidence": [], - "notes": "" - } - ] -} diff --git a/courses/learn-harness-engineering/docs/fr/resources/templates/index.md b/courses/learn-harness-engineering/docs/fr/resources/templates/index.md deleted file mode 100644 index bd93522a..00000000 --- a/courses/learn-harness-engineering/docs/fr/resources/templates/index.md +++ /dev/null @@ -1,255 +0,0 @@ -# Guide des modèles - -Ces modèles sont prêts à être copiés dans votre propre projet. Chacun remplit un -rôle spécifique dans le flux de travail de l'agent. Modifiez le contenu pour -l'adapter aux commandes, chemins, noms de fonctionnalités et étapes de vérification -de votre projet. - -## Pour commencer - -Copiez d'abord ces quatre fichiers à la racine de votre projet : - -1. `AGENTS.md` ou `CLAUDE.md` -2. `init.sh` -3. `claude-progress.md` -4. `feature_list.json` - -Ajoutez les fichiers restants au fur et à mesure que votre projet grandit. - ---- - -## AGENTS.md - -Le fichier d'instructions racine. C'est la première chose que l'agent lit lorsqu'il -démarre une session. Il définit les règles de fonctionnement : quoi faire avant -d'écrire du code, comment travailler et comment conclure. - -**Comment l'utiliser :** - -- Copiez à la racine de votre projet -- Remplacez les étapes du flux de démarrage par vos chemins et commandes réels -- Ajustez les règles de travail pour correspondre aux conventions de votre équipe -- Conservez la section de définition du « fini » — c'est la partie la plus importante - -**Ce qu'il fait pour l'agent :** - -- Lui dit de lire la progression et l'état des fonctionnalités avant de commencer le travail -- Le force à travailler sur une seule fonctionnalité à la fois -- Exige des preuves avant de marquer quoi que ce soit comme terminé -- Définit à quoi ressemble une fin de session propre - -Utilisez `AGENTS.md` pour Codex ou d'autres agents. Utilisez `CLAUDE.md` si vous -travaillez avec Claude Code — la structure est la même, simplement formatée pour le -style d'instructions de Claude. - -## init.sh - -Le script de démarrage. Exécute l'installation des dépendances, la vérification -et affiche la commande de lancement — le tout en une seule fois. - -**Comment l'utiliser :** - -- Copiez à la racine de votre projet -- Modifiez ces trois variables en haut : - - `INSTALL_CMD` — votre commande d'installation des dépendances (par ex. `npm install`, `pip install -r requirements.txt`) - - `VERIFY_CMD` — votre commande de vérification de base (par ex. `npm test`, `pytest`) - - `START_CMD` — votre commande de lancement du serveur de développement (par ex. `npm run dev`) -- Rendez-le exécutable : `chmod +x init.sh` - -**Ce qu'il fait :** - -1. Affiche le répertoire courant (pour confirmer qu'il s'exécute au bon endroit) -2. Installe les dépendances -3. Exécute la commande de vérification -4. Affiche la commande de lancement (ou l'exécute si `RUN_START_COMMAND=1` est défini) - -Si la vérification échoue, l'agent doit s'arrêter et corriger la ligne de base avant -de faire quoi que ce soit d'autre. - -## claude-progress.md - -Le journal de progression. Chaque session écrit dans ce fichier, et chaque nouvelle -session le lit en premier. - -**Comment l'utiliser :** - -- Copiez à la racine de votre projet -- Remplissez la section « État vérifié actuel » avec les informations de votre projet -- Après chaque session, mettez à jour l'enregistrement de session - -**Signification de chaque champ :** - -- **État vérifié actuel** — l'unique source de vérité sur l'état du projet - - `Racine du dépôt` — où se trouve le projet - - `Chemin de démarrage standard` — la commande pour lancer le projet - - `Chemin de vérification standard` — la commande pour exécuter les tests - - `Fonctionnalité inachevée la plus prioritaire` — sur quoi la prochaine session devrait travailler - - `Obstacle actuel` — tout ce qui est bloqué -- **Enregistrement de session** — une entrée par session - - `Objectif` — ce que vous aviez prévu de faire - - `Terminé` — ce qui a réellement été accompli - - `Vérification exécutée` — quels tests ont été lancés - - `Preuves enregistrées` — quelles preuves ont été capturées - - `Commits` — ce qui a été validé - - `Risques connus` — ce qui pourrait être cassé - - `Meilleure prochaine action` — où la prochaine session devrait commencer - -## feature_list.json - -Le suivi des fonctionnalités. Une liste lisible par machine de chaque fonctionnalité -que l'agent doit implémenter, avec son statut, ses étapes de vérification et ses preuves. - -**Comment l'utiliser :** - -- Copiez à la racine de votre projet -- Remplacez les exemples de fonctionnalités par les vôtres -- Chaque fonctionnalité nécessite : - - `id` — un identifiant court unique - - `priority` — entier, plus bas = plus prioritaire - - `area` — quelle partie de l'application (par ex. « chat », « import », « search ») - - `title` — description courte - - `user_visible_behavior` — ce que l'utilisateur doit voir quand ça fonctionne - - `status` — parmi `not_started`, `in_progress`, `blocked`, `passing` - - `verification` — instructions étape par étape pour confirmer le bon fonctionnement - - `evidence` — preuves enregistrées que la vérification a réussi (remplies par l'agent) - - `notes` — tout contexte supplémentaire - -**Règles de statut :** - -- `not_started` — n'a pas été touché -- `in_progress` — la fonctionnalité en cours de travail (une seule à la fois) -- `blocked` — ne peut pas progresser en raison d'un problème documenté -- `passing` — la vérification a réussi et les preuves sont enregistrées - -L'agent ne doit avoir qu'une seule fonctionnalité en `in_progress` à tout moment. - -## session-handoff.md - -Une note de passation compacte entre les sessions. Utilisez-la lorsqu'une session se -termine et que vous souhaitez que la prochaine reprenne rapidement. - -**Comment l'utiliser :** - -- Copiez à la racine de votre projet -- Remplissez-la à la fin de chaque session (ou demandez à l'agent de la remplir) - -**Ce que chaque section couvre :** - -- **Actuellement vérifié** — ce qui est confirmé fonctionnel et quelle vérification a été exécutée -- **Changements cette session** — quel code ou infrastructure a changé -- **Encore cassé ou non vérifié** — problèmes connus et zones à risque -- **Meilleure prochaine action** — ce que la prochaine session doit faire, et ce qu'il ne faut pas toucher -- **Commandes** — démarrage, vérification et commandes de débogage pour référence rapide - -Ce fichier est optionnel pour les petites sessions. Il devient important lorsque les -sessions sont longues ou lorsque le projet a plusieurs zones actives. - -## clean-state-checklist.md - -Une liste de contrôle à passer avant de terminer chaque session. S'assure que le dépôt -est dans un bon état pour que la prochaine session puisse démarrer proprement. - -**Comment l'utiliser :** - -- Copiez à la racine de votre projet -- Passez-la en revue avant de fermer une session -- L'agent doit également vérifier ces éléments dans le cadre de sa routine de fin de session - -**Ce qu'elle vérifie :** - -- Le démarrage standard fonctionne toujours -- La vérification standard s'exécute toujours -- Le journal de progression est mis à jour -- La liste des fonctionnalités reflète l'état réel (pas de fausses entrées `passing`) -- Aucun travail à moitié terminé laissé non enregistré -- La prochaine session peut continuer sans corrections manuelles - -## evaluator-rubric.md - -Une fiche d'évaluation pour la qualité des résultats de l'agent. Utilisez-la après -une session ou à des jalons du projet pour évaluer si le travail atteint le niveau requis. - -**Comment l'utiliser :** - -- Copiez à la racine de votre projet -- Après une session (ou un ensemble de sessions), notez le travail de l'agent sur six dimensions -- Chaque dimension est notée de 0 à 2 - -**Les six dimensions :** - -1. **Correction** — l'implémentation correspond-elle au comportement cible ? -2. **Vérification** — les vérifications requises ont-elles réellement été exécutées, avec des preuves ? -3. **Discipline de périmètre** — l'agent est-il resté dans la fonctionnalité sélectionnée ? -4. **Fiabilité** — le résultat survit-il à un redémarrage ou à une relance ? -5. **Maintenabilité** — le code et la documentation sont-ils suffisamment clairs pour la prochaine session ? -6. **Préparation à la passation** — une nouvelle session peut-elle continuer en utilisant uniquement les artefacts du dépôt ? - -**Options de verdict :** - -- Accepter — atteint le niveau requis -- Réviser — nécessite des correctifs avant acceptation -- Bloquer — problèmes fondamentaux à résoudre en premier - -**Important : l'évaluateur nécessite un réglage.** Tel quel, les agents sont de mauvais -juges d'eux-mêmes — ils identifient des problèmes puis se convainquent d'approuver. -Vous devrez itérer : - -1. Exécutez l'évaluateur sur un sprint terminé. -2. Comparez ses scores avec votre propre jugement humain. -3. Lorsqu'ils divergent, rendez la grille plus spécifique sur les critères de réussite/échec. -4. Réexécutez et vérifiez l'alignement. -5. Répétez jusqu'à ce que l'évaluateur corresponde systématiquement à la revue humaine. - -Prévoyez 3 à 5 cycles de réglage. Enregistrez chaque changement pour suivre ce qui a -amélioré l'alignement. - -## quality-document.md - -Un instantané qualité qui note chaque domaine produit et couche architecturale de votre -projet. Suit la santé du codebase au fil du temps, pas seulement les résultats de -sessions individuelles. - -**Comment l'utiliser :** - -- Copiez à la racine de votre projet -- Avant de démarrer une session : lisez-le pour comprendre où le codebase est le plus faible -- Après une session : mettez à jour les notes en fonction de ce qui a changé -- Au fil du temps : comparez les instantanés pour voir quels changements de harnais ont - réellement amélioré la santé du codebase - -**Ce qu'il évalue :** - -- **Domaines produit** (par ex., import de documents, flux Q&R, indexation) : chaque domaine - reçoit une note (A-D) selon l'état de vérification, la lisibilité agent, la stabilité des - tests et les lacunes principales -- **Couches architecturales** (par ex., main process, preload, renderer, services) : chaque - couche reçoit une note pour l'application des limites et la lisibilité agent - -**Pourquoi c'est important :** - -La grille d'évaluation note les résultats individuels de l'agent. Le document qualité note -le codebase lui-même. Ils répondent à des questions différentes : - -- Grille d'évaluation : « L'agent a-t-il fait du bon travail cette session ? » -- Document qualité : « Le projet devient-il plus fort ou plus faible au fil du temps ? » - -**Quand mettre à jour :** - -- Après chaque session significative -- Avant les comparaisons de benchmarks -- Après les passes de nettoyage ou de simplification -- Lors de l'intégration d'un nouvel agent ou modèle au projet - -**Lien avec la simplification du harnais :** - -Le document qualité soutient également la simplification du harnais. Chaque composant de -harnais encode une hypothèse sur ce que le modèle ne peut pas faire. À mesure que les -modèles s'améliorent, ces hypothèses deviennent obsolètes. Pour vérifier si un composant -est toujours nécessaire : - -1. Prenez un instantané du document qualité. -2. Retirez un composant de harnais. -3. Exécutez la suite de tâches de benchmark. -4. Prenez un autre instantané. -5. Comparez — si les notes n'ont pas baissé, le composant était superflu. Si elles ont baissé, - restaurez-le. diff --git a/courses/learn-harness-engineering/docs/fr/resources/templates/init.sh b/courses/learn-harness-engineering/docs/fr/resources/templates/init.sh deleted file mode 100755 index d7522d66..00000000 --- a/courses/learn-harness-engineering/docs/fr/resources/templates/init.sh +++ /dev/null @@ -1,29 +0,0 @@ -#!/usr/bin/env bash - -set -euo pipefail - -ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" -cd "$ROOT_DIR" - -# Replace these commands with the correct commands for your repository. -INSTALL_CMD=(npm install) -VERIFY_CMD=(npm test) -START_CMD=(npm run dev) - -echo "==> Working directory: $PWD" -echo "==> Syncing dependencies" -"${INSTALL_CMD[@]}" - -echo "==> Running baseline verification" -"${VERIFY_CMD[@]}" - -echo "==> Startup command" -printf ' %q' "${START_CMD[@]}" -printf '\n' - -if [ "${RUN_START_COMMAND:-0}" = "1" ]; then - echo "==> Starting the app" - exec "${START_CMD[@]}" -fi - -echo "Set RUN_START_COMMAND=1 if you want init.sh to launch the app directly." diff --git a/courses/learn-harness-engineering/docs/fr/resources/templates/quality-document.md b/courses/learn-harness-engineering/docs/fr/resources/templates/quality-document.md deleted file mode 100644 index 9ec1f0c5..00000000 --- a/courses/learn-harness-engineering/docs/fr/resources/templates/quality-document.md +++ /dev/null @@ -1,46 +0,0 @@ -# Document qualité - -Un instantané qualité pour chaque domaine produit et couche architecturale. Les agents -et les humains peuvent utiliser ce document pour comprendre rapidement où le codebase -est solide et où il a besoin de travail. - -**Cadence de mise à jour :** Après chaque session significative, ou avant de commencer -une nouvelle phase de travail. - -**Échelle de notation :** - -- **A** : Toutes les vérifications réussies, architecture propre, lisible par l'agent, tests stables -- **B** : Vérifications réussies, principalement propre, lacunes mineures en lisibilité ou en couverture de tests -- **C** : Partiellement fonctionnel, lacunes connues, certaines zones de code difficiles à comprendre pour les agents -- **D** : Non fonctionnel, ou problèmes structurels majeurs - ---- - -## Domaines produit - -| Domaine | Note | Vérification | Lisibilité agent | Stabilité des tests | Lacunes principales | Dernière mise à jour | -|---------|------|-------------|-----------------|--------------------|--------------------|---------------------| -| Import de documents | - | - | - | - | - | - | -| Gestion des documents | - | - | - | - | - | - | -| Indexation des documents | - | - | - | - | - | - | -| Flux Q&R | - | - | - | - | - | - | -| Réponses fondées | - | - | - | - | - | - | - -## Couches architecturales - -| Couche | Note | Application des limites | Lisibilité agent | Lacunes principales | Dernière mise à jour | -|--------|------|------------------------|-----------------|--------------------|---------------------| -| Main Process | - | - | - | - | - | -| Preload | - | - | - | - | - | -| Renderer | - | - | - | - | - | -| Services | - | - | - | - | - | - -## Historique des changements - -### YYYY-MM-DD - -- Changements : -- Domaines promus : -- Domaines rétrogradés : -- Nouvelles lacunes identifiées : -- Lacunes comblées : diff --git a/courses/learn-harness-engineering/docs/fr/resources/templates/session-handoff.md b/courses/learn-harness-engineering/docs/fr/resources/templates/session-handoff.md deleted file mode 100644 index 2460938d..00000000 --- a/courses/learn-harness-engineering/docs/fr/resources/templates/session-handoff.md +++ /dev/null @@ -1,30 +0,0 @@ -# Passation de session - -## Vérifié actuellement - -- Ce qui fonctionne actuellement : -- Quelle vérification a réellement été exécutée : - -## Modifié cette session - -- Code ou comportement ajouté : -- Changements d'infrastructure ou de harnais : - -## Cassé ou non vérifié - -- Défaut connu : -- Chemin non vérifié : -- Risque pour la prochaine session : - -- Meilleure prochaine étape - -- Fonctionnalité inachevée la plus prioritaire : -- Pourquoi c'est la suivante : -- Ce qui compte comme réussi : -- Ce qui ne doit pas changer pendant cette étape : - -## Commandes - -- Démarrage : -- Vérification : -- Commande de débogage ciblée : diff --git a/courses/learn-harness-engineering/docs/fr/skills/index.md b/courses/learn-harness-engineering/docs/fr/skills/index.md deleted file mode 100644 index 3ae647e3..00000000 --- a/courses/learn-harness-engineering/docs/fr/skills/index.md +++ /dev/null @@ -1,49 +0,0 @@ -# Skills - -Ce dossier contient les skills d'agents IA livrés avec ce cours. Les skills sont des modèles de prompt autonomes que des agents de codage IA, comme Claude Code, Codex, Cursor ou Windsurf, peuvent charger pour effectuer des tâches spécialisées. - -## harness-creator - -Un skill de harness engineering de niveau production pour agents de codage IA. Il vous aide à créer, évaluer et améliorer les cinq sous-systèmes centraux d'un harness : instructions, état, vérification, scope et cycle de vie de session. - -### Ce qu'il fait - -- **Créer des harnesses depuis zéro** — AGENTS.md, listes de fonctionnalités, workflows de vérification -- **Améliorer des harnesses existants** — évaluation des cinq sous-systèmes avec améliorations priorisées -- **Concevoir la continuité de session** — persistance mémoire, suivi de progression, procédures de handoff -- **Appliquer des patterns de production** — mémoire, context engineering, sécurité des outils, coordination multi-agent - -### Démarrage rapide - -Les fichiers du skill se trouvent dans le dépôt sous [`skills/harness-creator/`](https://github.com/walkinglabs/learn-harness-engineering/tree/main/skills/harness-creator). - -Pour l'utiliser avec Claude Code, copiez le dossier `harness-creator/` dans le chemin de skills de votre projet, ou pointez votre agent vers le fichier SKILL.md. - -### Patterns de référence - -Le skill inclut 6 documents de référence détaillés : - -| Pattern | Quand l'utiliser | -|---------|------------------| -| Memory Persistence | L'agent oublie entre les sessions | -| Context Engineering | Gestion du budget de contexte, chargement JIT | -| Tool Registry | Sécurité des outils, contrôle de concurrence | -| Multi-Agent Coordination | Parallélisme, workflows de spécialisation | -| Lifecycle & Bootstrap | Hooks, tâches en arrière-plan, initialisation | -| Gotchas | 15 modes de défaillance non évidents avec correctifs | - -### Modèles - -Le skill fournit des modèles prêts à l'emploi : - -- `agents.md` — scaffold AGENTS.md avec règles de travail -- `feature-list.json` — JSON Schema et exemple de liste de fonctionnalités -- `init.sh` — script d'initialisation standard -- `progress.md` — modèle de journal de progression de session - -### Comment ce skill a été construit - -`harness-creator` a été développé avec la méthodologie **skill-creator**, le méta-skill officiel d'Anthropic pour créer, tester et itérer sur des skills d'agents. skill-creator fournit un workflow structuré brouillon → test → évaluation → itération, avec runners d'évaluation, graders et visualiseur de benchmark. - -- **Source de skill-creator** : [anthropics/skills — skill-creator](https://github.com/anthropics/skills/tree/main/skills/skill-creator) -- **Documentation des skills Claude Code** : [anthropics/claude-code — plugin-dev/skills](https://github.com/anthropics/claude-code/tree/main/plugins/plugin-dev/skills) diff --git a/courses/learn-harness-engineering/docs/index.md b/courses/learn-harness-engineering/docs/index.md deleted file mode 100644 index 3ad52b0e..00000000 --- a/courses/learn-harness-engineering/docs/index.md +++ /dev/null @@ -1,58 +0,0 @@ ---- -layout: page ---- - - - - - -
-
-
-

Loading...

-
-
diff --git a/courses/learn-harness-engineering/docs/ja/index.md b/courses/learn-harness-engineering/docs/ja/index.md deleted file mode 100644 index d1d9f44f..00000000 --- a/courses/learn-harness-engineering/docs/ja/index.md +++ /dev/null @@ -1,73 +0,0 @@ -# Learn Harness Engineering へようこそ - -Learn Harness Engineering は、AI コーディングエージェントを工程として扱うためのコースです。業界で進んでいる Harness Engineering の理論と実践を調査し、実際の開発に使える形に整理しています。主な参考資料は次のとおりです。 -- [OpenAI: Harness engineering: leveraging Codex in an agent-first world](https://openai.com/index/harness-engineering/) -- [Anthropic: Effective harnesses for long-running agents](https://www.anthropic.com/engineering/effective-harnesses-for-long-running-agents) -- [Anthropic: Harness design for long-running application development](https://www.anthropic.com/engineering/harness-design-long-running-apps) -- [Awesome Harness Engineering](https://github.com/walkinglabs/awesome-harness-engineering) - -このコースでは、環境設計、状態管理、検証、制御の仕組みを体系的に学び、Codex や Claude Code のようなエージェント型コーディングツールを本当に信頼できるものにする方法を扱います。明示的なルールと境界で AI コーディングアシスタントを制約しながら、機能追加、バグ修正、開発作業の自動化を進めるための実践的な方法を学びます。 - -## はじめる - -学習経路を選んで開始してください。このコースは、理論講義、実践プロジェクト、そのままコピーして使えるリソースライブラリで構成されています。 - - - -## Harness の中核メカニズム - -harness は「モデルを賢くする」ものではありません。モデルが働くための閉じたループの**作業システム**を作るものです。中核の流れは次の図で理解できます。 - -```mermaid -graph TD - A["Clear Objective
AGENTS.md"] --> B("Initialization
init.sh") - B --> C{"Run Tasks
AI Agent"} - C -->|Encounter Issues| D["Runtime Feedback
CLI / Logs"] - D -->|Auto-fix| C - C -->|Code Completed| E{"Verify & QA
Test suite"} - E -->|Failed| D - E -->|Passed| F["Cleanup & Handoff
claude-progress.md"] - - classDef primary fill:#D95C41,stroke:#C14E36,color:#fff,font-weight:bold; - classDef process fill:#F4F3EE,stroke:#D1D1D1,color:#1A1A1A; - classDef check fill:#EAE8E1,stroke:#B3B3B3,color:#1A1A1A; - - class A,F primary; - class B,D process; - class C,E check; -``` - -## 学べること - -このコースで身につける主要な考え方は次のとおりです。 - -
    -
  • 明示的なルールと境界でエージェントの挙動を制約する。
  • -
  • 長時間・複数セッションのタスクでも文脈を維持する。
  • -
  • 早すぎる完了宣言を防ぐ。
  • -
  • フルパイプラインのテストと自己レビューで作業を検証する。
  • -
  • ランタイムを観測可能にし、デバッグしやすくする。
  • -
- -## 次のステップ - -基本概念を理解したら、次のガイドでさらに深く進めます。 - - diff --git a/courses/learn-harness-engineering/docs/ja/lectures/lecture-01-why-capable-agents-still-fail/code/failure-pattern-demo.ts b/courses/learn-harness-engineering/docs/ja/lectures/lecture-01-why-capable-agents-still-fail/code/failure-pattern-demo.ts deleted file mode 100644 index 311e665b..00000000 --- a/courses/learn-harness-engineering/docs/ja/lectures/lecture-01-why-capable-agents-still-fail/code/failure-pattern-demo.ts +++ /dev/null @@ -1,170 +0,0 @@ -/** - * failure-pattern-demo.ts - * - * Simulates the 4-step failure pattern that capable agents fall into: - * 1. Incomplete context - * 2. Locally reasonable changes - * 3. No global verification - * 4. Premature completion - * - * Run: npx tsx docs/lectures/lecture-01-why-capable-agents-still-fail/code/failure-pattern-demo.ts - */ - -// --------------------------------------------------------------------------- -// Types -// --------------------------------------------------------------------------- - -interface StepState { - step: number; - name: string; - contextAvailable: string[]; - contextMissing: string[]; - actionTaken: string; - localOutcome: string; - globalImpact: string; - completed: boolean; -} - -// --------------------------------------------------------------------------- -// Simulated "model" -- a simple decision function that bases its output -// solely on whatever context it has been given. -// --------------------------------------------------------------------------- - -function modelDecide(context: string[], task: string): string { - const has = (s: string) => context.some((c) => c.includes(s)); - - // The task is to "add a search endpoint to the API". - // Correct answer requires knowing about auth middleware and rate limiting. - - if (!has("auth")) { - return "Created new route handler /search without authentication checks"; - } - if (!has("rate-limit")) { - return "Added search route with auth but forgot rate limiting"; - } - if (!has("test-standards")) { - return "Implemented search with auth and rate-limit, but no tests"; - } - return "Fully implemented search endpoint with auth, rate-limit, and tests"; -} - -// --------------------------------------------------------------------------- -// Failure simulation -// --------------------------------------------------------------------------- - -function simulateFailurePattern(): StepState[] { - const steps: StepState[] = []; - - // ---- Step 1: Incomplete Context ---- - const step1Context = ["project structure", "route definitions"]; - const step1Missing = ["auth middleware", "rate-limiting policy", "test standards"]; - const step1Decision = modelDecide(step1Context, "add search endpoint"); - - steps.push({ - step: 1, - name: "Incomplete Context", - contextAvailable: step1Context, - contextMissing: step1Missing, - actionTaken: step1Decision, - localOutcome: "Looks good -- route compiles, returns data", - globalImpact: "Missing auth means unauthenticated access to search", - completed: false, - }); - - // ---- Step 2: Locally Reasonable Changes ---- - // The agent adds auth after a hint, but still lacks other context. - const step2Context = [...step1Context, "auth middleware"]; - const step2Missing = ["rate-limiting policy", "test standards"]; - const step2Decision = modelDecide(step2Context, "add search endpoint"); - - steps.push({ - step: 2, - name: "Locally Reasonable Changes", - contextAvailable: step2Context, - contextMissing: step2Missing, - actionTaken: step2Decision, - localOutcome: "Route has auth -- looks complete locally", - globalImpact: "No rate limiting means the endpoint can be abused", - completed: false, - }); - - // ---- Step 3: No Global Verification ---- - const step3Context = [...step2Context, "rate-limiting policy"]; - const step3Missing = ["test standards"]; - const step3Decision = modelDecide(step3Context, "add search endpoint"); - - steps.push({ - step: 3, - name: "No Global Verification", - contextAvailable: step3Context, - contextMissing: step3Missing, - actionTaken: step3Decision, - localOutcome: "Feature appears fully implemented", - globalImpact: "No tests -- regression risk, violates project standards", - completed: false, - }); - - // ---- Step 4: Premature Completion ---- - steps.push({ - step: 4, - name: "Premature Completion", - contextAvailable: step3Context, - contextMissing: step3Missing, - actionTaken: 'Agent outputs: "Done. Added search endpoint."', - localOutcome: "Agent is satisfied, task marked complete", - globalImpact: "Task is incomplete -- missing tests, no E2E verification", - completed: true, - }); - - return steps; -} - -// --------------------------------------------------------------------------- -// Comparison table -// --------------------------------------------------------------------------- - -function printComparisonTable(steps: StepState[]): void { - console.log("\n" + "=".repeat(90)); - console.log(" FAILURE PATTERN DEMO -- 4 Steps to a Broken Deliverable"); - console.log("=".repeat(90)); - - for (const s of steps) { - console.log(`\n Step ${s.step}: ${s.name}`); - console.log(" " + "-".repeat(60)); - console.log(` Context available : ${s.contextAvailable.join(", ")}`); - console.log(` Context missing : ${s.contextMissing.join(", ") || "(none)"}`); - console.log(` Action taken : ${s.actionTaken}`); - console.log(` Local outcome : ${s.localOutcome}`); - console.log(` Global impact : ${s.globalImpact}`); - console.log(` Marked complete? : ${s.completed ? "YES (premature)" : "No"}`); - } - - // Summary comparison - console.log("\n" + "=".repeat(90)); - console.log(" COMPARISON: What the agent saw vs. what was actually needed"); - console.log("=".repeat(90)); - - const totalRequired = ["project structure", "route definitions", "auth middleware", "rate-limiting policy", "test standards"]; - const finalAvailable = steps[steps.length - 1].contextAvailable; - - const header = "| Criterion | Available | Missing | Status |"; - const sep = "|------------------------|-----------|---------|----------|"; - console.log("\n" + header); - console.log(sep); - - for (const item of totalRequired) { - const avail = finalAvailable.includes(item); - const row = `| ${item.padEnd(23)}| ${(avail ? "Yes" : "No").padEnd(10)}| ${(!avail ? "Yes" : "").padEnd(8)}| ${(avail ? "OK" : "GAP").padEnd(9)}|`; - console.log(row); - } - - const gapCount = totalRequired.filter((i) => !finalAvailable.includes(i)).length; - console.log("\n Result: Agent completed with " + gapCount + " of " + totalRequired.length + " context items missing."); - console.log(" This is the core failure pattern: each step looked reasonable in isolation.\n"); -} - -// --------------------------------------------------------------------------- -// Run -// --------------------------------------------------------------------------- - -printComparisonTable(simulateFailurePattern()); diff --git a/courses/learn-harness-engineering/docs/ja/lectures/lecture-01-why-capable-agents-still-fail/code/failure-signals-checklist.md b/courses/learn-harness-engineering/docs/ja/lectures/lecture-01-why-capable-agents-still-fail/code/failure-signals-checklist.md deleted file mode 100644 index 0cbe5b6a..00000000 --- a/courses/learn-harness-engineering/docs/ja/lectures/lecture-01-why-capable-agents-still-fail/code/failure-signals-checklist.md +++ /dev/null @@ -1,9 +0,0 @@ -# 失敗シグナルチェックリスト - -弱い harness の実行結果をレビューする際にこのチェックリストを使用してください。 - -- エージェントはアプリの起動方法を尋ねたか、または誤って推測したか? -- 予定された製品と一致しないディレクトリや抽象を作成したか? -- 完全なワークフローなしに可視的な UI シェルを作成した後に停止したか? -- 今後の実行が継続するのに役立つメモや成果物を残したか? -- 新しいセッションは5分以内に何が起きたか理解できるか? diff --git a/courses/learn-harness-engineering/docs/ja/lectures/lecture-01-why-capable-agents-still-fail/code/index.md b/courses/learn-harness-engineering/docs/ja/lectures/lecture-01-why-capable-agents-still-fail/code/index.md deleted file mode 100644 index 001c6095..00000000 --- a/courses/learn-harness-engineering/docs/ja/lectures/lecture-01-why-capable-agents-still-fail/code/index.md +++ /dev/null @@ -1,7 +0,0 @@ -# 講義 01 のコード - -このフォルダーは、以下を示す小さな例に使用してください: - -- 強力なモデルが弱い環境で失敗する様子 -- 不十分に指定されたリポジトリ設定 -- 欠落しているフィードバックループ diff --git a/courses/learn-harness-engineering/docs/ja/lectures/lecture-01-why-capable-agents-still-fail/code/underspecified-task.md b/courses/learn-harness-engineering/docs/ja/lectures/lecture-01-why-capable-agents-still-fail/code/underspecified-task.md deleted file mode 100644 index 75d7c78f..00000000 --- a/courses/learn-harness-engineering/docs/ja/lectures/lecture-01-why-capable-agents-still-fail/code/underspecified-task.md +++ /dev/null @@ -1,18 +0,0 @@ -# 仕様不足のタスク例 - -AI 質問応答機能付きのデスクトップナレッジベースアプリを構築する。 - -制約: - -- 何も指定されていない -- 起動コマンドが与えられていない -- フォルダー構造のガイダンスがない -- データモデルが定義されていない -- 明示的な完了基準がない - -この種のプロンプトの典型的な結果: - -- エージェントがアドホックに構造をでっち上げる -- アプリはコンパイルできるが一貫して起動できない -- 利用可能なインポート/クエリパスが存在する前に UI が表示される -- エージェントは表面的な成功の後に停止することが多い diff --git a/courses/learn-harness-engineering/docs/ja/lectures/lecture-01-why-capable-agents-still-fail/index.md b/courses/learn-harness-engineering/docs/ja/lectures/lecture-01-why-capable-agents-still-fail/index.md deleted file mode 100644 index 8081d448..00000000 --- a/courses/learn-harness-engineering/docs/ja/lectures/lecture-01-why-capable-agents-still-fail/index.md +++ /dev/null @@ -1,117 +0,0 @@ -[中文版本 →](../../../zh/lectures/lecture-01-why-capable-agents-still-fail/) - -> コード例: [code/](https://github.com/walkinglabs/learn-harness-engineering/blob/main/docs/ja/lectures/lecture-01-why-capable-agents-still-fail/code/) -> 実践プロジェクト: [プロジェクト 01. プロンプトのみ vs ルール優先](./../../projects/project-01-baseline-vs-minimal-harness/index.md) - -# 講義 01. 強いモデルは信頼できる実行を意味しない - -あなたは AI の世界にかなり慣れているつもりです。Claude Pro を契約し、GPT-4o API キーを持ち、SWE-bench のリーダーボードも覚えている。ある日、ついに本物のプロジェクトを AI エージェントに任せます。自信満々です。結果はどうでしょう。機能を追加したがテストを壊す。バグを直したが 2 つ増やす。20 分走って誇らしげに「完了」と言う。そしてコードを見ると、そもそも頼んだものではありません。 - -最初の反応は「このモデルはまだ足りない。アップグレードしよう」かもしれません。少し待ってください。財布を開く前に、問題はモデルではない可能性があります。 - -数字を見てみましょう。2025 年後半時点で、最強クラスの coding agent でも SWE-bench Verified ではおおむね 50-60% 程度です。しかもこれは、明確な issue 説明と既存テストがある、慎重に選ばれたタスクでの数字です。日常の開発環境、つまり曖昧な要求、既存テストなし、暗黙のビジネスルールが散らばった環境に移ると、その数字はさらに下がります。 - -しかし、この数字の裏には直感に反する真実があります。 - -## 同じ馬、違う運命 - -Anthropic は制御実験を行いました。同じプロンプト(「2D レトロゲームメーカーを作る」)、同じモデル(Opus 4.5)。1 回目は bare、支援なし。20 分、9 ドル、ゲームの中核機能はまったく動きませんでした。2 回目は完全な harness(planner + generator + evaluator の 3 エージェント構成)。6 時間、200 ドル、ゲームはプレイ可能でした。 - -モデルは変えていません。Opus 4.5 は同じ Opus 4.5 です。変わったのは馬具です。 - -OpenAI の harness engineering 記事ははっきり言っています。よく harness されたリポジトリでは、Codex は「不可靠」から「可靠」へ変わる。表現に注目してください。「少し良くなる」ではなく、質的な変化です。サラブレッドのようなものです。馬具なしでも乗れますが、うまく制御できず、遠くへは行けず、落ちても不思議ではありません。harness はその一式の馬具です。つまり、**モデル重みの外側にあるエンジニアリング基盤のすべて**です。 - -## エージェントは実際どこで詰まるのか - -具体的には何が起きるのでしょうか。 - -最も多いのは、タスクを明確に定義していないことです。「検索機能を追加して」と言うと、エージェントの理解はあなたとまったく違うかもしれません。何を検索するのか。全文検索か構造化検索か。ページネーションは。ハイライトは。指定しなければ、エージェントは推測します。当たれば運、外れれば最初に具体化するより高くつきます。レストランで「魚をください」と言うようなものです。煮魚か蒸し魚か鍋かは運任せです。 - -指定したとしても、プロジェクトにはエージェントが知らない暗黙のアーキテクチャ慣習があります。チームは SQLAlchemy 2.0 構文で統一しているのに、エージェントはデフォルトで 1.x のコードを書く。全 API は OAuth 2.0 認証必須なのに、そのルールはあなたの頭の中と 3 か月前の Slack にしかない。エージェントには見えません。従いたくないのではなく、文字通りそのルールの存在を知りません。 - -環境も罠です。不完全な dev 環境、欠けた依存関係、違うツールバージョン。エージェントは本来の課題ではなく、`pip install` の失敗や Node バージョン不一致に貴重なコンテキストを燃やします。腕のいい大工を雇ったのに、ハンマーも釘も水平な作業台も渡さないようなものです。どれほど有能でも仕事はできません。 - -さらに多いのは、検証する方法がないことです。テストがない。lint がない。検証コマンドをエージェントに伝えていない。エージェントはコードを書き、眺め、問題なさそうだと判断して「完了」と言います。答え合わせなしで宿題を提出させるようなものです。本人は正しいと思っていますが、採点すると間違いだらけです。Anthropic は、エージェントがコンテキスト不足を感じると急いで終わらせ、検証を飛ばし、最適解より簡単な解を選ぶ現象も観察しています。彼らはこれを「context anxiety」と呼びます。試験時間が残り少ないと気づいて、残りの選択問題を適当に埋めるのと同じです。 - -セッションをまたぐ長いタスクはさらに悪化します。前セッションの発見は失われ、新しいセッションはプロジェクト構造とコード構成を毎回探索し直します。永続状態を持たないエージェントは、30 分を超えるタスクで失敗率が急上昇します。 - -## 重要用語 - -こうした状況を踏まえると、次の概念は単なる流行語ではありません。 - -- **能力ギャップ**: ベンチマーク上のモデル性能と実タスクでの性能の大きな差。SWE-bench Verified で 50-60% ということは、実 issue のほぼ半分は解けないということです。 -- **Harness**: モデルの外側にあるすべて。指示、ツール、環境、状態管理、検証フィードバック。モデル重みでなければ harness です。この講義で言う「馬具」です。 -- **Harness 起因の失敗**: モデルには能力があるのに、実行環境に構造的欠陥があるため失敗すること。Anthropic の制御実験がこれを示しています。 -- **検証ギャップ**: エージェントの出力への自信と実際の正しさの差。エージェントが「完了」と言うが完了していない。最も多い失敗モードです。 -- **診断ループ**: 実行し、失敗を観察し、特定の harness 層に原因を割り当て、その層を修正し、再実行する。harness engineering の中核手法です。 -- **Definition of Done**: 機械的に検証可能な完了条件。テスト通過、lint クリーン、型チェック通過。明示しなければ、エージェントは勝手に完了条件を作ります。 - -## 失敗したら、まず harness を直す - -中核原則はこれです。**失敗したら、まずモデルを替えるのではなく harness を確認する。** 同じモデルが似た構造化タスクで成功するなら、harness の問題だと仮定します。車が止まったとき、いきなりエンジンを疑うのではなく、まずガソリン切れを確認するのと同じです。 - -具体的には次のように進めます。 - -**すべての失敗を特定の層に帰属させる。** 「モデルがだめ」と言わないでください。タスクが曖昧だったのか。コンテキスト不足だったのか。検証手段がなかったのか。各失敗を 5 つの防御層(タスク仕様、コンテキスト提供、実行環境、検証フィードバック、状態管理)に対応づけます。これは実践的な診断レイヤーであり、上の 6 つの用語とは別の分類です。この習慣がつくと、「モデルが足りない」はログにだんだん出なくなります。 - -**すべてのタスクに明示的な Definition of Done を書く。** 「検索機能を追加して」ではなく、こう書きます。 -``` -Completion criteria: -- New endpoint GET /api/search?q=xxx -- Supports pagination, default 20 items -- Results include highlighted snippets -- All new code passes pytest -- Type checking passes (mypy --strict) -``` - -**AGENTS.md を作る。** リポジトリルートに置き、技術スタック、アーキテクチャ慣習、検証コマンドをエージェントに伝えます。これは harness engineering の第一歩であり、最も ROI の高い手順です。高価なモデルへアップグレードするより、1 つの `AGENTS.md` のほうが効くことがあります。冗談ではありません。 - -**診断ループを作る。** 失敗を「またモデルが馬鹿をした」と扱わない。harness に欠陥があるという信号として扱います。失敗ごとに層を特定し、修正し、同じ失敗を繰り返さない。数回回すと harness は強くなり、エージェント性能は安定します。道路補修のように、穴を埋めるほど次の区間が滑らかになります。 - -**改善を定量化する。** 各タスクが成功したか失敗したか、どの層が原因だったかを簡単に記録します。数回後にはボトルネック層が見えてくるので、そこに力を集中します。 - -## 100 万行の実験 - -OpenAI は 2025 年に強烈な実験を行いました。空の git リポジトリから、Codex で完全な社内プロダクトを構築する。5 か月後、そのリポジトリにはおよそ 100 万行のコードがありました。アプリケーションロジック、インフラ、ツール、ドキュメント、社内開発ツール。すべてエージェント生成です。3 人のエンジニアが Codex を操作し、約 1,500 PR を作成してマージしました。1 人あたり 1 日平均 3.5 PR です。 - -重要な制約は、**人間が直接コードを書かない**ことでした。これは見世物ではありません。エンジニアの主仕事がコードを書くことではなく、環境を設計し、意図を表現し、フィードバックループを作ることになったとき、何が変わるかを強制的に学ぶための設計でした。 - -初期の進捗は予想より遅かった。Codex が無能だったからではなく、環境が不十分だったからです。エージェントには高レベル目標を進めるためのツール、抽象、内部構造が足りませんでした。エンジニアの仕事は、大きな目標を小さな構成要素(設計、コード、レビュー、テスト)に分解し、エージェントに組み立てさせ、それらの構成要素でより複雑なタスクを解けるようにすることになりました。何かが失敗したときの修正は、ほぼ常に「もっと頑張れ」ではなく、「エージェントに足りない能力は何か、それを理解可能かつ実行可能にするにはどうするか」でした。 - -この実験は、本講義の中心命題を直接示しています。**同じモデルでも、bare な環境と完全な harness を持つ環境では根本的に違う出力を出す。** モデルは変わっていません。環境が変わったのです。 - -> Source: [OpenAI: Harness engineering: leveraging Codex in an agent-first world](https://openai.com/index/harness-engineering/) - -## もっと身近な例 - -あるチームは Claude Sonnet を使って、中規模の Python Web アプリ(FastAPI + PostgreSQL + Redis、約 15,000 行)に新しい API endpoint を追加しようとしました。 - -最初は「`/api/v2/users` 配下に user preferences endpoints を追加して」という 1 文だけを渡しました。結果は、エージェントがコンテキストの 40% をリポジトリ探索に使い、見た目は妥当だがプロジェクトのエラーハンドリングパターンに従わず、古い SQLAlchemy 構文を使い、endpoint にランタイムエラーがあるのに完了宣言しました。次のセッションは探索をやり直す必要がありました。 - -その後、彼らは `AGENTS.md`(プロジェクト構造と技術スタックバージョンの説明)、明示的な検証コマンド(`pytest tests/api/v2/ && python -m mypy src/`)、ADR を追加しました。同じモデルは 3 回の独立実行すべてで成功し、コンテキスト効率は約 60% 改善しました。 - -モデルは変えていません。harness を変えました。 - -## 重要なポイント - -- モデル能力と実行信頼性は別物です。サラブレッドにも良い馬具が必要です。 -- 失敗したら、まず harness を確認し、その後でモデルを疑います。モデル交換は最も高価な選択肢で、多くの場合はモデル問題ですらありません。 -- すべての失敗は信号です。harness に構造的欠陥があります。見つけて直してください。 -- 5 つの防御層: タスク仕様、コンテキスト提供、実行環境、検証フィードバック、状態管理。医師が一般的な原因から除外するように、体系的に確認します。 -- 1 つの `AGENTS.md` は、高価なモデルに替えるより効果的かもしれません。本当に。 - -## 参考資料 - -- [OpenAI: Harness Engineering — Leveraging Codex in an Agent-First World](https://openai.com/index/harness-engineering/) -- [Anthropic: Effective Harnesses for Long-Running Agents](https://www.anthropic.com/engineering/effective-harnesses-for-long-running-agents) -- [HumanLayer: Skill Issue — Harness Engineering for Coding Agents](https://humanlayer.dev/articles/harness-engineering-for-coding-agents/) -- [SWE-bench Leaderboard](https://www.swebench.com/) -- [Thoughtworks Technology Radar: Harness Engineering](https://www.thoughtworks.com/radar) - -## 演習 - -1. **比較実験**: よく知っているコードベースと非自明な変更タスクを選びます。まず harness 支援なしでエージェントを実行し、失敗を記録します。次に明示的な検証コマンドを含む `AGENTS.md` を追加し、同じエージェントで再実行します。結果を比較し、各失敗を 5 つの防御層のどれかに帰属させます。 - -2. **検証ギャップ測定**: 5 つの coding task を選びます。各タスク後、エージェントが完了を主張したかを記録し、独立したテストで実際の正しさを確認します。完了と言ったが実際は完了していない割合を計算します。それが検証ギャップです。どの検証コマンドがこの割合を下げるか考えてください。 - -3. **診断ループ練習**: プロジェクト内でエージェントが繰り返し失敗するタスクを探します。1 回実行し、失敗を記録します。5 つの層のどれかに原因を割り当てます。その層を修正し、再実行します。3-5 回繰り返し、改善を記録してください。 diff --git a/courses/learn-harness-engineering/docs/ja/lectures/lecture-02-what-a-harness-actually-is/code/harness-components.md b/courses/learn-harness-engineering/docs/ja/lectures/lecture-02-what-a-harness-actually-is/code/harness-components.md deleted file mode 100644 index 7ad304f2..00000000 --- a/courses/learn-harness-engineering/docs/ja/lectures/lecture-02-what-a-harness-actually-is/code/harness-components.md +++ /dev/null @@ -1,21 +0,0 @@ -# Harness コンポーネントの例 - -ローカルリポジトリで作業するコーディングエージェントの場合: - -- モデル: - LLM 自体 - -- Harness: - - システムプロンプト - - AGENTS.md - - bash ツール - - ファイル読み書きツール - - git アクセス - - ローカルファイルシステム - - 起動スクリプト - - テストコマンド - - 停止フック - - lint チェック - - 評価ループ(evaluator loop) - -上記の harness 構成要素のいずれかを変更すると、実質的なエージェントが変わります。 diff --git a/courses/learn-harness-engineering/docs/ja/lectures/lecture-02-what-a-harness-actually-is/code/harness-vs-no-harness.ts b/courses/learn-harness-engineering/docs/ja/lectures/lecture-02-what-a-harness-actually-is/code/harness-vs-no-harness.ts deleted file mode 100644 index d856ba36..00000000 --- a/courses/learn-harness-engineering/docs/ja/lectures/lecture-02-what-a-harness-actually-is/code/harness-vs-no-harness.ts +++ /dev/null @@ -1,180 +0,0 @@ -/** - * harness-vs-no-harness.ts - * - * Side-by-side comparison of the same task executor running with and without - * a harness. The harness version adds explicit rules, verification steps, - * and stop conditions. - * - * Run: npx tsx docs/lectures/lecture-02-what-a-harness-actually-is/code/harness-vs-no-harness.ts - */ - -// --------------------------------------------------------------------------- -// Types & helpers -// --------------------------------------------------------------------------- - -interface TaskResult { - name: string; - passed: boolean; - durationMs: number; - issues: string[]; -} - -function pad(s: string, len: number): string { - return s.length >= len ? s : s + " ".repeat(len - s.length); -} - -// --------------------------------------------------------------------------- -// Simulated task executor -// --------------------------------------------------------------------------- - -/** A simple task that can succeed or fail depending on rules. */ -type Task = { - name: string; - requiresAuth: boolean; - hasTests: boolean; - withinScope: boolean; -}; - -const tasks: Task[] = [ - { name: "Add search endpoint", requiresAuth: true, hasTests: false, withinScope: true }, - { name: "Add delete endpoint", requiresAuth: true, hasTests: true, withinScope: true }, - { name: "Refactor auth middleware", requiresAuth: true, hasTests: true, withinScope: false }, - { name: "Add health check", requiresAuth: false, hasTests: true, withinScope: true }, - { name: "Add rate limiter", requiresAuth: true, hasTests: false, withinScope: true }, -]; - -// --------------------------------------------------------------------------- -// Run WITHOUT harness -- just execute every task, no checks -// --------------------------------------------------------------------------- - -function runWithoutHarness(taskList: Task[]): TaskResult[] { - const results: TaskResult[] = []; - - for (const t of taskList) { - const start = performance.now(); - const issues: string[] = []; - - // The "agent" does the work and calls it done. - let passed = true; - - // Problems that go undetected without a harness - if (t.requiresAuth && !t.hasTests) { - issues.push("No tests for auth-protected endpoint"); - // Agent doesn't notice -- marks as pass anyway - } - if (!t.withinScope) { - issues.push("Task is outside current scope"); - // Agent doesn't notice - } - - const duration = performance.now() - start; - results.push({ name: t.name, passed, durationMs: duration, issues }); - } - - return results; -} - -// --------------------------------------------------------------------------- -// Run WITH harness -- rules, verification, stop conditions -// --------------------------------------------------------------------------- - -function runWithHarness(taskList: Task[]): TaskResult[] { - const rules = { - requireTestsForAuth: true, - enforceScope: true, - }; - - const results: TaskResult[] = []; - - for (const t of taskList) { - const start = performance.now(); - const issues: string[] = []; - let passed = true; - - // Rule: auth endpoints must have tests - if (rules.requireTestsForAuth && t.requiresAuth && !t.hasTests) { - issues.push("BLOCKED: Auth-protected endpoint missing tests"); - passed = false; - } - - // Rule: stay in scope - if (rules.enforceScope && !t.withinScope) { - issues.push("BLOCKED: Task outside active scope -- skipped"); - passed = false; - } - - // Verification: re-check after execution - if (passed && !t.hasTests) { - issues.push("WARNING: No tests exist for this change"); - } - - const duration = performance.now() - start; - results.push({ name: t.name, passed, durationMs: duration, issues }); - } - - return results; -} - -// --------------------------------------------------------------------------- -// Reporting -// --------------------------------------------------------------------------- - -function printComparison( - noHarness: TaskResult[], - withHarness: TaskResult[] -): void { - console.log("\n" + "=".repeat(80)); - console.log(" HARNESS vs NO-HARNESS COMPARISON"); - console.log("=".repeat(80)); - - const header = `| ${pad("Task", 28)}| ${pad("No-Harness", 12)}| ${pad("Issues(NH)", 35)}| ${pad("Harness", 12)}| ${pad("Issues(H)", 35)}|`; - const sep = `|${"-".repeat(30)}|${"-".repeat(14)}|${"-".repeat(37)}|${"-".repeat(14)}|${"-".repeat(37)}|`; - - console.log("\n" + header); - console.log(sep); - - for (let i = 0; i < noHarness.length; i++) { - const nh = noHarness[i]; - const wh = withHarness[i]; - const nhPass = nh.passed ? "PASS" : "FAIL"; - const whPass = wh.passed ? "PASS" : "FAIL"; - const nhIssue = nh.issues[0] ?? "(none)"; - const whIssue = wh.issues[0] ?? "(none)"; - - console.log( - `| ${pad(nh.name, 28)}| ${pad(nhPass, 12)}| ${pad(nhIssue, 35)}| ${pad(whPass, 12)}| ${pad(whIssue, 35)}|` - ); - } - - // Metrics - const nhPassed = noHarness.filter((r) => r.passed).length; - const whPassed = withHarness.filter((r) => r.passed).length; - const nhIssues = noHarness.reduce((sum, r) => sum + r.issues.length, 0); - const whIssues = withHarness.reduce((sum, r) => sum + r.issues.length, 0); - - console.log("\n" + "=".repeat(80)); - console.log(" SUMMARY METRICS"); - console.log("=".repeat(80)); - - const metricsHeader = `| ${pad("Metric", 30)}| ${pad("No Harness", 15)}| ${pad("With Harness", 15)}|`; - const metricsSep = `|${"-".repeat(32)}|${"-".repeat(17)}|${"-".repeat(17)}|`; - console.log("\n" + metricsHeader); - console.log(metricsSep); - console.log(`| ${pad("Tasks passed", 30)}| ${pad(String(nhPassed) + "/" + noHarness.length, 15)}| ${pad(String(whPassed) + "/" + withHarness.length, 15)}|`); - console.log(`| ${pad("Issues detected", 30)}| ${pad(String(nhIssues), 15)}| ${pad(String(whIssues), 15)}|`); - console.log(`| ${pad("False positives (passed but flawed)", 30)}| ${pad(String(nhPassed - truePassCount(noHarness)), 15)}| ${pad(String(whPassed - truePassCount(withHarness)), 15)}|`); - - console.log("\n The harness catches problems the no-harness run silently ignores."); - console.log(" Without a harness, every task 'passes' even when it shouldn't.\n"); -} - -/** Count tasks that actually pass (have tests and are in scope). */ -function truePassCount(results: TaskResult[]): number { - return results.filter((r) => r.passed && r.issues.length === 0).length; -} - -// --------------------------------------------------------------------------- -// Run -// --------------------------------------------------------------------------- - -printComparison(runWithoutHarness(tasks), runWithHarness(tasks)); diff --git a/courses/learn-harness-engineering/docs/ja/lectures/lecture-02-what-a-harness-actually-is/code/index.md b/courses/learn-harness-engineering/docs/ja/lectures/lecture-02-what-a-harness-actually-is/code/index.md deleted file mode 100644 index adb8ea57..00000000 --- a/courses/learn-harness-engineering/docs/ja/lectures/lecture-02-what-a-harness-actually-is/code/index.md +++ /dev/null @@ -1,8 +0,0 @@ -# 講義 02 のコード - -このフォルダーは、以下を区別する小さな例に使用してください: - -- モデルの動作 -- harness の動作 -- プロンプトのみのセットアップ -- 環境に基づくエージェントのセットアップ diff --git a/courses/learn-harness-engineering/docs/ja/lectures/lecture-02-what-a-harness-actually-is/code/minimal-harness-loop.ts b/courses/learn-harness-engineering/docs/ja/lectures/lecture-02-what-a-harness-actually-is/code/minimal-harness-loop.ts deleted file mode 100644 index e56cddd8..00000000 --- a/courses/learn-harness-engineering/docs/ja/lectures/lecture-02-what-a-harness-actually-is/code/minimal-harness-loop.ts +++ /dev/null @@ -1,36 +0,0 @@ -type Message = { - role: "user" | "assistant"; - content: string; -}; - -type ToolResult = { - ok: boolean; - output: string; -}; - -function runTool(name: string, input: string): ToolResult { - if (name === "read_file") { - return { ok: true, output: `contents of ${input}` }; - } - - return { ok: false, output: `unknown tool: ${name}` }; -} - -export function minimalHarness(messages: Message[]) { - const nextAction = { - tool: "read_file", - input: "README.md" - }; - - const result = runTool(nextAction.tool, nextAction.input); - - return { - messages: [ - ...messages, - { - role: "assistant", - content: `Tool ${nextAction.tool} returned: ${result.output}` - } - ] - }; -} diff --git a/courses/learn-harness-engineering/docs/ja/lectures/lecture-02-what-a-harness-actually-is/index.md b/courses/learn-harness-engineering/docs/ja/lectures/lecture-02-what-a-harness-actually-is/index.md deleted file mode 100644 index 4239dd0b..00000000 --- a/courses/learn-harness-engineering/docs/ja/lectures/lecture-02-what-a-harness-actually-is/index.md +++ /dev/null @@ -1,109 +0,0 @@ -[中文版本 →](../../../zh/lectures/lecture-02-what-a-harness-actually-is/) - -> コード例: [code/](https://github.com/walkinglabs/learn-harness-engineering/blob/main/docs/ja/lectures/lecture-02-what-a-harness-actually-is/code/) -> 実践プロジェクト: [Project 01. Prompt-only vs. rules-first](./../../projects/project-01-baseline-vs-minimal-harness/index.md) - -# 講義 02. Harness とは何か - -「harness」という言葉は AI コーディングエージェントの界隈でよく使われますが、正直なところ、ほとんどの人は harness と言っても「プロンプトファイル」のことを指しています。それは harness ではありません。食材だけを用意してレストランを開くようなものです — コンロも包丁もレシピも盛り付けのワークフローもありません。それはレストランではなく、冷蔵庫です。 - -この講義では、正確で実践的な harness の定義を提示します。学術的な抽象論ではなく、今日から使えるフレームワークです。harness は 5 つのサブシステムで構成され、それぞれに明確な責任と評価基準があります。 - -## 例えから始めよう - -新しく入社したエンジニアとして、ドキュメントが一切ないプロジェクトに放り込まれたと想像してください。README もなく、コードにコメントもなく、テストの実行方法を教えてくれる人もおらず、CI の設定はどこかに埋もれています。良いコードを書けるでしょうか。もしあなたが十分に賢く、忍耐強ければ、おそらく書けるでしょう。しかし、「このプロジェクトが何なのかを理解する」ことに膨大な時間を費やすことになり、「問題を解決する」ことにはあまり時間が割けません。 - -AI エージェントも全く同じ状況に直面します。しかも状況はさらに悪いです — あなたは少なくとも同僚に質問できます。エージェントは目の前に置かれたファイルと実行可能なコマンドしか見ることができません。誰かの肩を叩いて「このプロジェクトで使っている ORM のバージョンは何ですか?」と聞くことはできません。 - -OpenAI は核心となる原則を「リポジトリこそが仕様である」と表現しています — 必要なコンテキストはすべてリポジトリ内に存在し、構造化された指示ファイル、明示的な検証コマンド、明確なディレクトリ構成を通じて提供されるべきだとしています。Anthropic の長時間実行エージェントに関するドキュメントは、状態の永続化、明示的なリカバリパス、構造化された進捗追跡を強調しています。2 社は異なる側面に焦点を当てていますが、言っていることは同じです。**モデルの外側にあるエンジニアリングインフラのすべてが、モデルの能力がどれだけ実際に発揮されるかを決定します。** - -すでに知っているツールをいくつか見てみましょう。 - -**Claude Code** は harness の考え方を体現しています。リポジトリから `CLAUDE.md` を読み込み(レシピ棚)、シェルコマンドを実行でき(包丁ラック)、ローカル環境で実行し(コンロ)、セッション履歴を維持し(仕込み台)、テストを実行して結果を確認できます(品質チェック窓口)。しかし、テストの実行方法を教えなければ、品質チェック窓口は壊れた状態です — 料理がちゃんと火を通っているか誰もわかりません。 - -**Cursor** も同様のロジックに従っています。`.cursorrules` ファイルがレシピ棚、ターミナルが包丁ラック、プロジェクト構造と lint 設定を読み取ってコンロとして機能させます。ただし、Cursor の状態管理は比較的弱いです — IDE を閉じて再度開くと、以前のコンテキストは消えてしまいます。 - -**Codex**(OpenAI のコーディングエージェント)は git worktrees を使って各タスクの実行環境を分離し、ローカルのオブザーバビリティスタック(ログ、メトリクス、トレース)と組み合わせることで、すべての変更が独立した環境で検証されます。`AGENTS.md` と明確な検証コマンドがあるリポジトリでは、「素の」リポジトリよりもはるかに良いパフォーマンスを発揮します。 - -**AutoGPT** は反面教師です — 構造化された状態管理がないため、長時間のタスクでコンテキストが蓄積し、正確なフィードバックメカニズムがないためエージェントがループに陥ります。多くの人が AutoGPT は「動かない」と言いますが、実際には AutoGPT の harness が動いていないのです — 壊れたコンロをシェフに渡せば、どんなに優れた食材でも料理は完成しません。 - -## 中核概念 - -- **harness とは何か**: モデルの重みパラメータ以外のエンジニアリングインフラのすべて。OpenAI はエンジニアの中心的な役割を3つに要約しています。環境の設計、意図の表現、フィードバックループの構築です。Anthropic は Claude Agent SDK を「汎用エージェント harness」と呼んでいます。 -- **リポジトリが唯一の信頼できる情報源**: エージェントが見られないものは、実質的に存在しません。OpenAI はリポジトリを「system of record」として扱います — 必要なコンテキストはすべてそこに存在し、構造化されたファイルと明確なディレクトリ構成を通じて提供される必要があります。 -- **マニュアルではなく地図を与える**: OpenAI の経験 — `AGENTS.md` は百科事典ではなく目次ページであるべきです。100行程度で十分です。収まらない場合は `docs/` ディレクトリに分割し、エージェントが必要に応じて読み込むようにします。 -- **細かく管理するのではなく制約を与える**: 良い harness は、実行可能なルールを使ってエージェントを制約し、指示を一つ一つ列挙するのではありません。OpenAI は「不変条件を強制し、実装を細かく管理しない」と述べています。Anthropic は、エージェントが自信満々に自分の仕事を賞賛することを発見し、解決策は「仕事をする人」と「仕事をチェックする人」を分離することだとしています。 -- **コンポーネントを一つずつ削除する**: 各 harness コンポーネントの限界貢献を定量化するには、一つずつ削除し、どの削除が最大のパフォーマンス低下を引き起こすかを確認します。Anthropic はこの手法を用い、モデルが強力になるにつれて一部のコンポーネントは重要でなくなるが、常に新しいコンポーネントが必要になることを発見しました。 - -## 5サブシステム harness モデル - -キッチンの例えに戻りましょう。完全なキッチンには5つの機能領域があり、harness には5つのサブシステムがあります。 - -```mermaid -flowchart LR - Rules["Project rules
AGENTS.md / CLAUDE.md"] --> Agent["AI Agent"] - State["Progress and git
PROGRESS.md / commits"] --> Agent - Agent --> Tools["Tools
shell / files / tests"] - Tools --> Env["Runtime
deps / services / versions"] - Env --> Checks["Check results
test / lint / build"] - Checks --> Agent -``` - -**指示サブシステム(レシピ棚)**: `AGENTS.md`(または `CLAUDE.md`)を作成し、プロジェクトの概要と目的(1文)、技術スタックとバージョン(Python 3.11、FastAPI 0.100+、PostgreSQL 15)、初回実行コマンド(`make setup`、`make test`)、譲れないハード制約(「すべての API は OAuth 2.0 を使用すること」)、より詳細なドキュメントへのリンクを含めます。 - -**ツールサブシステム(包丁ラック)**: エージェントが十分なツールアクセスを持つことを確保します。「セキュリティ」のためにシェルを無効にしないでください — エージェントが `pip install` すら実行できないなら、どうやって作業するのでしょうか。しかし、すべてを開放するのも良くありません — 最小権限の原則に従ってください。 - -**環境サブシステム(コンロ)**: 環境の状態を自己記述的にします。`pyproject.toml` や `package.json` で依存関係をロックし、`.nvmrc` や `.python-version` でランタイムバージョンを指定し、Docker や devcontainers で再現性を確保します。 - -**状態サブシステム(仕込み台)**: 長時間のタスクには進捗追跡が必要です。シンプルな `PROGRESS.md` ファイルを使って、完了したこと、進行中のこと、ブロックされていることを記録します。各セッションの終了前に更新し、次のセッション開始時に読み込みます。 - -**フィードバックサブシステム(品質チェック窓口)**: これが最も投資対効果の高いサブシステムです。`AGENTS.md` に検証コマンドを明示的に列挙します。 -``` -Verification commands: -- Tests: pytest tests/ -x -- Type check: mypy src/ --strict -- Lint: ruff check src/ -- Full verification: make check (includes all above) -``` - -どれか一つのサブシステムが欠けていると、キッチンの機能領域が欠けているのと同じです — 料理はできますが、常に不便です。 - -**harness コンポーネントの価値を定量化する方法**: 「等尺性モデル制御」を使用します。モデルを固定し、サブシステムを一つずつ削除し、どの削除が最大のパフォーマンス低下を引き起こすかを測定します。最大の低下は、そのタスクで限界貢献が最も大きいコンポーネントを示しますが、それだけでボトルネックを証明するわけではありません。ボトルネックを見つけるには、失敗ログと原因帰属も組み合わせます。タスクが曖昧だったのか、コンテキスト不足だったのか、環境が再現不能だったのか、検証フィードバックが欠けていたのか、状態管理が途切れていたのかを確認します。 - -## あるチームの実話 - -あるチームが GPT-4o を使って TypeScript + React のフロントエンドアプリ(約20,000行)に取り組みました。彼らは4つの段階を経ました — 本質的にはキッチン設備を一つずつ追加していったのです。 - -**段階1 — 空のキッチン**: README に基本的なプロジェクト説明だけがある状態。5回の試行のうち1回しか成功しませんでした(20%)。主な失敗理由は、間違ったパッケージマネージャーの選択(npm と yarn の混同)、コンポーネント命名規則の非遵守、テストの実行不可でした。 - -**段階2 — レシピ棚を設置**: `AGENTS.md` に技術スタックのバージョン、命名規則、主要なアーキテクチャ決定を追加。成功率は60%に上昇。残りの失敗は主に環境の問題と検証の欠如でした。 - -**段階3 — 品質チェック窓口を開設**: `AGENTS.md` に検証コマンドを列挙: `yarn test && yarn lint && yarn build`。成功率は80%に上昇。 - -**段階4 — 仕込み台を準備**: エージェントが各実行で完了した作業と未完了の作業を記録する進捗ファイルテンプレートを導入。成功率は80〜100%で安定。 - -4回の反復で、モデルは全く変更していませんが、成功率は20%からほぼ100%になりました。これが harness engineering の力です。より高価な食材を買ったのではなく、キッチンを適切に整理しただけです。 - -## 重要なポイント - -- Harness = 指示 + ツール + 環境 + 状態 + フィードバック。5つのサブシステムは、キッチンの5つの機能領域のように — すべて不可欠です。 -- モデルの重みパラメータでないものは、すべて harness です。あなたの harness がモデルの能力をどれだけ発揮させるかを決定します。 -- 5つのサブシステムの中で、フィードバックサブシステムは通常、最も投資が少なく最もリターンが大きいです。まず検証コマンドを正しく設定しましょう — 品質チェック窓口は最も価値のあるアップグレードです。 -- 「等尺性モデル制御」を使って各サブシステムの限界貢献を定量化しましょう。感覚に頼らず、失敗ログと原因帰属で本当のボトルネックを見つけます。 -- Harness はコードと同じように腐敗します。定期的に監査し、技術的負債を返済するように harness の負債も返済しましょう。 - -## 参考資料 - -- [OpenAI: Harness Engineering](https://openai.com/index/harness-engineering/) -- [Anthropic: Effective Harnesses for Long-Running Agents](https://www.anthropic.com/engineering/effective-harnesses-for-long-running-agents) -- [HumanLayer: Harness Engineering for Coding Agents](https://humanlayer.dev/articles/harness-engineering-for-coding-agents/) -- [SWE-agent: Agent-Computer Interfaces](https://github.com/princeton-nlp/SWE-agent) -- [Thoughtworks: Harness Engineering on Technology Radar](https://www.thoughtworks.com/radar) - -## 演習 - -1. **5要素 harness 監査**: AI エージェントを使用しているプロジェクトを取り上げ、5要素フレームワークを使って完全な監査を行います。各サブシステムを1〜5で評価します。最も低いスコアのサブシステムを見つけ、30分かけて改善し、その後エージェントのパフォーマンスの変化を観察してください。 - -2. **等尺性モデル制御実験**: 1つのモデルと1つの難しいタスクを選びます。順番に指示を削除し(AGENTS.md を削除)、フィードバックを削除し(検証コマンドを提供しない)、状態を削除し(進捗ファイルなし) — 一度に1つだけ削除してパフォーマンスの低下を測定します。結果に基づいて、プロジェクトにおけるサブシステムの重要度をランク付けしてください。 - -3. **アフォーダンス分析**: プロジェクトでエージェントが「何かをしたいのにできない」シナリオを見つけます(例: パラメータ化クエリを使うべきだと知っているが、プロジェクトの ORM パターンを知らない)。これが実行のギャップ(方法がわからない)か評価のギャップ(正しいかどうかわからない)かを分析し、それを埋める harness の改善を設計してください。 diff --git a/courses/learn-harness-engineering/docs/ja/lectures/lecture-03-why-the-repository-must-become-the-system-of-record/code/index.md b/courses/learn-harness-engineering/docs/ja/lectures/lecture-03-why-the-repository-must-become-the-system-of-record/code/index.md deleted file mode 100644 index 660744c4..00000000 --- a/courses/learn-harness-engineering/docs/ja/lectures/lecture-03-why-the-repository-must-become-the-system-of-record/code/index.md +++ /dev/null @@ -1,7 +0,0 @@ -# 講義 03 のコード - -このフォルダーは、以下の例に使用してください: - -- エージェントが読み取れるリポジトリ構造 -- ドキュメントを記録システム(system of record)として扱う方法 -- ナレッジの配置の良し悪し diff --git a/courses/learn-harness-engineering/docs/ja/lectures/lecture-03-why-the-repository-must-become-the-system-of-record/code/repo-knowledge-layout.txt b/courses/learn-harness-engineering/docs/ja/lectures/lecture-03-why-the-repository-must-become-the-system-of-record/code/repo-knowledge-layout.txt deleted file mode 100644 index 45315dac..00000000 --- a/courses/learn-harness-engineering/docs/ja/lectures/lecture-03-why-the-repository-must-become-the-system-of-record/code/repo-knowledge-layout.txt +++ /dev/null @@ -1,13 +0,0 @@ -AGENTS.md -docs/ - ARCHITECTURE.md - PRODUCT.md - RELIABILITY.md - references/ - electron.md - sqlite.md - plans/ - active/ - completed/ -src/ -scripts/ diff --git a/courses/learn-harness-engineering/docs/ja/lectures/lecture-03-why-the-repository-must-become-the-system-of-record/code/repo-reader.ts b/courses/learn-harness-engineering/docs/ja/lectures/lecture-03-why-the-repository-must-become-the-system-of-record/code/repo-reader.ts deleted file mode 100644 index 46f6a62a..00000000 --- a/courses/learn-harness-engineering/docs/ja/lectures/lecture-03-why-the-repository-must-become-the-system-of-record/code/repo-reader.ts +++ /dev/null @@ -1,189 +0,0 @@ -/** - * repo-reader.ts - * - * Reads a directory structure and scores it on discoverability. - * Checks for: AGENTS.md, docs/, architecture docs, feature tracking, - * handoff files, and other signals of a repository that serves as the - * system of record. - * - * Usage: - * npx tsx docs/lectures/lecture-03.../code/repo-reader.ts [path] - * (defaults to current working directory if no path given) - * - * Run: npx tsx docs/lectures/lecture-03-why-the-repository-must-become-the-system-of-record/code/repo-reader.ts - */ - -import * as fs from "node:fs"; -import * as path from "node:path"; - -// --------------------------------------------------------------------------- -// Scoring criteria -// --------------------------------------------------------------------------- - -interface Check { - name: string; - description: string; - maxPoints: number; - check: (dir: string) => { points: number; found: string[]; missing: string[] }; -} - -const checks: Check[] = [ - { - name: "AGENTS.md / CLAUDE.md", - description: "Agent-readable instruction file at repo root", - maxPoints: 15, - check: (dir) => { - const candidates = ["AGENTS.md", "CLAUDE.md", ".claude/CLAUDE.md"]; - const found = candidates.filter((f) => fs.existsSync(path.join(dir, f))); - return { points: found.length > 0 ? 15 : 0, found, missing: found.length > 0 ? [] : candidates }; - }, - }, - { - name: "Documentation directory", - description: "Dedicated docs/ or documentation/ directory", - maxPoints: 10, - check: (dir) => { - const candidates = ["docs", "documentation", "doc"]; - const found = candidates.filter((f) => { - const p = path.join(dir, f); - return fs.existsSync(p) && fs.statSync(p).isDirectory(); - }); - return { points: found.length > 0 ? 10 : 0, found, missing: found.length > 0 ? [] : ["docs/"] }; - }, - }, - { - name: "Architecture documentation", - description: "Files describing system architecture", - maxPoints: 15, - check: (dir) => { - const patterns = [ - "architecture.md", "ARCHITECTURE.md", "docs/architecture.md", - "docs/architecture/", "design.md", "DESIGN.md", - ]; - const found = patterns.filter((f) => fs.existsSync(path.join(dir, f))); - return { points: found.length > 0 ? 15 : 0, found, missing: found.length > 0 ? [] : ["architecture.md or docs/architecture/"] }; - }, - }, - { - name: "Feature tracking", - description: "Feature list or task tracking file", - maxPoints: 15, - check: (dir) => { - const patterns = [ - "feature_list.json", "features.md", "FEATURES.md", - "docs/features.md", "tasks.json", "TODO.md", - ]; - const found = patterns.filter((f) => fs.existsSync(path.join(dir, f))); - return { points: found.length > 0 ? 15 : 0, found, missing: found.length > 0 ? [] : ["feature_list.json or features.md"] }; - }, - }, - { - name: "Handoff / session continuity", - description: "Files for multi-session continuity", - maxPoints: 15, - check: (dir) => { - const patterns = [ - "HANDOFF.md", "handoff.md", "SESSION_NOTES.md", - "docs/handoff.md", ".handoff", "PROGRESS.md", - ]; - const found = patterns.filter((f) => fs.existsSync(path.join(dir, f))); - return { points: found.length > 0 ? 15 : 0, found, missing: found.length > 0 ? [] : ["HANDOFF.md or PROGRESS.md"] }; - }, - }, - { - name: "Testing structure", - description: "Test directory or test configuration", - maxPoints: 10, - check: (dir) => { - const patterns = ["test", "tests", "__tests__", "spec"]; - const found = patterns.filter((f) => { - const p = path.join(dir, f); - return fs.existsSync(p) && fs.statSync(p).isDirectory(); - }); - return { points: found.length > 0 ? 10 : 0, found, missing: found.length > 0 ? [] : ["test/ or tests/"] }; - }, - }, - { - name: "Configuration files", - description: "Project config (package.json, tsconfig, etc.)", - maxPoints: 10, - check: (dir) => { - const patterns = ["package.json", "tsconfig.json", "pyproject.toml", "Cargo.toml", "go.mod"]; - const found = patterns.filter((f) => fs.existsSync(path.join(dir, f))); - return { points: found.length > 0 ? 10 : 0, found, missing: found.length > 0 ? [] : ["package.json or equivalent"] }; - }, - }, - { - name: "README", - description: "Root README file", - maxPoints: 10, - check: (dir) => { - const patterns = ["README.md", "README.rst", "README.txt", "README"]; - const found = patterns.filter((f) => fs.existsSync(path.join(dir, f))); - return { points: found.length > 0 ? 10 : 0, found, missing: found.length > 0 ? [] : ["README.md"] }; - }, - }, -]; - -// --------------------------------------------------------------------------- -// Report generation -// --------------------------------------------------------------------------- - -function pad(s: string, len: number): string { - return s.length >= len ? s : s + " ".repeat(len - s.length); -} - -function scoreRepo(targetDir: string): void { - const resolved = path.resolve(targetDir); - - if (!fs.existsSync(resolved)) { - console.error(` Error: Directory not found: ${resolved}`); - process.exit(1); - } - - console.log("\n" + "=".repeat(80)); - console.log(" REPOSITORY DISCOVERABILITY SCORE"); - console.log("=".repeat(80)); - console.log(` Target: ${resolved}\n`); - - const header = `| ${pad("Criterion", 30)}| ${pad("Points", 8)}| ${pad("Status", 10)}| Details`; - const sep = `|${"-".repeat(32)}|${"-".repeat(10)}|${"-".repeat(12)}|${"-".repeat(30)}`; - console.log(header); - console.log(sep); - - let totalScore = 0; - let maxScore = 0; - - for (const check of checks) { - const result = check.check(resolved); - totalScore += result.points; - maxScore += check.maxPoints; - const status = result.points > 0 ? "PASS" : "FAIL"; - const detail = result.found.length > 0 ? result.found.join(", ") : result.missing[0]; - - console.log(`| ${pad(check.name, 30)}| ${pad(result.points + "/" + check.maxPoints, 8)}| ${pad(status, 10)}| ${detail}`); - } - - // Final score - console.log("\n" + "-".repeat(80)); - console.log(` TOTAL SCORE: ${totalScore} / ${maxScore} (${Math.round((totalScore / maxScore) * 100)}%)`); - - // Grade - const pct = totalScore / maxScore; - let grade: string; - if (pct >= 0.9) grade = "A -- Repository is a strong system of record"; - else if (pct >= 0.7) grade = "B -- Good foundation, minor gaps"; - else if (pct >= 0.5) grade = "C -- Partial structure, significant gaps"; - else if (pct >= 0.3) grade = "D -- Minimal structure, hard for agents to navigate"; - else grade = "F -- Repository lacks discoverability signals"; - - console.log(` GRADE: ${grade}`); - console.log("=".repeat(80) + "\n"); -} - -// --------------------------------------------------------------------------- -// Main -// --------------------------------------------------------------------------- - -const target = process.argv[2] || process.cwd(); -scoreRepo(target); diff --git a/courses/learn-harness-engineering/docs/ja/lectures/lecture-03-why-the-repository-must-become-the-system-of-record/code/system-of-record-checklist.md b/courses/learn-harness-engineering/docs/ja/lectures/lecture-03-why-the-repository-must-become-the-system-of-record/code/system-of-record-checklist.md deleted file mode 100644 index 539e4ef9..00000000 --- a/courses/learn-harness-engineering/docs/ja/lectures/lecture-03-why-the-repository-must-become-the-system-of-record/code/system-of-record-checklist.md +++ /dev/null @@ -1,11 +0,0 @@ -# 記録システム(System of Record)チェックリスト - -新しいエージェントは、リポジトリだけから以下を発見できるか? - -- どのような製品が構築されているか? -- アプリはユーザーのために何をすべきか? -- コードベースはどのように整理されているか? -- アプリはどのように起動するか? -- 正常性はどのように確認するか? -- 現在どのような作業が進行中か? -- どのような品質基準が重要か? diff --git a/courses/learn-harness-engineering/docs/ja/lectures/lecture-03-why-the-repository-must-become-the-system-of-record/index.md b/courses/learn-harness-engineering/docs/ja/lectures/lecture-03-why-the-repository-must-become-the-system-of-record/index.md deleted file mode 100644 index a0efc2cc..00000000 --- a/courses/learn-harness-engineering/docs/ja/lectures/lecture-03-why-the-repository-must-become-the-system-of-record/index.md +++ /dev/null @@ -1,137 +0,0 @@ -[中文版本 →](../../../zh/lectures/lecture-03-why-the-repository-must-become-the-system-of-record/) - -> コード例: [code/](https://github.com/walkinglabs/learn-harness-engineering/blob/main/docs/ja/lectures/lecture-03-why-the-repository-must-become-the-system-of-record/code/) -> 実践プロジェクト: [プロジェクト 02. エージェントが読めるワークスペース](./../../projects/project-02-agent-readable-workspace/index.md) - -# 講義 03. リポジトリを唯一の信頼できる情報源にする - -あなたのチームのアーキテクチャ判断は、Confluence、Slack、Jira、そして数人のシニアエンジニアの頭の中に散らばっている。人間にとっては、これはぎりぎり機能する。同僚に聞けるし、チャット履歴を検索できるし、ドキュメントを掘り返せる。どうにもならなければ、休憩室で誰かを捕まえることもできる。しかし AI エージェントにとって、リポジトリに存在しない情報は、単に存在しない。 - -これは誇張ではない。エージェントの入力が実際に何かを考えてみよう。システムプロンプトとタスク説明、リポジトリ内のファイル内容、ツール実行の出力。それだけだ。Slack 履歴、Jira チケット、Confluence ページ、金曜午後に同僚とコーヒーを飲みながら話したアーキテクチャ判断。エージェントにはどれも見えない。「誰かに聞きに行く」ことも「チャット履歴を検索する」こともできない。リポジトリの中に閉じ込められたエンジニアであり、外側のことは何も知らない。 - -では問いはこうなる。このエンジニアに、よい地図を渡すつもりはあるか? - -## 地図に載せるべきもの - -OpenAI はこれをはっきり述べている。**リポジトリに存在しない情報は、エージェントにとって存在しない。** 彼らはこれを "repo as spec" 原則と呼ぶ。リポジトリ自体が、最も権威ある仕様書である。 - -Anthropic の長時間実行エージェントに関するドキュメントも同じことを示している。永続状態は、長いタスクの連続性に必要な条件だ。セッションをまたいで知識を回復できるかどうかが、タスク成功率を直接左右する。そしてその状態はリポジトリ内に存在しなければならない。エージェントにとって、そこだけが安定してアクセスできるストレージだからだ。 - -「うちのチームは小さいし、知識はみんなの頭の中にあって、それでうまく回っている」と思うかもしれない。人間にとってはそうだろう。しかしエージェントを使うなら、この事実を受け入れる必要がある。エージェントは人に聞けない。知る必要があることはすべて書き出し、見つけられる場所に置かなければならない。 - -これは「もっとドキュメントを書こう」という話ではない。「意思決定情報を正しい場所に置こう」という話だ。`src/api/` ディレクトリにある 50 行の `ARCHITECTURE.md` は、誰も保守していない Confluence の 500 ページ設計書より 1 万倍役に立つ。机に貼った手描きのオフィス地図と、ファイルキャビネットに鍵をかけてしまった美しい建築図面の違いだ。前者は必要なとき目の前にある。後者は技術的には優れていても、その瞬間には役に立たない。 - -## 知識の可視性 - -```mermaid -flowchart LR - Slack["Slack 内のルール"] --> Write["リポジトリファイルへ書く
AGENTS.md / ARCHITECTURE.md / PROGRESS.md"] - Confluence["Confluence 内のルール"] --> Write - Heads["人の頭の中のルール"] --> Write - Jira["Jira チケット内のルール"] --> Write - Write --> Repo["リポジトリファイル"] - Repo --> Agent["新しいエージェントセッション
リポジトリを直接読む"] - Warning["ルールがリポジトリにないなら
エージェントには見えない"] --> Agent -``` - -地図が十分かどうかはどう検証するか? 「コールドスタートテスト」を実行する。リポジトリ内容だけを使うまったく新しいエージェントセッションを開き、次の 5 つの基本質問に答えられるかを見る。 - -```mermaid -flowchart TB - Q1["このシステムは何か?"] --> A1["AGENTS.md / README"] - Q2["どう構成されているか?"] --> A2["ARCHITECTURE.md / モジュールドキュメント"] - Q3["どう実行するか?"] --> A3["Makefile / init.sh / package scripts"] - Q4["どう検証するか?"] --> A4["Test、lint、check コマンド"] - Q5["現在どこまで進んでいるか?"] --> A5["PROGRESS.md / feature list / git history"] - - A1 --> Ready["新しいセッションが
人に聞かず作業を開始できる"] - A2 --> Ready - A3 --> Ready - A4 --> Ready - A5 --> Ready -``` - -答えられないなら、地図に空白がある。地図が空白の場所では、エージェントは推測する。間違った推測はバグになり、過剰な推測はコンテキストを浪費する。そして新しいセッションのたびに、同じ推測をやり直す。推測のコストは、最初から地図をきちんと描くコストより常に高い。 - -## 中核概念 - -- **Knowledge Visibility Gap**: プロジェクト知識全体のうち、リポジトリに存在しないものの割合。ギャップが大きいほどエージェントの失敗率は上がる。このプロジェクトについて、あなたの頭の中にある暗黙知はいくつあるか。全部数え、そのうちどれだけがリポジトリに入っているかを見る。その差が可視性ギャップだ。 -- **System of Record**: プロジェクト判断、アーキテクチャ制約、実行状態、検証基準の権威ある情報源としてのコードリポジトリ。最終判断はリポジトリにあり、それ以外は数えない。「通行止め」と書かれた地図のようなものだ。その道には行かない。しかしその情報が誰か一人の頭の中にしかないなら、毎回その人に聞く必要がある。 -- **Cold-Start Test**: 上の 5 つの質問。いくつ答えられるかが、地図の完成度を示す。 -- **Discovery Cost**: エージェントがリポジトリ内で重要情報を見つけるために消費するコンテキスト予算。情報が隠れているほど discovery cost は高くなり、実タスクに残る予算は少なくなる。重要情報を 10 階層下の README に隠すのは、消火器を地下の金庫にしまうようなものだ。存在はするが、必要なとき見つけられない。 -- **Knowledge Decay Rate**: 知識項目が単位時間あたりに古くなる割合。ドキュメントがコードと同期しなくなることが最大の敵だ。ドキュメントがないより悪い。エージェントを間違った方向へ進ませるのに、エージェントは自分が正しいと思ってしまう。 -- **ACID Analogy**: データベーストランザクションの原則(Atomicity、Consistency、Isolation、Durability)をエージェント状態管理に適用する考え方。下で詳しく説明する。 - -## よい地図の描き方 - -**原則 1: 知識はコードの近くに置く。** API エンドポイント認証に関するルールは、巨大なグローバル文書に埋めるのではなく、API コードの近くに置くべきだ。各モジュールディレクトリに短いドキュメントを置き、そのモジュールの責務、インターフェース、特別な制約を説明する。図書館の棚ラベルのようなものだ。歴史の本が欲しければ「History」と書かれた棚に行けばよい。図書館全体を探す必要はない。 - -**原則 2: 標準化された入口ファイルを使う。** `AGENTS.md`(または `CLAUDE.md`)はエージェントのランディングページだ。すべての情報を含む必要はないが、「このプロジェクトは何か」「どう実行するか」「どう検証するか」という 3 つの質問に素早く答えられなければならない。50-100 行で十分だ。 - -**原則 3: 最小だが完全にする。** すべての知識には明確な用途が必要だ。あるルールを削除してもエージェントの判断品質に影響がないなら、そのルールは存在すべきではない。しかしコールドスタートテストのすべての質問には答えが必要だ。多すぎず、少なすぎず、必要十分にするという繊細なバランスだ。 - -**原則 4: コードと一緒に更新する。** 知識更新をコード変更に結びつける。最も単純な方法は、アーキテクチャドキュメントを対応モジュールのディレクトリに置くことだ。コードを変更するとき、そのドキュメントを自然に目にする。コード変更後、CI がドキュメント更新の必要性を確認するよう促すこともできる。 - -**具体的なリポジトリ構造**: - -``` -project/ -├── AGENTS.md # Entry: project overview, run commands, hard constraints -├── src/ -│ ├── api/ -│ │ ├── ARCHITECTURE.md # API layer architecture decisions -│ │ └── ... -│ ├── db/ -│ │ ├── CONSTRAINTS.md # Database operation hard constraints -│ │ └── ... -│ └── ... -├── PROGRESS.md # Current progress: done, in-progress, blocked -└── Makefile # Standardized commands: setup, test, lint, check -``` - -## ACID 原則でエージェント状態を管理する - -この比喩はデータベースのトランザクション管理から来ている。複雑にしすぎだと思うかもしれないが、実際にはかなり実用的な枠組みを与えてくれる。 - -- **Atomicity**: 各「論理操作」(例: 「新しいエンドポイントを追加し、テストを更新する」)を 1 つの git commit にする。途中で失敗したら `git stash` で戻す。全部やるか、何もやらないか。「半分だけ完了」はない。 -- **Consistency**: 「一貫した状態」を検証する述語を定義する。すべてのテストが通る、lint エラーがゼロ、など。エージェントは各操作後に検証を実行し、一貫しない中間状態は commit しない。銀行振込のように、引き落としだけして入金しないことはできない。 -- **Isolation**: 複数のエージェントが同時に作業するときは、状態ファイルが競合しないよう設計する。単純な方法は、各エージェントに独自の progress ファイルを使わせるか、git branch で分離することだ。2 人の料理人が同じ鍋に同時に味付けすることはできない。塩辛くなったら誰が責任を取るのか? -- **Durability**: 重要なプロジェクト知識は git 管理されたファイルに置く。一時状態はセッションメモリでもよいが、セッションをまたぐ知識はファイルに永続化する必要がある。頭の中にあるものは数えない。紙に書かれたものだけを数える。 - -## 実際の変革事例 - -あるチームは、約 30 個のマイクロサービスを持つ e コマース基盤を保守していた。アーキテクチャ判断(サービス間通信プロトコル、データ整合性戦略、API バージョニング規則)は、Confluence(一部古い)、Slack(検索しづらい)、数人のシニアエンジニアの頭の中(スケールしない)、散発的なコードコメント(体系的ではない)に散らばっていた。 - -AI エージェント導入後、タスクの 70% が人間の介入を必要とした。ほぼすべての失敗は、「みんな知っているが誰も書いていない」暗黙制約にエージェントが違反したことに関係していた。新入社員に「昼食注文はグループチャットに投稿する必要がある」と誰も教えないようなものだ。本人は間違って推測し、叱られる。しかし叱ったあとも、誰もそのルールを書かない。 - -チームは変革を実行した。 -1. リポジトリルートに `AGENTS.md` を作成し、プロジェクト概要、技術スタックのバージョン、グローバルな強制制約を記載 -2. 各マイクロサービスディレクトリに `ARCHITECTURE.md` を追加し、責務、インターフェース、依存関係を説明 -3. 明示的な "MUST/MUST NOT" 表現でハード制約をまとめた中央 `CONSTRAINTS.md` を作成 -4. 各サービスディレクトリに `PROGRESS.md` を追加し、現在の作業状態を追跡 - -変革後、同じエージェントはコールドスタートで重要なプロジェクト質問すべてに答えられるようになり、タスク完了品質は大きく改善した。 - -## 重要なポイント - -- リポジトリにない知識は、エージェントにとって存在しない。重要な判断をリポジトリに置くことは、最も基本的な harness 投資だ。迷わないように、よい地図を描く。 -- リポジトリ品質の評価には「コールドスタートテスト」を使う。新しいセッションがリポジトリ内容だけで 5 つの基本質問に答えられるか? -- 知識はコードの近くに、最小だが完全に、そしてコードと一緒に更新されるべきだ。これはドキュメント量を増やす話ではなく、情報を正しい場所に置く話だ。 -- エージェント状態には ACID 原則を使う。atomic な commit、一貫性検証、並行作業の分離、重要知識の永続化。 -- 知識の劣化が最大の敵だ。コードと同期していないドキュメントは、ドキュメントがないより危険だ。エージェントを間違った方向に進ませながら、エージェント自身には正しいと思わせてしまう。 - -## 参考資料 - -- [OpenAI: Harness Engineering](https://openai.com/index/harness-engineering/) -- [Anthropic: Effective Harnesses for Long-Running Agents](https://www.anthropic.com/engineering/effective-harnesses-for-long-running-agents) -- [Infrastructure as Code — Martin Fowler](https://martinfowler.com/bliki/InfrastructureAsCode.html) -- [ADR: Architecture Decision Records](https://adr.github.io/) -- [The Twelve-Factor App](https://12factor.net/) - -## 演習 - -1. **コールドスタートテスト**: プロジェクトで完全に新しいエージェントセッションを開く(口頭コンテキストなし、リポジトリ内容のみ)。5 つの質問をする。このシステムは何か? どう構成されているか? どう実行するか? どう検証するか? 現在の進捗は? 答えられなかったものを記録し、答えられるようになるまでリポジトリを改善する。 - -2. **知識外部化の定量化**: プロジェクトの開発作業に重要な判断と制約をすべて列挙する。それぞれがリポジトリ内にあるか外にあるかを印づけする。Knowledge Visibility Gap(リポジトリ外の割合)を計算し、10% 未満に下げる計画を立てる。 - -3. **ACID 評価**: この講義の ACID 比喩で、プロジェクトの状態管理を評価する。Atomicity - エージェント操作をきれいにロールバックできるか? Consistency - 「一貫した状態」の検証があるか? Isolation - 並行エージェントが互いに踏み合わないか? Durability - セッションをまたぐ知識はすべて永続化されているか? diff --git a/courses/learn-harness-engineering/docs/ja/lectures/lecture-04-why-one-giant-instruction-file-fails/code/AGENTS-short.md b/courses/learn-harness-engineering/docs/ja/lectures/lecture-04-why-one-giant-instruction-file-fails/code/AGENTS-short.md deleted file mode 100644 index 6f34e05a..00000000 --- a/courses/learn-harness-engineering/docs/ja/lectures/lecture-04-why-one-giant-instruction-file-fails/code/AGENTS-short.md +++ /dev/null @@ -1,15 +0,0 @@ -# AGENTS.md - -## まずここから - -- `docs/ARCHITECTURE.md` を読む -- `docs/PRODUCT.md` を読む -- `npm run dev` でアプリを起動する -- 作業を完了とマークする前に `npm run check` を実行する - -## 厳格なルール - -- `docs/ARCHITECTURE.md` を読まずに Electron の main/preload/renderer の境界を - 変更しない -- 検証なしに機能を完了とマークしない -- 次のセッションのためにクリーンな状態を残す diff --git a/courses/learn-harness-engineering/docs/ja/lectures/lecture-04-why-one-giant-instruction-file-fails/code/anti-patterns.md b/courses/learn-harness-engineering/docs/ja/lectures/lecture-04-why-one-giant-instruction-file-fails/code/anti-patterns.md deleted file mode 100644 index a5f0980a..00000000 --- a/courses/learn-harness-engineering/docs/ja/lectures/lecture-04-why-one-giant-instruction-file-fails/code/anti-patterns.md +++ /dev/null @@ -1,7 +0,0 @@ -# 指示ファイルのアンチパターン - -- リポジトリのすべての知識を 1 つのファイルに詰め込む -- 同じルールを複数の場所で繰り返す -- 誰も監査しない古いルールを残す -- ほとんど適用されないほど具体的な条件付き指示を書く -- 起動コンテキストに長いツールマニュアルを組み込む diff --git a/courses/learn-harness-engineering/docs/ja/lectures/lecture-04-why-one-giant-instruction-file-fails/code/index.md b/courses/learn-harness-engineering/docs/ja/lectures/lecture-04-why-one-giant-instruction-file-fails/code/index.md deleted file mode 100644 index 978d4c38..00000000 --- a/courses/learn-harness-engineering/docs/ja/lectures/lecture-04-why-one-giant-instruction-file-fails/code/index.md +++ /dev/null @@ -1,7 +0,0 @@ -# 講義 04 のコード - -このフォルダーは、以下の例に使用してください: - -- 単一の巨大な指示ファイル -- 短いエントリポイント -- 段階的開示(progressive disclosure)パターン diff --git a/courses/learn-harness-engineering/docs/ja/lectures/lecture-04-why-one-giant-instruction-file-fails/code/split-vs-monolithic.ts b/courses/learn-harness-engineering/docs/ja/lectures/lecture-04-why-one-giant-instruction-file-fails/code/split-vs-monolithic.ts deleted file mode 100644 index 59a96328..00000000 --- a/courses/learn-harness-engineering/docs/ja/lectures/lecture-04-why-one-giant-instruction-file-fails/code/split-vs-monolithic.ts +++ /dev/null @@ -1,209 +0,0 @@ -/** - * split-vs-monolithic.ts - * - * Creates a monolithic instruction file (~200 lines) and then shows how - * splitting into 4 focused files dramatically reduces the context needed - * for any single query. Simulates an "agent" searching for a specific rule - * and measures how many lines it must read in each approach. - * - * Run: npx tsx docs/lectures/lecture-04-why-one-giant-instruction-file-fails/code/split-vs-monolithic.ts - */ - -// --------------------------------------------------------------------------- -// Simulated monolithic instruction file (200 lines of rules) -// --------------------------------------------------------------------------- - -const monolithicInstructions: { lineNumber: number; section: string; content: string }[] = []; - -// Section 1: Project Overview (lines 1-50) -for (let i = 1; i <= 50; i++) { - monolithicInstructions.push({ - lineNumber: i, - section: "Project Overview", - content: i === 25 ? "This project uses React 18 with TypeScript strict mode." : `Overview detail line ${i}`, - }); -} - -// Section 2: Code Style Rules (lines 51-100) -for (let i = 51; i <= 100; i++) { - monolithicInstructions.push({ - lineNumber: i, - section: "Code Style", - content: - i === 72 - ? "RULE: Always use explicit return types on exported functions." - : i === 78 - ? "RULE: Use const assertions for immutable arrays." - : `Style rule detail line ${i}`, - }); -} - -// Section 3: Testing Standards (lines 101-150) -for (let i = 101; i <= 150; i++) { - monolithicInstructions.push({ - lineNumber: i, - section: "Testing", - content: - i === 120 - ? "RULE: Every new endpoint must have integration tests." - : i === 135 - ? "RULE: Test files must mirror the source file structure." - : `Testing detail line ${i}`, - }); -} - -// Section 4: Deployment Rules (lines 151-200) -for (let i = 151; i <= 200; i++) { - monolithicInstructions.push({ - lineNumber: i, - section: "Deployment", - content: - i === 175 - ? "RULE: Never deploy on Fridays. Deploy window is Tue-Thu 10am-3pm." - : `Deployment detail line ${i}`, - }); -} - -// --------------------------------------------------------------------------- -// Split instruction files (4 focused files) -// --------------------------------------------------------------------------- - -const splitInstructions: Record = { - "01-project-overview.md": monolithicInstructions.filter((l) => l.section === "Project Overview"), - "02-code-style.md": monolithicInstructions.filter((l) => l.section === "Code Style"), - "03-testing.md": monolithicInstructions.filter((l) => l.section === "Testing"), - "04-deployment.md": monolithicInstructions.filter((l) => l.section === "Deployment"), -}; - -// --------------------------------------------------------------------------- -// Simulated queries -- the agent needs to find specific rules -// --------------------------------------------------------------------------- - -interface Query { - description: string; - targetRule: string; - relevantSection: string; -} - -const queries: Query[] = [ - { - description: "Find the rule about return types", - targetRule: "explicit return types", - relevantSection: "Code Style", - }, - { - description: "Find the deployment window rule", - targetRule: "deploy on Fridays", - relevantSection: "Deployment", - }, - { - description: "Find the integration test rule", - targetRule: "integration tests", - relevantSection: "Testing", - }, - { - description: "Find the test file structure rule", - targetRule: "mirror the source file", - relevantSection: "Testing", - }, -]; - -// --------------------------------------------------------------------------- -// Search simulation -// --------------------------------------------------------------------------- - -function searchMonolithic(query: Query): { linesRead: number; found: boolean } { - // Agent must scan from the top, reading each line until it finds the rule. - // In the worst case it reads all lines. - let linesRead = 0; - let found = false; - - for (const line of monolithicInstructions) { - linesRead++; - if (line.content.toLowerCase().includes(query.targetRule.toLowerCase())) { - found = true; - break; - } - } - - return { linesRead, found }; -} - -function searchSplit(query: Query): { linesRead: number; found: boolean; fileAccessed: string } { - // Agent knows which file to look in based on the section. - // It only reads lines from that one file. - const fileMap: Record = { - "Project Overview": "01-project-overview.md", - "Code Style": "02-code-style.md", - Testing: "03-testing.md", - Deployment: "04-deployment.md", - }; - - const targetFile = fileMap[query.relevantSection]; - const lines = splitInstructions[targetFile]; - let linesRead = 0; - let found = false; - - for (const line of lines) { - linesRead++; - if (line.content.toLowerCase().includes(query.targetRule.toLowerCase())) { - found = true; - break; - } - } - - return { linesRead, found, fileAccessed: targetFile }; -} - -// --------------------------------------------------------------------------- -// Reporting -// --------------------------------------------------------------------------- - -function pad(s: string, len: number): string { - return s.length >= len ? s : s + " ".repeat(len - s.length); -} - -function run(): void { - console.log("\n" + "=".repeat(90)); - console.log(" MONOLITHIC vs SPLIT INSTRUCTION FILES"); - console.log("=".repeat(90)); - - console.log("\n Monolithic file: 1 file, " + monolithicInstructions.length + " lines total"); - console.log(" Split files: 4 files, ~" + Math.round(monolithicInstructions.length / 4) + " lines each\n"); - - const header = `| ${pad("Query", 42)}| ${pad("Monolithic (lines)", 20)}| ${pad("Split (lines)", 15)}| ${pad("File Accessed", 22)}| Savings`; - const sep = `|${"-".repeat(44)}|${"-".repeat(22)}|${"-".repeat(17)}|${"-".repeat(24)}|${"-".repeat(10)}`; - console.log(header); - console.log(sep); - - let totalMono = 0; - let totalSplit = 0; - - for (const q of queries) { - const mono = searchMonolithic(q); - const split = searchSplit(q); - totalMono += mono.linesRead; - totalSplit += split.linesRead; - - const savings = Math.round(((mono.linesRead - split.linesRead) / mono.linesRead) * 100); - console.log( - `| ${pad(q.description, 42)}| ${pad(String(mono.linesRead), 20)}| ${pad(String(split.linesRead), 15)}| ${pad(split.fileAccessed, 22)}| ${savings}%` - ); - } - - console.log(sep); - const avgMono = Math.round(totalMono / queries.length); - const avgSplit = Math.round(totalSplit / queries.length); - console.log( - `| ${pad("AVERAGE", 42)}| ${pad(String(avgMono), 20)}| ${pad(String(avgSplit), 15)}| ${pad("(targeted file)", 22)}| ${Math.round(((avgMono - avgSplit) / avgMono) * 100)}%` - ); - - console.log("\n" + "=".repeat(90)); - console.log(" KEY INSIGHT"); - console.log("=".repeat(90)); - console.log(" With a monolithic file, the agent must scan up to " + monolithicInstructions.length + " lines for every query."); - console.log(" With split files, it reads only the relevant " + Math.round(monolithicInstructions.length / 4) + "-line file."); - console.log(" This means less context window usage, fewer hallucinations, and faster execution.\n"); -} - -run(); diff --git a/courses/learn-harness-engineering/docs/ja/lectures/lecture-04-why-one-giant-instruction-file-fails/index.md b/courses/learn-harness-engineering/docs/ja/lectures/lecture-04-why-one-giant-instruction-file-fails/index.md deleted file mode 100644 index a1bf3ef7..00000000 --- a/courses/learn-harness-engineering/docs/ja/lectures/lecture-04-why-one-giant-instruction-file-fails/index.md +++ /dev/null @@ -1,132 +0,0 @@ -[中文版本 →](../../../zh/lectures/lecture-04-why-one-giant-instruction-file-fails/) - -> コード例: [code/](https://github.com/walkinglabs/learn-harness-engineering/blob/main/docs/ja/lectures/lecture-04-why-one-giant-instruction-file-fails/code/) -> 実践プロジェクト: [プロジェクト 02. エージェントが読めるワークスペース](./../../projects/project-02-agent-readable-workspace/index.md) - -# 講義 04. 指示を複数ファイルに分割する - -あなたは Harness Engineering に本気で取り組み始めた。`AGENTS.md` を作り、思いつく限りのルール、制約、学びをすべて詰め込んだ。1か月後には 300 行、2か月後には 450 行、3か月後には 600 行に膨れ上がった。そして気づく。エージェントの性能がむしろ悪くなっている。単純なバグ修正でも、関係のないデプロイ手順を読むために大量のコンテキストを消費する。300 行目に埋もれた重要なセキュリティ制約は完全に無視される。矛盾する 3 つのコードスタイル規則があり、エージェントは毎回ランダムに 1 つを選ぶ。 - -これが「巨大な指示ファイル」の罠だ。スーツケースに荷物を詰め込みすぎるのに似ている。どれも役に立ちそうだから、ファスナーがはち切れそうになるまで全部入れてしまう。替えの下着を探すだけで、バッグ全体を空にしなければならない。満杯のスーツケースを運んでいるのに、実際に使うのは中身の 3 分の 1 くらいだ。 - -## 根本にある悪循環 - -最もよくある悪循環はこうだ。エージェントがミスをする。あなたは「これを防ぐルールを追加しよう」と考え、`AGENTS.md` に追加する。一時的には効く。するとエージェントが別のミスをする。またルールを追加する。これを繰り返し、ファイルは制御不能に膨れ上がる。 - -これはあなたのせいではない。何かがうまくいかないたびに「ルールを追加する」のは、とても自然な反応だ。家を出るたびに「念のため」と荷物を 1 つ追加するようなものだ。しかし累積効果は破滅的になる。具体的に何が壊れるのかを見ていこう。 - -**コンテキスト予算が食い尽くされる。** エージェントのコンテキストウィンドウは有限だ。たとえば 200K トークンのウィンドウがあるとする(Claude の標準的な規模)。肥大化した指示ファイルは 10-20K トークンを消費するかもしれない。まだ十分余裕があるように見えるだろうか? しかし複雑なタスクでは多数のソースファイルを読む必要があり、ツール実行結果もコンテキストを使い、会話履歴も蓄積する。エージェントがコードを理解する段階では、すでに予算が厳しくなっている。まるで「念のため」の荷物でいっぱいになり、ノート PC を入れる場所がなくなったスーツケースだ。 - -**真ん中で失われる。** "Lost in the Middle" 論文(Liu et al., 2023)は、LLM が長いテキストの中央にある情報を、冒頭や末尾の情報より大幅にうまく使えないことを明確に示した。あなたの `AGENTS.md` が 600 行あり、300 行目に「すべてのデータベースクエリはパラメータ化クエリを使うこと」と書かれているとする。これはセキュリティ上の強い制約だ。しかし真ん中に埋もれているため、エージェントはかなりの確率で無視する。詰め込みすぎたスーツケースの底にある日焼け止めのようなものだ。そこにあるのは分かっている。3 回探す。でも見つからず、結局もう 1 本買う。 - -**優先順位が衝突する。** ファイルには、交渉不能な強制制約("never use eval()")、重要な設計指針("prefer functional style")、特定の過去の教訓("先週 WebSocket のメモリリークを直したので類似パターンに注意")が混ざっている。この 3 つは重要度がまったく違うのに、ファイル上では同じように見える。エージェントには見分ける信頼できるシグナルがない。スーツケースの中でパスポートと充電ケーブルが一緒くたになっていて、どちらがより緊急か分からない状態だ。 - -**保守が劣化する。** 大きなファイルは本質的に保守しにくい。古い指示はなかなか削除されない。削除の影響が不確かだからだ(「何かがこのルールに依存しているかもしれない」)。一方で、新しい指示を追加するのは無料に感じる。その結果、ファイルは大きくなる一方で小さくならず、信号対雑音比は下がり続ける。これはソフトウェアにおける技術的負債の蓄積そのものだ。 - -**矛盾が蓄積する。** 異なる時期に追加された指示が互いに矛盾し始める。片方は「TypeScript strict mode を使う」と言い、もう片方は「一部のレガシーファイルでは any 型を許可する」と言う。エージェントは毎回ランダムにどちらかを選ぶ。母親が「暖かくして行きなさい」と言い、父親が「着込みすぎるな」と言い、玄関で誰の言うことを聞けばよいか分からないようなものだ。 - -## 中核概念 - -- **Instruction Bloat**: 指示ファイルがコンテキストウィンドウの 10-15% を超えると、コード読解とタスク推論の予算を圧迫し始める。600 行の `AGENTS.md` は 10,000-20,000 トークンを消費し得る。128K ウィンドウなら、エージェントが作業を始める前に 8-15% が消える。 -- **Lost in the Middle Effect**: Liu et al. の 2023 年の研究は、LLM が長文の中央にある情報を、冒頭や末尾の情報より大幅に使いにくいことを示した。600 行ファイルの 300 行目に埋もれた重要制約は、実質的に無視される確率が高い。 -- **Instruction Signal-to-Noise Ratio (SNR)**: ファイル内の指示のうち、現在のタスクに関係するものの割合。バグ修正中に 50 行のデプロイ手順を読まされるなら、それは低 SNR だ。 -- **Routing File**: すべてを内包するのではなく、詳細ドキュメントへの道案内を主目的とする短い入口ファイル。50-200 行で十分だ。 -- **Progressive Disclosure**: まず概要を与え、必要になった時点で詳細を与える。よい Harness 設計はよい UI 設計に似ている。すべての選択肢を一度に投げつけない。 -- **Priority Ambiguity**: すべての指示が同じ形式・同じ場所にあると、エージェントは交渉不能な強制制約と提案的なガイドラインを区別できない。 - -## 指示アーキテクチャ - -```mermaid -flowchart LR - Mono["600 行の AGENTS.md 1 つ"] --> MonoLoad["小さなバグ修正でも
デプロイ規則と古いメモを読む必要がある"] - MonoLoad --> MonoRisk["真ん中に埋もれた重要規則は
見落とされやすい"] - - Router["短い AGENTS.md"] --> Topics["必要なときだけ API / DB / testing docs を読む"] - Topics --> RoutedResult["コード読解と検証に使える
コンテキストが増える"] -``` - -```mermaid -flowchart TB - File["600 行の指示ファイル"] --> Top["冒頭セクション
quick start + 強制制約"] - File --> Mid["中央セクション
300 行目の重要セキュリティ規則"] - File --> Bot["末尾セクション
明示的な終了チェックリスト"] - Top --> Seen["思い出される確率が高い"] - Bot --> Seen - Mid --> Missed["薄まる、または見落とされる確率が高い"] -``` - -## 分割方法 - -基本原則は、頻繁に必要な情報は手元に置き、ときどき必要な情報はしまっておき、使わない情報は残さないことだ。 - -入口ファイル `AGENTS.md` は 50-200 行に保ち、最も頻繁に使うものだけを含める。プロジェクト概要(1-2 文)、初回実行コマンド(`make setup && make test`)、グローバルな強制制約(交渉不能な規則は 15 個以下)、トピック別ドキュメントへのリンク(1 行説明 + 適用条件)だ。 - -```markdown -# AGENTS.md - -## プロジェクト概要 -Python 3.11 FastAPI backend, PostgreSQL 15 database. - -## Quick Start -- Install: `make setup` -- Test: `make test` -- Full verification: `make check` - -## Hard Constraints -- All APIs must use OAuth 2.0 authentication -- All database queries must use SQLAlchemy 2.0 syntax -- All PRs must pass pytest + mypy --strict + ruff check - -## Topic Docs -- [API Design Patterns](docs/api-patterns.md) — Required reading when adding endpoints -- [Database Rules](docs/database-rules.md) — Required when modifying database operations -- [Testing Standards](docs/testing-standards.md) — Reference when writing tests -``` - -各トピック文書は 50-150 行にし、`docs/` ディレクトリか対応モジュールの近くで主題ごとに整理する。エージェントは必要なときだけ読む。スーツケースのパッキングキューブのようなものだ。下着は 1 つ、洗面用品は別、充電器はさらに別。探し物のためにバッグ全体を空にする必要はない。 - -一部の情報はコードの中に直接置いたほうがよい。型定義、インターフェースコメント、設定ファイル内の説明などだ。エージェントはコードを読むとき自然にそれらを見るため、指示に重複させる必要はない。 - -すべての指示には、由来(「なぜこのルールが追加されたのか」)、適用条件(「いつ必要なのか」)、失効条件(「どの状況なら削除できるのか」)を持たせるべきだ。定期的に監査し、古いもの、重複したもの、矛盾したものを削除する。指示はコード依存関係と同じように管理する。使っていない依存は削除すべきで、残しておくとシステムを遅くするだけだ。 - -どうしても入口ファイルに置く必要がある指示は、冒頭か末尾に置く。中央には置かない。"lost in the middle" 効果が示すように、LLM は中央よりも両端の情報を大幅にうまく使う。ただし、よりよい方法は、必要時に読むトピック文書へ移すことだ。 - -OpenAI と Anthropic はどちらも、暗黙に分割アプローチを支持している。OpenAI は入口ファイルを「短く、ルーティング中心」にすべきだと言い、Anthropic は長時間実行エージェントの制御情報を「簡潔で高優先度」にすべきだと言う。どちらも同じことを言っている。すべてを 1 ファイルに詰め込むな。スーツケースに必要なのは整理であって、力任せに押し込むことではない。 - -## 実例 - -ある SaaS チームの `AGENTS.md` は 50 行から 600 行に膨れ上がった。中身は、技術スタックのバージョン、コーディング規約、過去のバグ修正メモ、API 利用ガイド、デプロイ手順、チームメンバー個人の好みまで混在していた。スーツケース全体がはち切れそうな状態だ。 - -エージェントの性能は目に見えて低下し始めた。単純なバグ修正の間に、関係のないデプロイ指示の処理で大量のコンテキストを使う。セキュリティ制約「すべてのデータベースクエリはパラメータ化クエリを使う」は 300 行目に埋もれ、頻繁に無視される。矛盾する 3 つのコードスタイル規則が、エージェントの行動をランダムにする。 - -チームは「スーツケースの再整理」を実行した。 -1. `AGENTS.md` を 80 行に削減。プロジェクト概要、実行コマンド、15 個のグローバル強制制約だけにした -2. トピック文書を作成: `docs/api-patterns.md`(120 行)、`docs/database-rules.md`(60 行)、`docs/testing-standards.md`(80 行) -3. ルーティングファイルにトピック文書リンクを追加 -4. 過去メモはテストケースに変換するか削除 - -リファクタ後、同じタスク群の成功率は 45% から 72% に上がった。セキュリティ制約の遵守率は 60% から 95% になった。理由は、その制約がファイル中央からルーティングファイル冒頭へ移り、もはや「真ん中で失われ」なくなったからだ。 - -## 重要なポイント - -- 「ルールを追加する」は短期的な鎮痛剤であり、長期的には毒になる。ルールを追加する前に問うこと: これはトピック文書のほうがよいのではないか? スーツケースに詰め込み続けてはいけない。 -- 入口ファイルはルーターであり、百科事典ではない。50-200 行で、概要、強制制約、リンクだけにする。 -- "lost in the middle" 効果を利用する。重要情報は冒頭か末尾へ。重要でない情報はトピック文書へ移す。 -- 指示の肥大化は技術的負債として管理する。定期監査を行い、すべての指示に由来、適用条件、失効条件を持たせる。 -- 分割後は SNR が改善し、エージェントは関係のない指示処理ではなく、実際のタスクにより多くのコンテキスト予算を使える。 - -## 参考資料 - -- [OpenAI: Harness Engineering](https://openai.com/index/harness-engineering/) -- [Anthropic: Effective Harnesses for Long-Running Agents](https://www.anthropic.com/engineering/effective-harnesses-for-long-running-agents) -- [Lost in the Middle: How Language Models Use Long Contexts](https://arxiv.org/abs/2307.03172) -- [HumanLayer: Harness Engineering for Coding Agents](https://humanlayer.dev/articles/harness-engineering-for-coding-agents/) -- [Nielsen Norman Group: Progressive Disclosure](https://www.nngroup.com/articles/progressive-disclosure/) - -## 演習 - -1. **SNR 監査**: 現在の入口指示ファイルを取り出し、すべての指示項目を一覧化する。よくあるタスク種別を 5 つ選び、それぞれの指示がそのタスクに関係するかを印づけする。タスク種別ごとに SNR を計算する。多くのタスクでノイズになる指示はトピック文書へ移す。 - -2. **Progressive disclosure リファクタ**: 300 行を超える指示ファイルがあるなら、(a) 100 行未満のルーティングファイル、(b) 3-5 個のトピック文書に分割する。分割前後で同じタスク群(少なくとも 5 件)を実行し、成功率を比較する。 - -3. **Lost in the middle 検証**: 長い指示ファイルの冒頭、中央、末尾にそれぞれ重要制約を置き、同じタスク群を実行する(各位置で少なくとも 5 回)。遵守率に差があるか確認する。位置効果の強さに驚くかもしれない。 diff --git a/courses/learn-harness-engineering/docs/ja/lectures/lecture-05-why-long-running-tasks-lose-continuity/code/continuity-checklist.md b/courses/learn-harness-engineering/docs/ja/lectures/lecture-05-why-long-running-tasks-lose-continuity/code/continuity-checklist.md deleted file mode 100644 index 092412e8..00000000 --- a/courses/learn-harness-engineering/docs/ja/lectures/lecture-05-why-long-running-tasks-lose-continuity/code/continuity-checklist.md +++ /dev/null @@ -1,6 +0,0 @@ -# 継続性チェックリスト - -- 新しいエージェントは5分以内に最近の作業を特定できるか? -- 現在の安定した起動パスは文書化されているか? -- 未完了の作業は明確に特定されているか? -- 古いチャットログを読まずに次に最適なタスクが見えるか? diff --git a/courses/learn-harness-engineering/docs/ja/lectures/lecture-05-why-long-running-tasks-lose-continuity/code/index.md b/courses/learn-harness-engineering/docs/ja/lectures/lecture-05-why-long-running-tasks-lose-continuity/code/index.md deleted file mode 100644 index 8be7f5fb..00000000 --- a/courses/learn-harness-engineering/docs/ja/lectures/lecture-05-why-long-running-tasks-lose-continuity/code/index.md +++ /dev/null @@ -1,7 +0,0 @@ -# 講義 05 のコード - -このフォルダーは、以下の例に使用してください: - -- 壊れたマルチセッションタスク -- 欠落している継続性アーティファクト -- 継続性回復パターン diff --git a/courses/learn-harness-engineering/docs/ja/lectures/lecture-05-why-long-running-tasks-lose-continuity/code/session-handoff.md b/courses/learn-harness-engineering/docs/ja/lectures/lecture-05-why-long-running-tasks-lose-continuity/code/session-handoff.md deleted file mode 100644 index e133cd24..00000000 --- a/courses/learn-harness-engineering/docs/ja/lectures/lecture-05-why-long-running-tasks-lose-continuity/code/session-handoff.md +++ /dev/null @@ -1,17 +0,0 @@ -# セッションハンドオフの例 - -## 完了 - -- Markdown インポートサポートを追加 -- レンダラーに基本的なドキュメントリストを追加 - -## 壊れているまたは未検証 - -- `.md` のインポートは成功するが、大きな `.txt` ファイルで失敗する -- アプリは起動するが、詳細ビューが接続されていない - -## 次の最適ステップ - -- `.txt` インポートパスを修正 -- インポートをエンドツーエンドで検証 -- その後、ドキュメント詳細パネルを追加 diff --git a/courses/learn-harness-engineering/docs/ja/lectures/lecture-05-why-long-running-tasks-lose-continuity/code/session-simulator.ts b/courses/learn-harness-engineering/docs/ja/lectures/lecture-05-why-long-running-tasks-lose-continuity/code/session-simulator.ts deleted file mode 100644 index 69f9c3e2..00000000 --- a/courses/learn-harness-engineering/docs/ja/lectures/lecture-05-why-long-running-tasks-lose-continuity/code/session-simulator.ts +++ /dev/null @@ -1,162 +0,0 @@ -/** - * session-simulator.ts - * - * Simulates two sessions working on a multi-step task. - * Run 1: No handoff file -- Session B starts from scratch and duplicates work. - * Run 2: With handoff file -- Session B picks up where Session A left off. - * - * Run: npx tsx docs/lectures/lecture-05-why-long-running-tasks-lose-continuity/code/session-simulator.ts - */ - -// --------------------------------------------------------------------------- -// Types -// --------------------------------------------------------------------------- - -interface TaskStep { - id: number; - name: string; - durationMs: number; -} - -interface SessionResult { - session: string; - stepsCompleted: number; - totalDurationMs: number; - duplicatedSteps: number; - output: string[]; -} - -// --------------------------------------------------------------------------- -// Task definition -// --------------------------------------------------------------------------- - -const taskSteps: TaskStep[] = [ - { id: 1, name: "Read project structure", durationMs: 50 }, - { id: 2, name: "Understand existing auth module", durationMs: 80 }, - { id: 3, name: "Design new search endpoint", durationMs: 60 }, - { id: 4, name: "Implement search endpoint", durationMs: 100 }, - { id: 5, name: "Write integration tests", durationMs: 70 }, - { id: 6, name: "Update documentation", durationMs: 40 }, -]; - -// --------------------------------------------------------------------------- -// Simulated session executor -// --------------------------------------------------------------------------- - -/** Simulate a session doing work. */ -function runSession( - sessionName: string, - startStep: number, - endStep: number -): SessionResult { - const output: string[] = []; - let totalDuration = 0; - - for (let i = startStep; i <= endStep; i++) { - const step = taskSteps.find((s) => s.id === i)!; - totalDuration += step.durationMs; - output.push(` [${sessionName}] Step ${step.id}: ${step.name} (${step.durationMs}ms)`); - } - - return { - session: sessionName, - stepsCompleted: endStep - startStep + 1, - totalDurationMs: totalDuration, - duplicatedSteps: 0, - output, - }; -} - -// --------------------------------------------------------------------------- -// Run 1: No handoff file -// --------------------------------------------------------------------------- - -function simulateNoHandoff(): { sessionA: SessionResult; sessionB: SessionResult } { - // Session A does steps 1-3 before timing out - const sessionA = runSession("Session A", 1, 3); - - // Session B has no context, starts from scratch - const sessionB = runSession("Session B", 1, 6); - sessionB.duplicatedSteps = 3; // Redoes steps 1-3 that A already did - - return { sessionA, sessionB }; -} - -// --------------------------------------------------------------------------- -// Run 2: With handoff file -// --------------------------------------------------------------------------- - -function simulateWithHandoff(): { sessionA: SessionResult; sessionB: SessionResult } { - // Session A does steps 1-3 and writes a handoff file - const sessionA = runSession("Session A", 1, 3); - - // Session B reads handoff, starts from step 4 - const sessionB = runSession("Session B", 4, 6); - sessionB.duplicatedSteps = 0; - - return { sessionA, sessionB }; -} - -// --------------------------------------------------------------------------- -// Reporting -// --------------------------------------------------------------------------- - -function pad(s: string, len: number): string { - return s.length >= len ? s : s + " ".repeat(len - s.length); -} - -function printRun(title: string, result: { sessionA: SessionResult; sessionB: SessionResult }): void { - console.log("\n" + "=".repeat(80)); - console.log(` ${title}`); - console.log("=".repeat(80)); - - console.log("\n Session A output:"); - for (const line of result.sessionA.output) { - console.log(line); - } - - console.log("\n Session B output:"); - for (const line of result.sessionB.output) { - console.log(line); - } - - console.log("\n Session B notes:"); - if (result.sessionB.duplicatedSteps > 0) { - console.log(` - Redid ${result.sessionB.duplicatedSteps} steps that Session A already completed`); - console.log(` - Wasted ${result.sessionB.totalDurationMs - (taskSteps.slice(3).reduce((s, t) => s + t.durationMs, 0))}ms on duplicate work`); - } else { - console.log(" - Read handoff file, continued from step 4"); - console.log(" - No duplicate work performed"); - } -} - -function printComparison(): void { - const noHandoff = simulateNoHandoff(); - const withHandoff = simulateWithHandoff(); - - printRun("RUN 1: NO HANDOFF FILE", noHandoff); - printRun("RUN 2: WITH HANDOFF FILE", withHandoff); - - // Comparison table - console.log("\n" + "=".repeat(80)); - console.log(" COMPARISON TABLE"); - console.log("=".repeat(80) + "\n"); - - const header = `| ${pad("Metric", 35)}| ${pad("No Handoff", 15)}| ${pad("With Handoff", 15)}|`; - const sep = `|${"-".repeat(37)}|${"-".repeat(17)}|${"-".repeat(17)}|`; - console.log(header); - console.log(sep); - - const noTotal = noHandoff.sessionA.totalDurationMs + noHandoff.sessionB.totalDurationMs; - const withTotal = withHandoff.sessionA.totalDurationMs + withHandoff.sessionB.totalDurationMs; - - console.log(`| ${pad("Session A steps completed", 35)}| ${pad(String(noHandoff.sessionA.stepsCompleted), 15)}| ${pad(String(withHandoff.sessionA.stepsCompleted), 15)}|`); - console.log(`| ${pad("Session B steps completed", 35)}| ${pad(String(noHandoff.sessionB.stepsCompleted), 15)}| ${pad(String(withHandoff.sessionB.stepsCompleted), 15)}|`); - console.log(`| ${pad("Duplicated steps", 35)}| ${pad(String(noHandoff.sessionB.duplicatedSteps), 15)}| ${pad(String(withHandoff.sessionB.duplicatedSteps), 15)}|`); - console.log(`| ${pad("Total work (ms)", 35)}| ${pad(String(noTotal), 15)}| ${pad(String(withTotal), 15)}|`); - console.log(`| ${pad("Time saved by handoff", 35)}| ${pad("-", 15)}| ${pad(String(noTotal - withTotal) + "ms", 15)}|`); - - console.log("\n A handoff file eliminates duplicate work and ensures continuity across sessions.\n"); -} - -printComparison(); diff --git a/courses/learn-harness-engineering/docs/ja/lectures/lecture-05-why-long-running-tasks-lose-continuity/index.md b/courses/learn-harness-engineering/docs/ja/lectures/lecture-05-why-long-running-tasks-lose-continuity/index.md deleted file mode 100644 index bbc1f390..00000000 --- a/courses/learn-harness-engineering/docs/ja/lectures/lecture-05-why-long-running-tasks-lose-continuity/index.md +++ /dev/null @@ -1,180 +0,0 @@ -[中文版本 →](../../../zh/lectures/lecture-05-why-long-running-tasks-lose-continuity/) - -> コード例: [code/](https://github.com/walkinglabs/learn-harness-engineering/blob/main/docs/ja/lectures/lecture-05-why-long-running-tasks-lose-continuity/code/) -> 実践プロジェクト: [Project 03. Multi-session continuity](./../../projects/project-03-multi-session-continuity/index.md) - -# 講義 05. セッションをまたいでコンテキストを保つ - -Claude Code に完全な機能の実装を頼んだとします。30分間実行され、大部分の作業を終えますが、コンテキストが残り少なくなっています。新しいセッションを開始して続きをさせると — 前回どのような決定がなされたか、なぜオプション A が B より選ばれたか、どのファイルがすでに変更されたか、テストがどのような状態かを覚えていないことがわかります。プロジェクトを再探索するのに15分を費やし、前回のアプローチと矛盾する可能性もあります。 - -毎朝目覚めるとすべてを忘れてしまう職人だと想像してください。建設現場全体に再度慣れる必要があります — どの壁が半分まで建っているか、なぜ赤レンガが青レンガより選ばれたか、配管はどこまで進んでいるか。さらに悪いことに、昨日すでに設置された窓を、完了したことを覚えていないために取り壊してしまうかもしれません。 - -これはまさに、AI コーディングエージェントがセッションをまたぐタスクで直面する窮状です。この講義では、エージェントが長時間のタスク中に「記憶喪失」になる理由と、構造化された状態の永続化がエージェントを信頼できる日記をつける職人のようにできる方法を説明します — まだ記憶喪失ですが、日記がすべてを覚えています。 - -## コンテキストウィンドウ: 無限ではない - -コンテキストウィンドウは有限です。これはモデルのアップグレードでは解決できません — ウィンドウサイズが1Mトークンに成長しても、複雑なタスクはそれを使い果たします。なぜなら、エージェントはコードを生成するだけでなく、コードベースを理解し、自身の決定履歴を追跡し、ツールの出力を処理し、会話のコンテキストを維持しているからです。この情報はすべて、ウィンドウの拡大よりも速く増大します。 - -さらに深い問題があります。エージェントが生成する情報は、すべてが同じ重要度ではありません。中間的な推論ステップには決定の「理由」が含まれています — なぜ A ではなく B が選ばれたのか、なぜあのライブラリではなくこのライブラリなのか、なぜ特定の最適化がスキップされたのか。最終的な出力には「何をしたか」しか含まれていません — コード自体です。圧縮戦略は通常、後者を保持しますが、前者を失います。次のセッションはコードを見ますが、なぜそのように書かれたかを知らず、意図的な設計決定を「最適化」してしまう可能性があります。 - -Anthropic は長時間実行エージェントの研究で興味深いことを発見しました。エージェントはコンテキストが残り少なくなっていると感じると、「早期収束」行動を示します — 現在の作業を急いで終わらせ、検証ステップをスキップし、最適なものよりもシンプルな解決策を選びます。試験の残り時間が少なくなっていることに気づき、残りの選択問題に適当に答えを埋めるようなものです。Anthropic はこれを「コンテキスト不安」と呼んでいます。 - -## セッション継続性フロー - -継続性アーティファクトがないと、新しいセッションは毎回災難です。 - -```mermaid -flowchart LR - S1["Session 1
feature is half done"] --> End1["Context is nearly full
session ends"] - End1 --> S2["Session 2 starts fresh"] - S2 --> Guess["Re-read folders, rerun tests,
guess why the code was written this way"] - Guess --> Drift["Work gets repeated
and recovery is slow"] -``` - -継続性アーティファクトがあれば、新しいセッションはすぐに再開できます。 - -```mermaid -flowchart LR - Work["Session 1 work"] --> Progress["PROGRESS.md
done / in progress / next step"] - Work --> Decisions["DECISIONS.md
why this approach was chosen"] - Work --> Verify["Verification notes
which tests pass and fail"] - Work --> Commit["Git checkpoint
exact repo state"] - - Progress --> Rebuild["Session 2 rebuild"] - Decisions --> Rebuild - Verify --> Rebuild - Commit --> Rebuild - - Rebuild --> Resume["New session picks up quickly"] -``` - -## 中核概念 - -- **コンテキストウィンドウは有限**: ウィンドウサイズがどう謳われていても(128K、200K、1M)、長時間のタスクは最終的にそれを使い果たします。使い切った後は、圧縮(情報を失う)かリセット(新しいセッション)が必要です。どちらも何かを失います。 -- **継続性アーティファクト**: 新しいセッションが前回のセッションの続きを明確に再開できるようにする、永続化された状態ファイル。基本形: 進捗ログ + 検証記録 + 次のアクション。あの職人の日記です。 -- **リビルドコスト**: 新しいセッションが実行可能な状態に到達するまでに必要な時間。良い harness はリビルドコストを15分から3分に圧縮できます。 -- **ドリフト**: エージェントの理解とコードリポジトリの実際の状態との乖離。すべてのセッション境界でドリフトが発生し、制御しなければ複利で増大します。 -- **コンテキスト不安**: Anthropic が観察した現象 — エージェントは知覚されるコンテキスト制限に近づくと早期収束行動を示し、情報損失を避けるためにタスクを早期に終了させます。これは非合理的なリソース不安です。 -- **圧縮 vs リセット**: 圧縮は同じセッション内でコンテキストを要約します(「何をしたか」は保持、「なぜ」は失われる可能性あり)。リセットは新しいセッションを開始し、永続化された状態から再構築します(クリーンだが、アーティファクトの完全性に依存)。 - -## 継続性が壊れたときに何が起きるか - -前のセッションは、3つのアプローチを分析してオプション B を選ぶために大量のコンテキスト予算を費やしました。今回のセッションのエージェントはその分析を知らず、不完全な情報に基づいて再決定する可能性があります — オプション A を選ぶかもしれません。赤レンガが選ばれた理由を覚えていない記憶喪失の職人が、今日は青レンガの方がきれいだと思い、昨日の壁を取り壊して建て直すようなものです。 - -さらに悪いのは重複作業です。エージェントは特定の作業がすでに完了したかどうか確信がなく、もう一度行います。さらに悪いことに — 半分やってから既存の実装との競合を発見し、やり直しになります。建設現場で、2つのチームが同じ壁を同時に建てることはできません — しかし進捗記録がなければ、新しいチームは誰かがすでに取り組んでいることを知る由がありません。 - -複数のセッションにわたって、実装の方向が元の要件から知らず知らずのうちにずれていくことがあります。新しいセッションごとにプロジェクトの目標に対する理解が少し異なります。伝言ゲームのように — 10人がメッセージを伝えるうちに、「コーヒーを買ってきて」が「コーヒーマシンを買って」になってしまうかもしれません。 - -検証のギャップもあります。前のセッションの検証結果(どのテストが合格し、どれが失敗し、なぜ失敗したか)が記録されていません。新しいセッションは現在の状態を理解するためにすべての検証を再実行しなければなりません。毎回ゼロから再診断し、毎回貴重なコンテキストを無駄にします。 - -OpenAI も Anthropic もドキュメントで構造化された状態の永続化を強調しています。OpenAI の harness engineering の記事はリポジトリを「運用記録」として扱っています — すべての操作の結果がリポジトリに追跡可能な証拠を残すべきだとしています。Anthropic の長時間実行エージェントのドキュメントは具体的に「ハンドオフファイル」を推奨しています — 現在の状態、既知の問題、次のアクションを含む構造化されたドキュメントです。 - -## 記憶喪失の職人に日記を - -核心的なアプローチ: **エージェントを記憶喪失の優秀なエンジニアとして扱う。** 「退勤」する前に、次の「シフト」のエージェントがすぐに引き継げるよう、重要な情報を書き留めなければなりません。 - -**ツール1: 進捗ファイル(PROGRESS.md)。** 最も基本的な継続性アーティファクト — 日記の核心です。 - -```markdown -# Project Progress - -## Current State -- Latest commit: abc1234 (feat: add user preferences endpoint) -- Test status: 42/43 passing (test_pagination_edge_case failing) -- Lint: passing - -## Completed -- [x] User model and database migration -- [x] Basic CRUD endpoints -- [x] Auth middleware integration - -## In Progress -- [ ] Pagination feature (90% - edge case test failing) - -## Known Issues -- test_pagination_edge_case returns 500 on empty result sets -- Need to confirm whether deleted users should appear in listings - -## Next Steps -1. Fix pagination edge case bug -2. Add "include deleted users" query parameter -3. Update API documentation -``` - -**ツール2: 決定ログ(DECISIONS.md)。** 重要な設計決定とその理由を記録します。詳細な設計ドキュメントは不要 — 「何を決定したか、なぜ、いつ」だけ — 日記の中のメモです。 - -```markdown -# Design Decisions - -## 2024-01-15: Use Redis for user preferences caching -- Reason: High read frequency (every API call), small data size -- Rejected alternative: PostgreSQL materialized view (high change frequency makes maintenance cost not worthwhile) -- Constraint: Cache TTL of 5 minutes, active invalidation on write -``` - -**ツール3: Git コミットをチェックポイントとして。** 各作業の原子単位が完了するたびにコミットします。コミットメッセージには何をしたかとなぜを記載します。これらは無料で、自動的にバージョン管理される状態のスナップショットです。 - -**ツール4: init.sh または harness 初期化フロー。** `AGENTS.md` に「出勤」ルーティンと「退勤」ルーティンを指定します。 - -```markdown -## At session start (clock in) -1. Read PROGRESS.md for current state -2. Read DECISIONS.md for important decisions -3. Run make check to confirm repo is in consistent state -4. Continue from PROGRESS.md "Next Steps" section - -## Before session end (clock out) -1. Update PROGRESS.md -2. Run make check to confirm consistent state -3. Commit all completed work -``` - -**混合戦略**: すべてのタスクがコンテキストのリセットを必要とするわけではありません。短いタスク(30分未満)は1つのセッション内で完了できます。長時間のタスク(セッションにまたがるもの)は、継続性のために進捗ファイルと決定ログを使用しなければなりません。判断基準: タスクがウィンドウの60%以上を必要とする場合、ハンドオフの準備を始めます。 - -### コンテキスト不安の詳細 - -Anthropic の2026年3月の研究は、コンテキスト不安の具体的な兆候をさらに明らかにしました: Sonnet 4.5 では、コンテキストがウィンドウ制限に近づくと、エージェントは強い「早期収束」行動を示します。試験の時間がほとんどなくなったことに気づき、素早く選択問題にランダムな答えを埋めるようなものです。 - -これに対処する2つの戦略があります。 - -**圧縮**: 同じセッション内で早期の会話を要約します。利点: 継続性を維持し、エージェントは「何をしたか」を見られます。欠点: 要約で「なぜ」が失われることが多い — なぜ A ではなく B が選ばれたのか、なぜ特定の最適化がスキップされたのか。さらに重要なことに、圧縮はコンテキスト不安を排除しません — エージェントはコンテキストがかつて大きかったことを知っており、心理的にはまだ急いで終わらせようとする傾向があります。 - -**コンテキストリセット**: コンテキストを完全にクリアし、新しいセッションを開始し、永続化されたアーティファクトから再構築します。利点: クリーンな精神状態 — 新しいセッションには「時間が足りない」という不安がありません。欠点: ハンドオフアーティファクトの完全性に依存します。日記に重要な情報が欠けていると、新しいセッションは間違った方向に時間を浪費する可能性があります。 - -Anthropic の実際のデータ: Sonnet 4.5 では、コンテキスト不安が深刻であり、圧縮だけでは不十分です — コンテキストリセットが harness 設計の重要なコンポーネントになります。しかし Opus 4.5 ではこの行動が大幅に減少し、圧縮だけでリセットに頼らずにコンテキストを管理できます。これは次のことを意味します。**harness 設計はターゲットモデルを具体的に理解する必要があり、万能のテンプレートではありません。** - -> 出典: [Anthropic: Harness design for long-running application development](https://www.anthropic.com/engineering/harness-design-long-running-apps) - -## 実例 - -エージェントにユーザー認証付きブログシステムの実装を依頼しました — 12の機能ポイント、5セッションが必要と推定。 - -**日記なしのベースライン**: セッション1でユーザーモデルと基本ルートを実装。セッション2はエージェントが認証ミドルウェアのインターフェース契約を覚えておらず、前回の設計意図を推測するのに約15分を費やしました。セッション3までに蓄積されたドリフトにより、エージェントはすでに完了した機能の再実装を始めました。セッション5までに、リポジトリには大量の冗長なコードが含まれていましたが、コアの認証機能は依然としてエンドツーエンドテストに合格していませんでした。12の機能ポイントのうち7つしか完了せず、3つには隠れた正確性の問題がありました。日記を一切書かない職人のように — 5日目には建設現場は混乱し、一部の壁は2度建てられ、建てるべきだった壁はまだ着手されていません。 - -**日記あり**: 進捗ファイル、決定ログ、検証記録、Git チェックポイントを使用。各セッション終了時に状態レポートを自動更新。セッション2のリビルドコストは約3分に低下。セッション5までに、12の機能ポイントすべてが完了し検証済み。 - -定量的な比較: リビルド時間が約78%削減、機能完了率は58%から100%、隠れた欠陥率は43%から8%に低下。職人はまだ記憶喪失ですが、日記のおかげで毎日の開始は昨日の停止地点からとなり、ゼロからではありません。 - -## 重要なポイント - -- コンテキストウィンドウは有限のリソースです。長時間のタスクはセッションにまたがり、セッションは情報を失います — 毎日忘れる職人のように、これは客観的な現実です。 -- 解決策はより大きなウィンドウではなく、より良い状態の永続化です。進捗ファイル + 決定ログ + Git チェックポイント — 記憶喪失の職人に信頼できる日記を与えます。 -- エージェントを記憶喪失のエンジニアとして扱う: 「退勤」前に、何をしたか、なぜ、次は何かを書き留める。 -- リビルドコストが主要な指標です。良い harness は新しいセッションを3分以内に実行可能な状態にするべきです。 -- 混合戦略: 短いタスクはセッション内で、長時間のタスクは構造化されたアーティファクトで継続性を確保。 - -## 参考資料 - -- [Anthropic: Effective Harnesses for Long-Running Agents](https://www.anthropic.com/engineering/effective-harnesses-for-long-running-agents) -- [OpenAI: Harness Engineering](https://openai.com/index/harness-engineering/) -- [Lost in the Middle: How Language Models Use Long Contexts](https://arxiv.org/abs/2307.03172) -- [Claude Code Documentation](https://docs.anthropic.com/ja/docs/claude-code) -- [HumanLayer: Harness Engineering for Coding Agents](https://humanlayer.dev/articles/harness-engineering-for-coding-agents/) - -## 演習 - -1. **継続性損失の測定**: 3セッション以上を必要とする開発タスクを選びます。継続性アーティファクトを一切提供せず、各セッションの開始時にエージェントが「前回何が起きたかを理解する」のにどれくらいのコンテキストを費やすかを記録します。各セッション終了後に進捗ファイルを作成し、次のセッションをそこから開始させます。進捗ファイルの有無によるリビルドコストを比較してください。 - -2. **ハンドオフテンプレートの設計**: 4つのフィールドを持つ最小限のハンドオフテンプレートを設計します: リポジトリの状態(コミットハッシュ)、実行状態(テスト合格率)、ブロッカー、次のアクション。完全に新しいエージェントセッションにこのテンプレートだけを使ってプロジェクトの状態を復元させます。復元中に遭遇した曖昧さを記録し、テンプレートを改善するために反復してください。 - -3. **混合戦略の実験**: 5セッションの開発タスクで、3つの戦略を比較します: (a) 常に新しいセッションを開始 + 進捗ファイル、(b) 1つのセッションでできるだけ多くを行う(コンテキスト圧縮)、(c) 混合戦略(短いタスクはセッション内、長時間のタスクはセッションをまたぐ + 進捗ファイル)。リビルド時間、機能完了率、決定の一貫性を比較してください。 diff --git a/courses/learn-harness-engineering/docs/ja/lectures/lecture-06-why-initialization-needs-its-own-phase/code/index.md b/courses/learn-harness-engineering/docs/ja/lectures/lecture-06-why-initialization-needs-its-own-phase/code/index.md deleted file mode 100644 index cf486254..00000000 --- a/courses/learn-harness-engineering/docs/ja/lectures/lecture-06-why-initialization-needs-its-own-phase/code/index.md +++ /dev/null @@ -1,8 +0,0 @@ -# 講義 06 のコード - -このフォルダーは、以下の例に使用してください: - -- イニシャライザーの出力 -- 初期化スクリプト -- 進捗ファイル -- 初回実行のスキャフォールディング diff --git a/courses/learn-harness-engineering/docs/ja/lectures/lecture-06-why-initialization-needs-its-own-phase/code/init-check.ts b/courses/learn-harness-engineering/docs/ja/lectures/lecture-06-why-initialization-needs-its-own-phase/code/init-check.ts deleted file mode 100644 index 52dcd985..00000000 --- a/courses/learn-harness-engineering/docs/ja/lectures/lecture-06-why-initialization-needs-its-own-phase/code/init-check.ts +++ /dev/null @@ -1,296 +0,0 @@ -/** - * init-check.ts - * - * Programmatically checks initialization prerequisites: - * - Node.js version - * - Dependencies installed (node_modules exists) - * - Data directory exists - * - Config files present - * - * Simulates running with and without an explicit init phase, - * showing how missing prerequisites cause silent failures later. - * - * Run: npx tsx docs/lectures/lecture-06-why-initialization-needs-its-own-phase/code/init-check.ts - */ - -import * as fs from "node:fs"; -import * as path from "node:path"; -import * as childProcess from "node:child_process"; - -// --------------------------------------------------------------------------- -// Types -// --------------------------------------------------------------------------- - -interface CheckItem { - name: string; - category: string; - check: () => { pass: boolean; detail: string }; - impactIfMissing: string; -} - -interface CheckResult { - name: string; - category: string; - passed: boolean; - detail: string; - impactIfMissing: string; -} - -// --------------------------------------------------------------------------- -// Checks -// --------------------------------------------------------------------------- - -function createChecks(targetDir: string): CheckItem[] { - return [ - { - name: "Node.js version >= 18", - category: "Runtime", - check: () => { - try { - const version = process.version; // e.g. "v20.11.0" - const major = parseInt(version.replace("v", "").split(".")[0], 10); - return { - pass: major >= 18, - detail: `Detected: ${version} (need >= v18)`, - }; - } catch { - return { pass: false, detail: "Could not detect Node.js version" }; - } - }, - impactIfMissing: "TypeScript features and built-in APIs unavailable", - }, - { - name: "package.json exists", - category: "Config", - check: () => { - const p = path.join(targetDir, "package.json"); - const exists = fs.existsSync(p); - return { - pass: exists, - detail: exists ? `Found: ${p}` : "Not found in target directory", - }; - }, - impactIfMissing: "Cannot install dependencies or run scripts", - }, - { - name: "Dependencies installed (node_modules)", - category: "Dependencies", - check: () => { - const p = path.join(targetDir, "node_modules"); - const exists = fs.existsSync(p); - return { - pass: exists, - detail: exists ? `Found: ${p}` : "node_modules/ not found -- run npm install", - }; - }, - impactIfMissing: "All imports fail at runtime", - }, - { - name: "TypeScript available", - category: "Toolchain", - check: () => { - try { - const result = childProcess.execSync("npx tsc --version", { - encoding: "utf-8", - timeout: 10000, - stdio: ["pipe", "pipe", "pipe"], - }).trim(); - return { pass: true, detail: `Detected: ${result}` }; - } catch { - return { pass: false, detail: "TypeScript not available via npx" }; - } - }, - impactIfMissing: "Cannot compile TypeScript files", - }, - { - name: "tsconfig.json exists", - category: "Config", - check: () => { - const p = path.join(targetDir, "tsconfig.json"); - const exists = fs.existsSync(p); - return { - pass: exists, - detail: exists ? `Found: ${p}` : "Not found", - }; - }, - impactIfMissing: "TypeScript compiler uses defaults, may not match project needs", - }, - { - name: "Source directory exists", - category: "Structure", - check: () => { - const candidates = ["src", "lib", "app"]; - const found = candidates.filter((d) => { - const p = path.join(targetDir, d); - return fs.existsSync(p) && fs.statSync(p).isDirectory(); - }); - return { - pass: found.length > 0, - detail: found.length > 0 ? `Found: ${found.join(", ")}` : "No src/ lib/ or app/ directory found", - }; - }, - impactIfMissing: "Agent cannot locate source files to modify", - }, - { - name: "Test directory exists", - category: "Structure", - check: () => { - const candidates = ["test", "tests", "__tests__", "spec"]; - const found = candidates.filter((d) => { - const p = path.join(targetDir, d); - return fs.existsSync(p) && fs.statSync(p).isDirectory(); - }); - return { - pass: found.length > 0, - detail: found.length > 0 ? `Found: ${found.join(", ")}` : "No test directory found", - }; - }, - impactIfMissing: "Agent cannot find or run existing tests", - }, - { - name: "Git repository initialized", - category: "Version Control", - check: () => { - const gitDir = path.join(targetDir, ".git"); - const exists = fs.existsSync(gitDir); - return { - pass: exists, - detail: exists ? "Git repository detected" : "No .git directory found", - }; - }, - impactIfMissing: "No rollback capability, no change history", - }, - ]; -} - -// --------------------------------------------------------------------------- -// Simulation: with and without init phase -// --------------------------------------------------------------------------- - -interface SimResult { - scenario: string; - checksRun: boolean; - failuresBeforeWork: number; - workAttempted: boolean; - workSucceeded: boolean; - timeWastedMs: number; -} - -function simulateWithoutInit(): SimResult { - // Agent skips init, goes straight to work. - // Encounters failures one at a time as it hits missing prerequisites. - const checks = createChecks(process.cwd()); - let failures = 0; - let timeWasted = 0; - - for (const check of checks) { - const result = check.check(); - if (!result.pass) { - failures++; - timeWasted += 200; // Agent spends time discovering each missing piece - } - } - - return { - scenario: "NO INIT PHASE", - checksRun: false, - failuresBeforeWork: 0, // Didn't check upfront - workAttempted: true, - workSucceeded: failures === 0, - timeWastedMs: timeWasted, - }; -} - -function simulateWithInit(): SimResult { - // Agent runs init phase first, discovers all issues upfront. - const checks = createChecks(process.cwd()); - let failures = 0; - - for (const check of checks) { - if (!check.check().pass) { - failures++; - } - } - - return { - scenario: "WITH INIT PHASE", - checksRun: true, - failuresBeforeWork: failures, - workAttempted: failures === 0, - workSucceeded: failures === 0, - timeWastedMs: 0, - }; -} - -// --------------------------------------------------------------------------- -// Reporting -// --------------------------------------------------------------------------- - -function pad(s: string, len: number): string { - return s.length >= len ? s : s + " ".repeat(len - s.length); -} - -function run(): void { - const targetDir = process.cwd(); - console.log("\n" + "=".repeat(80)); - console.log(" INITIALIZATION PREREQUISITE CHECK"); - console.log("=".repeat(80)); - console.log(` Target: ${targetDir}\n`); - - const checks = createChecks(targetDir); - const results: CheckResult[] = checks.map((c) => { - const r = c.check(); - return { - name: c.name, - category: c.category, - passed: r.pass, - detail: r.detail, - impactIfMissing: c.impactIfMissing, - }; - }); - - // Detailed report - const header = `| ${pad("Check", 35)}| ${pad("Category", 12)}| ${pad("Status", 6)}| Detail`; - const sep = `|${"-".repeat(37)}|${"-".repeat(14)}|${"-".repeat(8)}|${"-".repeat(40)}`; - console.log(header); - console.log(sep); - - for (const r of results) { - const status = r.passed ? "PASS" : "FAIL"; - console.log(`| ${pad(r.name, 35)}| ${pad(r.category, 12)}| ${pad(status, 6)}| ${r.detail}`); - } - - const passCount = results.filter((r) => r.passed).length; - const failCount = results.filter((r) => !r.passed).length; - - console.log("\n" + "-".repeat(80)); - console.log(` Results: ${passCount} passed, ${failCount} failed out of ${results.length} checks`); - - if (failCount > 0) { - console.log("\n FAILED CHECKS -- Impact if not resolved:"); - for (const r of results.filter((r) => !r.passed)) { - console.log(` - ${r.name}: ${r.impactIfMissing}`); - } - } - - // Comparison: with vs without init - const noInit = simulateWithoutInit(); - const withInit = simulateWithInit(); - - console.log("\n" + "=".repeat(80)); - console.log(" INIT PHASE COMPARISON"); - console.log("=".repeat(80) + "\n"); - - const cHeader = `| ${pad("Metric", 35)}| ${pad("No Init Phase", 18)}| ${pad("With Init Phase", 18)}|`; - const cSep = `|${"-".repeat(37)}|${"-".repeat(20)}|${"-".repeat(20)}|`; - console.log(cHeader); - console.log(cSep); - console.log(`| ${pad("Prerequisites checked upfront", 35)}| ${pad("No", 18)}| ${pad("Yes", 18)}|`); - console.log(`| ${pad("Work attempted despite issues", 35)}| ${pad(String(noInit.workAttempted), 18)}| ${pad(String(withInit.workAttempted), 18)}|`); - console.log(`| ${pad("Time wasted discovering issues late", 35)}| ${pad(noInit.timeWastedMs + "ms", 18)}| ${pad(withInit.timeWastedMs + "ms", 18)}|`); - console.log(`| ${pad("Work succeeded", 35)}| ${pad(String(noInit.workSucceeded), 18)}| ${pad(String(withInit.workSucceeded), 18)}|`); - - console.log("\n An explicit init phase catches problems before they waste time.\n"); -} - -run(); diff --git a/courses/learn-harness-engineering/docs/ja/lectures/lecture-06-why-initialization-needs-its-own-phase/code/init.sh b/courses/learn-harness-engineering/docs/ja/lectures/lecture-06-why-initialization-needs-its-own-phase/code/init.sh deleted file mode 100644 index 2c6e1b0c..00000000 --- a/courses/learn-harness-engineering/docs/ja/lectures/lecture-06-why-initialization-needs-its-own-phase/code/init.sh +++ /dev/null @@ -1,10 +0,0 @@ -#!/usr/bin/env bash -set -euo pipefail - -echo "[init] installing dependencies" -npm install - -echo "[init] starting the docs site is optional" -echo "[init] use npm run docs:dev for course docs" - -echo "[init] project-specific startup would go here" diff --git a/courses/learn-harness-engineering/docs/ja/lectures/lecture-06-why-initialization-needs-its-own-phase/code/initializer-output-checklist.md b/courses/learn-harness-engineering/docs/ja/lectures/lecture-06-why-initialization-needs-its-own-phase/code/initializer-output-checklist.md deleted file mode 100644 index 7fc15234..00000000 --- a/courses/learn-harness-engineering/docs/ja/lectures/lecture-06-why-initialization-needs-its-own-phase/code/initializer-output-checklist.md +++ /dev/null @@ -1,7 +0,0 @@ -# イニシャライザー出力チェックリスト - -- 正規の起動コマンドはあるか? -- 正規の検証コマンドはあるか? -- 最初の進捗アーティファクトはあるか? -- 安定した最初のコミットはあるか? -- 後のセッション向けに可視的な機能面はあるか? diff --git a/courses/learn-harness-engineering/docs/ja/lectures/lecture-06-why-initialization-needs-its-own-phase/index.md b/courses/learn-harness-engineering/docs/ja/lectures/lecture-06-why-initialization-needs-its-own-phase/index.md deleted file mode 100644 index ce4f308a..00000000 --- a/courses/learn-harness-engineering/docs/ja/lectures/lecture-06-why-initialization-needs-its-own-phase/index.md +++ /dev/null @@ -1,157 +0,0 @@ -[中文版本 →](../../../zh/lectures/lecture-06-why-initialization-needs-its-own-phase/) - -> コード例: [code/](https://github.com/walkinglabs/learn-harness-engineering/blob/main/docs/ja/lectures/lecture-06-why-initialization-needs-its-own-phase/code/) -> 実践プロジェクト: [Project 03. Multi-session continuity](./../../projects/project-03-multi-session-continuity/index.md) - -# 講義 06. すべてのエージェントセッション前に初期化する - -新しいエージェントセッションを開始し、「検索機能を追加して」と言います。エージェントはすぐにコーディングに飛び込みます — 勇気ある意欲です。20分後、テストフレームワークが正しく設定されていないことを発見し、それを直すのにさらに10分、次にデータベースのマイグレーションスクリプトの形式が間違っていて、さらに調整。検索機能は最終的に追加されましたが、セッション全体として非効率でした — ほとんどの時間は「このプロジェクトがどう動いているかを理解する」ことに費やされ、「検索機能を書く」ことにはほとんど使われませんでした。 - -より良いアプローチ: エージェントに作業を始めさせる前に、別のフェーズを使って基本環境を準備し、検証コマンドを通るようにし、プロジェクト構造を理解させます。家を建てるようなものです — 基礎を打つことと壁を立てることを同時には行いません。同時に行うと、基礎が固まる前に壁が立ち上がり、建物全体を取り壊してやり直すことになります。まず基礎を打ち、固まるのを待ってから壁を建てる — きれいで効率的です。 - -この講義では、初期化がなぜ実装と混ぜるのではなく、独立したフェーズでなければならないかを説明します。 - -## 基礎と壁: 本質的に異なる2つの仕事 - -初期化と実装は全く異なる最適化の目標を持っています。実装フェーズの最適化の対象は: 検証済み機能の量と品質の最大化。初期化フェーズの最適化の対象は: その後のすべての実装の信頼性と効率の最大化。 - -初期化と実装を混ぜると、エージェントは多目的最適化問題に直面します — インフラを構築しながら機能のコードを書くことです。明示的な優先順位の設定がない場合、エージェントは自然にコードを書く方に引き寄せられます(それが直接目に見える出力だからです)、一方でインフラは犠牲になります(その価値は後続のセッションでのしか現れないからです)。建設作業員に基礎を打つことと壁を建てることを同時にさせるようなものです — おそらく壁を建てることを急ぐでしょう。壁は目に見え、実証可能だからです。しかし基礎の悪い家には、後にシステム的な問題が発生します。 - -## 初期化ライフサイクル - -```mermaid -flowchart TB - subgraph Wrong["Mixed session (wrong)"] - W1["Start feature work immediately"] --> W2["Discover env and test gaps mid-task"] - W2 --> W3["Accumulate unverified code"] - W3 --> W4["Next session must rediscover project state"] - end - - subgraph Right["Dedicated initialization (right)"] - R1["Session 1: environment runnable"] --> R2["Example test passing"] - R2 --> R3["Bootstrap contract + task list written"] - R3 --> R4["Clean checkpoint committed"] - R4 --> R5["Later sessions start directly on verified tasks"] - end -``` - -## 混ぜたときに何が起きるか - -最も直接的な問題: 基礎が正しく固まらない。エージェントは労力の80%を機能コードに、20%を適当にインフラの設定に使います。テストフレームワークは設定されたが検証されていない、lint ルールは設定されたが緩すぎる、進捗ファイルは作成されていない。これらの欠陥は最初のセッションでは明らかではありません(エージェントは自分が何をしたかまだ覚えているからです)が、2回目のセッションで表面化します — 新しいエージェントは実行方法、テスト方法、現状がどうなっているかを知りません。ずさんな基礎、ぐらつく建物です。 - -より隠れたコストは「未検証の蓄積」です — テストフレームワークが設定される前に書かれた機能コードは、検証のないコードです。後からそのコードにテストを追加しに行くと、設計が最初から間違っていたことに気づくかもしれません — 知っていれば別の方法で実装していたでしょう。生のコンクリートの上にタイルを貼るようなものです — 床が水平でないことに気づいたとき、すべてのタイルを剥がしてやり直さなければなりません。 - -セッション予算も無駄にされています。初期化作業(環境の設定、テストのセットアップ、プロジェクト構造の理解)は多大な予算を消費し、実際の機能実装に使える分が減ります。結果: 最初のセッションは機能の半分しか完了せず、2回目のセッションはプロジェクトの理解をやり直さなければなりません。基礎に予算を使ったのに、基礎も固まっていない — どちらの目標も達成できません。 - -最も見落とされやすい問題は、暗黙の前提の地雷です。初期化中にエージェントが行う決定(どのテストフレームワーク、ディレクトリの整理方法、依存関係の管理)— 明示的に記録されていなければ、後続のセッションはこれらの選択を理解できません。さらに悪いことに、後続のセッションが矛盾する選択をする可能性があります。最初の建設チームがコンクリート基礎を使ったのに、2番目のチームはそれを知らずに木製の杭を打ち込んだ — 基礎にひびが入ります。 - -Anthropic の長時間アプリケーション開発研究は、初期化と実装を分離することを明確に推奨しています。彼らの実験データ: 専用の初期化フェーズを使用したプロジェクトは、混在アプローチと比較してマルチセッションシナリオで31%高い機能完了率を示しました。重要な洞察 — 初期化フェーズに投資した時間は、次の3〜4セッションで完全に回収されます。基礎がしっかりしているほど、壁は速く立ち上がります。 - -OpenAI の Codex harness engineering ガイドも「リポジトリを運用記録とする」原則を強調しています — 最初の実行から明確な運用構造を確立しなければ、新しいセッションは毎回プロジェクトの規約を推測し直すことになります。 - -## 中核概念 - -- **初期化フェーズ**: エージェントのライフサイクルの最初のフェーズ — 機能実装は一切行わず、その後のすべての実装フェーズの前提条件を確立するだけです。出力はコードではなく、インフラです。 -- **ブートストラップコントラクト**: プロジェクトが新しいエージェントセッションによって明確に操作可能な条件 — 起動できる、テストできる、進捗が見える、次のステップに取りかかれる。4つの条件、すべて必須。 -- **コールドスタート vs ウォームスタート**: コールドスタートは空のディレクトリから始まり、エージェントがプロジェクト構造を推測しなければならない状態。ウォームスタートはテンプレートや既存のプロジェクトから始まり、インフラがすでに整っている状態。ウォームスタートはコールドスタートを大幅に上回ります — 水道と電気が通っている現場で作業を始めるのと、荒野から始めるのとの違いです。 -- **ハンドオフ準備完了**: いつでも新しいエージェントが引き継げる状態にプロジェクトがあること。口頭での説明は不要 — リポジトリの内容だけで十分。 -- **初回検証までの時間**: プロジェクト開始から最初の機能ポイントが検証に通るまでの時間。これは初期化の効率を測る中核的な指標です。 -- **下流の使いやすさ**: 初期化の品質を測る最良の指標 — 暗黙の知識に依存せずにタスクを正常に実行できる後続セッションの割合。 - -## 正しい初期化の方法 - -**初期化を専用のフェーズとして扱う。** 最初のセッションは初期化のみを行います — ビジネス機能のコードは一切書きません。初期化が生成するもの: - -**1. 実行可能な環境。** プロジェクトが起動し、依存関係がインストールされ、環境の問題がないこと。基礎が打たれ、ひび割れなし。 - -**2. 検証可能なテストフレームワーク。** 少なくとも1つのサンプルテストが通ること。これがテストフレームワーク自体が正しく設定されていることを証明します — 基礎の上に柱を立てて荷重に耐えられることを証明するようなものです。 - -**3. ブートストラップコントラクト文書。** 後続のセッションに明確に伝える文書: -```markdown -# Initialization Contract - -## Start Commands -- Install dependencies: `make setup` -- Start dev server: `make dev` -- Run tests: `make test` -- Full verification: `make check` - -## Current State -- All dependencies installed and locked -- Test framework configured (Vitest + React Testing Library) -- Example test passing (1/1) -- Lint rules configured (ESLint + Prettier) - -## Project Structure -- src/ — Source code -- src/components/ — React components -- src/api/ — API client -- tests/ — Test files -``` - -**4. タスクの分割。** プロジェクト全体を順序付きのタスクリストに分割し、各タスクに明確な受け入れ基準を設定: -```markdown -# Task Breakdown - -## Task 1: User Authentication Basics -- Implement JWT auth middleware -- Add login/register endpoints -- Acceptance: pytest tests/test_auth.py all passing - -## Task 2: User Profile Page -- Implement user profile CRUD -- Add profile edit form -- Acceptance: pytest tests/test_profile.py all passing - -## Task 3: Search Feature -- ... -``` - -**5. Git コミットをチェックポイントとして。** 初期化が完了したら、クリーンなチェックポイントとしてコミットします。その後のすべての作業はこのチェックポイントから始まります。 - -**ウォームスタート戦略**: 空のディレクトリから始めないでください。プロジェクトテンプレート(create-react-app、fastapi-template など)を使って、標準的なディレクトリ構造、依存関係の設定、テストフレームワークをあらかじめ設定します。一般的な初期化ステップをテンプレートに組み込み、プロジェクト固有の初期化作業だけを残します。水道と電気が通っている現場で作業を始めるようなもの — 荒野から始めるより1万倍良いです。 - -**初期化の完了基準**: 「どれだけコードを書いたか」ではなく、ブートストラップコントラクトの4つの条件が満たされているかどうかです — 起動できる、テストできる、進捗が見える、次のステップに取りかかれる。初期化を検証するにはこのチェックリストを使います: - -```markdown -## Initialization Acceptance Checklist -- [ ] `make setup` succeeds from scratch -- [ ] `make test` has at least one passing test -- [ ] A new agent session can answer "how to run" and "how to test" from repo contents alone -- [ ] Task breakdown file exists with at least 3 tasks -- [ ] Everything committed to git -``` - -## 実例 - -React フロントエンドプロジェクトの2つの初期化アプローチ: - -**混在アプローチ(基礎を打ちながら壁を建てる)**: エージェントはセッション1でプロジェクトのスキャフォールディングと最初の機能の実装を同時に行いました。セッション終了時、リポジトリには実行可能なコードがありましたが: 明示的な起動/テストコマンドのドキュメントがなく、進捗追跡ファイルがなく、タスク分割もありませんでした。セッション2はプロジェクト構造、テストフレームワーク、ビルドプロセスの推測に約20分を費やしました — 新しい建設チームが現場に到着し、基礎がどこまで進んでいるか、配管がどこを通っているかわからず、一つずつ穴を掘って確認するようなものです。 - -**専用初期化(まず基礎を打つ)**: セッション1は初期化のみを行いました — テンプレートからディレクトリ構造を作成し、テストフレームワーク(Vitest + React Testing Library)を設定し、1つのサンプルテストを書いて検証し、ブートストラップコントラクト文書とタスク分割ファイルを作成し、初期チェックポイントをコミットしました。セッション2のリビルド時間は3分未満で、タスクリストから直接作業を開始しました — 作業員が到着し、青写真を一瞥して、どこから続ければよいか正確にわかる。 - -プロジェクト全体のサイクル比較: 混在アプローチの総リビルド時間(全セッション合計)は、専用初期化アプローチより約60%多かった。初期化に費やした余分な20分は、後続のセッションで何倍にも回収されました。しっかりした基礎が壁を速く立ち上げる — 遅い方が速い。 - -## 重要なポイント - -- 初期化と実装は異なる最適化の目標を持っています — 混ぜると両方をダメにするだけです。まず基礎を打ち、それから壁を建てましょう。 -- 初期化の出力はコードではなく、インフラです: 実行可能な環境、検証可能なテスト、ブートストラップコントラクト、タスク分割。 -- ブートストラップコントラクトの4つの条件で初期化を検証する: 起動できる、テストできる、進捗が見える、次のステップに取りかかれる。 -- ウォームスタートがコールドスタートに勝る。プロジェクトテンプレートを使って標準化されたインフラをあらかじめ設定する。 -- 初期化に投資した時間は次の3〜4セッションで完全に回収される。これは追加コストではなく、先行投資です。基礎がしっかりしているほど、建物は速く立ち上がる。 - -## 参考資料 - -- [Anthropic: Effective Harnesses for Long-Running Agents](https://www.anthropic.com/engineering/effective-harnesses-for-long-running-agents) -- [OpenAI: Harness Engineering](https://openai.com/index/harness-engineering/) -- [HumanLayer: Harness Engineering for Coding Agents](https://humanlayer.dev/articles/harness-engineering-for-coding-agents/) -- [Infrastructure as Code — Martin Fowler](https://martinfowler.com/bliki/InfrastructureAsCode.html) -- [SWE-agent: Agent-Computer Interfaces](https://github.com/princeton-nlp/SWE-agent) - -## 演習 - -1. **ブートストラップコントラクトの設計**: 開発中のプロジェクトの完全なブートストラップコントラクトを書きます。次に、完全に新しいエージェントセッションを開き、リポジトリの内容だけを見せ(口頭のコンテキストは与えず)、プロジェクトの起動、テストの実行、現在の進捗の理解を試みさせてください。遭遇するすべての問題を記録する — それぞれがブートストラップコントラクトの欠落条項に対応しています。 - -2. **比較実験**: 中程度に複雑な新規プロジェクトを選びます。アプローチ A: エージェントに初期化と最初の実装を同時に行わせる。アプローチ B: 1セッションを専用の初期化に費やし、セッション2で実装を開始する。4セッション後、初回検証までの時間、リビルドコスト、機能完了率を比較してください。 - -3. **初期化受け入れチェックリスト**: プロジェクトの初期化受け入れチェックリストを設計する。新しいエージェントセッションに各チェックリスト項目を実行させ、合格したものと失敗したものを記録する。失敗した項目が、harness の強化が必要な箇所です。 diff --git a/courses/learn-harness-engineering/docs/ja/lectures/lecture-07-why-agents-overreach-and-under-finish/code/index.md b/courses/learn-harness-engineering/docs/ja/lectures/lecture-07-why-agents-overreach-and-under-finish/code/index.md deleted file mode 100644 index bc4c79d6..00000000 --- a/courses/learn-harness-engineering/docs/ja/lectures/lecture-07-why-agents-overreach-and-under-finish/code/index.md +++ /dev/null @@ -1,8 +0,0 @@ -# 講義 07 のコード - -このフォルダーは、以下の例に使用してください: - -- ワンショットの失敗 -- 大きすぎるタスクプロンプト -- 段階的なタスク形成 -- 構造化された機能面 diff --git a/courses/learn-harness-engineering/docs/ja/lectures/lecture-07-why-agents-overreach-and-under-finish/code/next-task-template.md b/courses/learn-harness-engineering/docs/ja/lectures/lecture-07-why-agents-overreach-and-under-finish/code/next-task-template.md deleted file mode 100644 index 7216961f..00000000 --- a/courses/learn-harness-engineering/docs/ja/lectures/lecture-07-why-agents-overreach-and-under-finish/code/next-task-template.md +++ /dev/null @@ -1,6 +0,0 @@ -# 次のタスクテンプレート - -- 現在の最優先機能: -- なぜこの機能が次なのか: -- 何が合格とみなされるか: -- このステップで変更してはならないもの: diff --git a/courses/learn-harness-engineering/docs/ja/lectures/lecture-07-why-agents-overreach-and-under-finish/code/scope-surface-example.md b/courses/learn-harness-engineering/docs/ja/lectures/lecture-07-why-agents-overreach-and-under-finish/code/scope-surface-example.md deleted file mode 100644 index dd488591..00000000 --- a/courses/learn-harness-engineering/docs/ja/lectures/lecture-07-why-agents-overreach-and-under-finish/code/scope-surface-example.md +++ /dev/null @@ -1,17 +0,0 @@ -# スコープ面の例 - -タスク: - -- Electron ナレッジアプリにインデックス機能を追加 - -悪いスコープ: - -- 「インデックス機能を実装」 - -より良いスコープ: - -- インポートされたドキュメントを解析する -- ドキュメントをチャンクに分割する -- チャンクメタデータを永続化する -- UI にインデックス状況を表示する -- 再インデックスアクションを追加する diff --git a/courses/learn-harness-engineering/docs/ja/lectures/lecture-07-why-agents-overreach-and-under-finish/code/scope-tracker.ts b/courses/learn-harness-engineering/docs/ja/lectures/lecture-07-why-agents-overreach-and-under-finish/code/scope-tracker.ts deleted file mode 100644 index c587a1dc..00000000 --- a/courses/learn-harness-engineering/docs/ja/lectures/lecture-07-why-agents-overreach-and-under-finish/code/scope-tracker.ts +++ /dev/null @@ -1,151 +0,0 @@ -/** - * scope-tracker.ts - * - * Reads a feature list and a change log. Enforces single-active-feature - * policy. Given a log of changes, flags any changes outside the active - * feature scope. Demonstrates how scope drift happens and how the tracker - * catches it. - * - * Run: npx tsx docs/lectures/lecture-07-why-agents-overreach-and-under-finish/code/scope-tracker.ts - */ - -// --------------------------------------------------------------------------- -// Types -// --------------------------------------------------------------------------- - -interface Feature { - id: string; - name: string; - status: "active" | "pending" | "done"; -} - -interface ChangeLogEntry { - step: number; - file: string; - description: string; - featureId: string; // The feature this change claims to belong to -} - -// --------------------------------------------------------------------------- -// Sample data -// --------------------------------------------------------------------------- - -const features: Feature[] = [ - { id: "F-001", name: "Search endpoint", status: "active" }, - { id: "F-002", name: "Delete endpoint", status: "pending" }, - { id: "F-003", name: "Rate limiting", status: "pending" }, - { id: "F-004", name: "User dashboard", status: "pending" }, -]; - -// A realistic change log where the agent gradually drifts from the active feature -const changeLog: ChangeLogEntry[] = [ - { step: 1, file: "src/routes/search.ts", description: "Add search route handler", featureId: "F-001" }, - { step: 2, file: "src/routes/search.ts", description: "Add query parameter validation", featureId: "F-001" }, - { step: 3, file: "src/routes/search.ts", description: "Add search results pagination", featureId: "F-001" }, - { step: 4, file: "src/routes/delete.ts", description: "Add delete route handler", featureId: "F-002" }, // DRIFT - { step: 5, file: "src/middleware/rate-limit.ts", description: "Add rate limiter middleware", featureId: "F-003" }, // DRIFT - { step: 6, file: "src/routes/search.ts", description: "Integrate rate limiter into search", featureId: "F-001" }, - { step: 7, file: "src/dashboard/ui.tsx", description: "Create dashboard layout component", featureId: "F-004" }, // DRIFT - { step: 8, file: "src/routes/search.ts", description: "Add search response formatting", featureId: "F-001" }, - { step: 9, file: "src/routes/delete.ts", description: "Add delete confirmation logic", featureId: "F-002" }, // DRIFT - { step: 10, file: "src/routes/search.ts", description: "Add search tests", featureId: "F-001" }, -]; - -// --------------------------------------------------------------------------- -// Scope tracker -// --------------------------------------------------------------------------- - -interface ScopeCheckResult { - step: number; - file: string; - description: string; - featureId: string; - inScope: boolean; - activeFeature: string; -} - -function trackScope( - featureList: Feature[], - changes: ChangeLogEntry[] -): ScopeCheckResult[] { - const activeFeatures = featureList.filter((f) => f.status === "active"); - const activeIds = new Set(activeFeatures.map((f) => f.id)); - const activeNames = activeFeatures.map((f) => f.name).join(", "); - - return changes.map((change) => ({ - step: change.step, - file: change.file, - description: change.description, - featureId: change.featureId, - inScope: activeIds.has(change.featureId), - activeFeature: activeNames, - })); -} - -// --------------------------------------------------------------------------- -// Reporting -// --------------------------------------------------------------------------- - -function pad(s: string, len: number): string { - return s.length >= len ? s : s + " ".repeat(len - s.length); -} - -function run(): void { - const results = trackScope(features, changeLog); - - console.log("\n" + "=".repeat(100)); - console.log(" SCOPE TRACKER -- Single Active Feature Enforcement"); - console.log("=".repeat(100)); - - console.log("\n Active feature: " + features.filter((f) => f.status === "active").map((f) => `${f.id} (${f.name})`).join(", ")); - console.log(" Pending features: " + features.filter((f) => f.status === "pending").map((f) => `${f.id} (${f.name})`).join(", ")); - - // Detailed change log - console.log("\n" + "-".repeat(100)); - const header = `| ${pad("Step", 5)}| ${pad("File", 35)}| ${pad("Description", 40)}| ${pad("Feature", 8)}| ${pad("In Scope", 10)}|`; - const sep = `|${"-".repeat(7)}|${"-".repeat(37)}|${"-".repeat(42)}|${"-".repeat(10)}|${"-".repeat(12)}|`; - console.log(header); - console.log(sep); - - let inScopeCount = 0; - let driftCount = 0; - - for (const r of results) { - const scopeLabel = r.inScope ? "OK" : "DRIFT"; - if (r.inScope) inScopeCount++; - else driftCount++; - - const marker = r.inScope ? " " : ">>"; - console.log(`${marker}| ${pad(String(r.step), 5)}| ${pad(r.file, 35)}| ${pad(r.description, 40)}| ${pad(r.featureId, 8)}| ${pad(scopeLabel, 10)}|`); - } - - // Summary - console.log("\n" + "=".repeat(100)); - console.log(" SCOPE DRIFT SUMMARY"); - console.log("=".repeat(100) + "\n"); - - const sHeader = `| ${pad("Metric", 40)}| ${pad("Value", 15)}|`; - const sSep = `|${"-".repeat(42)}|${"-".repeat(17)}|`; - console.log(sHeader); - console.log(sSep); - console.log(`| ${pad("Total changes", 40)}| ${pad(String(results.length), 15)}|`); - console.log(`| ${pad("Changes within active scope (F-001)", 40)}| ${pad(String(inScopeCount), 15)}|`); - console.log(`| ${pad("Changes outside active scope (DRIFT)", 40)}| ${pad(String(driftCount), 15)}|`); - console.log(`| ${pad("Features touched (total)", 40)}| ${pad(String(new Set(results.map((r) => r.featureId)).size), 15)}|`); - - // Drift detail - const driftFeatures = [...new Set(results.filter((r) => !r.inScope).map((r) => r.featureId))]; - if (driftFeatures.length > 0) { - console.log("\n DRIFTED FEATURES:"); - for (const fid of driftFeatures) { - const feat = features.find((f) => f.id === fid); - const driftChanges = results.filter((r) => r.featureId === fid); - console.log(` ${fid} (${feat?.name}): ${driftChanges.length} unauthorized changes`); - } - } - - console.log("\n Without a scope tracker, the agent silently worked on " + driftFeatures.length + " unrelated features."); - console.log(" The tracker catches this drift and enforces the single-active-feature policy.\n"); -} - -run(); diff --git a/courses/learn-harness-engineering/docs/ja/lectures/lecture-07-why-agents-overreach-and-under-finish/index.md b/courses/learn-harness-engineering/docs/ja/lectures/lecture-07-why-agents-overreach-and-under-finish/index.md deleted file mode 100644 index f3cb6b25..00000000 --- a/courses/learn-harness-engineering/docs/ja/lectures/lecture-07-why-agents-overreach-and-under-finish/index.md +++ /dev/null @@ -1,134 +0,0 @@ -[中文版本 →](../../../zh/lectures/lecture-07-why-agents-overreach-and-under-finish/) - -> コード例: [code/](https://github.com/walkinglabs/learn-harness-engineering/blob/main/docs/ja/lectures/lecture-07-why-agents-overreach-and-under-finish/code/) -> 実践プロジェクト: [Project 04. Runtime feedback and scope control](./../../projects/project-04-incremental-indexing/index.md) - -# 講義 07. エージェントのタスク境界を明確にする - -Claude Code に「このプロジェクトにユーザー認証を追加して」と頼むと、データベーススキーマの変更を始め、ルートを書き、フロントエンドのコンポーネントを変更し、ついでにエラーハンドリングミドルウェアのリファクタリングまで始めます。2時間後に確認すると: 12ファイルが変更され、800行の新しいコードがあり、エンドツーエンドで動く機能は一つもありません。 - -背に腹は代えられない — この言葉は AI エージェントに特に当てはまります。エージェントには「ついでにもう少しやっておこう」という衝動が生まれつき備わっています — 関連するものを見ると、つい道沿いに処理してしまう。醤油を1本買いにスーパーに行って、満載のカートを押して出てくるような人です。問題は、買いすぎた人はお金を無駄にするだけですが、エージェントが同時に多くのことをしようとすると、どれも適切に完了しないということです。 - -Anthropic の「Effective harnesses for long-running agents」エンジニアリングブログは明確に述べています: プロンプトが広すぎると、エージェントは「1つを先に終わらせる」のではなく「複数のことを同時に始める」傾向があります。OpenAI の Codex エンジニアリングプラクティスも同じことを発見しました — 明示的なスコープ制御のないタスクは完了率が急激に低下します。これはモデルの問題ではありません — harness の問題です。あなたが境界を引かなかったのです。 - -## 注意力は有限のリソース - -これは比喩ではなく、数学です。エージェントのコンテキスト容量を C とし、k 個のタスクを同時に起動すると仮定します。各タスクは平均 C/k の推論リソースを得ます。C/k が1つのタスクを完了するのに必要な最低閾値を下回ると、どれも完了しません。胃袋は有限 — 10個の餃子を一度に詰め込んでも全部消化できず、10回の消化不良になるだけです。 - -Claude Code の実際の行動がそれを物語っています。「ユーザー登録を追加して」と頼むと、次のようにするかもしれません: - -1. User モデルを作成する -2. 登録ルートを書く -3. メール検証が必要なことに気づき、メールサービスを追加する -4. パスワードのハッシュ化が必要なことに気づき、bcrypt を導入する -5. エラーハンドリングが不統一なことに気づき、グローバルエラーミドルウェアをリファクタリングする -6. テストファイルの構造が乱雑なことに気づき、ディレクトリを再編成する - -6ステップ後、すべてが半完成。エンドツーエンドの検証はなく、半完成のコード間に複雑な結合があり、次のセッションで片付けを引き継ぐ者は完全に途方に暮れます。6つの料理を同時に作るようなもの — すべての料理が鍋に入っているが、どれも皿に盛られていません。すべて焦げます。 - -Anthropic の実験データはこれを直接的に裏付けています: 「小さな次のステップ」戦略(WIP=1 に相当)を使うエージェントは、広範なプロンプトを使うエージェントより37%高いタスク完了率を示します。さらに興味深いことに、エージェントが生成するコード行数と実際の機能完了率は弱い負の相関があります — より多くのコードを書いた方が、より少ない機能が完了する。背に腹は代えられない、データによる証明です。 - -## WIP=1 ワークフロー - -```mermaid -flowchart LR - Queue["Feature queue"] --> Pick["Pick exactly one task"] - Pick --> Active["Only one active item"] - Active --> Verify["Run end-to-end verification"] - Verify -->|pass| Commit["Commit and unlock next task"] - Verify -->|fail| Active - Commit --> Queue -``` - -```mermaid -flowchart TB - Budget["Available reasoning budget = C"] --> One["WIP = 1
C / 1 per task"] - Budget --> Many["WIP = 5
C / 5 per task"] - - One --> Finish["One feature reaches passing state"] - Many --> Partial["Five partial implementations"] - Partial --> VCR["Low verified completion rate
high rework next session"] -``` - -## 中核概念 - -- **オーバーリーチ**: エージェントが1つのセッションで最適な数よりも多くのタスクを起動すること。定量化可能 — 5つの機能をやってエンドツーエンドで通るものが0個ならオーバーリーチです。 -- **アンダーフィニッシュ**: 起動したタスクのうち、エンドツーエンドの検証に通ったタスクの割合が閾値を下回ること。コードは書かれているがテストが通っていないのはアンダーフィニッシュです。 -- **WIP 制限(Work-in-Progress Limit)**: カンバン手法から。核となる考え方: 一度に進行中のタスク数を制限する。エージェントの場合、WIP=1 が最も安全なデフォルト — 次を始める前に1つを完了する。ビュッフェのように — 皿に山盛りにせず、1枚の皿を済ませてから次を取りに行く。 -- **完了の証拠**: タスクが「進行中」から「完了」に移行するために満たさなければならない検証可能な条件。これがないと、エージェントは「コードは問題なさそう」を「動作がテストに通る」の代わりに使ってしまいます。 -- **スコープサーフェス**: 各ノードが作業単位で、エッジが依存関係を表す DAG 構造。状態は4つに限定: not_started、active、blocked、passing。 -- **完了プレッシャー**: harness が WIP 制限と完了の証拠の要件を通じて加える制約力で、エージェントに新しいタスクを始める前に現在のタスクを完了させるよう強制します。 - -## オーバーリーチとアンダーフィニッシュは共生する - -これら2つの問題は独立していません — 互いに増幅し合います。オーバーリーチは注意力を希薄化させ、希薄化された注意力はアンダーフィニッシュを引き起こし、残された半完成のコードがシステムの複雑さを増し、それが次のタスクでのさらなるオーバーリーチを招きます。悪循環です。 - -カンバンの言葉で言えば: リトルの法則は L = lambda * W と教えています。仕掛品 L が高すぎる(一度に多くのことをやっている)場合、各タスクのリードタイム W は必然的に増加します。エージェントにとって、これは各機能が開始から検証済み完了までにより長い時間がかかり、失敗の確率が増大することを意味します。 - -これは人間の世界でも古い問題です — Steve McConnell は *Rapid Development* の中で、スコープクリープがプロジェクト失敗の主要因であることを文書化しています。しかし人間には少なくとも「もう十分やった」という直感があります。エージェントにはそれがありません。次のアイデアを生成することはモデルにとってほぼ追加コストなしです — 「ついでにこれも直しておきましょう」と書くことはほとんど負担になりません — しかし、追加の変更ごとにエージェントの注意力は希薄化します。ビュッフェで追加の皿ごとの限界コストはほぼゼロですが、胃袋の容量には限界があるようなものです。 - -## 正しく行う方法 - -### 1. WIP=1 を強制する - -これが最も直接的で効果的な方法です。harness の中で、エージェントに明示的に伝えます: **いつでも「active」ステータスでよいタスクは1つだけ。** Claude Code の `CLAUDE.md` や Codex の `AGENTS.md` に次のように書きます: - -``` -## Work Rules -- Work on one feature at a time -- Only start the next feature after the current one passes end-to-end verification -- Don't "also refactor" feature B while implementing feature A -``` - -ビュッフェで食事するように — 一度に1枚の皿、済ませてから次を取りに行く。 - -### 2. 各タスクに明示的な完了の証拠を定義する - -完了とは「コードが書かれた」ではなく、「動作検証が通った」です。feature list で、各エントリに検証コマンドが必要です: - -``` -F01: User Registration - Verification: curl -X POST /api/register -d '{"email":"test@example.com","password":"123456"}' | jq .status == 201 - State: passing -``` - -### 3. スコープサーフェスを外部化する - -機械可読なファイル(JSON または Markdown)を使って、すべてのタスク状態を記録します。新しいセッションはこのファイルを読むだけで、どのタスクがアクティブか、何が完了とみなされるか、どの検証が通ったかを即座に把握できます。 - -### 4. 検証済み完了率を監視する - -harness は VCR(Verified Completion Rate)= 検証済みタスク数 / 起動タスク数 を継続的に追跡するべきです。VCR < 1.0 のときは新しいタスクの起動をブロックします。 - -## 実例 - -8つの機能を持つ REST API プロジェクト、2つの戦略を比較: - -**ビュッフェモード(制約なし)**: エージェントはセッション1で5つの機能を同時に起動。12ファイルにわたり約800行を生成。エンドツーエンドテストの合格率: 20% — ユーザー登録だけが動作。他の4つの機能: データベーススキーマは作成されたが検証ロジックが欠落、ルートは定義されたが誤ったレスポンス形式を返す。6つの料理を同時に作っているのに、1つだけかろうじて食べられる状態。セッション3の終了までに、8つの機能のうち3つしか完了せず。 - -**1枚の皿モード(WIP=1)**: エージェントはセッション1でユーザー登録のみに取り組む。4ファイルにわたり約200行を生成。エンドツーエンドテスト: 100%合格。クリーンで検証済みの実装をコミット。セッション4の終了までに、8つの機能のうち7つが完了(8番目は外部依存関係でブロック)。 - -結果: 総コード量は少ない(800 vs 1200行)が、有効なコードは多い。完了率: 87.5% vs 37.5%。一口ずつ食べれば、実際により多く食べられます。 - -## 重要なポイント - -- **WIP=1 はエージェント harness のデフォルトの安全設定** — 1つ完了させてから次に取りかかる。並列化しようとしない。一口で太ることはできない。 -- **完了の証拠は実行可能でなければならない** — 「コードは問題なさそう」はダメ。「curl が 201 を返す」ならOK。 -- **スコープサーフェスはファイルとして外部化する** — 会話中に言及するだけでなく、リポジトリ内の機械可読形式で記録する。 -- **オーバーリーチとアンダーフィニッシュは共生する** — 片方を解決すればもう片方も解決する。 -- **「少なくても完了させる」は常に「多くても半完成で終わる」に勝る** — エージェントのコード行数と機能完了率は負の相関。品質が常に量に勝る。 - -## 参考資料 - -- [Effective harnesses for long-running agents - Anthropic](https://www.anthropic.com/engineering/effective-harnesses-for-long-running-agents) — Anthropic のエンジニアリングブログ、「小さな次のステップ」戦略の詳細な議論 -- [Harness Engineering - OpenAI](https://openai.com/index/harness-engineering/) — OpenAI の harness engineering の完全な解説 -- [Kanban: Successful Evolutionary Change - David Anderson](https://www.goodreads.com/book/show/1070822.Kanban) — WIP 制限に関する古典的な文献 -- [Rapid Development - Steve McConnell](https://www.goodreads.com/book/show/125171.Rapid_Development) — スコープクリープがプロジェクト失敗の主要因であることの実証データ - -## 演習 - -1. **タスクの原子化**: 広範な要件(例: 「ユーザー管理システムを実装する」)を選び、少なくとも5つの原子作業単位に分解する。各単位について: (a) 単一の動作記述、(b) 実行可能な検証コマンド、(c) 依存関係を指定する。分解が WIP=1 の制約を満たしているか確認してください。 - -2. **比較実験**: 同じプロジェクトを2回実行する — 1回は制約なし、1回は WIP=1 を強制。検証済み完了率、総コード行数、有効コード比率を比較してください。 - -3. **完了の証拠の監査**: 最近のエージェント実行の出力を確認し、各コード変更を「完了した動作」「不完全な動作」「スキャフォールディング」に分類する。不完全な動作それぞれについて欠けている検証コマンドを追加してください。 diff --git a/courses/learn-harness-engineering/docs/ja/lectures/lecture-08-why-feature-lists-are-harness-primitives/code/feature-list-validator.ts b/courses/learn-harness-engineering/docs/ja/lectures/lecture-08-why-feature-lists-are-harness-primitives/code/feature-list-validator.ts deleted file mode 100644 index 52d6a2b9..00000000 --- a/courses/learn-harness-engineering/docs/ja/lectures/lecture-08-why-feature-lists-are-harness-primitives/code/feature-list-validator.ts +++ /dev/null @@ -1,218 +0,0 @@ -/** - * feature-list-validator.ts - * - * Reads a feature_list.json, validates its schema, and checks for features - * marked "pass" without verification evidence. Outputs a structured report. - * Can run against any project directory that has a feature_list.json. - * - * Usage: - * npx tsx docs/lectures/lecture-08.../code/feature-list-validator.ts [path-to-dir] - * (defaults to the directory containing this script) - * - * Run: npx tsx docs/lectures/lecture-08-why-feature-lists-are-harness-primitives/code/feature-list-validator.ts - */ - -import * as fs from "node:fs"; -import * as path from "node:path"; - -// --------------------------------------------------------------------------- -// Types -// --------------------------------------------------------------------------- - -interface FeatureEntry { - id?: string; - category?: string; - description?: string; - verification?: string[]; - passes?: boolean; - // Allow unknown fields for flexibility - [key: string]: unknown; -} - -interface ValidationResult { - featureId: string; - schemaValid: boolean; - schemaErrors: string[]; - hasVerification: boolean; - markedPassWithoutEvidence: boolean; - passes: boolean; - verificationCount: number; -} - -// --------------------------------------------------------------------------- -// Schema validation -// --------------------------------------------------------------------------- - -function validateSchema(entry: FeatureEntry, index: number): string[] { - const errors: string[] = []; - const label = entry.id ?? `entry ${index + 1}`; - - if (!entry.id || typeof entry.id !== "string") { - errors.push(`[${label}] Missing or invalid 'id' field`); - } - if (!entry.category || typeof entry.category !== "string") { - errors.push(`[${label}] Missing or invalid 'category' field`); - } - if (!entry.description || typeof entry.description !== "string") { - errors.push(`[${label}] Missing or invalid 'description' field`); - } - if (entry.verification !== undefined && !Array.isArray(entry.verification)) { - errors.push(`[${label}] 'verification' must be an array if present`); - } - if (entry.passes !== undefined && typeof entry.passes !== "boolean") { - errors.push(`[${label}] 'passes' must be a boolean if present`); - } - - return errors; -} - -// --------------------------------------------------------------------------- -// Evidence validation -// --------------------------------------------------------------------------- - -function checkEvidence(entry: FeatureEntry): { - hasVerification: boolean; - markedPassWithoutEvidence: boolean; -} { - const hasVerification = Array.isArray(entry.verification) && entry.verification.length > 0; - const markedPassWithoutEvidence = entry.passes === true && !hasVerification; - - return { hasVerification, markedPassWithoutEvidence }; -} - -// --------------------------------------------------------------------------- -// Process feature list -// --------------------------------------------------------------------------- - -function processFeatureList(entries: FeatureEntry[]): ValidationResult[] { - return entries.map((entry, index) => { - const schemaErrors = validateSchema(entry, index); - const evidence = checkEvidence(entry); - - return { - featureId: (entry.id as string) ?? `entry-${index + 1}`, - schemaValid: schemaErrors.length === 0, - schemaErrors, - hasVerification: evidence.hasVerification, - markedPassWithoutEvidence: evidence.markedPassWithoutEvidence, - passes: entry.passes === true, - verificationCount: Array.isArray(entry.verification) ? entry.verification.length : 0, - }; - }); -} - -// --------------------------------------------------------------------------- -// Reporting -// --------------------------------------------------------------------------- - -function pad(s: string, len: number): string { - return s.length >= len ? s : s + " ".repeat(len - s.length); -} - -function run(): void { - // Resolve target directory - const scriptDir = path.dirname(new URL(import.meta.url).pathname); - const targetDir = process.argv[2] - ? path.resolve(process.argv[2]) - : scriptDir; - - const filePath = path.join(targetDir, "feature_list.json"); - - console.log("\n" + "=".repeat(90)); - console.log(" FEATURE LIST VALIDATOR"); - console.log("=".repeat(90)); - console.log(` Reading: ${filePath}\n`); - - if (!fs.existsSync(filePath)) { - console.error(` ERROR: feature_list.json not found at ${filePath}`); - console.error(" Usage: npx tsx feature-list-validator.ts [path-to-directory-containing-feature_list.json]\n"); - process.exit(1); - } - - let entries: FeatureEntry[]; - try { - const raw = fs.readFileSync(filePath, "utf-8"); - entries = JSON.parse(raw); - } catch (err) { - console.error(` ERROR: Could not parse feature_list.json: ${err}`); - process.exit(1); - } - - if (!Array.isArray(entries)) { - console.error(" ERROR: feature_list.json must contain a JSON array at the top level."); - process.exit(1); - } - - // For demo purposes, also validate an extended test set - const demoEntries: FeatureEntry[] = [ - ...entries, - { - id: "qna-002", - category: "import", - description: "User can import a PDF document.", - verification: ["Upload a PDF file", "Verify it appears in the document list"], - passes: true, - }, - { - id: "qna-003", - category: "grounded_qa", - description: "System hallucination rate is below 5%.", - verification: [], // Empty -- no evidence - passes: true, // Marked as pass WITHOUT evidence - }, - { - id: "missing-fields", - // Missing 'category' and 'description' - passes: true, - } as FeatureEntry, - ]; - - const results = processFeatureList(demoEntries); - - // Print report - const header = `| ${pad("Feature ID", 14)}| ${pad("Schema", 8)}| ${pad("Passes", 7)}| ${pad("Verifications", 14)}| ${pad("Evidence?", 12)}| Notes`; - const sep = `|${"-".repeat(16)}|${"-".repeat(10)}|${"-".repeat(9)}|${"-".repeat(16)}|${"-".repeat(14)}|${"-".repeat(30)}`; - console.log(header); - console.log(sep); - - for (const r of results) { - const schemaLabel = r.schemaValid ? "OK" : "INVALID"; - const passesLabel = r.passes ? "PASS" : "FAIL"; - const evidenceLabel = r.hasVerification ? "Present" : "MISSING"; - const notes = r.markedPassWithoutEvidence - ? "FLAGGED: passes without evidence!" - : r.schemaErrors.length > 0 - ? r.schemaErrors[0] - : ""; - - const marker = r.markedPassWithoutEvidence ? ">>" : r.schemaValid ? " " : "!!"; - console.log( - `${marker}| ${pad(r.featureId, 14)}| ${pad(schemaLabel, 8)}| ${pad(passesLabel, 7)}| ${pad(String(r.verificationCount), 14)}| ${pad(evidenceLabel, 12)}| ${notes}` - ); - } - - // Summary - const total = results.length; - const schemaOk = results.filter((r) => r.schemaValid).length; - const passing = results.filter((r) => r.passes).length; - const flagged = results.filter((r) => r.markedPassWithoutEvidence).length; - const withEvidence = results.filter((r) => r.hasVerification).length; - - console.log("\n" + "-".repeat(90)); - console.log(" SUMMARY"); - console.log("-".repeat(90)); - console.log(` Total features: ${total}`); - console.log(` Schema valid: ${schemaOk}/${total}`); - console.log(` Marked as passing: ${passing}/${total}`); - console.log(` With verification evidence: ${withEvidence}/${total}`); - console.log(` Flagged (pass without evidence): ${flagged}`); - - if (flagged > 0) { - console.log(`\n WARNING: ${flagged} feature(s) marked as "pass" without any verification evidence.`); - console.log(" These features need verification before they can be trusted.\n"); - } else { - console.log("\n All passing features have verification evidence. Feature list is healthy.\n"); - } -} - -run(); diff --git a/courses/learn-harness-engineering/docs/ja/lectures/lecture-08-why-feature-lists-are-harness-primitives/code/feature_list.json b/courses/learn-harness-engineering/docs/ja/lectures/lecture-08-why-feature-lists-are-harness-primitives/code/feature_list.json deleted file mode 100644 index 8b973e31..00000000 --- a/courses/learn-harness-engineering/docs/ja/lectures/lecture-08-why-feature-lists-are-harness-primitives/code/feature_list.json +++ /dev/null @@ -1,15 +0,0 @@ -[ - { - "id": "qna-001", - "category": "grounded_qa", - "description": "User can ask a question about an imported document and receive an answer with visible citations.", - "verification": [ - "Import a markdown document", - "Open the Q&A panel", - "Ask a question about known content", - "Verify the answer is returned", - "Verify one or more citations are displayed" - ], - "passes": false - } -] diff --git a/courses/learn-harness-engineering/docs/ja/lectures/lecture-08-why-feature-lists-are-harness-primitives/code/index.md b/courses/learn-harness-engineering/docs/ja/lectures/lecture-08-why-feature-lists-are-harness-primitives/code/index.md deleted file mode 100644 index 11467c06..00000000 --- a/courses/learn-harness-engineering/docs/ja/lectures/lecture-08-why-feature-lists-are-harness-primitives/code/index.md +++ /dev/null @@ -1,8 +0,0 @@ -# 講義 08 のコード - -このフォルダーは、以下の例に使用してください: - -- パス状態ゲーティング -- エンドツーエンド検証 -- 弱い vs 強い完了基準 -- 評価ループの例 diff --git a/courses/learn-harness-engineering/docs/ja/lectures/lecture-08-why-feature-lists-are-harness-primitives/code/pass-gate-policy.md b/courses/learn-harness-engineering/docs/ja/lectures/lecture-08-why-feature-lists-are-harness-primitives/code/pass-gate-policy.md deleted file mode 100644 index 4405bef8..00000000 --- a/courses/learn-harness-engineering/docs/ja/lectures/lecture-08-why-feature-lists-are-harness-primitives/code/pass-gate-policy.md +++ /dev/null @@ -1,8 +0,0 @@ -# パスゲートポリシー - -機能は以下の条件をすべて満たした場合にのみ `passes: false` から `passes: true` に移行できる: - -- 期待されるワークフローが実行されている -- 成功の証拠が記録されている -- テスト済みパスにブロッキングエラーが存在しない -- 実装がアプリを壊れたまたは曖昧な状態にしていない diff --git a/courses/learn-harness-engineering/docs/ja/lectures/lecture-08-why-feature-lists-are-harness-primitives/index.md b/courses/learn-harness-engineering/docs/ja/lectures/lecture-08-why-feature-lists-are-harness-primitives/index.md deleted file mode 100644 index 0c7df27c..00000000 --- a/courses/learn-harness-engineering/docs/ja/lectures/lecture-08-why-feature-lists-are-harness-primitives/index.md +++ /dev/null @@ -1,138 +0,0 @@ -[中文版本 →](../../../zh/lectures/lecture-08-why-feature-lists-are-harness-primitives/) - -> コード例: [code/](https://github.com/walkinglabs/learn-harness-engineering/blob/main/docs/ja/lectures/lecture-08-why-feature-lists-are-harness-primitives/code/) -> 実践プロジェクト: [プロジェクト 04. 実行時フィードバックとスコープ制御](./../../projects/project-04-incremental-indexing/index.md) - -# 講義 08. 機能リストでエージェントの作業を制約する - -エージェントに e コマースサイトを作らせる。完了後、エージェントは「done」と言う。コードを見ると、ユーザー認証は動くが、カートのチェックアウトボタンは何もせず、決済フローも接続されていない。問題は、あなたが「done」の意味を伝えていなかったことだ。そのためエージェントは自分の基準、つまり「たくさんコードを書いたし、かなり完成して見える」を使った。 - -多くの人にとって、機能リストはただのメモだ。忘れないように書き出し、あとで脇に置くもの。しかし harness の世界では、機能リストは人間向けメモではない。harness 全体の背骨だ。スケジューラはそれを使ってタスクを選び、検証器は完了判定に使い、引き継ぎレポーターは要約生成に使う。背骨が折れれば、全身が動かなくなる。 - -Anthropic も OpenAI も強調している。**成果物は外部化されなければならない。** 機能状態は、構造化されていない会話テキストではなく、リポジトリ内の機械可読ファイルに置く必要がある。 - -## エージェントは「完了」の意味を知らない - -Claude Code も Codex も、あなたが「完了」と言うときの意味を自動では知らない。「ショッピングカート機能を追加して」と言うと、モデルは「Cart コンポーネントと addToCart メソッドを書く」と解釈するかもしれない。しかしあなたが意図したのは「ユーザーが商品を閲覧し、カートに追加し、エンドツーエンドで決済を完了できる」ことだ。機能リストがなければ、この理解の差は残り続ける。エージェントは自分の暗黙基準、たいていは「明らかな構文エラーがない」を使う。必要なのはエンドツーエンドの振る舞い検証だ。友人に「果物を買ってきて」と頼んだらレモンを買ってくるようなものだ。相手の果物とあなたの果物は同じではない。 - -よくある進捗メモを見てみよう。 - -``` -Did user auth, shopping cart mostly done, still need payments -``` - -新しいエージェントセッションは、このメモから次の質問に答えられるだろうか? 「mostly done」とは何か? カートはどのテストに通ったのか? 決済は何でブロックされているのか? 答えはすべて「誰も分からない」だ。医者に「最近お腹が痛いけど、まあ大丈夫」と言うようなもので、どんな薬を出せばよいのか分からない。 - -結果として、新しいセッションはプロジェクト状態の推測に 20 分を費やし、完了済み機能を再実装することさえある。Anthropic の engineering data では、よい進捗記録はセッション開始時の診断時間を 60-80% 削減する。 - -## 機能状態マシン - -```mermaid -flowchart LR - Feature["1 つの機能行"] --> Behavior["振る舞い
例: POST /cart/items returns 201"] - Feature --> Check["検証コマンド
実行すべき正確なチェック"] - Feature --> State["状態
not_started / active / blocked / passing"] - - Behavior --> Complete["3 つのフィールドが揃って初めて
機能行として使える"] - Check --> Complete - State --> Complete -``` - -```mermaid -flowchart LR - List["feature_list.json / features.md"] --> Scheduler["次の not_started 項目を選ぶ"] - Scheduler --> Agent["エージェントはその 1 項目に取り組む"] - Agent --> Verifier["その項目の検証コマンドを実行"] - Verifier -->|pass| Passing["passing に更新し
証拠を書く"] - Verifier -->|fail| Active["active のままにする"] - Verifier -->|dependency issue| Blocked["blocked にする"] - Passing --> Handoff["引き継ぎメモと
現在進捗を更新"] - Active --> Agent -``` - -## 中核概念 - -- **機能リストは harness primitive**: 「任意の計画ツール」ではなく、他の harness コンポーネントが依存する基礎データ構造だ。データベースのテーブル構造のように、「主キーは省略しよう」とは言えない。 -- **三要素構造**: 各機能項目は `(振る舞い説明, 検証コマンド, 現在状態)` の三つ組だ。どれか 1 つでも欠けると項目は不完全になる。 -- **状態マシンモデル**: 各機能項目には `not_started`、`active`、`blocked`、`passing` の 4 状態がある。状態遷移は harness が制御し、エージェントが自由に変えるものではない。 -- **Pass-state gating**: 機能が `active` から `passing` へ進む唯一の方法は、検証コマンドが成功することだ。これは不可逆で、一度 `passing` になれば戻らない。試験に合格したら合格であり、後から点数を変えられないのと同じだ。 -- **Single source of truth**: 「何をすべきか」に関する情報は、すべて 1 つの機能リストから導かれるべきだ。機能リストと会話履歴の間に矛盾を作らない。 -- **Back-pressure**: まだ通過していない機能の数が、harness がエージェントにかける圧力だ。圧力ゼロならプロジェクト完了。 - -## なぜ機能リストは「primitive」でなければならないのか - -ドキュメントは人間が読むものだ。primitive はシステムが実行するものだ。ドキュメントは無視できるが、primitive は迂回できない。 - -データベースのトリガー制約とアプリケーション層のチェックの違いを考えるとよい。前者はデータベースエンジンが強制し、どんな SQL もスキップできない。後者はアプリケーションコードの正しさに依存し、うっかり迂回されることがある。harness primitive としての機能リストは、具体的に 4 つの harness コンポーネントに使われる。 - -1. **Scheduler**: 状態を読み、次の `not_started` 機能を選ぶ。工場の生産計画システムのようなもの。 -2. **Verifier**: 検証コマンドを実行し、状態遷移を許可するか判断する。品質検査のようなもの。 -3. **Handoff reporter**: 機能リストからセッション引き継ぎ要約を自動生成する。自動の交代報告のようなもの。 -4. **Progress tracker**: 状態分布を集計し、プロジェクトの健全性指標を提供する。ダッシュボードのようなもの。 - -## 正しく行う方法 - -### 1. 最小の機能リスト形式を定義する - -複雑なシステムは不要だ。構造化 Markdown か JSON ファイルでよい。重要なのは、各エントリが三要素を持つことだ。 - -```json -{ - "id": "F03", - "behavior": "POST /cart/items with {product_id, quantity} returns 201", - "verification": "curl -X POST http://localhost:3000/api/cart/items -H 'Content-Type: application/json' -d '{\"product_id\":1,\"quantity\":2}' | jq .status == 201", - "state": "passing", - "evidence": "commit abc123, test output log" -} -``` - -### 2. 状態遷移は Harness に制御させる - -エージェントは機能状態を直接 `passing` に変更できない。検証リクエストを出せるだけだ。harness が検証コマンドを実行し、遷移を許可するか判断する。これが pass-state gating だ。 - -### 3. ルールを CLAUDE.md に書く - -``` -## Feature List Rules -- Feature list file: /docs/features.md -- Only one feature active at a time -- Verification command must pass before marking as passing -- Don't modify feature list states yourself — the verification script updates them automatically -``` - -### 4. 粒度を調整する - -各機能項目は「1 セッションで完了できる」範囲にする。広すぎると終わらず、狭すぎると管理コストが増える。「ユーザーが商品をカートに追加できる」はよい粒度だ。「ショッピングカートを実装する」は広すぎる。「Cart モデルに name フィールドを作る」は狭すぎる。ステーキを切るのと同じで、丸ごとでもひき肉でもない。 - -## 実例 - -10 個の機能を持つ e コマースプラットフォームで、2 つの追跡方法を比較した。 - -**メモモード**: エージェントは非構造化メモを使う。3 セッション後、メモは「user auth と product list は済み、shopping cart はだいたい完了だがバグあり、payments は未着手」になる。新しいセッションは状態推測に 20 分必要で、最終的に完了済み機能を再実装してしまう。買い物リストに「牛乳、パン、あと例のやつ」と書いてあるようなものだ。店に着いても、何を買えばよいか分からない。 - -**背骨モード**: すべての機能が明確な状態と検証コマンドを持つ。新しいセッションは機能リストを読み、3 分で F01-F05 は `passing`、F06 は `active`、F07-F10 は `not_started` と分かる。F06 から直接再開し、手戻りはゼロ。 - -定量結果: 構造化された機能リストを使うプロジェクトは、自由形式の追跡より機能完了率が 45% 高く、重複実装はゼロだった。 - -## 重要なポイント - -- **機能リストは harness の背骨**であり、人間向けメモではない。Scheduler、verifier、handoff reporter はすべてそれに依存する。 -- **すべての機能項目は三要素を持つ必要がある**: 振る舞い説明 + 検証コマンド + 現在状態。1 つでも欠けると不完全だ。3 本脚の椅子から 1 本抜けたようなものだ。 -- **状態遷移は harness が制御する**。エージェントは自分で状態を変えられない。検証成功だけが昇格経路だ。 -- **機能リストはプロジェクトの single source of truth**。すべての「何をするか」情報は 1 つのリストから導かれる。 -- **粒度は「1 セッションで完了できる」に合わせる。** - -## 参考資料 - -- [Building Effective Agents - Anthropic](https://www.anthropic.com/research/building-effective-agents) — エージェントのスコープ制御における「中核データ構造」として機能リストを明示 -- [Harness Engineering - OpenAI](https://openai.com/index/harness-engineering/) — 「成果物を外部化する」原則を強調 -- [Design by Contract - Bertrand Meyer](https://www.goodreads.com/book/show/130439.Object_Oriented_Software_Construction) — 契約設計の原則、機能リストの理論的基盤 -- [How Google Tests Software](https://www.goodreads.com/book/show/13563030-how-google-tests-software) — テストピラミッドと振る舞い仕様の実践 - -## 演習 - -1. **機能リスト設計**: 最小の機能リスト JSON schema を定義する。id、振る舞い説明、検証コマンド、現在状態、証拠参照を含める。実プロジェクトの 5 機能を記述してみる。 - -2. **検証厳密度の比較**: 3 つの機能を選び、「緩い」検証(例: 「コードに構文エラーがない」)と「厳密な」検証(例: 「エンドツーエンドテストが通る」)を設計する。それぞれの false positive rate を比較する。 - -3. **Single Source 原則監査**: 既存のエージェントプロジェクトを確認し、機能リストと矛盾するスコープ情報(会話内の暗黙要求、コード内 TODO コメントなど)がないか調べる。すべての情報を機能リストへ統合する計画を設計する。 diff --git a/courses/learn-harness-engineering/docs/ja/lectures/lecture-09-why-agents-declare-victory-too-early/code/clean-state-checklist.md b/courses/learn-harness-engineering/docs/ja/lectures/lecture-09-why-agents-declare-victory-too-early/code/clean-state-checklist.md deleted file mode 100644 index 8d42d8f2..00000000 --- a/courses/learn-harness-engineering/docs/ja/lectures/lecture-09-why-agents-declare-victory-too-early/code/clean-state-checklist.md +++ /dev/null @@ -1,6 +0,0 @@ -# クリーン状態チェックリスト - -- 手動修復なしでアプリが起動する -- 現在の進捗が記録されている -- 未完了のインポートやインデックスステップが未記録のまま残っていない -- 次のエージェントが標準起動・検証パスをすぐに実行できる diff --git a/courses/learn-harness-engineering/docs/ja/lectures/lecture-09-why-agents-declare-victory-too-early/code/index.md b/courses/learn-harness-engineering/docs/ja/lectures/lecture-09-why-agents-declare-victory-too-early/code/index.md deleted file mode 100644 index b1cfd8da..00000000 --- a/courses/learn-harness-engineering/docs/ja/lectures/lecture-09-why-agents-declare-victory-too-early/code/index.md +++ /dev/null @@ -1,8 +0,0 @@ -# 講義 09 のコード - -このフォルダーは、以下の例に使用してください: - -- フィードバックとしてのログ -- ランタイム状態の可視性 -- クリーン状態チェック -- リカバリの例 diff --git a/courses/learn-harness-engineering/docs/ja/lectures/lecture-09-why-agents-declare-victory-too-early/code/victory-detector.ts b/courses/learn-harness-engineering/docs/ja/lectures/lecture-09-why-agents-declare-victory-too-early/code/victory-detector.ts deleted file mode 100644 index fad1a3ba..00000000 --- a/courses/learn-harness-engineering/docs/ja/lectures/lecture-09-why-agents-declare-victory-too-early/code/victory-detector.ts +++ /dev/null @@ -1,241 +0,0 @@ -/** - * victory-detector.ts - * - * Simulates an agent that claims task completion, then checks the claimed - * state against actual verification. Outputs: claimed vs actual, highlighting - * gaps between what the agent said and what's really true. - * - * Run: npx tsx docs/lectures/lecture-09-why-agents-declare-victory-too-early/code/victory-detector.ts - */ - -// --------------------------------------------------------------------------- -// Types -// --------------------------------------------------------------------------- - -interface Task { - name: string; - claimedComplete: boolean; - checks: VerificationCheck[]; -} - -interface VerificationCheck { - description: string; - claimed: boolean; // What the agent says - actual: boolean; // What is actually true - severity: "critical" | "warning"; -} - -// --------------------------------------------------------------------------- -// Simulated tasks with claimed vs actual states -// --------------------------------------------------------------------------- - -const tasks: Task[] = [ - { - name: "Add search endpoint", - claimedComplete: true, - checks: [ - { - description: "Route handler exists", - claimed: true, - actual: true, - severity: "critical", - }, - { - description: "Authentication middleware applied", - claimed: true, - actual: false, // Agent forgot auth - severity: "critical", - }, - { - description: "Input validation added", - claimed: true, - actual: true, - severity: "critical", - }, - { - description: "Unit tests written", - claimed: true, - actual: false, // Agent skipped tests - severity: "critical", - }, - { - description: "Integration tests pass", - claimed: true, - actual: false, // No tests to run - severity: "critical", - }, - { - description: "Documentation updated", - claimed: true, - actual: false, // Agent skipped docs - severity: "warning", - }, - ], - }, - { - name: "Fix pagination bug", - claimedComplete: true, - checks: [ - { - description: "Bug reproduction case confirmed", - claimed: true, - actual: true, - severity: "critical", - }, - { - description: "Root cause identified", - claimed: true, - actual: true, - severity: "critical", - }, - { - description: "Fix applied", - claimed: true, - actual: true, - severity: "critical", - }, - { - description: "Edge cases tested", - claimed: true, - actual: false, // Only happy path tested - severity: "warning", - }, - { - description: "Regression tests added", - claimed: true, - actual: false, - severity: "critical", - }, - ], - }, - { - name: "Refactor auth module", - claimedComplete: true, - checks: [ - { - description: "Old code removed", - claimed: true, - actual: false, // Old code still present - severity: "warning", - }, - { - description: "All existing tests still pass", - claimed: true, - actual: false, // Two tests broke - severity: "critical", - }, - { - description: "New auth flow works", - claimed: true, - actual: true, - severity: "critical", - }, - { - description: "Migration guide written", - claimed: true, - actual: false, - severity: "warning", - }, - ], - }, -]; - -// --------------------------------------------------------------------------- -// Verification -// --------------------------------------------------------------------------- - -interface TaskVerification { - taskName: string; - claimedComplete: boolean; - actuallyComplete: boolean; - totalChecks: number; - claimedPassing: number; - actualPassing: number; - gaps: { description: string; severity: string }[]; -} - -function verifyTask(task: Task): TaskVerification { - const claimedPassing = task.checks.filter((c) => c.claimed).length; - const actualPassing = task.checks.filter((c) => c.actual).length; - const gaps = task.checks.filter((c) => c.claimed && !c.actual); - - return { - taskName: task.name, - claimedComplete: task.claimedComplete, - actuallyComplete: gaps.length === 0, - totalChecks: task.checks.length, - claimedPassing, - actualPassing, - gaps: gaps.map((g) => ({ description: g.description, severity: g.severity })), - }; -} - -// --------------------------------------------------------------------------- -// Reporting -// --------------------------------------------------------------------------- - -function pad(s: string, len: number): string { - return s.length >= len ? s : s + " ".repeat(len - s.length); -} - -function run(): void { - console.log("\n" + "=".repeat(90)); - console.log(" VICTORY DETECTOR -- Claimed vs Actual Verification"); - console.log("=".repeat(90)); - - const verifications = tasks.map(verifyTask); - - for (const v of verifications) { - console.log("\n Task: " + v.taskName); - console.log(" " + "-".repeat(70)); - console.log(` Agent claimed: ${v.claimedComplete ? "COMPLETE" : "NOT COMPLETE"}`); - console.log(` Actually is: ${v.actuallyComplete ? "COMPLETE" : "INCOMPLETE"}`); - - if (!v.actuallyComplete) { - console.log(` MISMATCH: Agent declared victory but ${v.gaps.length} check(s) failed!`); - } - - // Detailed checks for this task - const task = tasks.find((t) => t.name === v.taskName)!; - console.log("\n Check detail:"); - const header = ` | ${pad("Check", 40)}| ${pad("Claimed", 9)}| ${pad("Actual", 9)}| ${pad("Gap?", 6)}|`; - const sep = ` |${"-".repeat(42)}|${"-".repeat(11)}|${"-".repeat(11)}|${"-".repeat(8)}|`; - console.log(header); - console.log(sep); - - for (const check of task.checks) { - const claimedLabel = check.claimed ? "PASS" : "FAIL"; - const actualLabel = check.actual ? "PASS" : "FAIL"; - const gapLabel = check.claimed && !check.actual ? "GAP" : ""; - const marker = gapLabel ? ">>" : " "; - console.log(`${marker}| ${pad(check.description, 40)}| ${pad(claimedLabel, 9)}| ${pad(actualLabel, 9)}| ${pad(gapLabel, 6)}|`); - } - } - - // Overall summary - console.log("\n" + "=".repeat(90)); - console.log(" OVERALL SUMMARY"); - console.log("=".repeat(90) + "\n"); - - const sHeader = `| ${pad("Task", 28)}| ${pad("Claimed", 10)}| ${pad("Actual", 10)}| ${pad("Gaps", 6)}| ${pad("Accuracy", 10)}|`; - const sSep = `|${"-".repeat(30)}|${"-".repeat(12)}|${"-".repeat(12)}|${"-".repeat(8)}|${"-".repeat(12)}|`; - console.log(sHeader); - console.log(sSep); - - for (const v of verifications) { - const accuracy = Math.round((v.actualPassing / v.totalChecks) * 100); - const cLabel = v.claimedComplete ? "Done" : "Pending"; - const aLabel = v.actuallyComplete ? "Done" : "Incomplete"; - console.log(`| ${pad(v.taskName, 28)}| ${pad(cLabel, 10)}| ${pad(aLabel, 10)}| ${pad(String(v.gaps.length), 6)}| ${pad(accuracy + "%", 10)}|`); - } - - const totalGaps = verifications.reduce((s, v) => s + v.gaps.length, 0); - const falseVictories = verifications.filter((v) => v.claimedComplete && !v.actuallyComplete).length; - - console.log("\n False victories: " + falseVictories + " of " + verifications.length + " tasks"); - console.log(" Total undetected gaps: " + totalGaps); - console.log("\n The agent declared 'done' on every task, but verification reveals " + totalGaps + " unmet criteria."); - console.log(" Without explicit verification, premature victory declarations go unchecked.\n"); -} - -run(); diff --git a/courses/learn-harness-engineering/docs/ja/lectures/lecture-09-why-agents-declare-victory-too-early/index.md b/courses/learn-harness-engineering/docs/ja/lectures/lecture-09-why-agents-declare-victory-too-early/index.md deleted file mode 100644 index 8cb28e3c..00000000 --- a/courses/learn-harness-engineering/docs/ja/lectures/lecture-09-why-agents-declare-victory-too-early/index.md +++ /dev/null @@ -1,145 +0,0 @@ -[中文版本 →](../../../zh/lectures/lecture-09-why-agents-declare-victory-too-early/) - -> Code examples for this lecture: [code/](https://github.com/walkinglabs/learn-harness-engineering/blob/main/docs/ja/lectures/lecture-09-why-agents-declare-victory-too-early/code/) -> Hands-on practice: [Project 05. Let the agent verify its own work](./../../projects/project-05-grounded-qa-verification/index.md) - -# 講義 09. エージェントの早すぎる完了宣言を防ぐ - -agentに「パスワードリセット」機能の実装を依頼するとします。agentはデータベーススキーマを変更し、APIエンドポイントを書き、メールテンプレートを追加し、ユニットテストを実行(すべて合格)し、自信満々に「完了した」と報告します。しかし実際に動かしてみると——パスワードリセットリンクが送信できない(メールサービスの設定が不足)、データベースマイグレーションが途中で失敗する(スキーマの不整合)、end-to-endフローは一度も実行されていない。 - -この感覚は馴染み深いはずです——試験用紙を全部埋め、自信満々に最初に提出したのに、成績発表で不合格になるのと同じです。用紙が埋まっていることと、答えが合っていることは別なのです。 - -これは決して稀な出来事ではありません。Guoらによる2017年のICMLの古典的論文が証明しました:**現代のニューラルネットワークは体系的に過信状態にある**——モデルが報告する信頼度は実際の精度よりも大幅に高いのです。AIコーディングagentにも同じことが言えます。agentは「完了した」と「感じ」ますが、実際にはほど遠いのです。harnessは、agentの「感覚」を、外部化された実行ベースの検証に置き換えなければなりません。 - -## なだらかな斜面 - -早すぎる完了宣言は、ほぼ常に同じパターンを辿ります。コードは問題なさそうに見えます——構文は正しく、ロジックは妥当で、静的解析にも明らかなエラーはありません。しかし、harnessは包括的な実行検証を強制していないため、agentは実際の実行をスキップするか、部分的なテストしか実行しません。ユニットテストは実行するがインテグレーションテストはスキップし、テストは実行するがカバレッジは確認しません。最終的に「コードは問題ない」ことが「機能は完成している」の証拠として扱われます。そして試験用紙が提出されます。 - -すべての段階で情報が失われます。タスク仕様からコード実装、そしてランタイム動作に至るまで、すべての変換でバイアスが生じる可能性があり、スキップされた検証ごとに情報の非対称性が悪化します。 - -## 3層の終了チェック - -```mermaid -flowchart LR - Claim["Agent says: done"] --> L1["First run
lint / typecheck"] - L1 --> L2["Then run
tests and startup checks"] - L2 --> L3["Finally run
complete user flow"] - L3 --> Done["Pass all three to be done"] -``` - -```mermaid -flowchart LR - A["Code is written
unit tests are green"] --> B["But app didn't really start
full flow never ran"] - B --> C["Config, DB, external service issues
all remain hidden"] - C --> D["So agent declares victory too early"] -``` - -## 中核概念 - -- **早すぎる完了宣言(Premature Completion Declaration)**: agentはタスクが完了したと主張しますが、未達成の正確性仕様がまだ存在します。核心的な問題は、agentがコードレベルでの局所的な自信に基づいて判断しているのに対し、システムレベルの正確性はグローバルな検証を必要とすることです。 -- **信頼度キャリブレーションバイアス(Confidence Calibration Bias)**: agentの自己報告する完了信頼度と、実際の完了品質との間の体系的な乖離。複雑なマルチファイルタスクでは、このバイアスは有意に正の値になります——agentは常に実際のパフォーマンスよりも自信を持ちます。試験後に自分の点数を常に過大評価する学生のようなものです。 -- **終了基準(Termination Criteria)**: harness内で定義された、明確で実行可能な一連の判断条件。agentは完了を宣言する前にすべての条件を満たさなければなりません。「完了」は主観的判断から客観的判定へと移行します。 -- **検証・妥当性確認の二重ゲート(Verification-Validation Dual Gate)**: 第1の検証層は「コードが指定された動作を正しく実装したか」をチェックし、第2の妥当性確認層は「システムレベルの動作がend-to-endの要件を満たしているか」をチェックします。両方が合格して初めて完了と見なされます。 -- **ランタイムフィードバック信号(Runtime Feedback Signals)**: プログラム実行からのログ、プロセス状態、ヘルスチェック。これがharnessが完了品質を判断するための客観的根拠です。 -- **完了優先度制約(Completion Priority Constraint)**: まず機能の正確性を検証し、次にパフォーマンスを扱い、最後にスタイルに対処します。コア機能が検証されるまで、リファクタリングは禁止されます。 - -## ユニットテスト合格 ≠ タスク完了 - -これは最も一般的な罠であり、最も危険な罠でもあります。agentはコードを書き、ユニットテストを実行し、すべてグリーンになり、「完了」と言います。しかし、ユニットテストの設計思想——テスト対象を分離し、依存関係をモックすること——は、まさにクロスコンポーネントの問題を検出できない原因となっています: - -**インターフェースの不一致**: renderプロセスからpreloadスクリプトに渡されるファイルパスが相対パスなのに、preloadスクリプトは絶対パスを期待している。それぞれのユニットテストはモックを使用して合格しています。この問題が発見されるのはend-to-endテストの時だけです。バンドの各ミュージシャンがそれぞれ完璧に練習していても、一緒に演奏するとキーが違うことに気づくのと同じです。 - -**状態伝播エラー**: データベースマイグレーションがテーブルスキーマを変更しても、ORMキャッシュレイヤーが古いスキーマのキャッシュエントリを保持し続けている。ユニットテストは毎回新しいモック環境を提供するため、このクロスレイヤーの状態不整合を検出できません。 - -**環境依存性**: コードはテスト環境(すべてモックされている)では正しく動作するが、実際の環境では設定の差異、ネットワーク遅延、サービスの利用不可により失敗する。リハーサルルームでは完璧に歌えても、ステージでは音響機器の問題に直面するようなものです。 - -### 「ついでにリファクタリング」は完了判断にとっての毒 - -Claude Codeには一般的な行動パターンがあります。コア機能が検証を通過する前に、コードのリファクタリング、パフォーマンスの最適化、スタイルの改善を始めてしまうことです。Knuthの「早すぎる最適化はすべての悪の根源」という言葉は、agentのシナリオで新たな意味を持ちます——リファクタリングは検証済みコードと未検証コードの境界を変更し、暗黙的に正しかったコードパスを壊す可能性があります。数学の記述問題をまだ終えていないのに、選択問題の解答をきれいに書き直すようなものです——時間の無駄だけでなく、書き間違える可能性もあります。 - -### 自己評価の体系的バイアス - -Anthropicは2026年の研究で、より深い失敗パターンを発見しました:**agentに自身の成果を評価させると、人間の観察者が明らかに品質不十分と判断する場合でも、体系的に過度に肯定的な評価を提供します。** これは学生に自分の試験を採点させるようなもの——自分の答えには必ず甘くなります。 - -この問題は主観的なタスク(デザインの美しさなど)で特に深刻です。「レイアウトが洗練されている」かどうかは判断の問題であり、agentは確実に肯定的に偏ります。検証可能な結果を持つタスクであっても、agentのパフォーマンスは不適切な判断によって妨げられる可能性があります。 - -解決策は、agentを「より客観的に」することではありません——同じモデルが生成と評価を行うと、本質的に自分に甘くなります。**解決策は「作業者」と「検査者」を分離することです。** 学生が自分の試験を採点してはいけないのと同じ——独立した採点者が必要です。 - -独立した評価agent、特に「厳しい」ように調整されたものは、生成agentが自分自身を評価するよりもはるかに効果的です。Anthropicの実験データ: - -| Architecture | Runtime | Cost | Core Features Working? | -|--------------|---------|------|------------------------| -| Single Agent (bare run) | 20 mins | $9 | No (game entities unresponsive to input) | -| Three Agents (planner + generator + evaluator) | 6 hours | $200 | Yes (game is fully playable) | - -これは全く同じモデル(Opus 4.5)で、全く同じプロンプト("build a 2D retro game editor")を使用しています。唯一の違いはharnessです——「ベア実行」から「plannerが要件を展開 → generatorが機能ごとに実装 → evaluatorがPlaywrightを使って実際のクリックテストを実行」へ。 - -> Source: [Anthropic: Harness design for long-running application development](https://www.anthropic.com/engineering/harness-design-long-running-apps) - -## 早すぎる提出を防ぐ方法 - -### 1. 終了判断の外部化 - -完了判断はagent自身が行うべきではありません。harnessはagentの信頼度ではなく、ランタイム信号を入力として使用し、独立して終了検証を実行しなければなりません。これを`CLAUDE.md`に明確に記述します: - -``` -## Definition of Done -- Feature complete = end-to-end verification passed, not "code is written" -- Required verification levels: - 1. Unit tests pass - 2. Integration tests pass - 3. End-to-end flow verification passes -- Do not proceed to level 2 if level 1 fails -- Do not proceed to level 3 if level 2 fails -``` - -### 2. 3層の終了検証を構築する - -- **第1層:構文と静的解析**。最も低コストで、情報は最も少ないが、合格しなければなりません。これは最低限のチェックです——他の何を見る前に、まず言葉のつづりが正しくなければなりません。 -- **第2層:ランタイム動作検証**。テストの実行、アプリの起動チェック、クリティカルパスの検証。これが完了のコアとなる証拠です。書いただけでは不十分で、実行できなければなりません。 -- **第3層:システムレベルの確認**。End-to-endテスト、インテグレーション検証、ユーザーシナリオのシミュレーション。早すぎる宣言に対する最後の防衛線です。実行できるだけでなく、正しく実行できなければなりません。 - -### 3. agent向けの優れた「赤ペンの書き込み」を設計する - -OpenAIはCodexの実践で特に効果的なパターンを導入しました:**agent向けのエラーメッセージには修正手順を含めるべきです。** 怠惰な採点者のように大きな赤いバツ印だけをつけるのではなく、優しい教師のように余白に「ここはこう直すべきだ」と書きましょう。`"Test failed"`ではなく、`"Test failed: POST /api/reset-password returned 500. Check that the email service config exists in environment variables. The template file should be at templates/reset-email.html."`を使用してください。この具体的で実行可能なフィードバックにより、agentは人間の介入なしに自己修正できます。 - -### 4. ランタイム信号をキャプチャする - -効果的なランタイム信号には以下が含まれます: -- アプリケーションは正常に起動し、準備完了状態に到達したか? -- クリティカルな機能パスはランタイムで正常に実行されたか? -- データベース書き込み、ファイル操作、その他の副作用は正しかったか? -- 一時リソースはクリーンアップされたか? - -## 実際のケース - -**タスク**:ユーザーのパスワードリセット機能の実装。データベース操作、メール送信、APIエンドポイントの変更が含まれます。 - -**早すぎる提出パス**:agentはデータベーススキーマを変更し、APIエンドポイントを書き、メールテンプレートを追加し、ユニットテストを実行(合格)し、完了を宣言します。試験用紙は完全に埋まっています。 - -**実際の減点**:(1)End-to-endフローが未テスト——リセットリンクの実際の送信と検証が一度も確認されていない。(2)データベースマイグレーションが部分実行後に失敗し、スキーマの不整合が発生。(3)対象環境にメールサービスの設定が不足。 - -**Harness介入**:終了検証を強制——(1)フルアプリを起動してリセットエンドポイントのアクセシビリティを検証。(2)フルリセットフローを実行。(3)データベース状態の一貫性を検証。すべての欠陥がセッション内で発見され、後続の修正コストの5〜10倍を節約。独立した採点者が本当の問題を見つけました。 - -## 重要なポイント - -- **Agentは体系的に過信状態にある**——信頼度キャリブレーションバイアスは客観的な現実です。試験用紙を埋めたことと正解したことは別です。 -- **完了判断は外部化しなければならない**——harnessが独立して検証する。agentの「感覚」を信じてはいけません。学生は自分の試験を採点できません。 -- **3層すべての検証が不可欠**——構文合格、動作合格、システム合格、段階的に進める。 -- **エラーメッセージは優しい教師の赤ペンの書き込みのように**——具体的な修正手順を含め、agentが自己修正できるようにする。 -- **コア機能が検証されるまでリファクタリング禁止**——完了優先度制約が早すぎる最適化を防ぐ鍵。 - -## 参考資料 - -- [On Calibration of Modern Neural Networks - Guo et al.](https://arxiv.org/abs/1706.04599) — 現代のディープネットワークが体系的に過信状態であることを証明 -- [Building Effective Agents - Anthropic](https://www.anthropic.com/research/building-effective-agents) — 完了判断におけるランタイム証拠の重要な役割 -- [Harness Engineering - OpenAI](https://openai.com/index/harness-engineering/) — 早すぎる完了宣言はagentの主要な失敗モードの一つ -- [The Art of Software Testing - Myers](https://www.goodreads.com/book/show/137543.The_Art_of_Software_Testing) — テスト手法の階層と有効性に関する古典的参考文献 - -## 演習 - -1. **終了検証機能の設計**:データベースマイグレーションとAPI変更を含むタスクについて、完全な終了検証を設計する。必要なランタイム信号と各信号の合格/不合格基準をリストアップする。実際のタスクで実行し、どのような隠れた問題を発見したか記録する。 - -2. **キャリブレーションバイアスの測定**:10種類の異なるタイプのコーディングタスクを選び、agentの自己報告する完了信頼度と実際の完了品質を記録する。バイアス値を計算し、タスクの複雑さとの関係を分析する。 - -3. **多層防御の実験**:同じタスクセットで3つの構成を実行する——(a)静的解析のみ、(b)ユニットテストを追加、(c)完全な3層検証。早すぎる完了宣言の割合と、未検出の欠陥数を比較する。 diff --git a/courses/learn-harness-engineering/docs/ja/lectures/lecture-10-why-end-to-end-testing-changes-results/code/architecture-rules.md b/courses/learn-harness-engineering/docs/ja/lectures/lecture-10-why-end-to-end-testing-changes-results/code/architecture-rules.md deleted file mode 100644 index 862bca3d..00000000 --- a/courses/learn-harness-engineering/docs/ja/lectures/lecture-10-why-end-to-end-testing-changes-results/code/architecture-rules.md +++ /dev/null @@ -1,6 +0,0 @@ -# Electron アーキテクチャルール - -- レンダラーコードはファイルシステムに直接アクセスしてはならない。 -- Preload はレンダラーと Electron main の間の唯一の橋渡しである。 -- 検索・インデックスロジックは UI コンポーネントではなくサービスモジュールに配置する。 -- ログは構造化され、サービス境界から出力されるべきである。 diff --git a/courses/learn-harness-engineering/docs/ja/lectures/lecture-10-why-end-to-end-testing-changes-results/code/e2e-runner.ts b/courses/learn-harness-engineering/docs/ja/lectures/lecture-10-why-end-to-end-testing-changes-results/code/e2e-runner.ts deleted file mode 100644 index 24284165..00000000 --- a/courses/learn-harness-engineering/docs/ja/lectures/lecture-10-why-end-to-end-testing-changes-results/code/e2e-runner.ts +++ /dev/null @@ -1,253 +0,0 @@ -/** - * e2e-runner.ts - * - * A minimal E2E test harness. Defines test cases as user action sequences - * (import doc -> index -> ask question -> verify citation). Simulates - * running them and shows the difference between "unit tests pass" and - * "full pipeline works". - * - * Run: npx tsx docs/lectures/lecture-10-why-end-to-end-testing-changes-results/code/e2e-runner.ts - */ - -// --------------------------------------------------------------------------- -// Types -// --------------------------------------------------------------------------- - -interface PipelineStep { - name: string; - unitTestPasses: boolean; - // Simulated actual behavior in the pipeline - actualBehavior: "works" | "fails" | "partial"; - failureReason?: string; -} - -interface TestCase { - name: string; - steps: PipelineStep[]; -} - -interface TestResult { - testCase: string; - unitTestsPassed: number; - unitTestsTotal: number; - unitTestResult: "PASS" | "FAIL"; - e2eResult: "PASS" | "FAIL"; - e2eFailureStep?: string; - e2eFailureReason?: string; -} - -// --------------------------------------------------------------------------- -// Test cases -- realistic scenarios where unit tests pass but E2E fails -// --------------------------------------------------------------------------- - -const testCases: TestCase[] = [ - { - name: "Import document and ask question", - steps: [ - { - name: "Parse uploaded document", - unitTestPasses: true, - actualBehavior: "works", - }, - { - name: "Store document chunks", - unitTestPasses: true, - actualBehavior: "works", - }, - { - name: "Index chunks for retrieval", - unitTestPasses: true, - actualBehavior: "partial", // Indexes but with wrong embedding dimensions - failureReason: "Embedding dimension mismatch between indexer and retriever", - }, - { - name: "Retrieve relevant chunks", - unitTestPasses: true, // Unit test uses mock data with correct dimensions - actualBehavior: "fails", - failureReason: "Empty results due to dimension mismatch from previous step", - }, - { - name: "Generate answer with citations", - unitTestPasses: true, // Unit test provides pre-retrieved chunks - actualBehavior: "fails", - failureReason: "No chunks retrieved, so answer has no citations", - }, - ], - }, - { - name: "Delete document and verify removal", - steps: [ - { - name: "Find document by ID", - unitTestPasses: true, - actualBehavior: "works", - }, - { - name: "Delete document record", - unitTestPasses: true, - actualBehavior: "works", - }, - { - name: "Remove indexed chunks", - unitTestPasses: true, - actualBehavior: "fails", // Orphaned chunks remain in the index - failureReason: "Index cleanup query timed out, chunks remain orphaned", - }, - { - name: "Verify document not in search results", - unitTestPasses: true, // Unit test mocks the search - actualBehavior: "fails", - failureReason: "Orphaned chunks from previous step still appear in results", - }, - ], - }, - { - name: "Multi-user concurrent access", - steps: [ - { - name: "User A imports document", - unitTestPasses: true, - actualBehavior: "works", - }, - { - name: "User B imports document", - unitTestPasses: true, - actualBehavior: "works", - }, - { - name: "User A queries their document", - unitTestPasses: true, - actualBehavior: "partial", // Cross-contamination of results - failureReason: "No user-scoping on retrieval, returns chunks from User B's doc", - }, - { - name: "Verify only User A's results returned", - unitTestPasses: true, - actualBehavior: "fails", - failureReason: "Results include documents from other users", - }, - ], - }, -]; - -// --------------------------------------------------------------------------- -// Run tests -// --------------------------------------------------------------------------- - -function runUnitTests(tc: TestCase): { passed: number; total: number } { - const passed = tc.steps.filter((s) => s.unitTestPasses).length; - return { passed, total: tc.steps.length }; -} - -function runE2ETest(tc: TestCase): { pass: boolean; failureStep?: string; failureReason?: string } { - // Pipeline: if any step actually fails, the whole E2E fails - for (const step of tc.steps) { - if (step.actualBehavior === "fails") { - return { - pass: false, - failureStep: step.name, - failureReason: step.failureReason ?? "Unknown failure", - }; - } - // "partial" means the step technically completes but creates problems downstream - // We let it continue but track it - } - // Check if any step was "partial" (which may cause downstream issues) - const partialSteps = tc.steps.filter((s) => s.actualBehavior === "partial"); - if (partialSteps.length > 0) { - // The partial steps may or may not cause overall failure - // In our simulation, partial steps always lead to failure downstream - // unless there's an explicit "fails" step that already caught it - // This case means all steps were either "works" or "partial" - return { - pass: false, - failureStep: partialSteps[partialSteps.length - 1].name, - failureReason: partialSteps[partialSteps.length - 1].failureReason ?? "Partial completion", - }; - } - - return { pass: true }; -} - -// --------------------------------------------------------------------------- -// Reporting -// --------------------------------------------------------------------------- - -function pad(s: string, len: number): string { - return s.length >= len ? s : s + " ".repeat(len - s.length); -} - -function run(): void { - console.log("\n" + "=".repeat(95)); - console.log(" E2E TEST RUNNER -- Unit Tests vs Full Pipeline"); - console.log("=".repeat(95)); - - const results: TestResult[] = testCases.map((tc) => { - const unit = runUnitTests(tc); - const e2e = runE2ETest(tc); - - return { - testCase: tc.name, - unitTestsPassed: unit.passed, - unitTestsTotal: unit.total, - unitTestResult: unit.passed === unit.total ? "PASS" : "FAIL", - e2eResult: e2e.pass ? "PASS" : "FAIL", - e2eFailureStep: e2e.failureStep, - e2eFailureReason: e2e.failureReason, - }; - }); - - // Per-test-case detail - for (let i = 0; i < testCases.length; i++) { - const tc = testCases[i]; - const r = results[i]; - - console.log("\n Test Case: " + tc.name); - console.log(" " + "-".repeat(70)); - - const stepHeader = ` | ${pad("Step", 40)}| ${pad("Unit Test", 11)}| ${pad("E2E Actual", 12)}|`; - const stepSep = ` |${"-".repeat(42)}|${"-".repeat(13)}|${"-".repeat(14)}|`; - console.log(stepHeader); - console.log(stepSep); - - for (const step of tc.steps) { - const utLabel = step.unitTestPasses ? "PASS" : "FAIL"; - let e2eLabel: string; - if (step.actualBehavior === "works") e2eLabel = "PASS"; - else if (step.actualBehavior === "partial") e2eLabel = "PARTIAL*"; - else e2eLabel = "FAIL"; - - const marker = step.actualBehavior !== "works" ? ">>" : " "; - console.log(`${marker}| ${pad(step.name, 40)}| ${pad(utLabel, 11)}| ${pad(e2eLabel, 12)}|`); - } - - if (r.e2eFailureStep) { - console.log(`\n E2E Failure at: ${r.e2eFailureStep}`); - console.log(` Reason: ${r.e2eFailureReason}`); - } - } - - // Summary comparison - console.log("\n" + "=".repeat(95)); - console.log(" COMPARISON: Unit Tests vs E2E Tests"); - console.log("=".repeat(95) + "\n"); - - const header = `| ${pad("Test Case", 35)}| ${pad("Unit Tests", 15)}| ${pad("E2E Result", 12)}| Discrepancy`; - const sep = `|${"-".repeat(37)}|${"-".repeat(17)}|${"-".repeat(14)}|${"-".repeat(30)}`; - console.log(header); - console.log(sep); - - for (const r of results) { - const utLabel = `${r.unitTestsPassed}/${r.unitTestsTotal} ${r.unitTestResult}`; - const discrepancy = r.unitTestResult === "PASS" && r.e2eResult === "FAIL"; - const discLabel = discrepancy ? "UNIT PASS BUT E2E FAIL" : "Consistent"; - - console.log(`| ${pad(r.testCase, 35)}| ${pad(utLabel, 15)}| ${pad(r.e2eResult, 12)}| ${discLabel}`); - } - - const falseConfidence = results.filter((r) => r.unitTestResult === "PASS" && r.e2eResult === "FAIL").length; - console.log("\n False confidence count: " + falseConfidence + " of " + results.length); - console.log(" Unit tests pass but E2E reveals integration failures that units cannot catch.\n"); -} - -run(); diff --git a/courses/learn-harness-engineering/docs/ja/lectures/lecture-10-why-end-to-end-testing-changes-results/code/index.md b/courses/learn-harness-engineering/docs/ja/lectures/lecture-10-why-end-to-end-testing-changes-results/code/index.md deleted file mode 100644 index 709f6b07..00000000 --- a/courses/learn-harness-engineering/docs/ja/lectures/lecture-10-why-end-to-end-testing-changes-results/code/index.md +++ /dev/null @@ -1,8 +0,0 @@ -# 講義 10 のコード - -このフォルダーは、以下の例に使用してください: - -- アーキテクチャ制約 -- 構造テスト -- テストの不変条件(taste invariants) -- 修復指向の lint メッセージ diff --git a/courses/learn-harness-engineering/docs/ja/lectures/lecture-10-why-end-to-end-testing-changes-results/code/review-feedback-to-rule.md b/courses/learn-harness-engineering/docs/ja/lectures/lecture-10-why-end-to-end-testing-changes-results/code/review-feedback-to-rule.md deleted file mode 100644 index 0de230b5..00000000 --- a/courses/learn-harness-engineering/docs/ja/lectures/lecture-10-why-end-to-end-testing-changes-results/code/review-feedback-to-rule.md +++ /dev/null @@ -1,10 +0,0 @@ -# 例:レビューフィードバックをルールに変換 - -繰り返しのレビューコメント: - -> レンダラーからファイルシステムユーティリティを呼び出さないでください。Preload ブリッジを使用してください。 - -昇格された harness ルール: - -- レンダラーコードでの `fs` 使用を防ぐ lint または import ルールを追加 -- Preload 境界を説明する修復テキストを追加 diff --git a/courses/learn-harness-engineering/docs/ja/lectures/lecture-10-why-end-to-end-testing-changes-results/index.md b/courses/learn-harness-engineering/docs/ja/lectures/lecture-10-why-end-to-end-testing-changes-results/index.md deleted file mode 100644 index 3c8c7ea0..00000000 --- a/courses/learn-harness-engineering/docs/ja/lectures/lecture-10-why-end-to-end-testing-changes-results/index.md +++ /dev/null @@ -1,158 +0,0 @@ -[中文版本 →](../../../zh/lectures/lecture-10-why-end-to-end-testing-changes-results/) - -> Code examples for this lecture: [code/](https://github.com/walkinglabs/learn-harness-engineering/blob/main/docs/ja/lectures/lecture-10-why-end-to-end-testing-changes-results/code/) -> Hands-on practice: [Project 05. Let the agent verify its own work](./../../projects/project-05-grounded-qa-verification/index.md) - -# 講義 10. End-to-End テストだけが本当の検証である - -agentにElectronアプリへのファイルエクスポート機能の追加を依頼するとします。agentはrenderプロセスコンポーネント、preloadスクリプト、サービスレイヤーロジックを書きます。各コンポーネントのユニットテストは完璧に合格します。agentは「完了した」と言います。実際にエクスポートボタンをクリックしてみると——ファイルパスの形式が間違っていて、プログレスバーが更新されず、大きなファイルのエクスポートでメモリリークが発生します。5つのコンポーネント境界の欠陥があり、ユニットテストは一つも検出していません。 - -合唱のリハーサルのようなものです——各パートが単独で歌うときは完璧に聞こえますが、一緒に歌うと、ソプラノがベースより半拍速く、伴奏が主旋律から半音ずれています。各パートは単独では「正しい」のに、全体が音程を外しています。 - -Googleのテスティングピラミッドが教えてくれること:多数のユニットテストが基盤ですが、そこで止めると、コンポーネント間の相互作用の問題を体系的に見落とすことになります。AIコーディングagentの場合、この問題はさらに深刻です——agentは最速のテストだけを実行して完了を宣言する傾向があります。**End-to-endテストだけが、システムレベルの欠陥が存在しないことを証明できます。** - -## ユニットテストの盲点 - -ユニットテストの設計思想は分離です——依存関係をモックし、テスト対象のユニットのみに焦点を当てます。この思想により、ユニットテストは高速で正確になりますが、同時に体系的な盲点も生み出します。合唱のリハーサルで各パートがヘッドフォンをつけて練習するようなものです——自分たちには問題なく聞こえますが、一緒になったときに初めて問題が浮かび上がります: - -**インターフェースの不一致**: renderプロセスからpreloadスクリプトに渡されるファイルパスが相対パスなのに、preloadスクリプトは絶対パスを期待している。それぞれのユニットテストはモックを使用して合格しています。問題が発見されるのはend-to-endフローを実行したときだけです——2つのパートが独立して練習して問題を感じず、合奏で初めて一方が4/4拍子、もう一方が3/4拍子で歌っていることに気づくようなものです。 - -**状態伝播エラー**: データベースマイグレーションがテーブルスキーマを変更しても、ORMキャッシュレイヤーが古いスキーマのキャッシュエントリを保持し続けている。ユニットテストは毎回完全に新しいモック環境を提供するため、このクロスレイヤーの状態不整合を検出できません。曲の歌詞を変更したのに、誰かがまだ古いバージョンを歌っているようなものです。 - -**リソースライフサイクルの問題**: ファイルハンドル、データベース接続、ネットワークソケットの取得と解放は複数のコンポーネントにまたがります。ユニットテストは各テストで独立したリソースを作成・破棄するため、リソースの競合やリークを検出できません。リハーサルでは各パートが交代でマイクを使っていても、ステージで全員が同時に出るとマイクが足りないようなものです。 - -**環境依存性**: コードはテスト環境(すべてモックされている)では正しく動作するが、実際の環境では設定の差異、ネットワーク遅延、サービスの利用不可により失敗する。リハーサルルームでは完璧に歌えても、野外フェスでは音響フィードバックや風の干渉に直面するようなものです。 - -## End-to-Endテストは結果を変えるだけでなく、行動も変える - -多くの人が気づかないことですが、agentは自身の成果がend-to-endテストの対象になることを知ると、コーディングの行動が変わります。 - -1. **コンポーネント間の相互作用を考慮する**:コードを書く際、「このインターフェースが上流とどう接続されるか」を考えるようになり、単一の関数に集中するだけではなくなります。最終的に一緒に歌うことが分かっていれば、練習中も他のパートに注意を払うようなものです。 -2. **アーキテクチャ境界を尊重する**:アーキテクチャ制約のあるシステムでは、end-to-endテストがagentに境界ルールの遵守を強制します。楽譜に「ここでクレッシェンド」と書いてあれば、従わなければなりません。 -3. **エラーパスを処理する**:End-to-endテストは通常、失敗シナリオを含むため、agentは例外処理を考慮せざるを得なくなります。リハーサルで「マイクが突然死んだらどうするか」をシミュレーションしておくようなものです。 - -## テスティングピラミッドとレビューフィードバックの昇格 - -```mermaid -flowchart TB - subgraph Unit["Unit tests only check isolated parts"] - U1["Renderer tests"] - U2["Preload tests"] - U3["Service tests"] - end - - subgraph E2E["E2E runs through the real system"] - R["Click renderer button"] --> P["Preload bridge"] - P --> S["Service layer"] - S --> F["File System / OS"] - F --> Result["Actual exported file"] - end -``` - -```mermaid -flowchart LR - Review["Review feedback:
renderer cannot import fs directly"] --> Rule["Add a direct fs import check"] - Rule --> Message["Tell agent in error message
to move file access to preload"] - Message --> Harness["Add this check to harness"] - Harness --> Stronger["Will fail immediately next time"] -``` - -Codexのエンジニアリング実践において、OpenAIは次のように強調しています:**agent向けに書かれたエラーメッセージには修正手順を含めなければならない。** `"Direct filesystem access in renderer"`とだけ書くのではなく、`"Direct filesystem access in renderer. All file operations must go through the preload bridge. Move this call to preload/file-ops.ts and invoke it via window.api."`と書きます。これにより、アーキテクチャルールが自動修正ループに変わります。合唱の指揮者が「そこ間違ってた」と言うだけでなく、「ここは半拍速かった、アルトのリズムを聞いて、32小節から入って」と具体的に指示するようなものです。 - -## 中核概念 - -- **コンポーネント境界の欠陥(Component Boundary Defects)**: コンポーネントAとBがそれぞれユニットテストに合格しても、それらの相互作用が不正な動作を生み出す。これはend-to-endテストが最も得意とする種類の問題です——各パートは単独では正しいのに、一緒になると音程が外れるようなものです。 -- **テスト適切性グラデーション(Testing Adequacy Gradient)**: ユニットテストで検出される欠陥 <= インテグレーションテストで検出される欠陥 <= end-to-endテストで検出される欠陥。上の層に行くほど検出能力が上がります。 -- **アーキテクチャ境界強制ルール(Architectural Boundary Enforcement Rules)**: アーキテクチャ文書のルール(「renderプロセスはファイルシステムに直接アクセスできない」など)を実行可能で自動化されたチェックに変えること。「紙に書かれたもの」から「CIで動くもの」へ。 -- **レビューフィードバックの昇格(Review Feedback Promotion)**: 繰り返し出現するコードレビューのコメントを自動テストに変換すること。繰り返し発生する問題を見つけるたびにルールを追加し、harnessが自動的に強力になります。指揮者がよくあるリハーサルの間違いをウォームアップ練習に変えるようなものです——次に同じ間違いが起きても、練習自体がそれを浮き彫りにし、指揮者が一言も言う必要がありません。 -- **agent向けエラーメッセージ(Agent-Oriented Error Messages)**: 失敗メッセージは「何が問題だったか」を述べるだけでなく、agentに「どう直せばいいか」を正確に伝えるべきです。これにより、テストの失敗が自己修正のフィードバックループになります。 - -## 実践方法 - -### 0. アーキテクチャ境界を先に定義してからE2Eテストを書く - -End-to-endテストの前提条件は明確なシステム境界です。アーキテクチャがスパゲッティの皿のようなものなら、end-to-endテストが証明できるのは「このスパゲッティは動く」ということだけで、どこで設計意図が侵害されたかは教えてくれません。声部に分かれてすらいない合唱団のようなもの——いくらリハーサルしても上手くなりません。 - -OpenAIの経験:**agentが生成したコードベースでは、アーキテクチャ制約は初日の早い段階で前提条件として確立すべきであり、チームが大きくなってから考えるものではない。** 理由は単純です——agentはリポジトリ内の既存のパターンを、たとえそれが不均一で最適でなくてもコピーします。アーキテクチャ制約がなければ、agentはすべてのセッションでより多くの逸脱を持ち込みます。 - -OpenAIは「階層型ドメインアーキテクチャ」を採用しました——各ビジネスドメインは固定層に分割されます:Types → Config → Repo → Service → Runtime → UI。依存関係は厳密に前方に流れ、クロスドメインの関心事は明示的なProvidersインターフェースを通じて入ります。その他の依存関係は禁止され、カスタムlintによって機械的に強制されます。 - -重要な原則:**不変条件を強制し、実装を細かく管理しない。** 例えば、「データは境界で解析される」ことを要求しますが、どのライブラリを使うかは指定しません。エラーメッセージには修正手順を含めなければなりません——「違反」と言うだけでなく、agentに正確な変更方法を伝えます。 - -> Source: [OpenAI: Harness engineering: leveraging Codex in an agent-first world](https://openai.com/index/harness-engineering/) - -### 1. harnessにEnd-to-End層を含める - -検証フローで明示的に定義します:クロスコンポーネントの変更を含むタスクでは、end-to-endテストに合格することが完了の前提条件です: - -``` -## Validation Hierarchy -- Level 1: Unit tests (Must pass) -- Level 2: Integration tests (Must pass) -- Level 3: End-to-end tests (Must pass when cross-component changes are involved) -- Skipping any required level = Not Complete -``` - -### 2. アーキテクチャルールを実行可能なチェックに変える - -すべてのアーキテクチャ制約には、対応するテストまたはlintルールを設けます: - -```bash -# Check if the render process directly calls Node.js APIs -grep -r "require('fs')" src/renderer/ && exit 1 || echo "OK: no direct fs access in renderer" -``` - -### 3. agent向けのエラーメッセージを設計する - -失敗メッセージには3つの要素を含めます:何が問題だったか、なぜか、どう直すか: - -``` -ERROR: Found direct import of 'fs' in src/renderer/App.tsx:12 -WHY: Renderer process has no access to Node.js APIs for security -FIX: Move file operations to src/preload/file-ops.ts and call via window.api.readFile() -``` - -### 4. レビューフィードバック昇格プロセスを確立する - -コードレビュー中に新しいタイプのagentエラーが見つかるたびに、それを自動チェックに変換します。1ヶ月後には、harnessは月初めよりも大幅に強力になっています。合唱のリハーサルノートのようなもの——各リハーサルで見つかった問題を記録し、次回の前にチェックします。時間が経つにつれてよくあるエラーは減り、音楽はより調和の取れたものになります。 - -## 実際のケース - -**タスク**:Electronアプリにファイルエクスポート機能を実装する。renderプロセスUI、preloadスクリプトのファイルシステムプロキシ、サービスレイヤーのデータ変換が含まれます。 - -**各パートの単独歌唱(ユニットテスト合格)**:renderコンポーネントテスト(合格、ファイル操作はモック)、preloadスクリプトテスト(合格、ファイルシステムはモック)、サービスレイヤーテスト(合格、データソースはモック)。agentは完了を宣言。 - -**合同歌唱(End-to-Endテストで明らかになった欠陥)**: - -| Defect | Description | Unit Test | E2E | -|--------|-------------|-----------|-----| -| Interface Mismatch | Inconsistent file path format | Missed | Caught | -| State Propagation | Export progress not sent back to UI via IPC | Missed | Caught | -| Resource Leak | Large file export handles not released | Missed | Caught | -| Permission Issue | Different permissions in packaged environment | Missed | Caught | -| Error Propagation | Service layer exceptions didn't reach UI layer | Missed | Caught | - -5つの欠陥すべてがend-to-endテストで検出され、ユニットテストは一つも検出しませんでした。コストはテスト時間が2秒から15秒への増加——agentのワークフローでは完全に許容範囲です。各パートがどれほど上手に単独で歌えても、合同リハーサルには勝てません。 - -## 重要なポイント - -- **ユニットテストはコンポーネント境界の欠陥に対して体系的に盲点がある**——その分離設計こそが、相互作用の問題の検出を妨げている。全員が正しく歌えても、合唱の音程が合っているとは限らない。 -- **End-to-endテストは欠陥を検出するだけでなく、agentのコーディング行動も変える**——統合と境界により注目するようにさせる。 -- **アーキテクチャルールは実行可能でなければならない**——読まれるのを待つ文書に書くのではなく、すべてのコミットで自動的にチェックする。 -- **エラーメッセージはagent向けに設計しなければならない**——「どう直すか」の具体的な手順を含め、自己修正ループを形成する。 -- **レビューフィードバックの昇格がharnessを自動的に強力にする**——検出された欠陥の各カテゴリが永久の防衛線になる。 - -## 参考資料 - -- [How Google Tests Software - Whittaker et al.](https://www.goodreads.com/book/show/13563030-how-google-tests-software) — テスティングピラミッドモデルの古典的出典 -- [Harness Engineering - OpenAI](https://openai.com/index/harness-engineering/) — アーキテクチャ制約の自動実行のためのエンジニアリング実践 -- [Chaos Engineering - Netflix (Basiri et al.)](https://ieeexplore.ieee.org/document/7466237) — 能動的に障害を注入してシステムの回復力を検証 -- [QuickCheck - Claessen & Hughes](https://www.cs.tufts.edu/~nr/cs257/archive/john-hughes/quick.pdf) — プロパティテスト手法、例示テストと形式検証の中間に位置する - -## 演習 - -1. **クロスコンポーネント欠陥検出**:少なくとも3つのコンポーネントを含む変更タスクを選ぶ。まずユニットテストのみを実行して結果を記録し、次にend-to-endテストを実行する。追加で発見された各欠陥がどのタイプのクロスレイヤー相互作用の問題に属するか分析する。 - -2. **アーキテクチャルールの自動化**:プロジェクトからアーキテクチャ制約を一つ選び、実行可能なチェック(agent向けエラーメッセージ付き)に変換する。harnessに組み込み、ベースラインタスクで有効性を検証する。 - -3. **レビューフィードバックの昇格**:コードレビューの履歴から繰り返し出現するコメントのタイプを見つけ、5ステップのプロセスを使って自動チェックに変換する。昇格前後の問題の発生頻度を比較する。 diff --git a/courses/learn-harness-engineering/docs/ja/lectures/lecture-11-why-observability-belongs-inside-the-harness/code/evaluator-rubric.md b/courses/learn-harness-engineering/docs/ja/lectures/lecture-11-why-observability-belongs-inside-the-harness/code/evaluator-rubric.md deleted file mode 100644 index 1fe83cb4..00000000 --- a/courses/learn-harness-engineering/docs/ja/lectures/lecture-11-why-observability-belongs-inside-the-harness/code/evaluator-rubric.md +++ /dev/null @@ -1,8 +0,0 @@ -# 評価ルーブリックの例 - -各ディメンションに 1-5 のスコアを使用: - -- 根拠:回答はインポートされたソースに明確に結びついているか? -- 引用品質:ソース参照は可視的で具体的か? -- 機能性:ユーザーは質問-回答フローを完了できるか? -- 製品一貫性:ワークフローは統合されていると感じられるか? diff --git a/courses/learn-harness-engineering/docs/ja/lectures/lecture-11-why-observability-belongs-inside-the-harness/code/index.md b/courses/learn-harness-engineering/docs/ja/lectures/lecture-11-why-observability-belongs-inside-the-harness/code/index.md deleted file mode 100644 index d4a83366..00000000 --- a/courses/learn-harness-engineering/docs/ja/lectures/lecture-11-why-observability-belongs-inside-the-harness/code/index.md +++ /dev/null @@ -1,8 +0,0 @@ -# 講義 11 のコード - -このフォルダーは、以下の例に使用してください: - -- プランナーの出力 -- 評価ルーブリック -- ジェネレーター/エバリュエーターループ -- 単一エージェント vs マルチロールの比較 diff --git a/courses/learn-harness-engineering/docs/ja/lectures/lecture-11-why-observability-belongs-inside-the-harness/code/runtime-logger.ts b/courses/learn-harness-engineering/docs/ja/lectures/lecture-11-why-observability-belongs-inside-the-harness/code/runtime-logger.ts deleted file mode 100644 index 213dc198..00000000 --- a/courses/learn-harness-engineering/docs/ja/lectures/lecture-11-why-observability-belongs-inside-the-harness/code/runtime-logger.ts +++ /dev/null @@ -1,260 +0,0 @@ -/** - * runtime-logger.ts - * - * A structured logging module demo. Shows ad-hoc console.log output vs - * structured JSON log output when diagnosing a failure. Includes a seeded - * failure scenario and demonstrates how structured logs pinpoint the issue - * faster. - * - * Run: npx tsx docs/lectures/lecture-11-why-observability-belongs-inside-the-harness/code/runtime-logger.ts - */ - -// --------------------------------------------------------------------------- -// Types -// --------------------------------------------------------------------------- - -interface StructuredLogEntry { - timestamp: string; - level: "info" | "warn" | "error" | "debug"; - component: string; - action: string; - durationMs?: number; - input?: unknown; - output?: unknown; - error?: string; - correlationId: string; -} - -// --------------------------------------------------------------------------- -// Simulated pipeline with a seeded failure -// --------------------------------------------------------------------------- - -const CORRELATION_ID = "req-" + Math.random().toString(36).slice(2, 8); - -// Simulated stages of a document Q&A pipeline -interface PipelineStage { - component: string; - action: string; - durationMs: number; - success: boolean; - errorMessage?: string; - input?: unknown; - output?: unknown; -} - -function runPipeline(): PipelineStage[] { - return [ - { - component: "DocumentLoader", - action: "parse_upload", - durationMs: 45, - success: true, - input: { filename: "report.pdf", size: "2.3MB" }, - output: { chunks: 47 }, - }, - { - component: "ChunkIndexer", - action: "embed_and_store", - durationMs: 230, - success: true, - input: { chunks: 47 }, - output: { indexed: 47, embeddingDim: 1536 }, - }, - { - component: "QueryRouter", - action: "route_query", - durationMs: 12, - success: true, - input: { query: "What is the revenue target?" }, - output: { routedTo: "RetrievalEngine" }, - }, - { - // SEEDED FAILURE: Retrieval returns 0 results due to dimension mismatch - component: "RetrievalEngine", - action: "semantic_search", - durationMs: 180, - success: false, - errorMessage: "Vector dimension mismatch: query embedding dim=768, index embedding dim=1536", - input: { query: "What is the revenue target?", topK: 5 }, - output: { results: 0 }, - }, - { - component: "AnswerGenerator", - action: "generate_with_citations", - durationMs: 1500, - success: true, // Doesn't crash, but produces a bad answer - input: { context: [], query: "What is the revenue target?" }, - output: { answer: "I could not find relevant information.", citations: 0 }, - }, - ]; -} - -// --------------------------------------------------------------------------- -// Ad-hoc logging (console.log style) -// --------------------------------------------------------------------------- - -function printAdHocLog(stages: PipelineStage[]): void { - console.log("Starting document Q&A pipeline..."); - console.log("User uploaded report.pdf"); - - for (const stage of stages) { - if (stage.success) { - console.log(`${stage.component}: ${stage.action} done (${stage.durationMs}ms)`); - } else { - console.log(`${stage.component}: something went wrong`); - } - } - - console.log("Pipeline finished. Answer: I could not find relevant information."); -} - -// --------------------------------------------------------------------------- -// Structured logging (JSON) -// --------------------------------------------------------------------------- - -function printStructuredLog(stages: PipelineStage[]): StructuredLogEntry[] { - const entries: StructuredLogEntry[] = []; - - for (const stage of stages) { - entries.push({ - timestamp: new Date().toISOString(), - level: stage.success ? "info" : "error", - component: stage.component, - action: stage.action, - durationMs: stage.durationMs, - input: stage.input, - output: stage.output, - error: stage.errorMessage, - correlationId: CORRELATION_ID, - }); - } - - return entries; -} - -// --------------------------------------------------------------------------- -// Diagnose failure from structured logs -// --------------------------------------------------------------------------- - -function diagnoseFromStructured(entries: StructuredLogEntry[]): string[] { - const diagnosis: string[] = []; - - // Find errors - const errors = entries.filter((e) => e.level === "error"); - if (errors.length > 0) { - diagnosis.push("ERRORS FOUND:"); - for (const err of errors) { - diagnosis.push(` - ${err.component}.${err.action}: ${err.error}`); - } - } - - // Check for latency spikes - const slowStages = entries.filter((e) => (e.durationMs ?? 0) > 1000); - if (slowStages.length > 0) { - diagnosis.push("LATENCY SPIKES:"); - for (const s of slowStages) { - diagnosis.push(` - ${s.component}.${s.action}: ${s.durationMs}ms`); - } - } - - // Check for cascading failures - const emptyOutputs = entries.filter((e) => { - const out = e.output as Record | undefined; - return out && typeof out === "object" && "results" in out && (out.results as number) === 0; - }); - if (emptyOutputs.length > 0) { - diagnosis.push("EMPTY OUTPUTS (possible cascade):"); - for (const e of emptyOutputs) { - diagnosis.push(` - ${e.component}.${e.action}: returned 0 results`); - } - } - - return diagnosis; -} - -// --------------------------------------------------------------------------- -// Reporting -// --------------------------------------------------------------------------- - -function pad(s: string, len: number): string { - return s.length >= len ? s : s + " ".repeat(len - s.length); -} - -function run(): void { - const pipeline = runPipeline(); - - console.log("\n" + "=".repeat(90)); - console.log(" OBSERVABILITY DEMO -- Ad-hoc vs Structured Logging"); - console.log("=".repeat(90)); - - // --- Ad-hoc output --- - console.log("\n" + "-".repeat(90)); - console.log(" SCENARIO A: Ad-hoc console.log output"); - console.log("-".repeat(90) + "\n"); - printAdHocLog(pipeline); - console.log("\n Diagnosis from ad-hoc logs: ??? Hard to tell what went wrong."); - console.log(" The failure message is vague: 'something went wrong'."); - console.log(" No dimensions, no input/output data, no correlation ID."); - - // --- Structured output --- - console.log("\n" + "-".repeat(90)); - console.log(" SCENARIO B: Structured JSON log output"); - console.log("-".repeat(90) + "\n"); - - const structuredEntries = printStructuredLog(pipeline); - - const header = `| ${pad("Timestamp", 26)}| ${pad("Level", 6)}| ${pad("Component", 20)}| ${pad("Action", 25)}| ${pad("Duration", 9)}| Error?`; - const sep = `|${"-".repeat(28)}|${"-".repeat(8)}|${"-".repeat(22)}|${"-".repeat(27)}|${"-".repeat(11)}|${"-".repeat(30)}`; - console.log(header); - console.log(sep); - - for (const entry of structuredEntries) { - const hasError = entry.error ? entry.error.slice(0, 30) : ""; - const marker = entry.level === "error" ? ">>" : " "; - console.log( - `${marker}| ${pad(entry.timestamp, 26)}| ${pad(entry.level, 6)}| ${pad(entry.component, 20)}| ${pad(entry.action, 25)}| ${pad(String(entry.durationMs) + "ms", 9)}| ${hasError}` - ); - } - - // --- Diagnosis --- - console.log("\n" + "-".repeat(90)); - console.log(" AUTOMATED DIAGNOSIS FROM STRUCTURED LOGS"); - console.log("-".repeat(90) + "\n"); - - const errors = structuredEntries.filter((e) => e.level === "error"); - for (const err of errors) { - console.log(` ROOT CAUSE: ${err.component}.${err.action}`); - console.log(` Error: ${err.error}`); - console.log(` Input: ${JSON.stringify(err.input)}`); - console.log(` Output: ${JSON.stringify(err.output)}`); - console.log(` Correlation ID: ${err.correlationId}`); - } - - // Downstream impact - console.log("\n DOWNSTREAM IMPACT:"); - const answerGen = structuredEntries.find((e) => e.component === "AnswerGenerator"); - if (answerGen) { - const out = answerGen.output as Record; - console.log(` AnswerGenerator received empty context (${JSON.stringify(answerGen.input)})`); - console.log(` Produced answer: "${out.answer}" with ${out.citations} citations`); - } - - // Comparison summary - console.log("\n" + "=".repeat(90)); - console.log(" COMPARISON"); - console.log("=".repeat(90) + "\n"); - - const cHeader = `| ${pad("Metric", 35)}| ${pad("Ad-hoc Logs", 18)}| ${pad("Structured Logs", 18)}|`; - const cSep = `|${"-".repeat(37)}|${"-".repeat(20)}|${"-".repeat(20)}|`; - console.log(cHeader); - console.log(cSep); - console.log(`| ${pad("Root cause identifiable", 35)}| ${pad("No", 18)}| ${pad("Yes", 18)}|`); - console.log(`| ${pad("Input/output traceable", 35)}| ${pad("No", 18)}| ${pad("Yes", 18)}|`); - console.log(`| ${pad("Correlation across steps", 35)}| ${pad("No", 18)}| ${pad("Yes", 18)}|`); - console.log(`| ${pad("Machine-parseable", 35)}| ${pad("No", 18)}| ${pad("Yes", 18)}|`); - console.log(`| ${pad("Time to diagnose", 35)}| ${pad("Minutes (manual)", 18)}| ${pad("Seconds (auto)", 18)}|`); - - console.log("\n Structured logging transforms debugging from guesswork into a deterministic lookup.\n"); -} - -run(); diff --git a/courses/learn-harness-engineering/docs/ja/lectures/lecture-11-why-observability-belongs-inside-the-harness/code/sprint-contract.md b/courses/learn-harness-engineering/docs/ja/lectures/lecture-11-why-observability-belongs-inside-the-harness/code/sprint-contract.md deleted file mode 100644 index eaccc4ed..00000000 --- a/courses/learn-harness-engineering/docs/ja/lectures/lecture-11-why-observability-belongs-inside-the-harness/code/sprint-contract.md +++ /dev/null @@ -1,12 +0,0 @@ -# スプリント契約の例 - -スプリント目標: - -- 根拠のある Q&A 結果に可視的な引用を追加 - -「完了」の定義: - -- ユーザーが質問する -- アプリが回答を返す -- 少なくとも1つの引用が表示される -- 引用をクリックするとドキュメントビューでソースの場所が開く diff --git a/courses/learn-harness-engineering/docs/ja/lectures/lecture-11-why-observability-belongs-inside-the-harness/index.md b/courses/learn-harness-engineering/docs/ja/lectures/lecture-11-why-observability-belongs-inside-the-harness/index.md deleted file mode 100644 index c3031bee..00000000 --- a/courses/learn-harness-engineering/docs/ja/lectures/lecture-11-why-observability-belongs-inside-the-harness/index.md +++ /dev/null @@ -1,154 +0,0 @@ -[中文版本 →](../../../zh/lectures/lecture-11-why-observability-belongs-inside-the-harness/) - -> コード例: [code/](https://github.com/walkinglabs/learn-harness-engineering/blob/main/docs/ja/lectures/lecture-11-why-observability-belongs-inside-the-harness/code/) -> 実践プロジェクト: [プロジェクト 06. 完全な harness(総合課題)](./../../projects/project-06-runtime-observability-and-debugging/index.md) - -# 講義 11. エージェントのランタイムを観測可能にする - -## この講義が解決する問題 - -エージェントに機能実装を頼むと、20 分走って大量のファイルを変更し、「完了しました。ただしテストが 2 つ失敗しています」と言ってきます。なぜ失敗しているのか聞くと「よくわかりません。タイミング問題かもしれません」。どの重要経路を変えたのか聞くと「コードを見てみます……」。 - -これはエージェントの能力不足ではありません。harness が十分な観測性を提供していないことが問題です。**観測性がなければ、エージェントは不確実なまま意思決定し、評価は主観的判断になり、再試行は手探りになります。** OpenAI も Anthropic も、信頼性を証拠の問題として扱っています。harness は、次の判断を導ける形でランタイム挙動と評価信号を露出しなければなりません。 - -## 中核概念 - -- **ランタイム観測性**: ログ、トレース、プロセスイベント、ヘルスチェックなどのシステムレベル信号。「システムが何をしたか」に答える。 -- **プロセス観測性**: 計画、採点ルーブリック、受け入れ基準など、harness の判断成果物への可視性。「なぜこの変更を受け入れるべきか」に答える。 -- **タスクトレース**: タスク開始から完了までの判断経路の完全な記録。分散システムのリクエストトレースに近い。エージェントの各ステップと文脈を記録する。 -- **スプリント契約**: コーディング開始前に合意する短期契約。タスク範囲、検証基準、除外項目を明記する。プロセス観測性の中核ツール。 -- **評価ルーブリック**: 品質評価を主観判断から証拠ベースの構造化スコアに変える。同じ出力に対して評価者が近い結果を出せるようにする。 -- **階層化された観測性**: システム層とプロセス層の観測性を同時に設計し、相互に補強する。ランタイム信号は挙動を説明し、プロセス成果物は意図を説明する。 - -## 階層化された観測性 - -```mermaid -flowchart LR - Contract["最初にタスクを書く
何を変えるか / 変えないか / 合格基準"] --> Generator["Generator"] - Generator --> Signals["実行中にアプリログ、トレース、
ヘルスチェックを収集"] - Contract --> Review["結果を項目ごとに確認
挙動 / テスト / 境界"] - Signals --> Review - Review --> Verdict["失敗したチェックと
修正場所を示す"] - Verdict --> Generator -``` - -## なぜこれが起きるのか - -### 観測性不足の本当のコスト - -harness に観測性がないと、4 種類の問題が体系的に発生します。 - -**「正しい」と「正しそう」を区別できない**: コードレビュー上は完璧に見える関数でも、ランタイムでは特定入力のエッジケースで誤った結果を返すことがあります。実際の実行経路が期待から外れたことは、ランタイムトレースでしか見えません。 - -**評価が神秘主義になる**: 採点ルーブリックと受け入れ基準がないと、評価者(人間でもエージェントでも)は暗黙の前提に頼ります。同じ出力でも評価者によって判断が大きく変わり、品質評価が再現不能になります。 - -**再試行が盲目的な推測になる**: 失敗理由がわからないと、再試行の方向はランダムになります。本当の原因を無視して関係ないコード経路を直し続けるかもしれません。盲目的な再試行はすべてトークンと時間を消費します。 - -**セッション引き継ぎの情報断崖**: 未完了の作業を次セッションに渡すとき、観測性がないと新しいセッションはシステム状態をゼロから診断し直す必要があります。Anthropic の長時間エージェント観察では、この重複診断がセッション時間の 30-50% を消費することがあります。 - -### Claude Code での現実的なシナリオ - -「アプリにダークモードを追加する」タスクを、planner-generator-evaluator の 3 役割 workflow で実行する harness を想像してください。 - -**観測性なし**: planner は曖昧な説明を出します。generator はその曖昧さをもとにダークモードを実装しますが、planner の暗黙期待と合いません。evaluator は自分の暗黙基準で却下しますが、具体的に何が悪いか説明できません。generator は曖昧な却下理由をもとに手探りで再試行します。これが 3-4 回繰り返され、45 分ほどかかり、かろうじて受け入れ可能な出力になります。 - -**完全な観測性あり**: planner はスプリント契約を出します。どのコンポーネントを変更するか、各コンポーネントの検証基準、除外項目(印刷スタイルは扱わない等)を列挙します。generator は契約に従って実装します。ランタイム観測性は各コンポーネントのスタイル読み込みと適用過程を記録します。evaluator は採点ルーブリックで次元ごとに評価し、証拠を引用します。1 回の反復で高品質な結果が出て、約 15 分で終わります。 - -効率差は 3 倍です。変わったのは観測性だけです。 - -### エージェントだけでは解決できない理由 - -「エージェント自身にログを出させればよいのでは」と思うかもしれません。問題は次のとおりです。 - -1. エージェントは自分が何を知らないかを知らないため、必要な信号を自発的に記録しません。 -2. ログ形式が一貫しません。セッションごとに形式が違うと、体系的な分析ができません。 -3. プロセス観測性はログだけでは解決できません。スプリント契約や採点ルーブリックは、harness レベルの支援を必要とする構造化成果物です。 - -## 正しく行う方法 - -### 1. ランタイム信号収集を harness に組み込む - -エージェントが自分でログを出すことに頼らないでください。harness が次の信号を自動収集すべきです。 - -- **アプリケーションライフサイクル**: 起動、ready、running、shutdown の各状態 -- **機能経路の実行**: 重要経路の入口、チェックポイント、出口の記録 -- **データフロー**: コンポーネント間を流れるデータの記録 -- **リソース利用**: メモリが増え続けるなど異常な利用パターン -- **エラーと例外**: エラーメッセージだけでなく完全な文脈 - -### 2. スプリント契約を実装する - -各タスクの開始前に、generator と evaluator(同じエージェントの別呼び出しでもよい)が契約を合意します。 - -```markdown -# Sprint Contract: Dark Mode Support - -## Scope -- Modify the theme toggle component -- Update global CSS variables -- Add dark mode tests - -## Verification Standards -- Visual regression tests pass for each component -- Main flow end-to-end tests pass -- No flash of unstyled content (FOUC) - -## Exclusions -- Not handling print styles -- Not handling third-party component dark mode -``` - -### 3. 評価ルーブリックを整備する - -「良いか悪いか」を定量的な採点に変えます。 - -```markdown -# Scoring Rubric - -| Dimension | A | B | C | D | -|-----------|---|---|---|---| -| Code correctness | All tests pass | Main flow passes | Partial pass | Build fails | -| Architecture compliance | Fully compliant | Minor deviations | Obvious deviations | Serious violations | -| Test coverage | Main + edge cases | Main flow only | Only skeleton | No tests | -``` - -### 4. OpenTelemetry で標準化する - -各 harness セッションに trace、各タスクに span、各検証ステップに sub-span を作ります。標準属性で重要情報を注釈します。これにより、観測性データを Jaeger や Zipkin などの標準ツールチェーンに統合できます。 - -## 実例 - -planner-generator-evaluator workflow の harness で「ダークモード対応を追加する」場合です。 - -**観測不能な版**: 盲目的な再試行が 3-4 回、45 分、かろうじて許容できる出力。evaluator は「何か違う」と言うだけで具体化できず、generator は大きな時間を誤った方向に使います。 - -**完全に観測可能な版**: -- スプリント契約が範囲、基準、除外項目を明確化 -- ランタイムトレースが各コンポーネントのスタイル読み込み過程を記録 -- 採点ルーブリックが次元ごとの構造化評価を提供 -- 1 回の反復で高品質な結果、15 分 - -効率は 3 倍改善し、品質はより安定し、評価は再現可能になります。 - -## 重要なポイント - -- **観測性は harness のアーキテクチャ属性**です。後から足す機能ではなく、設計時に考慮すべき中核能力です。 -- **2 つの観測性レイヤーはどちらも必須**です。ランタイム信号は「何が起きたか」を説明し、プロセス成果物は「なぜそうしたか」を説明します。 -- **スプリント契約は事前に認識を揃える**ため、「generator が作ったものを evaluator が予見可能な理由で即却下する」事態を防ぎます。 -- **採点ルーブリックは評価を再現可能にする**ため、異なる評価者でも同じ出力に近いスコアを出せます。 -- **観測性不足はセッション時間の 30-50% を重複診断に浪費します。** - -## 参考資料 - -- [Observability Engineering - Charity Majors](https://www.honeycomb.io/blog/observability-engineering-book) — 現代的な observability engineering の理論と実践 -- [Dapper - Google (Sigelman et al.)](https://research.google/pubs/pub36356/) — 大規模分散トレーシングの先駆的実践 -- [Harness Design - Anthropic](https://www.anthropic.com/engineering/harness-design-long-running-apps) — スプリント契約と evaluator rubric の導入 -- [Site Reliability Engineering - Google](https://sre.google/sre-book/table-of-contents/) — 本番システムにおける観測性の体系的応用 - -## 演習 - -1. **観測性ギャップ分析**: 現在の harness をシステム層とプロセス層の観測性で監査します。既存信号では区別できないシステム状態を見つけ、追加案を出してください。 - -2. **スプリント契約練習**: 実タスクのスプリント契約を書きます。エージェントに契約に従って実行させ、契約あり/なしで効率と品質を比較してください。 - -3. **タスクトレース構築**: 完全なコーディングタスク中のエージェント操作をすべて記録します。OpenTelemetry の semantic conventions で注釈し、どのステップで判断に必要な信号が不足しているかを分析してください。 diff --git a/courses/learn-harness-engineering/docs/ja/lectures/lecture-12-why-every-session-must-leave-a-clean-state/code/benchmark-comparison-template.md b/courses/learn-harness-engineering/docs/ja/lectures/lecture-12-why-every-session-must-leave-a-clean-state/code/benchmark-comparison-template.md deleted file mode 100644 index d625a5c1..00000000 --- a/courses/learn-harness-engineering/docs/ja/lectures/lecture-12-why-every-session-must-leave-a-clean-state/code/benchmark-comparison-template.md +++ /dev/null @@ -1,18 +0,0 @@ -# ベンチマーク比較テンプレート - -Harness A: - -- 完了率 -- 平均リトライ回数 -- 人間のレビュー前に検出されたバグ - -Harness B: - -- 完了率 -- 平均リトライ回数 -- 人間のレビュー前に検出されたバグ - -解釈: - -- どの harness が結果を変えたか? -- どの harness が結果を得るためのコストを変えたか? diff --git a/courses/learn-harness-engineering/docs/ja/lectures/lecture-12-why-every-session-must-leave-a-clean-state/code/benchmark-runner.ts b/courses/learn-harness-engineering/docs/ja/lectures/lecture-12-why-every-session-must-leave-a-clean-state/code/benchmark-runner.ts deleted file mode 100644 index 0388cad7..00000000 --- a/courses/learn-harness-engineering/docs/ja/lectures/lecture-12-why-every-session-must-leave-a-clean-state/code/benchmark-runner.ts +++ /dev/null @@ -1,216 +0,0 @@ -/** - * benchmark-runner.ts - * - * Reads a benchmark task definition (JSON array of tasks with pass criteria), - * "executes" each task, records timing and pass/fail, outputs a comparison - * report showing which tasks pass and which fail. - * - * Run: npx tsx docs/lectures/lecture-12-why-every-session-must-leave-a-clean-state/code/benchmark-runner.ts - */ - -// --------------------------------------------------------------------------- -// Types -// --------------------------------------------------------------------------- - -interface BenchmarkTask { - id: string; - name: string; - category: string; - passCriteria: string[]; - expectedDurationMs: number; - // Simulated actual results - actualDurationMs: number; - actualPass: boolean; - failureReason?: string; -} - -interface BenchmarkResult { - id: string; - name: string; - category: string; - criteriaTotal: number; - criteriaPassed: number; - pass: boolean; - expectedMs: number; - actualMs: number; - durationDelta: number; - failureReason?: string; -} - -// --------------------------------------------------------------------------- -// Benchmark task definitions -// --------------------------------------------------------------------------- - -const benchmarkTasks: BenchmarkTask[] = [ - { - id: "bench-001", - name: "Import markdown document", - category: "Document Pipeline", - passCriteria: ["File accepted", "Chunks created", "Metadata stored"], - expectedDurationMs: 100, - actualDurationMs: 95, - actualPass: true, - }, - { - id: "bench-002", - name: "Import PDF document", - category: "Document Pipeline", - passCriteria: ["File accepted", "Text extracted", "Chunks created", "Metadata stored"], - expectedDurationMs: 200, - actualDurationMs: 310, - actualPass: false, - failureReason: "PDF text extraction failed on page 3 -- encoding issue", - }, - { - id: "bench-003", - name: "Ask grounded question", - category: "Q&A Pipeline", - passCriteria: ["Query received", "Relevant chunks retrieved", "Answer generated", "Citations present"], - expectedDurationMs: 500, - actualDurationMs: 480, - actualPass: true, - }, - { - id: "bench-004", - name: "Ask question with no relevant docs", - category: "Q&A Pipeline", - passCriteria: ["Query received", "Graceful 'no results' message", "No hallucinated citations"], - expectedDurationMs: 400, - actualDurationMs: 420, - actualPass: false, - failureReason: "Model hallucinated a citation instead of saying 'no results'", - }, - { - id: "bench-005", - name: "Delete imported document", - category: "Document Pipeline", - passCriteria: ["Document removed from list", "Chunks removed from index", "No orphan data"], - expectedDurationMs: 150, - actualDurationMs: 145, - actualPass: true, - }, - { - id: "bench-006", - name: "Concurrent user access", - category: "Security", - passCriteria: ["Users isolated", "No cross-user data leak", "Performance within SLA"], - expectedDurationMs: 300, - actualDurationMs: 850, - actualPass: false, - failureReason: "Cross-user data leak detected + response time exceeded SLA (850ms > 500ms)", - }, - { - id: "bench-007", - name: "Session continuity after restart", - category: "Reliability", - passCriteria: ["State persisted", "Session recovers", "No data loss"], - expectedDurationMs: 200, - actualDurationMs: 180, - actualPass: true, - }, - { - id: "bench-008", - name: "API rate limiting", - category: "Security", - passCriteria: ["Rate limit enforced", "429 response after limit", "Legitimate traffic unaffected"], - expectedDurationMs: 100, - actualDurationMs: 100, - actualPass: true, - }, -]; - -// --------------------------------------------------------------------------- -// Execution simulation -// --------------------------------------------------------------------------- - -function executeBenchmark(tasks: BenchmarkTask[]): BenchmarkResult[] { - return tasks.map((task) => ({ - id: task.id, - name: task.name, - category: task.category, - criteriaTotal: task.passCriteria.length, - criteriaPassed: task.actualPass ? task.passCriteria.length : Math.floor(task.passCriteria.length * 0.5), - pass: task.actualPass, - expectedMs: task.expectedDurationMs, - actualMs: task.actualDurationMs, - durationDelta: task.actualDurationMs - task.expectedDurationMs, - failureReason: task.failureReason, - })); -} - -// --------------------------------------------------------------------------- -// Reporting -// --------------------------------------------------------------------------- - -function pad(s: string, len: number): string { - return s.length >= len ? s : s + " ".repeat(len - s.length); -} - -function run(): void { - console.log("\n" + "=".repeat(100)); - console.log(" BENCHMARK RUNNER -- Task Execution Report"); - console.log("=".repeat(100)); - - const results = executeBenchmark(benchmarkTasks); - - // Detailed results - const header = `| ${pad("ID", 10)}| ${pad("Task", 35)}| ${pad("Category", 18)}| ${pad("Pass?", 6)}| ${pad("Criteria", 10)}| ${pad("Expected", 10)}| ${pad("Actual", 10)}| ${pad("Delta", 8)}|`; - const sep = `|${"-".repeat(12)}|${"-".repeat(37)}|${"-".repeat(20)}|${"-".repeat(8)}|${"-".repeat(12)}|${"-".repeat(12)}|${"-".repeat(12)}|${"-".repeat(10)}|`; - console.log("\n" + header); - console.log(sep); - - for (const r of results) { - const passLabel = r.pass ? "PASS" : "FAIL"; - const criteriaLabel = `${r.criteriaPassed}/${r.criteriaTotal}`; - const deltaLabel = r.durationDelta >= 0 ? `+${r.durationDelta}ms` : `${r.durationDelta}ms`; - const marker = r.pass ? " " : ">>"; - - console.log( - `${marker}| ${pad(r.id, 10)}| ${pad(r.name, 35)}| ${pad(r.category, 18)}| ${pad(passLabel, 6)}| ${pad(criteriaLabel, 10)}| ${pad(r.expectedMs + "ms", 10)}| ${pad(r.actualMs + "ms", 10)}| ${pad(deltaLabel, 8)}|` - ); - } - - // Failure details - const failures = results.filter((r) => !r.pass); - if (failures.length > 0) { - console.log("\n" + "-".repeat(100)); - console.log(" FAILURE DETAILS"); - console.log("-".repeat(100)); - for (const f of failures) { - console.log(`\n [${f.id}] ${f.name}`); - console.log(` Category: ${f.category}`); - console.log(` Criteria: ${f.criteriaPassed}/${f.criteriaTotal} passed`); - console.log(` Reason: ${f.failureReason ?? "Unknown"}`); - console.log(` Timing: Expected ${f.expectedMs}ms, actual ${f.actualMs}ms (delta: ${f.durationDelta >= 0 ? "+" : ""}${f.durationDelta}ms)`); - } - } - - // Summary - console.log("\n" + "=".repeat(100)); - console.log(" SUMMARY BY CATEGORY"); - console.log("=".repeat(100) + "\n"); - - const categories = [...new Set(results.map((r) => r.category))]; - const catHeader = `| ${pad("Category", 20)}| ${pad("Total", 8)}| ${pad("Passed", 8)}| ${pad("Failed", 8)}| ${pad("Pass Rate", 12)}|`; - const catSep = `|${"-".repeat(22)}|${"-".repeat(10)}|${"-".repeat(10)}|${"-".repeat(10)}|${"-".repeat(14)}|`; - console.log(catHeader); - console.log(catSep); - - for (const cat of categories) { - const catResults = results.filter((r) => r.category === cat); - const catPassed = catResults.filter((r) => r.pass).length; - const catFailed = catResults.filter((r) => !r.pass).length; - const rate = Math.round((catPassed / catResults.length) * 100); - - console.log(`| ${pad(cat, 20)}| ${pad(String(catResults.length), 8)}| ${pad(String(catPassed), 8)}| ${pad(String(catFailed), 8)}| ${pad(rate + "%", 12)}|`); - } - - // Overall - const totalPassed = results.filter((r) => r.pass).length; - const totalFailed = results.filter((r) => !r.pass).length; - console.log("\n" + "-".repeat(100)); - console.log(` OVERALL: ${totalPassed}/${results.length} passed (${Math.round((totalPassed / results.length) * 100)}%), ${totalFailed} failures`); - console.log("=".repeat(100) + "\n"); -} - -run(); diff --git a/courses/learn-harness-engineering/docs/ja/lectures/lecture-12-why-every-session-must-leave-a-clean-state/code/cleanup-loop.md b/courses/learn-harness-engineering/docs/ja/lectures/lecture-12-why-every-session-must-leave-a-clean-state/code/cleanup-loop.md deleted file mode 100644 index 166e4525..00000000 --- a/courses/learn-harness-engineering/docs/ja/lectures/lecture-12-why-every-session-must-leave-a-clean-state/code/cleanup-loop.md +++ /dev/null @@ -1,9 +0,0 @@ -# クリーンアップループの例 - -定期的なクリーンアップタスク: - -- 古いドキュメントをスキャン -- 構造違反をスキャン -- 品質グレードを更新 -- ターゲットを絞ったクリーンアップ PR を作成 -- クリーンアップ後に固定ベンチマークスライスを再実行 diff --git a/courses/learn-harness-engineering/docs/ja/lectures/lecture-12-why-every-session-must-leave-a-clean-state/code/cleanup-scanner.ts b/courses/learn-harness-engineering/docs/ja/lectures/lecture-12-why-every-session-must-leave-a-clean-state/code/cleanup-scanner.ts deleted file mode 100644 index 6b35905c..00000000 --- a/courses/learn-harness-engineering/docs/ja/lectures/lecture-12-why-every-session-must-leave-a-clean-state/code/cleanup-scanner.ts +++ /dev/null @@ -1,336 +0,0 @@ -/** - * cleanup-scanner.ts - * - * Scans a project directory for stale artifacts, dead code, structural - * violations, and outputs a cleanup report. Helps enforce the "clean state - * at end of every session" principle. - * - * Usage: - * npx tsx docs/lectures/lecture-12.../code/cleanup-scanner.ts [path] - * (defaults to current working directory) - * - * Run: npx tsx docs/lectures/lecture-12-why-every-session-must-leave-a-clean-state/code/cleanup-scanner.ts - */ - -import * as fs from "node:fs"; -import * as path from "node:path"; - -// --------------------------------------------------------------------------- -// Types -// --------------------------------------------------------------------------- - -interface ScanResult { - category: string; - check: string; - severity: "critical" | "warning" | "info"; - found: string[]; - description: string; -} - -// --------------------------------------------------------------------------- -// Scanner checks -// --------------------------------------------------------------------------- - -interface ScannerCheck { - category: string; - name: string; - severity: "critical" | "warning" | "info"; - description: string; - scan: (dir: string) => string[]; -} - -function createChecks(): ScannerCheck[] { - return [ - { - category: "Stale Artifacts", - name: "Temporary files (*.tmp, *.bak, *.swp)", - severity: "warning", - description: "Leftover temp files from editing or processing", - scan: (dir) => findFiles(dir, [".tmp", ".bak", ".swp", "~"]), - }, - { - category: "Stale Artifacts", - name: "Debug/log files in source", - severity: "warning", - description: "Log files that should not be in the source tree", - scan: (dir) => { - const found: string[] = []; - const logPatterns = [".log"]; - for (const p of findFiles(dir, logPatterns)) { - // Only flag log files in source directories - if (p.includes("/src/") || p.includes("/lib/") || p.includes("/app/")) { - found.push(p); - } - } - return found; - }, - }, - { - category: "Dead Code", - name: "Unused imports (placeholder detection)", - severity: "info", - description: "Files that may contain unused code (heuristic scan)", - scan: (dir) => { - const found: string[] = []; - const srcDirs = ["src", "lib", "app"]; - for (const srcDir of srcDirs) { - const srcPath = path.join(dir, srcDir); - if (fs.existsSync(srcPath)) { - scanForPatterns(srcPath, ["TODO:", "FIXME:", "HACK:", "XXX:"], found, dir); - } - } - return found; - }, - }, - { - category: "Structural Violations", - name: "Missing .gitignore", - severity: "warning", - description: "No .gitignore file found -- risk of committing build artifacts", - scan: (dir) => fs.existsSync(path.join(dir, ".gitignore")) ? [] : [".gitignore (MISSING)"], - }, - { - category: "Structural Violations", - name: "Build artifacts in source", - severity: "critical", - description: "Compiled output (dist/, build/) mixed with source", - scan: (dir) => { - const found: string[] = []; - // Check if dist/ or build/ exists inside src/ - const dirs = ["src/dist", "src/build", "lib/dist", "lib/build"]; - for (const d of dirs) { - if (fs.existsSync(path.join(dir, d))) { - found.push(d + "/"); - } - } - return found; - }, - }, - { - category: "Structural Violations", - name: "node_modules in source tree", - severity: "critical", - description: "node_modules directory found outside root (nested dependency)", - scan: (dir) => { - const found: string[] = []; - const srcDirs = ["src", "lib", "app", "test", "tests"]; - for (const srcDir of srcDirs) { - const nmPath = path.join(dir, srcDir, "node_modules"); - if (fs.existsSync(nmPath)) { - found.push(srcDir + "/node_modules/"); - } - } - return found; - }, - }, - { - category: "Session Cleanliness", - name: "Uncommitted changes indicator", - severity: "info", - description: "Checks for common artifacts of incomplete sessions", - scan: (dir) => { - const found: string[] = []; - const indicators = [ - "WIP.md", "IN_PROGRESS.md", "scratch.ts", "scratch.js", - "debug.ts", "test-manual.ts", "temp.ts", - ]; - for (const f of indicators) { - if (fs.existsSync(path.join(dir, f))) { - found.push(f); - } - } - return found; - }, - }, - { - category: "Session Cleanliness", - name: "Empty directories", - severity: "info", - description: "Empty directories that may be leftover from incomplete work", - scan: (dir) => { - const found: string[] = []; - const commonDirs = ["src", "lib", "app", "test", "tests", "docs"]; - for (const d of commonDirs) { - const dp = path.join(dir, d); - if (fs.existsSync(dp) && fs.statSync(dp).isDirectory()) { - try { - const contents = fs.readdirSync(dp); - if (contents.length === 0) { - found.push(d + "/"); - } - } catch { - // Skip directories we can't read - } - } - } - return found; - }, - }, - { - category: "Configuration", - name: "Environment files in source", - severity: "critical", - description: ".env files that may contain secrets", - scan: (dir) => { - const found: string[] = []; - const envFiles = [".env", ".env.local", ".env.production", ".env.staging"]; - for (const f of envFiles) { - if (fs.existsSync(path.join(dir, f))) { - found.push(f); - } - } - return found; - }, - }, - ]; -} - -// --------------------------------------------------------------------------- -// Helper functions -// --------------------------------------------------------------------------- - -function findFiles(dir: string, extensions: string[]): string[] { - const found: string[] = []; - - function walk(current: string, depth: number): void { - if (depth > 4) return; // Limit recursion depth - try { - const entries = fs.readdirSync(current, { withFileTypes: true }); - for (const entry of entries) { - // Skip common non-source directories - if (entry.isDirectory() && ["node_modules", ".git", "dist", "build", ".next", "coverage"].includes(entry.name)) { - continue; - } - - const fullPath = path.join(current, entry.name); - const relativePath = path.relative(dir, fullPath); - - if (entry.isDirectory()) { - walk(fullPath, depth + 1); - } else if (entry.isFile()) { - if (extensions.some((ext) => entry.name.endsWith(ext))) { - found.push(relativePath); - } - } - } - } catch { - // Skip directories we can't read - } - } - - walk(dir, 0); - return found; -} - -function scanForPatterns(dir: string, patterns: string[], results: string[], baseDir: string): void { - try { - const entries = fs.readdirSync(dir, { withFileTypes: true }); - for (const entry of entries) { - if (entry.isDirectory()) { - if (!["node_modules", ".git", "dist", "build"].includes(entry.name)) { - scanForPatterns(path.join(dir, entry.name), patterns, results, baseDir); - } - continue; - } - - const fullPath = path.join(dir, entry.name); - if (!entry.name.endsWith(".ts") && !entry.name.endsWith(".tsx") && !entry.name.endsWith(".js")) { - continue; - } - - try { - const content = fs.readFileSync(fullPath, "utf-8"); - const lines = content.split("\n"); - for (let i = 0; i < Math.min(lines.length, 50); i++) { - for (const pattern of patterns) { - if (lines[i].includes(pattern)) { - const relative = path.relative(baseDir, fullPath); - results.push(`${relative}:${i + 1} contains ${pattern}`); - } - } - } - } catch { - // Skip files we can't read - } - } - } catch { - // Skip directories we can't read - } -} - -// --------------------------------------------------------------------------- -// Reporting -// --------------------------------------------------------------------------- - -function pad(s: string, len: number): string { - return s.length >= len ? s : s + " ".repeat(len - s.length); -} - -function run(): void { - const targetDir = process.argv[2] ? path.resolve(process.argv[2]) : process.cwd(); - - console.log("\n" + "=".repeat(90)); - console.log(" CLEANUP SCANNER -- Stale Artifact & Violation Report"); - console.log("=".repeat(90)); - console.log(` Scanning: ${targetDir}\n`); - - const checks = createChecks(); - const results: ScanResult[] = []; - - for (const check of checks) { - const found = check.scan(targetDir); - results.push({ - category: check.category, - check: check.name, - severity: check.severity, - found, - description: check.description, - }); - } - - // Report - const header = `| ${pad("Category", 20)}| ${pad("Check", 40)}| ${pad("Severity", 10)}| ${pad("Count", 6)}| Details`; - const sep = `|${"-".repeat(22)}|${"-".repeat(42)}|${"-".repeat(12)}|${"-".repeat(8)}|${"-".repeat(30)}`; - console.log(header); - console.log(sep); - - for (const r of results) { - const count = r.found.length; - const detail = count > 0 ? r.found.slice(0, 2).join(", ") : "(clean)"; - const marker = count > 0 && r.severity === "critical" ? ">>" : count > 0 ? " * " : " "; - - console.log(`${marker}| ${pad(r.category, 20)}| ${pad(r.check, 40)}| ${pad(r.severity, 10)}| ${pad(String(count), 6)}| ${detail}`); - } - - // Summary - const criticals = results.filter((r) => r.severity === "critical" && r.found.length > 0); - const warnings = results.filter((r) => r.severity === "warning" && r.found.length > 0); - const infos = results.filter((r) => r.severity === "info" && r.found.length > 0); - const clean = results.filter((r) => r.found.length === 0); - - console.log("\n" + "=".repeat(90)); - console.log(" CLEANUP SUMMARY"); - console.log("=".repeat(90) + "\n"); - - console.log(` Critical issues: ${criticals.length}`); - console.log(` Warnings: ${warnings.length}`); - console.log(` Info items: ${infos.length}`); - console.log(` Clean checks: ${clean.length}/${results.length}`); - - if (criticals.length > 0) { - console.log("\n ACTION REQUIRED (Critical):"); - for (const c of criticals) { - console.log(` - ${c.check}: ${c.found.length} item(s) found`); - console.log(` ${c.description}`); - } - } - - const totalIssues = results.reduce((s, r) => s + r.found.length, 0); - if (totalIssues === 0) { - console.log("\n Project is in a clean state. No issues found.\n"); - } else { - console.log(`\n Total issues found: ${totalIssues}. Run this scanner at the end of every session to maintain clean state.\n`); - } -} - -run(); diff --git a/courses/learn-harness-engineering/docs/ja/lectures/lecture-12-why-every-session-must-leave-a-clean-state/code/index.md b/courses/learn-harness-engineering/docs/ja/lectures/lecture-12-why-every-session-must-leave-a-clean-state/code/index.md deleted file mode 100644 index f91d686a..00000000 --- a/courses/learn-harness-engineering/docs/ja/lectures/lecture-12-why-every-session-must-leave-a-clean-state/code/index.md +++ /dev/null @@ -1,8 +0,0 @@ -# 講義 12 のコード - -このフォルダーは、以下の例に使用してください: - -- ベンチマークスライス -- クリーンアップタスク -- エントロピー削減の例 -- 再現可能な harness 実行 diff --git a/courses/learn-harness-engineering/docs/ja/lectures/lecture-12-why-every-session-must-leave-a-clean-state/index.md b/courses/learn-harness-engineering/docs/ja/lectures/lecture-12-why-every-session-must-leave-a-clean-state/index.md deleted file mode 100644 index 6682cdf8..00000000 --- a/courses/learn-harness-engineering/docs/ja/lectures/lecture-12-why-every-session-must-leave-a-clean-state/index.md +++ /dev/null @@ -1,186 +0,0 @@ -[中文版本 →](../../../zh/lectures/lecture-12-why-every-session-must-leave-a-clean-state/) - -> コード例: [code/](https://github.com/walkinglabs/learn-harness-engineering/blob/main/docs/ja/lectures/lecture-12-why-every-session-must-leave-a-clean-state/code/) -> 実践プロジェクト: [Project 06. Complete harness (Capstone)](./../../projects/project-06-runtime-observability-and-debugging/index.md) - -# 講義 12. すべてのセッションをきれいな引き継ぎで終える - -## この講義が解決する問題 - -agentが午後ずっと動いて、20のファイルを変更し、コードをコミットし、セッションが終了します。次のagentセッションが始まると、すぐに発見します:ビルドが壊れている、テストが赤い、一時的なデバッグファイルが至る所にある、feature listが更新されていない、進捗が完全に不明。新しいセッションは最初の30分を「前のセッションが実際に何をしたのか」を理解するだけに費やします。 - -OpenAIとAnthropicはどちらも明確に述べています:**長期的な信頼性は、単発の実行成功ではなく、運用上の規律に依存する。** セッション終了時の状態の品質が、次のセッションの効率を直接的に決定します。Gitのベストプラクティスと同じように考えることができます——すべてのコミットはアトミックでコンパイル可能な変更であるべきで、未完成のコードの山ではありません。 - -## 中核概念 - -- **クリーン状態(Clean state)**: セッション終了時にシステムが5つの条件を満たしていること——ビルドが通る、テストが通る、進捗が記録されている、古いアーティファクトがない、起動パスが利用可能。どれか一つでも欠けていれば、そのセッションは「完了」ではありません。 -- **セッション整合性(Session integrity)**: データベーストランザクションに似ています——完全にコミットしてクリーンな状態を残すか、最後の一貫した状態にロールバックするかのどちらか。中間はありません。 -- **品質ドキュメント(Quality document)**: 各モジュールの品質評価を継続的に記録するアクティブなアーティファクト。一度きりの評価ではなく、コードベースが強くなっているか弱くなっているかを追跡するものです。 -- **クリーンアップループ(Cleanup loop)**: コードベースのエントロピーを体系的に削減することを目的とした定期的なメンテナンスセッション。緊急の修正ではなく、日常の運用です。 -- **harnessの簡素化(Harness simplification)**: モデルの能力が向上するにつれて、不要になったharnessコンポーネントを定期的に削除すること。今日不可欠な制約が、3ヶ月後には不要なオーバーヘッドになっているかもしれません。 -- **冪等なクリーンアップ(Idempotent cleanup)**: クリーンアップ操作は何回実行しても同じ結果を生成します。これにより、失敗時のリトライシナリオでもクリーンアップが安全であることが保証されます。 - -## クリーン状態の5つの次元 - -```mermaid -flowchart LR - Work["Feature work complete"] --> Build{"Build passes?"} - Build -->|yes| Test{"Tests pass?"} - Build -->|no| Fix["Fix before exit"] - Test -->|yes| Record["Update feature list + progress"] - Test -->|no| Fix - Record --> Cleanup["Remove temp artifacts / debug code"] - Cleanup --> Startup{"Standard startup path works?"} - Startup -->|yes| Clean["Clean handoff"] - Startup -->|no| Fix - Fix --> Build -``` - -```mermaid -flowchart LR - Dirty["Session ends with
red tests / temp files / no progress update"] --> Diagnose["Next session first has to
figure out what happened"] - Diagnose --> Fragile["New work starts on a messy repo"] - Fragile --> More["More debug files, more broken checks,
more unclear progress"] - More --> Dirty - - Clean["Session ends with
green tests / updated progress / temp files removed"] --> Fast["Next session can start coding immediately"] - Fast --> Stable["No need to rescue the repo first"] - Stable --> Clean -``` - -## なぜこれが起きるのか - -### エントロピーの増大がデフォルト状態 - -Lehmanのソフトウェア進化の法則が教えてくれること:継続的な変更を受けるシステムは、積極的に管理されない限り、必然的に複雑さが増大します。これはAIコーディングagentにとって特に当てはまります——すべてのセッションが変更を導入し、終了時のクリーンアップがなければ、技術的負債は指数関数的に蓄積します。 - -実際のデータが物語っています。agentを使って12週間開発されたプロジェクト、クリーンアップ戦略なしの場合: - -- 週1:ビルド合格率 100%、テスト合格率 100%、新セッション起動 5分 -- 週4:ビルド 95%、テスト 92%、起動 15分 -- 週8:ビルド 82%、テスト 78%、起動 35分 -- 週12:ビルド 68%、テスト 61%、起動 60分以上 - -同じプロジェクトでクリーンアップ戦略ありの場合: - -- 週1:100%、100%、5分 -- 週12:97%、95%、9分 - -12週間後:ビルド合格率の差は29ポイント、新セッション起動時間の差は85%。これは理論ではなく、観察された差異です。 - -### クリーン状態の5つの次元 - -クリーン状態とは単に「コードがコンパイルできる」ことではありません。5つの次元を総合的に評価するものです: - -**ビルドの次元**:コードはエラーなくビルドできるか?これが最も基本的です——次のセッションが最初にビルドエラーを修正しなければならないべきではありません。 - -**テストの次元**:すべてのテストは通るか?セッションの前に存在していたテストも含みます——セッションは既存の機能を壊さない責任があります。そして「自分のマシンでは動く」ではなく、CIで検証されるべきです。 - -**進捗の次元**:現在の進捗は機械可読なアーティファクトに記録されているか?合格基準と共に完了したサブタスク、現在の状態と共に進行中だが未完了のサブタスク、未着手のサブタスク。良い進捗記録は、セッション起動時の診断時間を60〜80%削減します。 - -**アーティファクトの次元**:古い、または不明瞭な一時アーティファクトが残っていないか?デバッグログ、一時ファイル、コメントアウトされたコード、TODOマーカー——これらはすべて次のセッションの認知負荷を増やします。 - -**起動の次元**:標準起動パスは利用可能か?次のセッションは手動の介入なしに作業を開始できるか?環境の初期化、コードベースの読み込み、コンテキストの取得、タスクの選択——これらのパスが壊れていてはなりません。 - -### 「後で片付ける」は「絶対に片付けない」と同じ - -最も一般的な精神的な罠は「このセッションは片付ける時間がない、次回やる」というものです。しかし、次のagentセッションはあなたが何を残したかを知りません——散らかったコードと不確かな状態を見るだけです。「このコードのどの部分が意図的で、どの部分が一時的なのか」を推測するのに多くの時間を費やすことになります。 - -さらに悪いことに、すべてのセッションには独自のタスク目標があります。新しいセッションは新しい作業をするために存在し、前のセッションの散らかりを片付けるためではありません。混乱を無視してその上に新しい作業を始め、混乱の上にさらに混乱を重ねます。これがエントロピーの正のフィードバックループです。 - -## 正しく行う方法 - -### 1. クリーン状態を完了要件にする - -harnessで明示的に定義します:**セッション完了 = タスクが検証に合格 AND クリーン状態チェックに合格。** どちらかが欠けてもセッションは完了ではありません。CLAUDE.mdに書きます: - -``` -## Session Exit Checklist -- [ ] Build passes (npm run build) -- [ ] All tests pass (npm test) -- [ ] Feature list updated -- [ ] No debug code remaining (console.log, debugger, TODO) -- [ ] Standard startup path available (npm run dev) -``` - -### 2. デュアルモードのクリーンアップ戦略 - -2つのクリーンアップモードを組み合わせます: - -**即時クリーンアップ(すべてのセッションの終了時)**:セッション中に作成された一時アーティファクトをクリーンアップし、feature listの状態を更新し、ビルドとテストが通ることを確認します。これは「参照カウント」型のクリーンアップです。 - -**定期クリーンアップ(週次)**:フルシステムスキャン——蓄積された構造的問題の処理、品質ドキュメントの更新、ドリフトを検出するためのベンチマークテストの実行。これは「トレーシング」型のクリーンアップです。 - -### 3. 品質ドキュメントを維持する - -品質ドキュメントは各モジュールを継続的にスコアリングするアクティブなアーティファクトです: - -```markdown -# Quality Document - -## User Authentication Module (Quality: A) -- Verification passing: Yes -- Agent understandable: Yes -- Test stability: Stable -- Architecture boundaries: Compliant -- Code conventions: Followed - -## Payment Module (Quality: C) -- Verification passing: Partial (payment callback untested) -- Agent understandable: Difficult (logic spread across 3 files) -- Test stability: Unstable (2 flaky tests) -- Architecture boundaries: Violations present -- Code conventions: Partially followed -``` - -新しいセッションはこのドキュメントを読み、すぐにどこを優先すべきかを把握します。まず最低スコアのモジュールを修正します。 - -### 4. 定期的にharnessを簡素化する - -Anthropicからの重要なインサイト:**すべてのharnessコンポーネントは、モデルが自力で何かを確実にできないために存在する。しかし、モデルが改善するにつれて、これらの前提は時代遅れになる。** 3ヶ月前に不可欠だった制約が、今日では不要なオーバーヘッドになっているかもしれません。 - -推奨される実践:毎月1つのharnessコンポーネントを選び、一時的に無効にして、ベンチマークタスクを実行する。結果が劣化しなければ、永久に削除する。劣化する場合は、復元するか、より軽量な代替手段に置き換える。 - -### 5. クリーンアップ操作は冪等でなければならない - -クリーンアップスクリプトは繰り返し実行しても安全であるべきです: - -```bash -# Idempotent cleanup operations -rm -f /tmp/debug-*.log # -f ensures no error when files don't exist -git checkout -- .env.local # Restore to known state -npm run test # Verify cleanup didn't break anything -``` - -## 実際のケース - -agentを使って12週間にわたり開発されたElectronアプリ、2つのアプローチの比較: - -**クリーンアップ戦略なし**(対照群):週12でビルド合格率 68%、テスト合格率 61%、新セッション起動 60分以上、古いアーティファクト 103。 - -**クリーンアップ戦略あり**(実験群):各セッション終了時にフルクリーン状態チェック + 週次クリーンアップループ。週12でビルド合格率 97%、テスト合格率 95%、新セッション起動 9分、古いアーティファクト 11。 - -週12までに、実験群のビルド合格率は29ポイント高く、テスト合格率は34ポイント高く、新セッション起動時間は85%短縮されました。 - -## 重要なポイント - -- **クリーン状態はセッション完了の必要条件**——オプションの片付けではなく、「完了の定義」の一部。 -- **5つの次元すべてが必須**——ビルド、テスト、進捗、アーティファクト、起動——それぞれを明示的にチェックしなければならない。 -- **品質ドキュメントがコードベースの健全性を追跡可能にする**——劣化していることを知って初めて修正できる。 -- **定期的にharnessを簡素化する**——モデルの能力が向上するにつれて、不要になった制約を削除する。 -- **「後で片付ける」は「片付けない」のと同じ**——エントロピーの増大がデフォルトであり、能動的なクリーンアップだけがそれに対抗できる。 - -## 参考資料 - -- [Clean Code - Robert C. Martin](https://www.goodreads.com/book/show/3735293-clean-code) — コードの清潔さの体系的原則 -- [Harness Engineering - OpenAI](https://openai.com/index/harness-engineering/) — 再現性を中核とするharness設計要件 -- [Effective Harnesses - Anthropic](https://www.anthropic.com/engineering/effective-harnesses-for-long-running-agents) — 長期的な信頼性に対するクリーンなセッション終了の重要な役割 -- [Programs, Life Cycles, and Laws of Software Evolution - Lehman](https://ieeexplore.ieee.org/document/1702314) — 能動的なメンテナンスなしにはシステムの複雑さが必然的に増大することを証明するソフトウェア進化の法則 - -## 演習 - -1. **クリーン状態チェックリスト**:自分のコードベース向けに5つの次元すべてを網羅するセッション終了チェックリストを設計する。5回連続のセッションに適用し、各次元ごとの違反を記録する。 - -2. **ベンチマーク比較**:固定タスクセットを使用し、2つのharnessバリアント(クリーン状態要件あり/なし)で比較する。完了率、リトライ回数、欠陥流出率を比較する。 - -3. **harness簡素化の実践**:1つのharnessコンポーネントを選び、一時的に無効にしてベンチマークタスクを実行する。あり/なしの結果を比較し、維持、削除、または置換のいずれかを決定する。 diff --git a/courses/learn-harness-engineering/docs/ja/projects/index.md b/courses/learn-harness-engineering/docs/ja/projects/index.md deleted file mode 100644 index 08a6e7d9..00000000 --- a/courses/learn-harness-engineering/docs/ja/projects/index.md +++ /dev/null @@ -1,23 +0,0 @@ -# プロジェクトへようこそ - -ここは Learn Harness Engineering の実践セクションです。講義を読むだけでは不十分です。自分で環境を作り、Codex、Claude Code、その他の AI エージェントがルールの違いでどう振る舞うかを観察します。 - -## プロジェクト概要 - -このコースには、信頼できるエージェント作業環境をゼロから構築するための 6 つの段階的な実践プロジェクトがあります。 - -1. **プロンプトのみ vs ルール優先**: プロンプトだけの場合と基本的な harness を使う場合で、エージェントの結果を比較します。 -2. **エージェントが読めるワークスペース**: AI が理解しやすいリポジトリ構造と引き継ぎの仕組みを作ります。 -3. **複数セッションの継続性**: 状態ファイルと初期化スクリプトを設計し、セッションをまたいで自然に作業を再開できるようにします。 -4. **ランタイムフィードバックとスコープ制御**: エージェントが自分のコードをテストし、実行中にエラーを修正できる道具を導入します。 -5. **自己検証と役割分離**: 幻覚や早すぎる完了宣言を防ぐため、独立したレビュー機構を作ります。 -6. **完全な harness(総合課題)**: 観測可能で end-to-end に動く最終的なエージェント作業環境を組み立てます。 - -## 進め方 - -各プロジェクトフォルダには通常、次のものが含まれています。 -- `starter/`: 開始用のワークスペース。 -- `solution/`: 詰まったときに参照する実装例。 -- 背景と具体的な目標を説明するタスク指示。 - -好みの AI Coding Agent(Claude Code、Cursor、Trae など)を使い、`starter/` ディレクトリ内でタスクを完了してください。 diff --git a/courses/learn-harness-engineering/docs/ja/projects/project-01-baseline-vs-minimal-harness/index.md b/courses/learn-harness-engineering/docs/ja/projects/project-01-baseline-vs-minimal-harness/index.md deleted file mode 100644 index 8eebfc88..00000000 --- a/courses/learn-harness-engineering/docs/ja/projects/project-01-baseline-vs-minimal-harness/index.md +++ /dev/null @@ -1,25 +0,0 @@ -[中文版本 →](../../../zh/projects/project-01-baseline-vs-minimal-harness/) - -> 関連講義: [講義 01. 強いモデルは信頼できる実行を意味しない](./../../lectures/lecture-01-why-capable-agents-still-fail/index.md) · [講義 02. harness とは何か](./../../lectures/lecture-02-what-a-harness-actually-is/index.md) -> テンプレートファイル: [templates/](https://github.com/walkinglabs/learn-harness-engineering/blob/main/docs/ja/resources/templates/) - -# プロジェクト 01. プロンプトのみ vs ルール優先: どれほど差が出るか - -## やること - -最小限の Electron ナレッジベースアプリの外枠を作ります。左にドキュメント一覧、右に Q&A パネル、ローカルデータディレクトリを持つウィンドウです。タスク自体は複雑ではありません。複雑なのは、エージェントにどう完了させるかです。 - -同じ作業を 2 回実行します。1 回目は準備なしでプロンプトだけ。2 回目は `AGENTS.md`、`init.sh`、`feature_list.json` をあらかじめリポジトリに置いてから実行します。その後、結果を比較します。 - -このプロジェクトの核心はコードを書くことではありません。「最初に 15 分かけてルールを用意する」ことと「そのままエージェントに任せる」ことの差がどれほど大きいかを測ることです。 - -## ツール - -- Claude Code または Codex(どちらかを選び、両方の実行で同じものを使う) -- Git(ブランチ管理と比較) -- Node.js + Electron(プロジェクトのスタック) -- タイマー(各実行時間を記録) - -## Harness メカニズム - -最小 harness: `AGENTS.md` + `init.sh` + `feature_list.json` diff --git a/courses/learn-harness-engineering/docs/ja/projects/project-02-agent-readable-workspace/index.md b/courses/learn-harness-engineering/docs/ja/projects/project-02-agent-readable-workspace/index.md deleted file mode 100644 index 16c17fa3..00000000 --- a/courses/learn-harness-engineering/docs/ja/projects/project-02-agent-readable-workspace/index.md +++ /dev/null @@ -1,22 +0,0 @@ -[中文版本 →](../../../zh/projects/project-02-agent-readable-workspace/) - -> 関連講義: [講義 03. リポジトリを唯一の信頼できる情報源にする](./../../lectures/lecture-03-why-the-repository-must-become-the-system-of-record/index.md) · [講義 04. 指示を複数ファイルに分ける](./../../lectures/lecture-04-why-one-giant-instruction-file-fails/index.md) -> テンプレートファイル: [templates/](https://github.com/walkinglabs/learn-harness-engineering/blob/main/docs/ja/resources/templates/) - -# プロジェクト 02. プロジェクトを読みやすくし、中断地点から再開できるようにする - -## やること - -新しいエージェントがプロジェクト構造、現在の進捗、次にやるべき作業をすばやく理解できるよう、リポジトリに「読みやすさ」を追加します。具体的には、ドキュメントのインポート、詳細表示、ローカル永続化を 2 セッションに分けて実装します。 - -同じ作業を 2 回実行します。1 回目は補助なし。2 回目は `ARCHITECTURE.md`、`PRODUCT.md`、`session-handoff.md` をあらかじめリポジトリに置いて実行します。 - -## ツール - -- Claude Code または Codex -- Git -- Node.js + Electron - -## Harness メカニズム - -エージェントが読めるワークスペース + 永続化された状態ファイル diff --git a/courses/learn-harness-engineering/docs/ja/projects/project-03-multi-session-continuity/index.md b/courses/learn-harness-engineering/docs/ja/projects/project-03-multi-session-continuity/index.md deleted file mode 100644 index 74b77df3..00000000 --- a/courses/learn-harness-engineering/docs/ja/projects/project-03-multi-session-continuity/index.md +++ /dev/null @@ -1,22 +0,0 @@ -[中文版本 →](../../../zh/projects/project-03-multi-session-continuity/) - -> 関連講義: [講義 05. セッションをまたいでコンテキストを保つ](./../../lectures/lecture-05-why-long-running-tasks-lose-continuity/index.md) · [講義 06. すべてのエージェントセッション前に初期化する](./../../lectures/lecture-06-why-initialization-needs-its-own-phase/index.md) -> テンプレートファイル: [templates/](https://github.com/walkinglabs/learn-harness-engineering/blob/main/docs/ja/resources/templates/) - -# プロジェクト 03. セッション再起動後もエージェントが作業を続けられるようにする - -## やること - -エージェントにスコープ制御と検証ゲートを追加します。ドキュメント分割、メタデータ抽出、インデックス進捗表示、引用付き Q&A フローを実装します。`feature_list.json` で機能状態を追跡し、一度に 1 機能だけ進め、検証証拠なしに `pass` とマークしないようにします。 - -同じ作業を 2 回実行します。1 回目は制約なし。2 回目は厳格なルールを適用します。 - -## ツール - -- Claude Code または Codex -- Git -- Node.js + Electron - -## Harness メカニズム - -進捗ログ + セッション引き継ぎ + 複数セッション継続性 diff --git a/courses/learn-harness-engineering/docs/ja/projects/project-04-incremental-indexing/index.md b/courses/learn-harness-engineering/docs/ja/projects/project-04-incremental-indexing/index.md deleted file mode 100644 index f581ea37..00000000 --- a/courses/learn-harness-engineering/docs/ja/projects/project-04-incremental-indexing/index.md +++ /dev/null @@ -1,22 +0,0 @@ -[中文版本 →](../../../zh/projects/project-04-incremental-indexing/) - -> 関連講義: [講義 07. エージェントのタスク境界を明確に引く](./../../lectures/lecture-07-why-agents-overreach-and-under-finish/index.md) · [講義 08. 機能リストでエージェントの作業を制約する](./../../lectures/lecture-08-why-feature-lists-are-harness-primitives/index.md) -> テンプレートファイル: [templates/](https://github.com/walkinglabs/learn-harness-engineering/blob/main/docs/ja/resources/templates/) - -# プロジェクト 04. ランタイムフィードバックでエージェントの挙動を修正する - -## やること - -起動ログ、インポート/インデックスログ、エラー状態などのランタイム観測性と、レイヤー越境を防ぐアーキテクチャ制約を追加します。さらに、エージェントに修正させるランタイムバグを仕込みます。 - -同じ作業を 2 回実行します。1 回目はログや制約なし。2 回目は適切なツールとルールを使います。 - -## ツール - -- Claude Code または Codex -- Git -- Node.js + Electron - -## Harness メカニズム - -ランタイムフィードバック + スコープ制御 + 増分インデックス diff --git a/courses/learn-harness-engineering/docs/ja/projects/project-05-grounded-qa-verification/index.md b/courses/learn-harness-engineering/docs/ja/projects/project-05-grounded-qa-verification/index.md deleted file mode 100644 index 04baf17d..00000000 --- a/courses/learn-harness-engineering/docs/ja/projects/project-05-grounded-qa-verification/index.md +++ /dev/null @@ -1,22 +0,0 @@ -[中文版本 →](../../../zh/projects/project-05-grounded-qa-verification/) - -> 関連講義: [講義 09. エージェントの早すぎる完了宣言を止める](./../../lectures/lecture-09-why-agents-declare-victory-too-early/index.md) · [講義 10. フルパイプライン実行だけが本当の検証である](./../../lectures/lecture-10-why-end-to-end-testing-changes-results/index.md) -> テンプレートファイル: [templates/](https://github.com/walkinglabs/learn-harness-engineering/blob/main/docs/ja/resources/templates/) - -# プロジェクト 05. エージェントに自分の作業を検証させる - -## やること - -役割分離を実装します。実装する generator、レビューする evaluator、必要に応じて planner を用意します。役割を追加するたびに効果を測るため、3 回実行します。 - -複数ターン会話、引用パネルの再設計、ドキュメントフィルタリングなど、実質的な機能改善を 1 つ選び、すべての実行で同じ対象を使います。 - -## ツール - -- Claude Code または Codex -- Git -- Node.js + Electron - -## Harness メカニズム - -自己検証 + 根拠付き Q&A + 証拠に基づく完了判定 diff --git a/courses/learn-harness-engineering/docs/ja/projects/project-06-runtime-observability-and-debugging/index.md b/courses/learn-harness-engineering/docs/ja/projects/project-06-runtime-observability-and-debugging/index.md deleted file mode 100644 index 4958077c..00000000 --- a/courses/learn-harness-engineering/docs/ja/projects/project-06-runtime-observability-and-debugging/index.md +++ /dev/null @@ -1,25 +0,0 @@ -[中文版本 →](../../../zh/projects/project-06-runtime-observability-and-debugging/) - -> 関連講義: [講義 11. エージェントのランタイムを観測可能にする](./../../lectures/lecture-11-why-observability-belongs-inside-the-harness/index.md) · [講義 12. すべてのセッション終了時にきれいな引き継ぎを残す](./../../lectures/lecture-12-why-every-session-must-leave-a-clean-state/index.md) -> テンプレートファイル: [templates/](https://github.com/walkinglabs/learn-harness-engineering/blob/main/docs/ja/resources/templates/) - -# プロジェクト 06. 完全なエージェント harness を構築する(総合課題) - -## やること - -これは総合プロジェクトです。最初の 5 つのプロジェクトで学んだすべてを組み合わせ、完全なベンチマークを実行し、その後クリーンアップを行って品質が維持可能かを検証します。 - -ドキュメントインポート、インデックス、引用付き Q&A、ランタイム観測性、読みやすく再開可能なリポジトリ状態を含む、固定された複数機能タスクセットを使います。まず弱い harness のベースラインで実行し、次に最も強い harness で実行し、その後クリーンアップして再実行します。最後に harness の ablation 実験を行い、コンポーネントを 1 つずつ外して、どれが本当に重要かを確認します。 - -## ツール - -- Claude Code または Codex -- Git -- Node.js + Electron -- 品質ドキュメントテンプレート -- 評価ルーブリック -- 最初の 5 プロジェクトで積み上げたすべての harness コンポーネント - -## Harness メカニズム - -完全な harness: すべてのメカニズム + 観測性 + ablation study diff --git a/courses/learn-harness-engineering/docs/ja/resources/index.md b/courses/learn-harness-engineering/docs/ja/resources/index.md deleted file mode 100644 index 5603263e..00000000 --- a/courses/learn-harness-engineering/docs/ja/resources/index.md +++ /dev/null @@ -1,50 +0,0 @@ -# 日本語リソースライブラリ - -このフォルダは、コースで扱う方法を、実際のリポジトリで使えるコピー可能なテンプレートと短いリファレンスに変換したものです。 - -## いつ使うか - -Codex、Claude Code、その他のコーディングエージェントに、セットアップ、進捗、スコープを毎回推測させず、複数セッションにわたって作業させたいときはここから始めます。 - -特に次のような場合に役立ちます。 - -- 作業が複数セッションにまたがる -- 機能が多く、途中で放置されやすい -- エージェントが早すぎる完了宣言をしがち -- 起動手順を毎回探し直している - -## 最初に使うもの - -最小構成では、次から始めます。 - -- ルート指示: [`templates/AGENTS.md`](./templates/AGENTS.md) または [`templates/CLAUDE.md`](./templates/CLAUDE.md) -- 機能状態: [`templates/feature_list.json`](./templates/feature_list.json) -- 進捗ログ: [`templates/claude-progress.md`](./templates/claude-progress.md) -- ブートストラップスクリプト参照: `docs/ja/resources/templates/init.sh` - -次に追加します。 - -- セッション引き継ぎ: [`templates/session-handoff.md`](./templates/session-handoff.md) -- クリーン終了チェックリスト: [`templates/clean-state-checklist.md`](./templates/clean-state-checklist.md) -- 評価ルーブリック: [`templates/evaluator-rubric.md`](./templates/evaluator-rubric.md) - -OpenAI の "Harness engineering" 記事に近い、より本格的なリポジトリ構造が必要な場合は、高度なパックを使います。 - -- [`openai-advanced/index.md`](./openai-advanced/index.md) - -## ライブラリ構成 - -- [`templates/`](./templates/index.md): 実際のリポジトリにコピーするテンプレート -- [`reference/`](./reference/index.md): 方法メモ、起動フロー、失敗モードのマップ -- [`openai-advanced/`](./openai-advanced/index.md): 高度なリポジトリ雛形、system-of-record ドキュメント、agent-first ガバナンステンプレート - -## 推奨される最小パック - -- `AGENTS.md` または `CLAUDE.md` -- `feature_list.json` -- `claude-progress.md` -- `init.sh` - -この 4 つのファイルだけでも、多くのエージェントワークフローは目に見えて安定します。 - -リポジトリが複数ドメイン、アクティブな計画、品質スコア、信頼性ポリシーを持つ長期運用システムに育ったら、最小パックを無理に拡張するのではなく、[`openai-advanced/`](./openai-advanced/index.md) パックへ移行してください。 diff --git a/courses/learn-harness-engineering/docs/ja/resources/openai-advanced/index.md b/courses/learn-harness-engineering/docs/ja/resources/openai-advanced/index.md deleted file mode 100644 index 9b5639d2..00000000 --- a/courses/learn-harness-engineering/docs/ja/resources/openai-advanced/index.md +++ /dev/null @@ -1,83 +0,0 @@ -# OpenAI アドバンストパック - -このフォルダーは、OpenAI の「Harness engineering: leveraging Codex in an agent-first world」 -記事で説明されている、よりオピニオネイトされたリポジトリ構造を -すぐにコピーできるスターターファイルとしてパッケージしたものです。 - -このパックは、最小限のハーネスではもう十分ではなく、リポジトリが次のものを -必要とする場合に使用してください: - -- 短いルーティングスタイルの `AGENTS.md` -- リポジトリ内の持続的なシステム・オブ・レコードドキュメント -- アクティブおよび完了した実行計画 -- 明示的なプロダクト、信頼性、セキュリティ、フロントエンドポリシーファイル -- プロダクトドメインとアーキテクチャレイヤーによる品質スコアリング -- モデルに適した参照資料フォルダー -- アーキテクチャ、ナレッジキャプチャ、ランタイム検証のための標準運用手順 - -## 含まれるスターターレイアウト - -[`repo-template/`](./repo-template/index.md) 配下のスターターパックは、 -以下の構造を反映しています: - -```text -AGENTS.md -ARCHITECTURE.md -docs/ -├── design-docs/ -│ ├── index.md -│ └── core-beliefs.md -├── exec-plans/ -│ ├── active/ -│ ├── completed/ -│ └── tech-debt-tracker.md -├── generated/ -│ └── db-schema.md -├── product-specs/ -│ ├── index.md -│ └── new-user-onboarding.md -├── references/ -│ ├── design-system-reference-llms.txt -│ ├── nixpacks-llms.txt -│ └── uv-llms.txt -├── DESIGN.md -├── FRONTEND.md -├── PLANS.md -├── PRODUCT_SENSE.md -├── QUALITY_SCORE.md -├── RELIABILITY.md -└── SECURITY.md -``` - -## 導入方法 - -1. リポジトリがまだ小規模な場合は、最小パックから始める。 -2. より強固な構造が必要になったら、[`repo-template/`](./repo-template/index.md) の - ファイルを自身のリポジトリにコピーする。 -3. `AGENTS.md` は短く保つ。より深いドキュメントへのルーターとして扱い、 - 百科事典としては扱わない。 -4. 品質、信頼性、計画ドキュメントは、独立したクリーンアップの日ではなく、 - 通常の作業の一部として更新する。 -5. 生成された成果物と外部参照は明示的に保持し、エージェントが - チャット履歴に頼らずに見つけられるようにする。 - -## SOP ライブラリ - -[`sops/`](./sops/index.md) フォルダーは、記事の図を -ステップバイステップの運用手順に変換したものです: - -- レイヤードドメインアーキテクチャのセットアップ -- 目に見えないナレッジをリポジトリにエンコード -- ローカルオブザーバビリティスタックとフィードバックループワークフロー -- UI作業のための Chrome DevTools 検証ループ - -## 設計原則 - -- 短いエントリポイント、深くリンクされたドキュメント -- リポジトリをシステム・オブ・レコードとして扱う -- 機械的チェックは記憶されたルールに勝る -- 計画と品質履歴はコードの隣に置く -- クリーンアップと単純化は第一級の責務 - -このパックは意図的にオピニオネイトされていますが、盲目的にコピーするのではなく、 -プロジェクトに合わせて適応させるべきです。 diff --git a/courses/learn-harness-engineering/docs/ja/resources/openai-advanced/repo-template/AGENTS.md b/courses/learn-harness-engineering/docs/ja/resources/openai-advanced/repo-template/AGENTS.md deleted file mode 100644 index 734ff7b8..00000000 --- a/courses/learn-harness-engineering/docs/ja/resources/openai-advanced/repo-template/AGENTS.md +++ /dev/null @@ -1,55 +0,0 @@ -# AGENTS.md - -このリポジトリは長時間実行されるコーディングエージェントの作業に最適化されています。このファイルは短く保ち、巨大な指示のダンプではなく、記録のシステム文書へのルーティング層として使用してください。 - -## スタートアップワークフロー - -コードを変更する前に: - -1. `pwd` でリポジトリルートを確認する。 -2. 現在のシステムマップと厳格な依存ルールを確認するため `ARCHITECTURE.md` を読む。 -3. どのドメインやレイヤーが最も弱いかを確認するため `docs/QUALITY_SCORE.md` を読む。 -4. `docs/PLANS.md` を読み、作業中のアクティブプランを開く。 -5. `docs/product-specs/` の関連するプロダクト仕様を読む。 -6. このリポジトリの標準ブートストラップと検証パスを実行する。 -7. ベースライン検証が失敗している場合、スコープを追加する前にベースラインを修復する。 - -## ルーティングマップ - -- `ARCHITECTURE.md`: ドメインマップ、レイヤーモデル、依存ルール -- `docs/design-docs/index.md`: 設計上の決定とコアビリーフ -- `docs/product-specs/index.md`: 現在のプロダクト動作と受け入れ基準 -- `docs/PLANS.md`: プランのライフサイクルと実行プランのポリシー -- `docs/QUALITY_SCORE.md`: プロダクトドメインとレイヤーの健全性 -- `docs/RELIABILITY.md`: ランタイムシグナル、ベンチマーク、再起動の期待値 -- `docs/SECURITY.md`: シークレット、サンドボックス、データ、外部アクションのルール -- `docs/FRONTEND.md`: UIの制約、デザインシステムルール、アクセシビリティチェック - -## ワーキングコントラクト - -- 一度に一つの境界付けられたプランまたはフィーチャースライスから作業する。 -- コードの検査だけで作業完了とマークしない。実行可能な証拠が必要である。 -- 動作を変更した場合、同じセッションで対応するプロダクト、プラン、または信頼性の文書を更新する。 -- 繰り返しのレビューフィードバックが見られた場合、チャットで再説明するのではなく、機械的なルール、チェック、またはリンターに昇格させる。 -- 生成された素材は `docs/generated/` に、ソース参照は `docs/references/` に配置する。 -- このファイルを肥大化させるのではなく、小さく最新の文書を追加することを優先する。 - -## 完了の定義 - -変更は以下のすべてが満たされた場合にのみ完了とみなされる: - -- ターゲット動作が実装されている -- 必要な検証が実際に実行された -- 証拠が関連するプランまたは品質文書にリンクされている -- 影響を受ける文書が最新の状態である -- リポジトリが標準スタートアップパスからクリーンに再起動できる - -## セッションの終了 - -セッションを終了する前に: - -1. アクティブな実行プランを更新する。 -2. ドメインやレイヤーに意味のある変更があった場合、`docs/QUALITY_SCORE.md` を更新する。 -3. 債務を先送りした場合、`docs/exec-plans/tech-debt-tracker.md` に新しい債務を記録する。 -4. 適切なタイミングで終了したプランを `docs/exec-plans/completed/` に移動する。 -5. 次のアクションが明確な再起動可能な状態でリポジトリを残す。 diff --git a/courses/learn-harness-engineering/docs/ja/resources/openai-advanced/repo-template/ARCHITECTURE.md b/courses/learn-harness-engineering/docs/ja/resources/openai-advanced/repo-template/ARCHITECTURE.md deleted file mode 100644 index 8c97ae03..00000000 --- a/courses/learn-harness-engineering/docs/ja/resources/openai-advanced/repo-template/ARCHITECTURE.md +++ /dev/null @@ -1,55 +0,0 @@ -# ARCHITECTURE.md - -このファイルはシステムの最上位マップです。簡潔に保ち、必要に応じてより詳細な文書を指すようにしてください。 - -## システム構成 - -- プロダクト: `[製品名に置き換え]` -- プライマリユーザーワークフロー: `[メインワークフローに置き換え]` -- ランタイムサーフェス: `[desktop / web / cli / services / workers]` -- プロダクト動作の信頼できる情報源: `docs/product-specs/` - -## ドメインマップ - -| ドメイン | 目的 | プライマリエントリーポイント | 関連仕様 | -|--------|---------|----------------------|--------------| -| `[domain-a]` | `[所有する機能]` | `[modules / routes / commands]` | `[spec path]` | -| `[domain-b]` | `[所有する機能]` | `[modules / routes / commands]` | `[spec path]` | - -## レイヤーモデル - -エージェントが場当たり的なアーキテクチャを発明しないよう、固定方向モデルを使用する: - -`Types -> Config -> Repo -> Service -> Runtime -> UI` - -横断的な関心事は、レイヤーを直接またぐのではなく、明示的なプロバイダーまたはアダプターの境界を通じて参加させるべきである。 - -## 厳格な依存ルール - -- 下位レイヤーは上位レイヤーに依存してはならない。 -- UIはランタイムまたはサービスの契約をバイパスしてはならない。 -- データアクセスはリポジトリまたは同等のアダプターを通じて行わなければならない。 -- 共有ユーティリティは汎用的であり続け、ドメインロジックを蓄積してはならない。 -- 新しい依存関係は、対応するプランまたは設計文書で正当化されるべきである。 - -## 横断的インターフェース - -| 関心事 | 承認された境界 | 備考 | -|--------|-------------------|-------| -| ロギングとトレーシング | `[provider / utility path]` | `[構造化のみ、場当たり的なコンソール使用は不可]` | -| 認証 | `[provider path]` | `[トークン/セッションルール]` | -| 外部 API | `[client or provider path]` | `[レート制限 / リトライガイダンス]` | -| フィーチャーフラグ | `[flag boundary]` | `[所有権]` | - -## 現在のホットスポット - -- `[エージェントが安全に変更するのが最も難しい領域]` -- `[境界が弱いかテストが脆弱な領域]` - -## 変更チェックリスト - -アーキテクチャ関連のコードに触れる場合: - -1. ドメインマップまたは許可された境界が変更された場合、このファイルを更新する。 -2. 理由が変更された場合、`docs/design-docs/` の関連設計文書を更新する。 -3. ルールを機械的に強制すべき場合、実行可能チェックを追加または更新する。 diff --git a/courses/learn-harness-engineering/docs/ja/resources/openai-advanced/repo-template/docs/DESIGN.md b/courses/learn-harness-engineering/docs/ja/resources/openai-advanced/repo-template/docs/DESIGN.md deleted file mode 100644 index 1591a750..00000000 --- a/courses/learn-harness-engineering/docs/ja/resources/openai-advanced/repo-template/docs/DESIGN.md +++ /dev/null @@ -1,25 +0,0 @@ -# DESIGN.md - -このファイルは設計のエントリポイントです。簡潔に保ち、`docs/design-docs/` 配下のより詳細なファイルへのルーティングに使用してください。 - -## 目的 - -単一のチャット、スプリント、またはレビュアーの記憶を超えて存続すべき、永続的なプロダクトおよびシステム設計上の決定を記録する。 - -## 以下の場合にこれを読む - -- 現在の設計哲学が必要な場合 -- 新しいパターンを導入しようとしている場合 -- どの設計上の決定が確定しており、どれがまだ未決定かを知る必要がある場合 - -## 正式な設計文書 - -- `docs/design-docs/index.md`: 承認済み、提案中、非推奨の文書のインデックス -- `docs/design-docs/core-beliefs.md`: プロジェクト全体のエージェントファーストなビリーフ - -## 設計ルール - -- 設計文書は小さく、最新に保つ。 -- 意思決定領域ごとに1つの文書を優先する。 -- 変更が設計文書に依存する場合、プランや仕様から設計文書にリンクする。 -- 設計ルールが運用上重要になった場合、自動チェックに昇格させるか `ARCHITECTURE.md` を更新する。 diff --git a/courses/learn-harness-engineering/docs/ja/resources/openai-advanced/repo-template/docs/FRONTEND.md b/courses/learn-harness-engineering/docs/ja/resources/openai-advanced/repo-template/docs/FRONTEND.md deleted file mode 100644 index c84c4908..00000000 --- a/courses/learn-harness-engineering/docs/ja/resources/openai-advanced/repo-template/docs/FRONTEND.md +++ /dev/null @@ -1,23 +0,0 @@ -# FRONTEND.md - -このファイルは、エージェントが予測不能にUIパターンを発明しないように、安定したフロントエンドの期待値を定義します。 - -## UIの原則 - -- 新しさよりも明確さを最適化する。 -- インタラクションフローを発見可能かつ再開可能に保つ。 -- 一回限りのバリアントよりも、少数の再利用可能なコンポーネントを優先する。 -- アクセシビリティチェックは仕上げ作業ではなく、通常の検証の一部である。 - -## ガードレール - -- デザインシステムまたはコンポーネントライブラリを `docs/references/` に文書化する。 -- 主要なユーザー向けの状態を記録する:空、読み込み中、成功、エラー、リトライ。 -- コピー、キーボード動作、視覚的階層をフロー全体で一貫させる。 -- UIバグを修正した場合、対応する検証ステップを追加または更新する。 - -## 検証の期待値 - -- 重要なユーザージャーニーの証拠をキャプチャする。 -- ブラウザまたはランタイムの検証ステップを関連するプランに記録する。 -- 視覚的リグレッションが頻発する場合、スクリーンショットまたはDOMチェックを標準化する。 diff --git a/courses/learn-harness-engineering/docs/ja/resources/openai-advanced/repo-template/docs/PLANS.md b/courses/learn-harness-engineering/docs/ja/resources/openai-advanced/repo-template/docs/PLANS.md deleted file mode 100644 index 4bf477ea..00000000 --- a/courses/learn-harness-engineering/docs/ja/resources/openai-advanced/repo-template/docs/PLANS.md +++ /dev/null @@ -1,34 +0,0 @@ -# PLANS.md - -このファイルは、実行プランの作成、更新、完了、アーカイブの方法を定義します。 - -## プランが必要な場合 - -以下の場合に実行プランを作成する: - -- 1セッションを超える作業 -- 複数のサブシステムを変更する作業 -- 自明でない検証またはロールアウトリスクがある作業 -- 記録すべき未決定の決定に依存する作業 - -## プランの場所 - -- `docs/exec-plans/active/`: 現在作業を推進しているプラン -- `docs/exec-plans/completed/`: 将来のエージェントコンテキストのために保持される完了プラン -- `docs/exec-plans/tech-debt-tracker.md`: 先送りされた作業とフォローアップ - -## プランの必須セクション - -- 目的 -- スコープとスコープ外 -- 検証パス -- リスクとブロッカー -- 進捗ログ -- 未決定事項 - -## 運用ルール - -- 1つのアクティブプランには1つの明確に所有された現在のステップがあるべきである。 -- 作業が進むにつれてプランを更新する。静的な文章として扱わない。 -- 決定が実装の方向を変更した場合、プランに記録する。 -- 終了したプランは `completed/` に移動し、エージェントが以前のコンテキストを発見できるようにする。 diff --git a/courses/learn-harness-engineering/docs/ja/resources/openai-advanced/repo-template/docs/PRODUCT_SENSE.md b/courses/learn-harness-engineering/docs/ja/resources/openai-advanced/repo-template/docs/PRODUCT_SENSE.md deleted file mode 100644 index 09c7cb01..00000000 --- a/courses/learn-harness-engineering/docs/ja/resources/openai-advanced/repo-template/docs/PRODUCT_SENSE.md +++ /dev/null @@ -1,24 +0,0 @@ -# PRODUCT_SENSE.md - -このファイルは、エージェントがコードだけからは確実に推測できない、永続的なプロダクト判断を記録します。 - -## プロダクトコア - -- プライマリユーザー: `[置き換え]` -- 達成すべきジョブ: `[置き換え]` -- 取り除くべき主な不満: `[置き換え]` -- 受け入れのための品質基準: `[置き換え]` - -## プロダクトルール - -- 機能数よりもユーザーに見える信頼性を優先する。 -- 曖昧な動作を推測の許可ではなく、仕様のギャップとして扱う。 -- 実装がユーザーが見るものや信頼するものを変更した場合、対応する仕様を更新する。 -- 具体的なフローにはプロダクト仕様を使用し、このファイルは横断的なプロダクト優先事項に使用する。 - -## 不可パターン - -- 隠された破壊的アクション -- ユーザーフィードバックのないサイレントな失敗 -- 表示状態の信頼できる情報源が不明確 -- 一文で説明できない機能 diff --git a/courses/learn-harness-engineering/docs/ja/resources/openai-advanced/repo-template/docs/QUALITY_SCORE.md b/courses/learn-harness-engineering/docs/ja/resources/openai-advanced/repo-template/docs/QUALITY_SCORE.md deleted file mode 100644 index 4e4958e9..00000000 --- a/courses/learn-harness-engineering/docs/ja/resources/openai-advanced/repo-template/docs/QUALITY_SCORE.md +++ /dev/null @@ -1,39 +0,0 @@ -# QUALITY_SCORE.md - -この文書は、リポジトリが時間とともに強くなっているか弱くなっているかを追跡します。 - -## 評価スケール - -- `A`: 検証済み、読みやすい、安定、境界が強制されている -- `B`: 軽微なギャップはあるが動作する -- `C`: 部分的に動作、顕著な混乱または不安定性 -- `D`: 壊れている、安全でない、または構造的に不明確 - -## プロダクトドメイン - -| ドメイン | 評価 | 検証 | エージェント可読性 | テスト安定性 | 主要なギャップ | 最終更新 | -|--------|-------|-------------|-----------------|---------------|----------|-------------| -| `[domain-a]` | - | - | - | - | - | - | -| `[domain-b]` | - | - | - | - | - | - | -| `[domain-c]` | - | - | - | - | - | - | - -## アーキテクチャレイヤー - -| レイヤー | 評価 | 境界の強制 | エージェント可読性 | 主要なギャップ | 最終更新 | -|-------|-------|---------------------|-----------------|----------|-------------| -| Types | - | - | - | - | - | -| Services | - | - | - | - | - | -| Runtime | - | - | - | - | - | -| UI | - | - | - | - | - | - -## ベンチマークスナップショット - -| 日付 | ハーネスバリアント | 完了率 | リトライ | レビュー前の欠陥 | 備考 | -|------|-----------------|----------------|--------|-----------------------|------| -| YYYY-MM-DD | `[baseline / improved / simplified]` | - | - | - | - | - -## 単純化ログ - -| 日付 | 削除されたコンポーネント | 結果 | 決定 | -|------|-------------------|---------|----------| -| YYYY-MM-DD | `[component]` | `[degraded / unchanged]` | `[restore / keep removed]` | diff --git a/courses/learn-harness-engineering/docs/ja/resources/openai-advanced/repo-template/docs/RELIABILITY.md b/courses/learn-harness-engineering/docs/ja/resources/openai-advanced/repo-template/docs/RELIABILITY.md deleted file mode 100644 index bce961fc..00000000 --- a/courses/learn-harness-engineering/docs/ja/resources/openai-advanced/repo-template/docs/RELIABILITY.md +++ /dev/null @@ -1,32 +0,0 @@ -# RELIABILITY.md - -このファイルは、システムが正常で再起動可能であることをどのように証明するかを定義します。 - -## 標準パス - -- ブートストラップ: `[command]` -- 検証: `[command]` -- アプリまたはサービスの起動: `[command]` -- ランタイムのデバッグまたは検査: `[command]` - -## 必要なランタイムシグナル - -- スタートアップとクリティカルフローの構造化ログ -- 主要サービスのヘルスチェック -- 利用可能な場合、遅いパスのトレースまたはタイミングデータ -- 回復可能な障害のユーザーに見えるエラー状態 - -## ゴールデンジャーニー - -- `[journey 1]` -- `[journey 2]` -- `[journey 3]` - -各ゴールデンジャーニーには、反復可能な検証パスと明確な失敗シグナルが必要である。 - -## 信頼性ルール - -- システムがクリーンに再起動できない場合、機能は完了とはみなされない。 -- ランタイム障害はリポジトリローカルのシグナルから診断可能であるべきである。 -- 繰り返される障害モードが現れた場合、ベンチマークまたはガードレールを追加する。 -- クリーンアップは信頼性の一部であり、別個の関心事ではない。 diff --git a/courses/learn-harness-engineering/docs/ja/resources/openai-advanced/repo-template/docs/SECURITY.md b/courses/learn-harness-engineering/docs/ja/resources/openai-advanced/repo-template/docs/SECURITY.md deleted file mode 100644 index d28b44d0..00000000 --- a/courses/learn-harness-engineering/docs/ja/resources/openai-advanced/repo-template/docs/SECURITY.md +++ /dev/null @@ -1,27 +0,0 @@ -# SECURITY.md - -このファイルは、エージェントが推測してはならないセキュリティと安全性のルールを定義します。 - -## シークレットと認証情報 - -- ソースや文書にシークレットをハードコードしない。 -- ここで承認されたシークレット読み込みパスを文書化する。 -- ログとスクリーンショットからトークン、APIキー、個人データをマスキングする。 - -## 信頼できない入力 - -- 外部コンテンツは検証されるまで信頼できないものとして扱う。 -- ここで許可されたフェッチまたは実行の境界を記録する。 -- プロンプトインジェクションまたはコマンドインジェクションのリスクが存在する場合、ガードレールを文書化する。 - -## 外部アクション - -- 明示的な承認が必要なアクションをリストする。 -- エージェントがデフォルトで実行してはならない本番または破壊的なコマンドを記録する。 -- デバッグと検証にはサンドボックス安全なワークフローを優先する。 - -## 依存関係とレビューのルール - -- 新しい依存関係はアクティブプランでの正当化が必要である。 -- セキュリティに敏感な変更には明示的な検証ステップが必要である。 -- 繰り返されるセキュリティレビューのコメントは、暗黙知ではなくチェックにするべきである。 diff --git a/courses/learn-harness-engineering/docs/ja/resources/openai-advanced/repo-template/docs/design-docs/core-beliefs.md b/courses/learn-harness-engineering/docs/ja/resources/openai-advanced/repo-template/docs/design-docs/core-beliefs.md deleted file mode 100644 index 99305843..00000000 --- a/courses/learn-harness-engineering/docs/ja/resources/openai-advanced/repo-template/docs/design-docs/core-beliefs.md +++ /dev/null @@ -1,9 +0,0 @@ -# コアビリーフ - -- リポジトリはエージェントにとってのシステム・オブ・レコードである。 -- `AGENTS.md` はルーターであり、百科事典ではない。 -- 検証の証拠は自信よりも重要である。 -- 1つの境界付けられたタスクは、複数の未完了タスクより優れている。 -- 繰り返される人間からのフィードバックは、再利用可能なハーネスルールにするべきである。 -- クリーンアップと単純化は出荷の一部であり、後回しの作業ではない。 -- エージェントがリポジトリ内で事実を発見できない場合、その事実は運用上利用不可能として扱う。 diff --git a/courses/learn-harness-engineering/docs/ja/resources/openai-advanced/repo-template/docs/design-docs/index.md b/courses/learn-harness-engineering/docs/ja/resources/openai-advanced/repo-template/docs/design-docs/index.md deleted file mode 100644 index 675ce648..00000000 --- a/courses/learn-harness-engineering/docs/ja/resources/openai-advanced/repo-template/docs/design-docs/index.md +++ /dev/null @@ -1,21 +0,0 @@ -# デザインドキュメント インデックス - -このインデックスを、デザイン履歴の発見可能なマップとして使用します。 - -## 承認済み - -- `core-beliefs.md`: エージェントファーストの運用信念と持続的なプロジェクト規範 - -## 提案中 - -- `[新しいデザインドキュメントのパスをここに追加]` - -## 非推奨 - -- `[古いまたは置き換えられたデザインドキュメントを置き換えリンクとともにここに移動]` - -## メンテナンスルール - -- すべてのデザインドキュメントには所有者または更新トリガーを設定する。 -- 古くなったドキュメントは削除するか、放置せずに非推奨としてマークする。 -- アクティブな実行計画を、それが依存するデザインドキュメントにリンクする。 diff --git a/courses/learn-harness-engineering/docs/ja/resources/openai-advanced/repo-template/docs/exec-plans/active/index.md b/courses/learn-harness-engineering/docs/ja/resources/openai-advanced/repo-template/docs/exec-plans/active/index.md deleted file mode 100644 index b658b6eb..00000000 --- a/courses/learn-harness-engineering/docs/ja/resources/openai-advanced/repo-template/docs/exec-plans/active/index.md +++ /dev/null @@ -1,11 +0,0 @@ -# アクティブな計画 - -このフォルダーには、アクティブな実行計画ごとに1つのマークダウンファイルを -保持してください。 - -推奨ファイル名パターン: - -- `YYYY-MM-DD-short-topic.md` - -各アクティブな計画は、新しいエージェントセッションが -リポジトリだけから作業を再開できる十分な最新状態であるべきです。 diff --git a/courses/learn-harness-engineering/docs/ja/resources/openai-advanced/repo-template/docs/exec-plans/completed/index.md b/courses/learn-harness-engineering/docs/ja/resources/openai-advanced/repo-template/docs/exec-plans/completed/index.md deleted file mode 100644 index 86d4b98a..00000000 --- a/courses/learn-harness-engineering/docs/ja/resources/openai-advanced/repo-template/docs/exec-plans/completed/index.md +++ /dev/null @@ -1,5 +0,0 @@ -# 完了した計画 - -完了した計画は削除せず、ここに移動してください。完了した計画は -リポジトリのメモリサーフェスの一部であり、後のエージェント実行が -コードが現在の状態になっている理由を理解するのに役立ちます。 diff --git a/courses/learn-harness-engineering/docs/ja/resources/openai-advanced/repo-template/docs/exec-plans/tech-debt-tracker.md b/courses/learn-harness-engineering/docs/ja/resources/openai-advanced/repo-template/docs/exec-plans/tech-debt-tracker.md deleted file mode 100644 index 2ecd680c..00000000 --- a/courses/learn-harness-engineering/docs/ja/resources/openai-advanced/repo-template/docs/exec-plans/tech-debt-tracker.md +++ /dev/null @@ -1,8 +0,0 @@ -# テクニカルデトラッカー - -このファイルは、現実に存在し、認識されており、意図的に先送りされている -テクニカルデットの記録に使用します。 - -| 日付 | 領域 | デット | 先送りの理由 | リスク | 次のトリガー | -|------|------|------|--------------|------|--------------| -| YYYY-MM-DD | `[area]` | `[debt]` | `[reason]` | `[risk]` | `[見直し時期]` | diff --git a/courses/learn-harness-engineering/docs/ja/resources/openai-advanced/repo-template/docs/generated/db-schema.md b/courses/learn-harness-engineering/docs/ja/resources/openai-advanced/repo-template/docs/generated/db-schema.md deleted file mode 100644 index c53a2ba7..00000000 --- a/courses/learn-harness-engineering/docs/ja/resources/openai-advanced/repo-template/docs/generated/db-schema.md +++ /dev/null @@ -1,14 +0,0 @@ -# データベーススキーマ - -このフォルダーは、エージェントがコードからリバースエンジニアリングすることなく -確認できる、生成済みまたは派生した成果物のために使用します。 - -## ソース - -- 生成元: `[command or source path]` -- 最終更新日: `YYYY-MM-DD` - -## 注意事項 - -- 生成されたセクションは手動で編集しないでください。 -- 基になるスキーマが変更された場合は、このファイルを再生成してください。 diff --git a/courses/learn-harness-engineering/docs/ja/resources/openai-advanced/repo-template/docs/product-specs/index.md b/courses/learn-harness-engineering/docs/ja/resources/openai-advanced/repo-template/docs/product-specs/index.md deleted file mode 100644 index ad409e01..00000000 --- a/courses/learn-harness-engineering/docs/ja/resources/openai-advanced/repo-template/docs/product-specs/index.md +++ /dev/null @@ -1,13 +0,0 @@ -# プロダクト仕様 インデックス - -このフォルダーは、現在のユーザー向け動作仕様のために使用します。 - -## アクティブな仕様 - -- `new-user-onboarding.md` - -## ルール - -- 仕様はユーザーに見える動作と受け入れ基準を記述する。 -- 実装が仕様から逸脱した場合、同じセッションでどちらかを更新する。 -- 新しいエージェントがプロダクトのスコープを素早く発見できるよう、このインデックスを最新に保つ。 diff --git a/courses/learn-harness-engineering/docs/ja/resources/openai-advanced/repo-template/docs/product-specs/new-user-onboarding.md b/courses/learn-harness-engineering/docs/ja/resources/openai-advanced/repo-template/docs/product-specs/new-user-onboarding.md deleted file mode 100644 index 2d2cfcee..00000000 --- a/courses/learn-harness-engineering/docs/ja/resources/openai-advanced/repo-template/docs/product-specs/new-user-onboarding.md +++ /dev/null @@ -1,26 +0,0 @@ -# 新規ユーザーオンボーディング - -## 目標 - -新規ユーザーが持つべき初回実行体験を記述する。 - -## 開始条件 - -- `[フロー開始前の状態]` - -## ユーザーフロー - -1. `[ステップ1]` -2. `[ステップ2]` -3. `[ステップ3]` - -## 受け入れ基準 - -- `[観察可能な結果]` -- `[観察可能な結果]` -- `[観察可能な結果]` - -## 障害状態 - -- `[回復可能なエラーとユーザーフィードバック]` -- `[ブロックされた状態とフォールバック]` diff --git a/courses/learn-harness-engineering/docs/ja/resources/openai-advanced/repo-template/docs/references/design-system-reference-llms.txt b/courses/learn-harness-engineering/docs/ja/resources/openai-advanced/repo-template/docs/references/design-system-reference-llms.txt deleted file mode 100644 index 6f1c612c..00000000 --- a/courses/learn-harness-engineering/docs/ja/resources/openai-advanced/repo-template/docs/references/design-system-reference-llms.txt +++ /dev/null @@ -1,9 +0,0 @@ -Purpose: store model-friendly reference material for the design system. - -Suggested contents: -- component naming rules -- spacing and typography tokens -- state variants -- accessibility expectations - -Keep this file concise and refresh it when the upstream design system changes. diff --git a/courses/learn-harness-engineering/docs/ja/resources/openai-advanced/repo-template/docs/references/nixpacks-llms.txt b/courses/learn-harness-engineering/docs/ja/resources/openai-advanced/repo-template/docs/references/nixpacks-llms.txt deleted file mode 100644 index c30cc9c2..00000000 --- a/courses/learn-harness-engineering/docs/ja/resources/openai-advanced/repo-template/docs/references/nixpacks-llms.txt +++ /dev/null @@ -1,8 +0,0 @@ -Purpose: store a clean agent-readable extract of the deployment or packaging -rules your repository depends on. - -Suggested contents: -- build entrypoints -- runtime assumptions -- environment variable expectations -- common failure signatures diff --git a/courses/learn-harness-engineering/docs/ja/resources/openai-advanced/repo-template/docs/references/uv-llms.txt b/courses/learn-harness-engineering/docs/ja/resources/openai-advanced/repo-template/docs/references/uv-llms.txt deleted file mode 100644 index f2ceba9a..00000000 --- a/courses/learn-harness-engineering/docs/ja/resources/openai-advanced/repo-template/docs/references/uv-llms.txt +++ /dev/null @@ -1,8 +0,0 @@ -Purpose: store a compact reference for your Python package and environment -workflow when `uv` or similar tooling matters to the repo. - -Suggested contents: -- install and sync commands -- lockfile policy -- virtualenv expectations -- verification commands diff --git a/courses/learn-harness-engineering/docs/ja/resources/openai-advanced/repo-template/index.md b/courses/learn-harness-engineering/docs/ja/resources/openai-advanced/repo-template/index.md deleted file mode 100644 index 719cbfb8..00000000 --- a/courses/learn-harness-engineering/docs/ja/resources/openai-advanced/repo-template/index.md +++ /dev/null @@ -1,21 +0,0 @@ -# Advanced Repo Template - -最小限のハーネスだけでなく、OpenAIスタイルのエージェントファーストなドキュメントサーフェスが必要な場合、このスターターを実際のリポジトリにコピーしてください。 - -## コピー順序 - -1. `AGENTS.md` と `ARCHITECTURE.md` をリポジトリルートにコピーする。 -2. `docs/` ツリー全体をコピーする。 -3. 最初に `docs/PRODUCT_SENSE.md`、`docs/QUALITY_SCORE.md`、`docs/RELIABILITY.md` を記入する。 -4. `docs/exec-plans/active/` に最初のアクティブプランを追加する。 -5. エントリポイントファイルは短く保ち、詳細はリンク先の文書に誘導する。 - -## このテンプレートが最適化しているもの - -- 永続的なリポジトリローカルコンテキスト -- 巨大な単一の指示ファイルではなく、プログレッシブディスクロージャー -- 明示的なプランライフサイクル -- 時間経過による品質追跡 -- エージェントと人間の両方にとって読みやすい境界 - -ここにあるすべてのファイルはスターターとして扱ってください。プレースホルダー、例、サンプルコマンドを実際のプロジェクトの詳細に置き換えてから使用してください。 diff --git a/courses/learn-harness-engineering/docs/ja/resources/openai-advanced/sops/chrome-devtools-validation-loop.md b/courses/learn-harness-engineering/docs/ja/resources/openai-advanced/sops/chrome-devtools-validation-loop.md deleted file mode 100644 index 69ea2d68..00000000 --- a/courses/learn-harness-engineering/docs/ja/resources/openai-advanced/sops/chrome-devtools-validation-loop.md +++ /dev/null @@ -1,52 +0,0 @@ -# SOP: Chrome DevTools 検証ループ - -この SOP は、UI 作業が実際のランタイムでの操作に依存しており、 -スクリーンショット、DOM 状態、コンソール出力がコードの検査単独よりも -重要な場合に使用します。 - -## 目標 - -UI 検証を、エージェントがジャーニーがクリーンになるまで実行できる -再現可能な操作ループに変換する。 - -## コアループ - -1. 対象のページまたはアプリインスタンスを選択する。 -2. 古いコンソールノイズをクリアする。 -3. BEFORE 状態をキャプチャする。 -4. UI パスをトリガーする。 -5. 操作中のランタイムイベントを観察する。 -6. AFTER 状態をキャプチャする。 -7. 必要に応じて修正を適用し、アプリを再起動する。 -8. ジャーニーがクリーンになるまで検証を再実行する。 - -## 必要な入力 - -- 安定した起動コマンド -- 再現可能な UI ジャーニー -- DOM、コンソール、またはスクリーンショットのスナップショットを取得する方法 -- 「クリーン」とみなされる基準のルール - -## 実行 SOP - -1. アクティブな計画にターゲットジャーニーを記述する。 -2. 成功を観察可能な条件で定義する: テキストの存在、ボタンの有効化、エラーの消失、 - コンソールのクリーン、リクエストの成功。 -3. 操作前に初期状態のスナップショットを取得する。 -4. 一度に正確に1つのパスをトリガーする。 -5. ランタイムイベント、DOM 変更、可視出力を記録する。 -6. ジャーニーが失敗した場合、最も責任のある最小のレイヤーを修正して再起動する。 -7. 同じパスを再実行し、BEFORE/AFTER の証拠を比較する。 - -## クリーンの基準 - -- 意図された可視状態が存在する -- 予期しないエラーが存在しない -- コンソールノイズが理解されているかクリアされている -- 同じパスを再実行すると同じ結果が得られる - -## 更新すべきリポジトリ成果物 - -- アクティブな実行計画 -- ジャーニーがゴールデンパスになった場合の `docs/RELIABILITY.md` -- 可視動作が変更された場合のプロダクト仕様 diff --git a/courses/learn-harness-engineering/docs/ja/resources/openai-advanced/sops/encode-knowledge-into-repo.md b/courses/learn-harness-engineering/docs/ja/resources/openai-advanced/sops/encode-knowledge-into-repo.md deleted file mode 100644 index edd76cb2..00000000 --- a/courses/learn-harness-engineering/docs/ja/resources/openai-advanced/sops/encode-knowledge-into-repo.md +++ /dev/null @@ -1,45 +0,0 @@ -# SOP: 目に見えないナレッジをリポジトリにエンコードする - -この SOP は、重要なコンテキストが Google ドキュメント、チャットスレッド、 -チケット、または人の頭の中にのみ存在する場合に使用します。 - -## 目標 - -エージェントから見えないナレッジをコードベースで発見可能にし、 -新しいセッションが事前の会話に頼らずに対応できるようにする。 - -## トリガーシグナル - -- エージェントがシステムの動作について繰り返し質問する。 -- 人が「Slackでこう決めた」や「先週Xが言ったことに従って」と言う。 -- レビューで、リポジトリ内に書かれていないプロダクトやセキュリティのルールが参照される。 -- 新しいセッションが、すでに確定されているはずの発見作業を繰り返す。 - -## 実行 SOP - -1. 目に見えないナレッジソースをリストアップする: ドキュメント、チャット、暗黙のチームルール、口頭の決定。 -2. 各ソースについて確認する: これはアーキテクチャ、プロダクト動作、セキュリティポリシー、 - 信頼性要件、計画コンテキスト、参照資料のいずれか? -3. 該当するリポジトリ成果物にエンコードする: - - アーキテクチャ -> `ARCHITECTURE.md` - - プロダクト動作 -> `docs/product-specs/` - - 設計の根拠 -> `docs/design-docs/` - - 実行状態 -> `docs/exec-plans/` - - 繰り返し使用される外部参照 -> `docs/references/` - - 品質または信頼性の要件 -> `docs/QUALITY_SCORE.md` または `docs/RELIABILITY.md` -4. 曖昧な記述を運用上有用な表現に置き換える。 -5. 古いコピーを削除または非推奨にし、リポジトリが1つの発見可能な真実を保つようにする。 - -## 良いエンコーディングのルール - -- 文学的な完全さではなく、発見可能性のために書く。 -- 明確なファイル名を持つ短いドキュメントを優先する。 -- 関連する成果物同士をリンクする。 -- 会議の議事録ではなく、持続的なルールを保存する。 -- 決定が行われたのと同じセッションでリポジトリを更新する。 - -## 完了の定義 - -- 新しいエージェントが人間に質問せずに関連ルールを発見できる。 -- 同じ事実が矛盾する複数のファイルに散らばっていない。 -- 新しい成果物が、それが管理するコードやワークフローの近くに配置されている。 diff --git a/courses/learn-harness-engineering/docs/ja/resources/openai-advanced/sops/index.md b/courses/learn-harness-engineering/docs/ja/resources/openai-advanced/sops/index.md deleted file mode 100644 index d3c8fc3a..00000000 --- a/courses/learn-harness-engineering/docs/ja/resources/openai-advanced/sops/index.md +++ /dev/null @@ -1,25 +0,0 @@ -# OpenAI アドバンスト SOP - -これらの SOP は、記事の運用パターンを、フォローまたは適応可能な -具体的な実行プレイブックに変換したものです。 - -## 含まれる SOP - -- [`layered-domain-architecture.md`](./layered-domain-architecture.md): - 明示的なレイヤーと横断的関心事の境界を確立する -- [`encode-knowledge-into-repo.md`](./encode-knowledge-into-repo.md): - 目に見えないナレッジをチャット、ドキュメント、記憶からリポジトリ内のファイルに移動する -- [`observability-feedback-loop.md`](./observability-feedback-loop.md): - エージェントにログ、メトリクス、トレース、再現可能なデバグループを提供する -- [`chrome-devtools-validation-loop.md`](./chrome-devtools-validation-loop.md): - ブラウザ自動化とスナップショットを使用して、クリーンになるまで UI の動作を検証する - -## 使用方法 - -1. 現在のボトルネックに合った SOP を選ぶ。 -2. チェックリストを使用して、欠けている成果物やツールをセットアップする。 -3. 結果として得られたルールを、コピーした `repo-template/` ドキュメントにエンコードする。 -4. 繰り返しのレビューコメントをチェック、スクリプト、ガードレールに変換する。 - -これらは盲目的にフォローすることを意図したものではありません。 -ハーネスをより読みやすく、強制可能で、再現可能にするためのものです。 diff --git a/courses/learn-harness-engineering/docs/ja/resources/openai-advanced/sops/layered-domain-architecture.md b/courses/learn-harness-engineering/docs/ja/resources/openai-advanced/sops/layered-domain-architecture.md deleted file mode 100644 index a7e75132..00000000 --- a/courses/learn-harness-engineering/docs/ja/resources/openai-advanced/sops/layered-domain-architecture.md +++ /dev/null @@ -1,51 +0,0 @@ -# SOP: レイヤードドメインアーキテクチャ - -この SOP は、エージェントが境界を繰り返し違反している、レイヤー間で -ロジックを複製している、または数セッション後にレビューが困難なコードを -生成している場合に使用します。 - -## 目標 - -ドメインの境界を、エージェントが構造を知らずに劣化させることなく -迅速に移動できるほど明示的にする。 - -## ターゲットモデル - -ビジネスドメイン内では、次の方向フローを推奨する: - -`Types -> Config -> Repo -> Service -> Runtime -> UI` - -横断的関心事は、明示的なプロバイダーまたはアダプターを通じて出入りするべきである。 -共有ユーティリティはドメインの外に置き、ドメインロジックを蓄積しないようにする。 - -## セットアップチェックリスト - -- `ARCHITECTURE.md` で現在のドメインを定義する。 -- `ARCHITECTURE.md` に許可される依存関係の方向を記述する。 -- 認証、テレメトリ、外部 API などの横断的インターフェースを記録する。 -- 現在最も困難な境界違反について短いメモを1つ追加する。 -- lint、テスト、またはスクリプトによって機械的に強制すべきものを決定する。 - -## 実行 SOP - -1. 実装スタイルに触れる前に、コードベースをドメインにマッピングする。 -2. 各ドメインについて、許可されるレイヤーの順序を特定する。 -3. すべての横断的関心事を特定し、プロバイダーまたはアダプターを通じてルーティングする。 -4. 曖昧な共有ロジックを、所有するドメインまたは真に汎用的なユーティリティに移動する。 -5. ルールを `ARCHITECTURE.md` に文書化する。 -6. 最もコストの高い違反に対して1つの実行可能ガードレールを追加する。 -7. 変更後に品質スコアを更新する。 - -## 完了の定義 - -- 新しいエージェントがどのレイヤーが変更を担当しているかを判断できる。 -- UI コードがリポジトリや外部副作用に直接アクセスしなくなる。 -- 横断的関心事に名前付きのエントリポイントがある。 -- 少なくとも1つの重要な境界が機械的に強制されている。 - -## 更新すべきリポジトリ成果物 - -- `ARCHITECTURE.md` -- `docs/QUALITY_SCORE.md` -- 根拠が変更された場合の `docs/design-docs/` -- `docs/PLANS.md` またはアクティブな実行計画 diff --git a/courses/learn-harness-engineering/docs/ja/resources/openai-advanced/sops/observability-feedback-loop.md b/courses/learn-harness-engineering/docs/ja/resources/openai-advanced/sops/observability-feedback-loop.md deleted file mode 100644 index aaf9102b..00000000 --- a/courses/learn-harness-engineering/docs/ja/resources/openai-advanced/sops/observability-feedback-loop.md +++ /dev/null @@ -1,45 +0,0 @@ -# SOP: オブザーバビリティフィードバックループ - -この SOP は、デバッグが遅い、エージェントが証拠なしに成功を主張し続ける、 -またはランタイムの動作がコードよりも確認が難しい場合に使用します。 - -## 目標 - -エージェントにログ、メトリクス、トレース、実行可能なワークロードに対する -ローカルフィードバックループを提供し、コードの検査だけでなく、 -実行結果から推論できるようにする。 - -## 最小スタック - -- アプリケーションが構造化ログを出力する -- 可能な場合、アプリケーションがメトリクスとトレースを出力する -- ローカルのファンアウトまたはコレクションレイヤー -- ログ、メトリクス、トレース用のクエリインターフェース -- 各変更後に再実行するための再現可能なワークロードまたはユーザージャーニー - -## 実行 SOP - -1. 最も重要なゴールデンランタイムジャーニーを定義する。 -2. スタートアップとクリティカルパスに構造化ログを追加する。 -3. レイテンシ、失敗数、キューサイズなどの有用なメトリクスを追加する。 -4. 遅いまたは複数ステップのフローにトレースまたはタイミングマーカーを追加する。 -5. ローカル開発環境からシグナルをクエリ可能にする。 -6. エージェントに再実行するための再現可能なワークロードまたはシナリオを1つ与える。 -7. ループを必須にする: クエリ -> 相関 -> 推論 -> 実装 -> 再起動 -> - 再実行 -> 検証。 - -## デバッグセッションチェックリスト - -- 何が失敗したか? -- どのシグナルが失敗を証明しているか? -- どのレイヤーが失敗を担当しているか? -- 修正後に何が変更されたか? -- アプリは正常に再起動したか? -- 同じワークロードは再実行後に成功したか? - -## 完了の定義 - -- エージェントがランタイムの証拠から障害モードを説明できる。 -- 同じワークロードを各変更後に再実行できる。 -- 再起動と再実行が通常のタスクループの一部である。 -- 信頼性シグナルが `docs/RELIABILITY.md` に文書化されている。 diff --git a/courses/learn-harness-engineering/docs/ja/resources/reference/coding-agent-startup-flow.md b/courses/learn-harness-engineering/docs/ja/resources/reference/coding-agent-startup-flow.md deleted file mode 100644 index 7f95f522..00000000 --- a/courses/learn-harness-engineering/docs/ja/resources/reference/coding-agent-startup-flow.md +++ /dev/null @@ -1,33 +0,0 @@ -# コーディングエージェント起動フロー - -初期化完了後、毎セッションの開始時にこれを使用してください。 - -## 固定起動テンプレート - -1. `pwd` を実行しリポジトリルートを確認する。 -2. `claude-progress.md` を読む。 -3. `feature_list.json` を読む。 -4. `git log --oneline -5` で最近のコミットを確認する。 -5. `./init.sh` を実行する。 -6. ベースラインスモークまたはエンドツーエンドパスを実行する。 -7. ベースラインが壊れている場合、まずそれを修正する。 -8. 最優先の未完了機能を選択する。 -9. 検証されるか明示的にブロックされるまでその機能のみに取り組む。 - -## なぜこの順序が重要か - -- `pwd` は誤ったディレクトリでの作業を防ぐ。 -- 進捗ファイルと機能ファイルは新しい編集が始まる前に永続的な状態を復元する。 -- 最近のコミットは最近何が変更されたかを説明する。 -- `init.sh` は記憶への依存ではなく起動を標準化する。 -- ベースライン検証は新しい作業が隠す前に壊れた開始状態を検出する。 - -## セッション終了ミラー - -同じセッションは以下で終了すべき: - -1. 進捗を記録 -2. 機能状態を更新 -3. 必要に応じて引き継ぎを書く -4. 安全な作業をコミット -5. きれいな再起動パスを残す diff --git a/courses/learn-harness-engineering/docs/ja/resources/reference/index.md b/courses/learn-harness-engineering/docs/ja/resources/reference/index.md deleted file mode 100644 index 0d50096d..00000000 --- a/courses/learn-harness-engineering/docs/ja/resources/reference/index.md +++ /dev/null @@ -1,31 +0,0 @@ -# 日本語リファレンス - -これらのノートは、テンプレートを緩いファイルの集まりではなく、動作する harness として -使用する方法を説明します。 - -## 内部リファレンスノート - -- [`method-map.md`](./method-map.md): 一般的な長時間実行の失敗モードを、最初に対処する成果物やポリシーにマッピング -- [`initializer-agent-playbook.md`](./initializer-agent-playbook.md): 機能作業が始まる前にイニシャライザーが残すべきもの -- [`coding-agent-startup-flow.md`](./coding-agent-startup-flow.md): 後のコーディング実行用の固定セッション開始フロー -- [`prompt-calibration.md`](./prompt-calibration.md): ルート指示を肥大化・脆弱化させずにシャープに保つ方法 - -## 主要記事 - -このリストは意図的に絞られています。Harness とはモデル周りの実行システムを意味します:エージェントループ、ツール実行、サンドボックス、状態、コンテキスト、検証、終了、オーケストレーション、オブザーバビリティです。 - -元の3つの記事がコースの骨格であり続けます: - -- [OpenAI: Harness engineering: leveraging Codex in an agent-first world](https://openai.com/index/harness-engineering/) (2026-02-11) -- [Anthropic: Effective harnesses for long-running agents](https://www.anthropic.com/engineering/effective-harnesses-for-long-running-agents) (2025-11-26) -- [Anthropic: Harness design for long-running application development](https://www.anthropic.com/engineering/harness-design-long-running-apps) (2026-03-24) - -## 推奨読書順 - -1. `method-map.md` -2. `initializer-agent-playbook.md` -3. `coding-agent-startup-flow.md` -4. `prompt-calibration.md` -5. OpenAI Harness engineering -6. Anthropic Effective harnesses -7. Anthropic Harness design diff --git a/courses/learn-harness-engineering/docs/ja/resources/reference/initializer-agent-playbook.md b/courses/learn-harness-engineering/docs/ja/resources/reference/initializer-agent-playbook.md deleted file mode 100644 index f59317c3..00000000 --- a/courses/learn-harness-engineering/docs/ja/resources/reference/initializer-agent-playbook.md +++ /dev/null @@ -1,37 +0,0 @@ -# イニシャライザーエージェントプレイブック - -インクリメンタルな機能作業が始まる前の、リポジトリでの最初の本格的なセッションに -このプレイブックを使用してください。 - -## 目標 - -後のセッションが起動コマンド、現在のステータス、タスク境界を再導出することなく -動作を実装できるよう、安定した稼働面を作成する。 - -## 必須出力 - -イニシャライザーは少なくとも以下の成果物を残すべき: - -- `AGENTS.md` や `CLAUDE.md` などのルート指示ファイル -- `feature_list.json` などの機械可読な機能面 -- `claude-progress.md` などの永続的な進捗成果物 -- `init.sh` などの標準起動ヘルパー -- ベースラインスキャフォールドを捉えた最初の安全なコミット - -## チェックリスト - -1. 標準起動パスを定義する。 -2. 標準検証パスを定義する。 -3. 進捗ログを作成し開始状態を記録する。 -4. 作業をステータス付きの明示的な機能に分解する。 -5. 最初のクリーンなベースラインコミットを作成する。 - -## 成功テスト - -以前のチャットコンテキストを持たない新しいセッションが以下に答えられるべき: - -- このリポジトリは何をするのか -- どうやって起動するのか -- どうやって検証するのか -- 何が未完了か -- 次の最適ステップは何か diff --git a/courses/learn-harness-engineering/docs/ja/resources/reference/method-map.md b/courses/learn-harness-engineering/docs/ja/resources/reference/method-map.md deleted file mode 100644 index 1c863b05..00000000 --- a/courses/learn-harness-engineering/docs/ja/resources/reference/method-map.md +++ /dev/null @@ -1,19 +0,0 @@ -# メソッドマップ - -この表は、長時間実行されるコーディングエージェントの最も一般的な失敗モードを、 -それを最初に修正する成果物や運用ルールにマッピングします。 - -| 失敗モード | 実践でどう見えるか | 主要な修正 | 補助成果物 | -| --- | --- | --- | --- | -| コールドスタートの混乱 | 新しいセッションがセットアップとステータスの再発見に大部分の時間を費やす | リポジトリをシステムオブレコードにする | `claude-progress.md` | -| スコープの拡散 | エージェントが複数の機能を開始し、どれもきれいに完了しない | アクティブスコープを制限する | `feature_list.json` | -| 早すぎる完了 | エージェントがコード編集後に完了を主張するが、実行可能な証拠の前 | 完了を証拠に紐付ける | `clean-state-checklist.md` | -| 脆弱な起動 | 毎セッションがプロジェクトの起動方法を再学習する | セットアップと検証を標準化する | `init.sh` | -| 弱い引き継ぎ | 次のセッションが何が検証済み、壊れている、次なのか判断できない | 明示的な引き継ぎで終了する | `session-handoff.md` | -| 主観的なレビュー | レビュー品質が好みや記憶に依存する | 固定カテゴリで出力を評価する | `evaluator-rubric.md` | - -## 運用原則 - -観察された失敗モードを直接解決する最小の成果物を追加する。 -すべての信頼性問題を1つのグローバル指示ファイルにより多くのテキストを投入して -解決することを避ける。 diff --git a/courses/learn-harness-engineering/docs/ja/resources/reference/prompt-calibration.md b/courses/learn-harness-engineering/docs/ja/resources/reference/prompt-calibration.md deleted file mode 100644 index 507adbe9..00000000 --- a/courses/learn-harness-engineering/docs/ja/resources/reference/prompt-calibration.md +++ /dev/null @@ -1,25 +0,0 @@ -# プロンプトキャリブレーション - -ルート指示は操作フレームを定義すべきで、可能なすべての手を定義すべきではない。 - -## ルートファイルに残すもの - -- リポジトリの目的とスコープ -- 起動パス -- 検証パス -- 譲れない制約 -- 必要な状態成果物 -- セッション終了ルール - -## ルートファイルから出すもの - -- 長い歴史的なエッジケース -- トピック固有の実装詳細 -- コードの近くにあるべきローカルアーキテクチャノート -- 1つのサブシステムにのみ適用される例 - -## ワーキングルール - -ルートファイルは新しいセッションが素早く方向付けされるのを助けるべき。ファイルが -過去のすべての失敗の捨て場になっている場合、詳細をより小さなドキュメントに分割し、 -代わりにリンクする。 diff --git a/courses/learn-harness-engineering/docs/ja/resources/templates/AGENTS.md b/courses/learn-harness-engineering/docs/ja/resources/templates/AGENTS.md deleted file mode 100644 index ef8fee05..00000000 --- a/courses/learn-harness-engineering/docs/ja/resources/templates/AGENTS.md +++ /dev/null @@ -1,53 +0,0 @@ -# AGENTS.md - -このリポジトリは長時間実行されるコーディングエージェント作業用に設計されています。目標は -生のコード出力を最大化することではありません。目標は、次のセッションが推測なしに -続けられる状態でリポジトリを残すことです。 - -## スタートアップワークフロー - -コードを書く前に: - -1. `pwd` で作業ディレクトリを確認する。 -2. `claude-progress.md` で最新の検証済み状態と次のステップを読む。 -3. `feature_list.json` を読み、最優先の未完了機能を選択する。 -4. `git log --oneline -5` で最近のコミットを確認する。 -5. `./init.sh` を実行する。 -6. 新しい作業を始める前に必要なスモークまたはエンドツーエンド検証を実行する。 - -ベースライン検証が既に失敗している場合、まずそれを修正する。壊れた開始状態の上に -新しい機能作業を積み上げないこと。 - -## ワーキングルール - -- 一度に一つの機能に取り組む。 -- コードを追加しただけで機能を完了とマークしない。 -- ブロッカーが狭範囲のサポート修正を強制しない限り、選択した機能スコープ内に変更を留める。 -- 実装中に検証ルールを黙って変更しない。 -- チャットサマリーより永続的なリポジトリ成果物を優先する。 - -## 必須成果物 - -- `feature_list.json`:機能状態の真実の情報源 -- `claude-progress.md`:セッションログと現在の検証済みステータス -- `init.sh`:標準の起動・検証パス -- `session-handoff.md`:大きなセッション用のオプションのコンパクトな引き継ぎ - -## 完了の定義 - -機能は以下がすべて true の場合にのみ完了: - -- ターゲット動作が実装されている -- 必要な検証が実際に実行された -- 証拠が `feature_list.json` または `claude-progress.md` に記録されている -- リポジトリが標準起動パスから再起動可能である - -## セッション終了時 - -セッションを終了する前に: - -1. `claude-progress.md` を更新する。 -2. `feature_list.json` を更新する。 -3. 未解決のリスクやブロッカーを記録する。 -4. 作業が安全な状態になったら説明的なメッセージでコミットする。 -5. 次のセッションがすぐ `./init.sh` を実行できるようリポジトリをきれいに残す。 diff --git a/courses/learn-harness-engineering/docs/ja/resources/templates/CLAUDE.md b/courses/learn-harness-engineering/docs/ja/resources/templates/CLAUDE.md deleted file mode 100644 index d819f000..00000000 --- a/courses/learn-harness-engineering/docs/ja/resources/templates/CLAUDE.md +++ /dev/null @@ -1,44 +0,0 @@ -# CLAUDE.md - -あなたは長時間の実装作業用に設計されたリポジトリで作業しています。 -速度よりも、信頼性のある完了、セッション間の継続性、明示的な検証を優先してください。 - -## 運用ループ - -毎セッションの開始時に: - -1. `pwd` を実行し、予想されるリポジトリルートにいることを確認する。 -2. `claude-progress.md` を読む。 -3. `feature_list.json` を読む。 -4. `git log --oneline -5` で最近のコミットを確認する。 -5. `./init.sh` を実行する。 -6. ベースラインのスモークまたはエンドツーエンドパスが既に壊れていないか確認する。 - -その後、未完了の機能を正確に1つ選び、検証されるかブロックされている理由が文書化されるまでその機能のみに取り組む。 - -## ルール - -- 一度に1つのアクティブ機能のみ。 -- 実行可能な証拠なしに完了を主張しない。 -- 未完了の作業を隠すために機能リストを書き直さない。 -- タスクが完了したように見せるためにテストを削除または弱体化させない。 -- リポジトリ成果物をシステムオブレコードとして使用する。 - -## 必須ファイル - -- `feature_list.json` -- `claude-progress.md` -- `init.sh` -- `session-handoff.md` — コンパクトな引き継ぎが有用な場合 - -## 完了ゲート - -必要な検証が成功し、結果が記録された後にのみ、機能を `passing` に移行できる。 - -## 停止する前に - -1. 進捗ログを更新する。 -2. 機能状態を更新する。 -3. まだ壊れているまたは未検証のものを記録する。 -4. リポジトリが安全に再開できる状態になったらコミットする。 -5. 次のセッションのためにきれいな再起動パスを残す。 diff --git a/courses/learn-harness-engineering/docs/ja/resources/templates/claude-progress.md b/courses/learn-harness-engineering/docs/ja/resources/templates/claude-progress.md deleted file mode 100644 index 34a384f7..00000000 --- a/courses/learn-harness-engineering/docs/ja/resources/templates/claude-progress.md +++ /dev/null @@ -1,35 +0,0 @@ -# 進捗ログ - -## 現在の検証済み状態 - -- リポジトリルート: -- 標準起動パス: -- 標準検証パス: -- 現在の最優先未完了機能: -- 現在のブロッカー: - -## セッションログ - -### セッション 001 - -- 日付: -- 目標: -- 完了: -- 検証実行: -- 記録された証拠: -- コミット: -- 更新されたファイルや成果物: -- 既知のリスクや未解決の問題: -- 次の最適ステップ: - -### セッション 002 - -- 日付: -- 目標: -- 完了: -- 検証実行: -- 記録された証拠: -- コミット: -- 更新されたファイルや成果物: -- 既知のリスクや未解決の問題: -- 次の最適ステップ: diff --git a/courses/learn-harness-engineering/docs/ja/resources/templates/clean-state-checklist.md b/courses/learn-harness-engineering/docs/ja/resources/templates/clean-state-checklist.md deleted file mode 100644 index d9e95927..00000000 --- a/courses/learn-harness-engineering/docs/ja/resources/templates/clean-state-checklist.md +++ /dev/null @@ -1,8 +0,0 @@ -# クリーン状態チェックリスト - -- [ ] 標準起動パスがまだ機能する。 -- [ ] 標準検証パスがまだ実行される。 -- [ ] 現在の進捗が進捗ログに記録されている。 -- [ ] 機能状態が実際に合格しているものと未検証のものを反映している。 -- [ ] 半完了のステップが未記録のまま残っていない。 -- [ ] 次のセッションが手動修復なしに継続できる。 diff --git a/courses/learn-harness-engineering/docs/ja/resources/templates/evaluator-rubric.md b/courses/learn-harness-engineering/docs/ja/resources/templates/evaluator-rubric.md deleted file mode 100644 index 99356053..00000000 --- a/courses/learn-harness-engineering/docs/ja/resources/templates/evaluator-rubric.md +++ /dev/null @@ -1,24 +0,0 @@ -# 評価ルーブリック - -実装後、最終承認前にこのルーブリックを使用してください。 - -| カテゴリ | 質問 | スコア (0-2) | ノート | -| --- | --- | --- | --- | -| 正確性 | 実装された動作は要求された機能に一致しているか? | | | -| 検証 | 必要なチェックが証拠付きで実際に実行されたか? | | | -| スコープ規律 | セッションは選択された機能スコープ内に留まったか? | | | -| 信頼性 | 結果は修復なしに再起動や再実行に耐えるか? | | | -| 保守性 | コードとドキュメントは次のセッションにとって十分明確か? | | | -| 引き継ぎ準備 | 新しいセッションがリポジトリ成果物のみから作業を続けられるか? | | | - -## 判定 - -- 承認 -- 修正 -- ブロック - -## 必須フォローアップ - -- 欠落している証拠: -- 必要な修正: -- 次回のレビュートリガー: diff --git a/courses/learn-harness-engineering/docs/ja/resources/templates/feature_list.json b/courses/learn-harness-engineering/docs/ja/resources/templates/feature_list.json deleted file mode 100644 index 138adc6a..00000000 --- a/courses/learn-harness-engineering/docs/ja/resources/templates/feature_list.json +++ /dev/null @@ -1,64 +0,0 @@ -{ - "project": "replace-with-project-name", - "last_updated": "YYYY-MM-DD", - "rules": { - "single_active_feature": true, - "passing_requires_evidence": true, - "do_not_skip_verification": true - }, - "status_legend": { - "not_started": "Work has not begun.", - "in_progress": "The feature is the current active task.", - "blocked": "Work cannot continue until a documented blocker is resolved.", - "passing": "Required verification has passed and evidence is recorded." - }, - "features": [ - { - "id": "chat-001", - "priority": 1, - "area": "chat", - "title": "Create a new conversation", - "user_visible_behavior": "A user can click New Chat and see a fresh empty conversation.", - "status": "not_started", - "verification": [ - "Open the app.", - "Click New Chat.", - "Verify a new conversation appears in the sidebar.", - "Verify the main panel shows an empty conversation state." - ], - "evidence": [], - "notes": "" - }, - { - "id": "chat-002", - "priority": 2, - "area": "chat", - "title": "Send a message in the current conversation", - "user_visible_behavior": "A user can submit a message and see it appear in the active thread.", - "status": "not_started", - "verification": [ - "Open an existing conversation.", - "Type a message into the input.", - "Submit the message.", - "Verify the new message appears in the thread." - ], - "evidence": [], - "notes": "" - }, - { - "id": "chat-003", - "priority": 3, - "area": "chat", - "title": "Persist the active conversation list", - "user_visible_behavior": "A user sees previously created conversations after restarting the app.", - "status": "not_started", - "verification": [ - "Create two conversations.", - "Restart the app.", - "Verify both conversations still appear in the sidebar." - ], - "evidence": [], - "notes": "" - } - ] -} diff --git a/courses/learn-harness-engineering/docs/ja/resources/templates/index.md b/courses/learn-harness-engineering/docs/ja/resources/templates/index.md deleted file mode 100644 index 341b3a06..00000000 --- a/courses/learn-harness-engineering/docs/ja/resources/templates/index.md +++ /dev/null @@ -1,41 +0,0 @@ -# テンプレートガイド - -これらのテンプレートはプロジェクトにコピーしてすぐ使えます。それぞれがエージェントのワークフロー内で特定の目的を持ちます。プロジェクトのコマンド、パス、機能名、検証ステップに合わせて内容を編集してください。 - -## はじめに - -まず以下の4つのファイルをプロジェクトルートにコピーしてください: - -1. `AGENTS.md` または `CLAUDE.md` -2. `init.sh` -3. `claude-progress.md` -4. `feature_list.json` - -プロジェクトが成長するにつれて残りのファイルを追加してください。 - ---- - -## AGENTS.md - -ルート指示ファイル。セッション開始時にエージェントが最初に読むファイルです。動作ルールを定義します:コードを書く前に何をするか、どう作業するか、どう終了するか。 - -Codex やその他のエージェントには `AGENTS.md` を使用。Claude Code と作業する場合は `CLAUDE.md` を使用 — 構造は同じで、Claude の指示スタイルに合わせた形式です。 - -## init.sh - -起動スクリプト。依存関係のインストール、検証、開始コマンドの表示を一度に実行します。 - -## claude-progress.md - -進捗ログ。毎セッションこのファイルに書き込み、新しいセッションは最初にこれを読みます。 - -## feature_list.json - -機能トラッカー。エージェントが実装する必要があるすべての機能とそのステータス、検証ステップ、証拠の機械可読リストです。 - -## その他のファイル - -- `session-handoff.md` — セッション間のコンパクトな引き継ぎメモ -- `clean-state-checklist.md` — 各セッション終了前に実行するチェックリスト -- `evaluator-rubric.md` — エージェント出力品質をレビューするスコアカード -- `quality-document.md` — 各プロダクトドメインとアーキテクチャレイヤーの品質スナップショット diff --git a/courses/learn-harness-engineering/docs/ja/resources/templates/init.sh b/courses/learn-harness-engineering/docs/ja/resources/templates/init.sh deleted file mode 100755 index d7522d66..00000000 --- a/courses/learn-harness-engineering/docs/ja/resources/templates/init.sh +++ /dev/null @@ -1,29 +0,0 @@ -#!/usr/bin/env bash - -set -euo pipefail - -ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" -cd "$ROOT_DIR" - -# Replace these commands with the correct commands for your repository. -INSTALL_CMD=(npm install) -VERIFY_CMD=(npm test) -START_CMD=(npm run dev) - -echo "==> Working directory: $PWD" -echo "==> Syncing dependencies" -"${INSTALL_CMD[@]}" - -echo "==> Running baseline verification" -"${VERIFY_CMD[@]}" - -echo "==> Startup command" -printf ' %q' "${START_CMD[@]}" -printf '\n' - -if [ "${RUN_START_COMMAND:-0}" = "1" ]; then - echo "==> Starting the app" - exec "${START_CMD[@]}" -fi - -echo "Set RUN_START_COMMAND=1 if you want init.sh to launch the app directly." diff --git a/courses/learn-harness-engineering/docs/ja/resources/templates/quality-document.md b/courses/learn-harness-engineering/docs/ja/resources/templates/quality-document.md deleted file mode 100644 index 5774696d..00000000 --- a/courses/learn-harness-engineering/docs/ja/resources/templates/quality-document.md +++ /dev/null @@ -1,43 +0,0 @@ -# 品質ドキュメント - -各プロダクトドメインとアーキテクチャレイヤーの品質スナップショット。エージェントと人間の両方がこのドキュメントを使用して、コードベースがどこが強いか、どこに作業が必要かを素早く理解できます。 - -**更新頻度:** 各重要セッションの後、または新しい作業フェーズを開始する前に。 - -**評価スケール:** - -- **A**:すべての検証合格、クリーンなアーキテクチャ、エージェント可読、安定したテスト -- **B**:検証合格、ほぼクリーン、可読性やテストカバレッジに小さなギャップ -- **C**:部分的に動作、既知のギャップ、一部のコード領域がエージェントにとって理解困難 -- **D**:動作しない、または重大な構造的問題 - ---- - -## プロダクトドメイン - -| ドメイン | 評価 | 検証 | エージェント可読性 | テスト安定性 | 主要ギャップ | 最終更新 | -|--------|------|------|-----------------|------------|------------|----------| -| ドキュメントインポート | - | - | - | - | - | - | -| ドキュメント管理 | - | - | - | - | - | - | -| ドキュメントインデックス | - | - | - | - | - | - | -| Q&A フロー | - | - | - | - | - | - | -| 根拠付き回答 | - | - | - | - | - | - | - -## アーキテクチャレイヤー - -| レイヤー | 評価 | 境界の強制 | エージェント可読性 | 主要ギャップ | 最終更新 | -|---------|------|-----------|-----------------|------------|----------| -| メインプロセス | - | - | - | - | - | -| Preload | - | - | - | - | - | -| Renderer | - | - | - | - | - | -| Services | - | - | - | - | - | - -## 変更履歴 - -### YYYY-MM-DD - -- 変更内容: -- 昇格したドメイン: -- 降格したドメイン: -- 新たに特定されたギャップ: -- 解消されたギャップ: diff --git a/courses/learn-harness-engineering/docs/ja/resources/templates/session-handoff.md b/courses/learn-harness-engineering/docs/ja/resources/templates/session-handoff.md deleted file mode 100644 index 741bc775..00000000 --- a/courses/learn-harness-engineering/docs/ja/resources/templates/session-handoff.md +++ /dev/null @@ -1,30 +0,0 @@ -# セッションハンドオフ - -## 現在検証済み - -- 現在動作しているもの: -- 実際に実行された検証: - -## このセッションでの変更 - -- 追加されたコードや動作: -- インフラや harness の変更: - -## 壊れているまたは未検証 - -- 既知の欠陥: -- 未検証のパス: -- 次セッションへのリスク: - -## 次の最適ステップ - -- 最優先の未完了機能: -- なぜこれが次なのか: -- 何が合格とみなされるか: -- そのステップ中に変更してはならないもの: - -## コマンド - -- 起動: -- 検証: -- フォーカス済みデバッグコマンド: diff --git a/courses/learn-harness-engineering/docs/ja/skills/index.md b/courses/learn-harness-engineering/docs/ja/skills/index.md deleted file mode 100644 index 51283ee4..00000000 --- a/courses/learn-harness-engineering/docs/ja/skills/index.md +++ /dev/null @@ -1,49 +0,0 @@ -# スキル - -このディレクトリには、このコースに同梱されている AI エージェント用スキルが含まれています。スキルは、Claude Code、Codex、Cursor、Windsurf などの AI コーディングエージェントが読み込んで専門的な作業を行うための、自己完結したプロンプトテンプレートです。 - -## harness-creator - -AI コーディングエージェント向けの本番級 harness engineering スキルです。指示、状態、検証、スコープ、セッションライフサイクルという 5 つの中核サブシステムを作成、評価、改善するのを助けます。 - -### できること - -- **harness をゼロから作る** — AGENTS.md、機能リスト、検証ワークフロー -- **既存の harness を改善する** — 5 サブシステム評価と優先度付き改善案 -- **セッション継続性を設計する** — メモリ永続化、進捗追跡、引き継ぎ手順 -- **本番パターンを適用する** — メモリ、コンテキストエンジニアリング、ツール安全性、マルチエージェント協調 - -### クイックスタート - -スキルファイルはリポジトリの [`skills/harness-creator/`](https://github.com/walkinglabs/learn-harness-engineering/tree/main/skills/harness-creator) にあります。 - -Claude Code で使う場合は、`harness-creator/` ディレクトリをプロジェクトのスキルパスにコピーするか、エージェントに SKILL.md を直接読ませます。 - -### 参考パターン - -このスキルには 6 つの詳細リファレンスがあります。 - -| パターン | 使う場面 | -|---------|----------| -| Memory Persistence | エージェントがセッション間で忘れてしまう | -| Context Engineering | コンテキスト予算管理、必要時ロード | -| Tool Registry | ツール安全性、並行制御 | -| Multi-Agent Coordination | 並列化、専門化ワークフロー | -| Lifecycle & Bootstrap | フック、バックグラウンドタスク、初期化 | -| Gotchas | 見落としやすい 15 個の失敗モードと修正策 | - -### テンプレート - -このスキルには、すぐ使えるテンプレートが同梱されています。 - -- `agents.md` — 作業ルール付き AGENTS.md 雛形 -- `feature-list.json` — JSON Schema と機能リスト例 -- `init.sh` — 標準初期化スクリプト -- `progress.md` — セッション進捗ログテンプレート - -### このスキルの作り方 - -`harness-creator` は **skill-creator** 方法論で開発されています。これは Anthropic 公式のメタスキルで、エージェントスキルの作成、テスト、反復改善に使います。skill-creator は、評価ランナー、採点器、ベンチマークビューアを備えた、ドラフト → テスト → 評価 → 反復の構造化ワークフローを提供します。 - -- **skill-creator ソース**: [anthropics/skills — skill-creator](https://github.com/anthropics/skills/tree/main/skills/skill-creator) -- **Claude Code skills docs**: [anthropics/claude-code — plugin-dev/skills](https://github.com/anthropics/claude-code/tree/main/plugins/plugin-dev/skills) diff --git a/courses/learn-harness-engineering/docs/ko/index.md b/courses/learn-harness-engineering/docs/ko/index.md deleted file mode 100644 index af6b0fd4..00000000 --- a/courses/learn-harness-engineering/docs/ko/index.md +++ /dev/null @@ -1 +0,0 @@ - diff --git a/courses/learn-harness-engineering/docs/ko/lectures/lecture-01-why-capable-agents-still-fail/code/failure-pattern-demo.ts b/courses/learn-harness-engineering/docs/ko/lectures/lecture-01-why-capable-agents-still-fail/code/failure-pattern-demo.ts deleted file mode 100644 index 311e665b..00000000 --- a/courses/learn-harness-engineering/docs/ko/lectures/lecture-01-why-capable-agents-still-fail/code/failure-pattern-demo.ts +++ /dev/null @@ -1,170 +0,0 @@ -/** - * failure-pattern-demo.ts - * - * Simulates the 4-step failure pattern that capable agents fall into: - * 1. Incomplete context - * 2. Locally reasonable changes - * 3. No global verification - * 4. Premature completion - * - * Run: npx tsx docs/lectures/lecture-01-why-capable-agents-still-fail/code/failure-pattern-demo.ts - */ - -// --------------------------------------------------------------------------- -// Types -// --------------------------------------------------------------------------- - -interface StepState { - step: number; - name: string; - contextAvailable: string[]; - contextMissing: string[]; - actionTaken: string; - localOutcome: string; - globalImpact: string; - completed: boolean; -} - -// --------------------------------------------------------------------------- -// Simulated "model" -- a simple decision function that bases its output -// solely on whatever context it has been given. -// --------------------------------------------------------------------------- - -function modelDecide(context: string[], task: string): string { - const has = (s: string) => context.some((c) => c.includes(s)); - - // The task is to "add a search endpoint to the API". - // Correct answer requires knowing about auth middleware and rate limiting. - - if (!has("auth")) { - return "Created new route handler /search without authentication checks"; - } - if (!has("rate-limit")) { - return "Added search route with auth but forgot rate limiting"; - } - if (!has("test-standards")) { - return "Implemented search with auth and rate-limit, but no tests"; - } - return "Fully implemented search endpoint with auth, rate-limit, and tests"; -} - -// --------------------------------------------------------------------------- -// Failure simulation -// --------------------------------------------------------------------------- - -function simulateFailurePattern(): StepState[] { - const steps: StepState[] = []; - - // ---- Step 1: Incomplete Context ---- - const step1Context = ["project structure", "route definitions"]; - const step1Missing = ["auth middleware", "rate-limiting policy", "test standards"]; - const step1Decision = modelDecide(step1Context, "add search endpoint"); - - steps.push({ - step: 1, - name: "Incomplete Context", - contextAvailable: step1Context, - contextMissing: step1Missing, - actionTaken: step1Decision, - localOutcome: "Looks good -- route compiles, returns data", - globalImpact: "Missing auth means unauthenticated access to search", - completed: false, - }); - - // ---- Step 2: Locally Reasonable Changes ---- - // The agent adds auth after a hint, but still lacks other context. - const step2Context = [...step1Context, "auth middleware"]; - const step2Missing = ["rate-limiting policy", "test standards"]; - const step2Decision = modelDecide(step2Context, "add search endpoint"); - - steps.push({ - step: 2, - name: "Locally Reasonable Changes", - contextAvailable: step2Context, - contextMissing: step2Missing, - actionTaken: step2Decision, - localOutcome: "Route has auth -- looks complete locally", - globalImpact: "No rate limiting means the endpoint can be abused", - completed: false, - }); - - // ---- Step 3: No Global Verification ---- - const step3Context = [...step2Context, "rate-limiting policy"]; - const step3Missing = ["test standards"]; - const step3Decision = modelDecide(step3Context, "add search endpoint"); - - steps.push({ - step: 3, - name: "No Global Verification", - contextAvailable: step3Context, - contextMissing: step3Missing, - actionTaken: step3Decision, - localOutcome: "Feature appears fully implemented", - globalImpact: "No tests -- regression risk, violates project standards", - completed: false, - }); - - // ---- Step 4: Premature Completion ---- - steps.push({ - step: 4, - name: "Premature Completion", - contextAvailable: step3Context, - contextMissing: step3Missing, - actionTaken: 'Agent outputs: "Done. Added search endpoint."', - localOutcome: "Agent is satisfied, task marked complete", - globalImpact: "Task is incomplete -- missing tests, no E2E verification", - completed: true, - }); - - return steps; -} - -// --------------------------------------------------------------------------- -// Comparison table -// --------------------------------------------------------------------------- - -function printComparisonTable(steps: StepState[]): void { - console.log("\n" + "=".repeat(90)); - console.log(" FAILURE PATTERN DEMO -- 4 Steps to a Broken Deliverable"); - console.log("=".repeat(90)); - - for (const s of steps) { - console.log(`\n Step ${s.step}: ${s.name}`); - console.log(" " + "-".repeat(60)); - console.log(` Context available : ${s.contextAvailable.join(", ")}`); - console.log(` Context missing : ${s.contextMissing.join(", ") || "(none)"}`); - console.log(` Action taken : ${s.actionTaken}`); - console.log(` Local outcome : ${s.localOutcome}`); - console.log(` Global impact : ${s.globalImpact}`); - console.log(` Marked complete? : ${s.completed ? "YES (premature)" : "No"}`); - } - - // Summary comparison - console.log("\n" + "=".repeat(90)); - console.log(" COMPARISON: What the agent saw vs. what was actually needed"); - console.log("=".repeat(90)); - - const totalRequired = ["project structure", "route definitions", "auth middleware", "rate-limiting policy", "test standards"]; - const finalAvailable = steps[steps.length - 1].contextAvailable; - - const header = "| Criterion | Available | Missing | Status |"; - const sep = "|------------------------|-----------|---------|----------|"; - console.log("\n" + header); - console.log(sep); - - for (const item of totalRequired) { - const avail = finalAvailable.includes(item); - const row = `| ${item.padEnd(23)}| ${(avail ? "Yes" : "No").padEnd(10)}| ${(!avail ? "Yes" : "").padEnd(8)}| ${(avail ? "OK" : "GAP").padEnd(9)}|`; - console.log(row); - } - - const gapCount = totalRequired.filter((i) => !finalAvailable.includes(i)).length; - console.log("\n Result: Agent completed with " + gapCount + " of " + totalRequired.length + " context items missing."); - console.log(" This is the core failure pattern: each step looked reasonable in isolation.\n"); -} - -// --------------------------------------------------------------------------- -// Run -// --------------------------------------------------------------------------- - -printComparisonTable(simulateFailurePattern()); diff --git a/courses/learn-harness-engineering/docs/ko/lectures/lecture-01-why-capable-agents-still-fail/code/failure-signals-checklist.md b/courses/learn-harness-engineering/docs/ko/lectures/lecture-01-why-capable-agents-still-fail/code/failure-signals-checklist.md deleted file mode 100644 index ace592c8..00000000 --- a/courses/learn-harness-engineering/docs/ko/lectures/lecture-01-why-capable-agents-still-fail/code/failure-signals-checklist.md +++ /dev/null @@ -1,9 +0,0 @@ -# 실패 신호 체크리스트 - -취약한 하네스(harness) 실행을 검토할 때 이 체크리스트를 활용하십시오. - -- 에이전트(agent)가 앱 시작 방법을 직접 물어보았는가, 아니면 잘못 추론했는가? -- 의도한 제품과 일치하지 않는 디렉터리 또는 추상화를 만들었는가? -- 완전한 워크플로 없이 눈에 보이는 UI 껍데기만 만든 뒤 멈췄는가? -- 이후 실행이 이어 진행할 수 있도록 메모나 산출물(deliverable)을 남겼는가? -- 새 세션이 5분 이내에 이전에 무슨 일이 있었는지 파악할 수 있는가? diff --git a/courses/learn-harness-engineering/docs/ko/lectures/lecture-01-why-capable-agents-still-fail/code/index.md b/courses/learn-harness-engineering/docs/ko/lectures/lecture-01-why-capable-agents-still-fail/code/index.md deleted file mode 100644 index 586dba87..00000000 --- a/courses/learn-harness-engineering/docs/ko/lectures/lecture-01-why-capable-agents-still-fail/code/index.md +++ /dev/null @@ -1,7 +0,0 @@ -# 강의 01 코드 예제 - -이 폴더는 다음을 보여주는 소규모 예제에 사용됩니다. - -- 강력한 모델이 취약한 환경에서 실패하는 사례 -- 명세가 부족한 저장소(repository) 설정 -- 피드백 루프가 없는 상황 diff --git a/courses/learn-harness-engineering/docs/ko/lectures/lecture-01-why-capable-agents-still-fail/code/underspecified-task.md b/courses/learn-harness-engineering/docs/ko/lectures/lecture-01-why-capable-agents-still-fail/code/underspecified-task.md deleted file mode 100644 index 75ac8fd4..00000000 --- a/courses/learn-harness-engineering/docs/ko/lectures/lecture-01-why-capable-agents-still-fail/code/underspecified-task.md +++ /dev/null @@ -1,18 +0,0 @@ -# 명세가 부족한 작업 예시 - -AI 질의응답 기능이 있는 데스크톱 지식 베이스 앱을 만드십시오. - -제약 조건: - -- 명시된 것 없음 -- 시작 명령어 없음 -- 폴더 구조 안내 없음 -- 데이터 모델 정의 없음 -- 명시적 완료 기준(Definition of Done) 없음 - -이런 종류의 프롬프트에서 발생하는 전형적인 결과: - -- 에이전트(agent)가 구조를 임시방편으로 만들어냄 -- 앱이 컴파일되더라도 일관되게 시작되지 않을 수 있음 -- 사용 가능한 수집/조회 경로가 없는 상태에서 UI가 먼저 나타날 수 있음 -- 에이전트가 겉으로만 보이는 성공 후에 멈추는 경우가 많음 diff --git a/courses/learn-harness-engineering/docs/ko/lectures/lecture-01-why-capable-agents-still-fail/index.md b/courses/learn-harness-engineering/docs/ko/lectures/lecture-01-why-capable-agents-still-fail/index.md deleted file mode 100644 index d630550b..00000000 --- a/courses/learn-harness-engineering/docs/ko/lectures/lecture-01-why-capable-agents-still-fail/index.md +++ /dev/null @@ -1,121 +0,0 @@ -[中文版本 →](../../../zh/lectures/lecture-01-why-capable-agents-still-fail/) - -> 코드 예제: [code/](https://github.com/walkinglabs/learn-harness-engineering/blob/main/docs/en/lectures/lecture-01-why-capable-agents-still-fail/code/) -> 실습 프로젝트: [Project 01. Prompt-only vs. rules-first](./../../projects/project-01-baseline-vs-minimal-harness/index.md) - -# 강의 01. 강력한 모델도 실행 신뢰성을 보장하지 않는다 - -AI 세계에 꽤 익숙하다고 자부합니다 — Claude Pro 구독, GPT-4o API 키, SWE-bench 리더보드 수치까지 외우고 있죠. 그러던 어느 날 드디어 실제 프로젝트를 AI 에이전트(agent)에게 맡겨봤습니다. 결과는? 기능 하나를 추가했더니 테스트가 깨지고, 버그 하나를 고쳤더니 두 개가 더 생기고, 20분 동안 열심히 실행하더니 당당하게 "완료"를 선언했는데 코드를 보면 요청한 것과 전혀 다른 내용입니다. - -에이전트(agent)란 자율적으로 작업을 수행하는 AI 소프트웨어 단위입니다. 코딩 에이전트(coding agent)는 이 중에서도 코드 작성·리팩터·테스트 등 개발 작업을 담당합니다. - -처음 든 생각은 이겁니다: "이 모델은 아직 부족해. 더 좋은 걸로 바꿔야겠어." 잠깐, 지갑을 꺼내기 전에 — 문제가 모델에 있지 않을 수도 있다는 점을 먼저 짚어봐야 합니다. - -숫자를 봅시다. 2025년 말 기준 SWE-bench Verified에서 가장 강력한 코딩 에이전트들은 대략 50-60%의 정확도를 기록합니다. 그것도 명확한 이슈 설명과 기존 테스트 케이스가 갖춰진 엄선된 작업 기준입니다. 막연한 요구사항, 테스트 없음, 암묵적 비즈니스 규칙이 여기저기 흩어진 실제 개발 환경으로 넘어오면 그 수치는 더 낮아집니다. - -그런데 이 수치들 뒤에는 직관에 반하는 진실이 있습니다. - -## 같은 말(馬), 다른 결과 - -Anthropic이 통제 실험을 진행했습니다. 동일한 프롬프트("2D 레트로 게임 메이커를 만들어라"), 동일한 모델(Opus 4.5). 첫 번째 실행: 아무런 지원 없이 맨손 — 20분, 9달러, 게임의 핵심 기능은 전혀 작동하지 않았습니다. 두 번째 실행: 완전한 하네스(harness) 적용(플래너 + 제너레이터 + 이밸류에이터 3에이전트 아키텍처) — 6시간, 200달러, 게임을 실제로 플레이할 수 있었습니다. - -하네스(harness)란 AI 코딩 에이전트가 안정적으로 동작하도록 환경·상태(state)·검증(verification)·제어를 묶어서 제공하는 외골격(스캐폴딩) 시스템입니다. 모델 가중치 외부에 있는 모든 엔지니어링 인프라가 하네스에 해당합니다. - -모델은 바꾸지 않았습니다. Opus 4.5는 여전히 Opus 4.5였죠. 바뀐 것은 마구(tack)였습니다. - -OpenAI의 2025년 하네스 엔지니어링 글은 명확하게 밝힙니다: 잘 갖춰진 저장소(repository)에서의 Codex는 "불안정"에서 "안정"으로 질적 전환을 이룬다고. 표현에 주목하세요 — "조금 나아진"이 아니라 질적 변화입니다. 마치 경주마처럼: 적절한 마구 없이도 탈 수 있지만, 멀리 갈 수 없고, 빠르게 달릴 수 없으며, 낙마도 놀랍지 않습니다. 하네스는 그 전체 마구입니다 — **모델 가중치 외부의 엔지니어링 인프라 전체입니다.** - -## 에이전트가 실제로 막히는 지점 - -그렇다면 구체적으로 무엇이 잘못되는 걸까요? - -가장 흔한 원인: 작업을 명확하게 정의하지 않았습니다. "검색 기능을 추가해"라고 하면 에이전트의 이해는 여러분의 이해와 전혀 다릅니다 — 무엇을 검색? 전문 검색 아니면 구조화 검색? 페이지네이션은? 하이라이팅은? 명시하지 않았으니 에이전트는 추측합니다. 맞는 추측은 운이고, 틀린 추측은 처음부터 명확히 했을 때보다 고치는 비용이 더 듭니다. 레스토랑에 가서 주방장에게 "생선으로 주세요"라고 하는 것과 같습니다 — 조림이 나올지, 찜이 나올지, 전골이 나올지는 순전히 운에 달려 있습니다. - -명확히 지시했더라도, 프로젝트에는 에이전트가 모르는 암묵적 아키텍처 컨벤션이 있습니다. 팀에서는 SQLAlchemy 2.0 문법을 표준으로 쓰는데 에이전트는 기본적으로 1.x 코드를 작성합니다. 모든 API 엔드포인트는 OAuth 2.0 인증을 사용해야 하는데, 그 규칙은 여러분 머릿속과 석 달 전 슬랙 메시지에만 존재합니다. 에이전트는 이것을 볼 수 없습니다 — 따르기 싫어서가 아니라 그 규칙의 존재 자체를 모릅니다. - -환경도 함정입니다. 불완전한 개발 환경, 누락된 의존성, 잘못된 도구 버전. 에이전트는 실제 작업 대신 `pip install` 실패와 Node 버전 불일치를 처리하느라 귀중한 컨텍스트 윈도(context window)를 낭비합니다. 숙련된 목수를 고용했는데 망치도, 못도, 평평한 작업대도 제공하지 않은 것과 같습니다 — 아무리 실력이 좋아도 작업을 할 수 없습니다. - -더 흔한 문제: 검증(verification) 방법이 아예 없습니다. 테스트도 없고, 린트도 없고, 검증 명령어도 에이전트에게 전달되지 않습니다. 에이전트는 코드를 작성하고, 보고, 괜찮다고 판단하고, "완료"라고 선언합니다. 답안지 없이 숙제를 제출하도록 한 것과 같습니다 — 학생은 맞혔다고 생각하지만 채점하면 오류 투성이입니다. Anthropic은 흥미로운 현상도 관찰했습니다: 에이전트가 컨텍스트 부족을 감지하면 서둘러 마무리하고, 검증을 건너뛰고, 최선 대신 단순한 해결책을 선택합니다. 이를 "컨텍스트 불안(context anxiety)"이라 부릅니다 — 시험 시간이 거의 다 됐을 때 남은 객관식 문제를 무작위로 찍기 시작하는 것과 같은 현상입니다. - -세션에 걸친 장시간 작업은 더욱 나쁩니다 — 이전 세션에서 발견한 모든 것이 사라지고, 매 새 세션마다 프로젝트 구조를 다시 탐색하고 코드 구성을 다시 이해해야 합니다. 영속적인 상태(state)가 없는 에이전트는 30분을 초과하는 작업에서 실패율이 급격히 치솟습니다. - -## 핵심 용어 - -이 시나리오들을 염두에 두면 다음 개념들이 단순한 전문 용어 이상으로 다가옵니다. - -- **역량 격차(Capability Gap)**: 벤치마크 성능과 실제 작업 성능 사이의 큰 간극. SWE-bench Verified에서 50-60%의 통과율은 실제 이슈의 거의 절반이 해결되지 않는다는 의미입니다. -- **하네스(Harness)**: 모델 외부의 모든 것 — 지시사항, 도구, 환경, 상태(state) 관리, 검증(verification) 피드백. 모델 가중치가 아니라면 모두 하네스입니다. 우리가 "마구"라고 부른 것입니다. -- **하네스 유발 실패(Harness-Induced Failure)**: 모델은 충분한 역량을 갖췄지만 실행 환경에 구조적 결함이 있는 경우. Anthropic의 통제 실험이 이를 이미 증명했습니다. -- **검증 격차(Verification Gap)**: 에이전트가 자신의 산출물(deliverable)에 대해 갖는 자신감과 실제 정확성 사이의 간극. 에이전트가 "완료했다"고 말하지만 완료하지 않은 상태 — 이것이 가장 흔한 실패 유형입니다. -- **진단 루프(Diagnostic Loop)**: 실행 → 실패 관찰 → 특정 하네스 레이어에 귀속 → 해당 레이어 수정 → 재실행. 이것이 하네스 엔지니어링의 핵심 방법론입니다. -- **완료 기준(Definition of Done)**: 기계 검증 가능한 조건들의 집합 — 테스트 통과, 린트 클린, 타입 검사 통과. 명시적인 완료 기준이 없으면 에이전트는 자체 기준을 만들어냅니다. - -## 실패하면 먼저 하네스를 점검하라 - -핵심 원칙: **실패가 발생하면 먼저 모델을 교체하지 말고 하네스를 점검하십시오.** 같은 모델이 비슷하게 잘 구성된 작업에서 성공한다면, 하네스 문제라고 가정하십시오. 자동차가 고장났을 때처럼 — 바로 엔진을 의심하지 않습니다. 먼저 연료가 있는지 확인합니다. - -구체적인 단계: - -**모든 실패를 특정 레이어에 귀속시키십시오.** "모델이 별로야"라고만 하지 마십시오. 물어보십시오: 작업이 불명확했는가? 컨텍스트(context)가 불충분했는가? 검증 방법이 없었는가? 각 실패를 다섯 가지 실패 레이어(작업 명세, 컨텍스트 제공, 실행 환경, 검증 피드백, 상태 관리) 중 하나에 매핑하십시오. 이것은 실무 진단 레이어이지, 위의 여섯 가지 용어와 같은 분류가 아닙니다. 이 습관을 들이면 로그에서 "모델이 부족해"라는 말이 점점 사라집니다. - -**모든 작업에 명시적인 완료 기준을 작성하십시오.** "검색 기능을 추가해"라고 하지 마십시오. 이렇게 하십시오: -``` -완료 기준: -- 새 엔드포인트 GET /api/search?q=xxx -- 페이지네이션 지원, 기본 20개 항목 -- 결과에 하이라이트된 스니펫 포함 -- 모든 새 코드가 pytest 통과 -- 타입 검사 통과 (mypy --strict) -``` - -**AGENTS.md 파일을 만드십시오.** 저장소(repository) 루트에 배치하여 에이전트에게 프로젝트의 기술 스택, 아키텍처 컨벤션, 검증 명령어를 알려주십시오. 이것이 하네스 엔지니어링의 첫 번째 단계이자 투자 대비 효과가 가장 높은 단계입니다. AGENTS.md 파일 하나가 더 비싼 모델로 업그레이드하는 것보다 효과적일 수도 있습니다 — 농담이 아닙니다. - -**진단 루프를 구축하십시오.** 실패를 "모델이 또 멍청한 짓을 했네"로 취급하지 마십시오. 하네스에 결함이 있다는 신호로 취급하십시오. 매 실패마다 레이어를 찾아 수정하고, 같은 방식으로 다시 실패하지 않도록 하십시오. 몇 라운드를 거치면 하네스가 강화되고 에이전트 성능이 안정됩니다. 도로 보수처럼 — 모든 움푹 파인 곳을 메울수록 다음 구간이 더 매끄러워집니다. - -**개선 사항을 정량화하십시오.** 간단한 로그를 유지하십시오: 각 작업이 성공했는지 실패했는지, 그리고 어느 레이어가 실패를 야기했는지. 몇 라운드 후에는 어느 레이어가 병목인지 보이기 시작합니다 — 거기에 에너지를 집중하십시오. - -## 백만 줄 코드 실험 - -OpenAI는 2025년에 공격적인 실험을 진행했습니다: Codex를 사용하여 빈 git 저장소에서 완전한 내부 제품을 구축하는 것. 5개월 후 저장소에는 약 100만 줄의 코드가 생성되었습니다 — 애플리케이션 로직, 인프라, 툴링, 문서, 내부 개발 도구 모두 에이전트가 생성했습니다. 세 명의 엔지니어가 Codex를 운영하며 약 1,500개의 PR을 열고 병합했습니다. 1인당 하루 평균 3.5개의 PR. - -핵심 제약: **인간은 코드를 직접 작성하지 않습니다.** 이것은 단순한 실험적 설정이 아니었습니다 — 엔지니어의 주요 업무가 더 이상 코드 작성이 아니라 환경 설계, 의도 표현, 피드백 루프 구축이 될 때 무엇이 달라지는지 파악하도록 강제하기 위한 설계였습니다. - -초기 진행은 예상보다 느렸습니다. Codex의 역량이 부족해서가 아니라 환경이 아직 충분히 갖춰지지 않았기 때문입니다 — 에이전트에게 고수준 목표를 진전시키는 데 필요한 도구, 추상화, 내부 구조가 부족했습니다. 엔지니어들의 작업은 이렇게 바뀌었습니다: 큰 목표를 작은 구성 요소(설계, 코드, 리뷰, 테스트)로 분해하고, 에이전트가 조합하도록 한 다음, 그 구성 요소들을 활용하여 더 복잡한 작업을 가능하게 하는 것. 무언가 실패했을 때 해결책은 거의 "더 열심히 해봐"가 아니었습니다 — "에이전트에게 어떤 역량이 부족하고, 어떻게 하면 이해 가능하고 실행 가능하게 만들 수 있는가?"였습니다. - -이 실험은 이 강의의 핵심 테제를 직접 증명합니다: **동일한 모델이 맨손 환경과 완전한 하네스 환경에서 근본적으로 다른 산출물을 만들어냅니다.** 모델은 변하지 않았습니다. 환경이 변했습니다. - -> 출처: [OpenAI: Harness engineering: leveraging Codex in an agent-first world](https://openai.com/index/harness-engineering/) - -## 현실적인 예시 - -한 팀이 중간 규모의 Python 웹 앱(FastAPI + PostgreSQL + Redis, 약 15,000줄)에 새 API 엔드포인트를 추가하기 위해 Claude Sonnet을 사용했습니다. - -처음에는 한 문장만 제공했습니다: "/api/v2/users 아래에 사용자 환경설정 엔드포인트를 추가해." 결과는? 에이전트가 컨텍스트(context) 윈도의 40%를 저장소 구조 탐색에 사용했고, 겉으로는 합리적으로 보이지만 프로젝트의 오류 처리 패턴을 따르지 않는 코드를 생성했으며, 구식 SQLAlchemy 문법을 사용했고, 엔드포인트에 런타임 오류가 있는 상태에서 완료를 선언했습니다. 다음 세션은 모든 탐색 작업을 처음부터 다시 해야 했습니다. - -이후 AGENTS.md(프로젝트 아키텍처 및 기술 스택 버전 설명), 명시적 검증 명령어(`pytest tests/api/v2/ && python -m mypy src/`), 아키텍처 결정 기록을 추가했습니다. 같은 모델이 독립적으로 세 번 실행한 모두에서 성공했고, 컨텍스트 효율도 약 60% 향상되었습니다. - -모델을 바꾸지 않았습니다. 하네스를 바꿨습니다. - -## 핵심 정리 - -- 모델 역량과 실행 신뢰성은 별개입니다. 경주마도 좋은 마구가 필요합니다. -- 실패 시 먼저 하네스를 점검하고, 그 다음 모델을 확인하십시오. 모델 교체는 가장 비싼 선택지입니다 — 그리고 대부분 모델 문제가 아닙니다. -- 모든 실패는 신호입니다: 하네스에 구조적 결함이 있습니다. 찾아서 고치십시오. -- 다섯 가지 방어 레이어: 작업 명세, 컨텍스트 제공, 실행 환경, 검증 피드백, 상태 관리. 의사가 가장 흔한 원인부터 먼저 배제하듯이 체계적으로 점검하십시오. -- AGENTS.md 파일 하나가 더 비싼 모델로 업그레이드하는 것보다 효과적일 수 있습니다. 진심입니다. - -## 더 읽을거리 - -- [OpenAI: Harness Engineering — Leveraging Codex in an Agent-First World](https://openai.com/index/harness-engineering/) -- [Anthropic: Effective Harnesses for Long-Running Agents](https://www.anthropic.com/engineering/effective-harnesses-for-long-running-agents) -- [HumanLayer: Skill Issue — Harness Engineering for Coding Agents](https://humanlayer.dev/articles/harness-engineering-for-coding-agents/) -- [SWE-bench Leaderboard](https://www.swebench.com/) -- [Thoughtworks Technology Radar: Harness Engineering](https://www.thoughtworks.com/radar) - -## 연습 문제 - -1. **비교 실험**: 잘 아는 코드베이스와 비사소한 수정 작업을 선택하십시오. 먼저 하네스 지원 없이 에이전트를 실행하고 실패를 기록하십시오. 그런 다음 명시적 검증 명령어가 포함된 AGENTS.md를 추가하고 같은 에이전트로 다시 실행하십시오. 결과를 비교하고, 각 실패를 다섯 가지 방어 레이어 중 하나에 귀속시키십시오. - -2. **검증 격차 측정**: 코딩 작업 5개를 선택하십시오. 각 작업 후 에이전트가 완료를 주장하는지 기록한 다음, 독립적인 테스트로 실제 정확성을 검증하십시오. 에이전트가 실제로는 완료하지 않았는데 완료했다고 주장하는 비율을 계산하십시오 — 그것이 여러분의 검증 격차입니다. 그런 다음 어떤 검증 명령어가 이 비율을 낮출 수 있는지 생각해보십시오. - -3. **진단 루프 실습**: 프로젝트에서 에이전트가 반복적으로 실패하는 작업을 찾으십시오. 한 번 실행하고 실패를 기록하십시오. 다섯 가지 레이어 중 하나에 귀속시키십시오. 그 레이어를 수정하십시오. 다시 실행하십시오. 3~5라운드를 반복하면서 매번 개선 사항을 기록하십시오. diff --git a/courses/learn-harness-engineering/docs/ko/lectures/lecture-02-what-a-harness-actually-is/code/harness-components.md b/courses/learn-harness-engineering/docs/ko/lectures/lecture-02-what-a-harness-actually-is/code/harness-components.md deleted file mode 100644 index 236a5bd3..00000000 --- a/courses/learn-harness-engineering/docs/ko/lectures/lecture-02-what-a-harness-actually-is/code/harness-components.md +++ /dev/null @@ -1,21 +0,0 @@ -# 하네스 구성 요소 예시 - -로컬 저장소(repository)에서 작업하는 코딩 에이전트(coding agent)의 경우: - -- 모델: - LLM 자체 - -- 하네스(harness): - - 시스템 프롬프트 - - AGENTS.md - - bash 도구 - - 파일 읽기/쓰기 도구 - - git 접근 - - 로컬 파일 시스템 - - 시작 스크립트 - - 테스트 명령어 - - 중지 훅(hook) - - 린트 검사 - - 평가자 루프 - -위의 하네스 요소 중 하나라도 변경하면 실효적인 에이전트가 바뀝니다. diff --git a/courses/learn-harness-engineering/docs/ko/lectures/lecture-02-what-a-harness-actually-is/code/harness-vs-no-harness.ts b/courses/learn-harness-engineering/docs/ko/lectures/lecture-02-what-a-harness-actually-is/code/harness-vs-no-harness.ts deleted file mode 100644 index d856ba36..00000000 --- a/courses/learn-harness-engineering/docs/ko/lectures/lecture-02-what-a-harness-actually-is/code/harness-vs-no-harness.ts +++ /dev/null @@ -1,180 +0,0 @@ -/** - * harness-vs-no-harness.ts - * - * Side-by-side comparison of the same task executor running with and without - * a harness. The harness version adds explicit rules, verification steps, - * and stop conditions. - * - * Run: npx tsx docs/lectures/lecture-02-what-a-harness-actually-is/code/harness-vs-no-harness.ts - */ - -// --------------------------------------------------------------------------- -// Types & helpers -// --------------------------------------------------------------------------- - -interface TaskResult { - name: string; - passed: boolean; - durationMs: number; - issues: string[]; -} - -function pad(s: string, len: number): string { - return s.length >= len ? s : s + " ".repeat(len - s.length); -} - -// --------------------------------------------------------------------------- -// Simulated task executor -// --------------------------------------------------------------------------- - -/** A simple task that can succeed or fail depending on rules. */ -type Task = { - name: string; - requiresAuth: boolean; - hasTests: boolean; - withinScope: boolean; -}; - -const tasks: Task[] = [ - { name: "Add search endpoint", requiresAuth: true, hasTests: false, withinScope: true }, - { name: "Add delete endpoint", requiresAuth: true, hasTests: true, withinScope: true }, - { name: "Refactor auth middleware", requiresAuth: true, hasTests: true, withinScope: false }, - { name: "Add health check", requiresAuth: false, hasTests: true, withinScope: true }, - { name: "Add rate limiter", requiresAuth: true, hasTests: false, withinScope: true }, -]; - -// --------------------------------------------------------------------------- -// Run WITHOUT harness -- just execute every task, no checks -// --------------------------------------------------------------------------- - -function runWithoutHarness(taskList: Task[]): TaskResult[] { - const results: TaskResult[] = []; - - for (const t of taskList) { - const start = performance.now(); - const issues: string[] = []; - - // The "agent" does the work and calls it done. - let passed = true; - - // Problems that go undetected without a harness - if (t.requiresAuth && !t.hasTests) { - issues.push("No tests for auth-protected endpoint"); - // Agent doesn't notice -- marks as pass anyway - } - if (!t.withinScope) { - issues.push("Task is outside current scope"); - // Agent doesn't notice - } - - const duration = performance.now() - start; - results.push({ name: t.name, passed, durationMs: duration, issues }); - } - - return results; -} - -// --------------------------------------------------------------------------- -// Run WITH harness -- rules, verification, stop conditions -// --------------------------------------------------------------------------- - -function runWithHarness(taskList: Task[]): TaskResult[] { - const rules = { - requireTestsForAuth: true, - enforceScope: true, - }; - - const results: TaskResult[] = []; - - for (const t of taskList) { - const start = performance.now(); - const issues: string[] = []; - let passed = true; - - // Rule: auth endpoints must have tests - if (rules.requireTestsForAuth && t.requiresAuth && !t.hasTests) { - issues.push("BLOCKED: Auth-protected endpoint missing tests"); - passed = false; - } - - // Rule: stay in scope - if (rules.enforceScope && !t.withinScope) { - issues.push("BLOCKED: Task outside active scope -- skipped"); - passed = false; - } - - // Verification: re-check after execution - if (passed && !t.hasTests) { - issues.push("WARNING: No tests exist for this change"); - } - - const duration = performance.now() - start; - results.push({ name: t.name, passed, durationMs: duration, issues }); - } - - return results; -} - -// --------------------------------------------------------------------------- -// Reporting -// --------------------------------------------------------------------------- - -function printComparison( - noHarness: TaskResult[], - withHarness: TaskResult[] -): void { - console.log("\n" + "=".repeat(80)); - console.log(" HARNESS vs NO-HARNESS COMPARISON"); - console.log("=".repeat(80)); - - const header = `| ${pad("Task", 28)}| ${pad("No-Harness", 12)}| ${pad("Issues(NH)", 35)}| ${pad("Harness", 12)}| ${pad("Issues(H)", 35)}|`; - const sep = `|${"-".repeat(30)}|${"-".repeat(14)}|${"-".repeat(37)}|${"-".repeat(14)}|${"-".repeat(37)}|`; - - console.log("\n" + header); - console.log(sep); - - for (let i = 0; i < noHarness.length; i++) { - const nh = noHarness[i]; - const wh = withHarness[i]; - const nhPass = nh.passed ? "PASS" : "FAIL"; - const whPass = wh.passed ? "PASS" : "FAIL"; - const nhIssue = nh.issues[0] ?? "(none)"; - const whIssue = wh.issues[0] ?? "(none)"; - - console.log( - `| ${pad(nh.name, 28)}| ${pad(nhPass, 12)}| ${pad(nhIssue, 35)}| ${pad(whPass, 12)}| ${pad(whIssue, 35)}|` - ); - } - - // Metrics - const nhPassed = noHarness.filter((r) => r.passed).length; - const whPassed = withHarness.filter((r) => r.passed).length; - const nhIssues = noHarness.reduce((sum, r) => sum + r.issues.length, 0); - const whIssues = withHarness.reduce((sum, r) => sum + r.issues.length, 0); - - console.log("\n" + "=".repeat(80)); - console.log(" SUMMARY METRICS"); - console.log("=".repeat(80)); - - const metricsHeader = `| ${pad("Metric", 30)}| ${pad("No Harness", 15)}| ${pad("With Harness", 15)}|`; - const metricsSep = `|${"-".repeat(32)}|${"-".repeat(17)}|${"-".repeat(17)}|`; - console.log("\n" + metricsHeader); - console.log(metricsSep); - console.log(`| ${pad("Tasks passed", 30)}| ${pad(String(nhPassed) + "/" + noHarness.length, 15)}| ${pad(String(whPassed) + "/" + withHarness.length, 15)}|`); - console.log(`| ${pad("Issues detected", 30)}| ${pad(String(nhIssues), 15)}| ${pad(String(whIssues), 15)}|`); - console.log(`| ${pad("False positives (passed but flawed)", 30)}| ${pad(String(nhPassed - truePassCount(noHarness)), 15)}| ${pad(String(whPassed - truePassCount(withHarness)), 15)}|`); - - console.log("\n The harness catches problems the no-harness run silently ignores."); - console.log(" Without a harness, every task 'passes' even when it shouldn't.\n"); -} - -/** Count tasks that actually pass (have tests and are in scope). */ -function truePassCount(results: TaskResult[]): number { - return results.filter((r) => r.passed && r.issues.length === 0).length; -} - -// --------------------------------------------------------------------------- -// Run -// --------------------------------------------------------------------------- - -printComparison(runWithoutHarness(tasks), runWithHarness(tasks)); diff --git a/courses/learn-harness-engineering/docs/ko/lectures/lecture-02-what-a-harness-actually-is/code/index.md b/courses/learn-harness-engineering/docs/ko/lectures/lecture-02-what-a-harness-actually-is/code/index.md deleted file mode 100644 index 09770dcc..00000000 --- a/courses/learn-harness-engineering/docs/ko/lectures/lecture-02-what-a-harness-actually-is/code/index.md +++ /dev/null @@ -1,8 +0,0 @@ -# 강의 02 코드 예제 - -이 폴더는 다음을 구분하는 소규모 예제에 사용됩니다. - -- 모델 동작 -- 하네스(harness) 동작 -- 프롬프트만 사용하는 설정 -- 환경 기반 에이전트(agent) 설정 diff --git a/courses/learn-harness-engineering/docs/ko/lectures/lecture-02-what-a-harness-actually-is/code/minimal-harness-loop.ts b/courses/learn-harness-engineering/docs/ko/lectures/lecture-02-what-a-harness-actually-is/code/minimal-harness-loop.ts deleted file mode 100644 index e56cddd8..00000000 --- a/courses/learn-harness-engineering/docs/ko/lectures/lecture-02-what-a-harness-actually-is/code/minimal-harness-loop.ts +++ /dev/null @@ -1,36 +0,0 @@ -type Message = { - role: "user" | "assistant"; - content: string; -}; - -type ToolResult = { - ok: boolean; - output: string; -}; - -function runTool(name: string, input: string): ToolResult { - if (name === "read_file") { - return { ok: true, output: `contents of ${input}` }; - } - - return { ok: false, output: `unknown tool: ${name}` }; -} - -export function minimalHarness(messages: Message[]) { - const nextAction = { - tool: "read_file", - input: "README.md" - }; - - const result = runTool(nextAction.tool, nextAction.input); - - return { - messages: [ - ...messages, - { - role: "assistant", - content: `Tool ${nextAction.tool} returned: ${result.output}` - } - ] - }; -} diff --git a/courses/learn-harness-engineering/docs/ko/lectures/lecture-02-what-a-harness-actually-is/index.md b/courses/learn-harness-engineering/docs/ko/lectures/lecture-02-what-a-harness-actually-is/index.md deleted file mode 100644 index 09c26ee1..00000000 --- a/courses/learn-harness-engineering/docs/ko/lectures/lecture-02-what-a-harness-actually-is/index.md +++ /dev/null @@ -1,109 +0,0 @@ -[中文版本 →](../../../zh/lectures/lecture-02-what-a-harness-actually-is/) - -> 코드 예제: [code/](https://github.com/walkinglabs/learn-harness-engineering/blob/main/docs/en/lectures/lecture-02-what-a-harness-actually-is/code/) -> 실습 프로젝트: [Project 01. Prompt-only vs. rules-first](./../../projects/project-01-baseline-vs-minimal-harness/index.md) - -# 강의 02. 하네스란 실제로 무엇인가 - -"하네스(harness)"라는 단어는 AI 코딩 에이전트 분야에서 자주 언급되지만, 솔직히 말하면 대부분의 사람들이 하네스라고 할 때 "프롬프트 파일"을 의미합니다. 그것은 하네스가 아닙니다. 재료만 있고 가스레인지도, 칼도, 레시피도, 플레이팅 워크플로도 없이 레스토랑을 여는 것과 같습니다. 그것은 레스토랑이 아닙니다. 냉장고입니다. - -이 강의에서는 정확하고 실행 가능한 하네스의 정의를 제시합니다. 학술적 추상이 아니라 오늘 바로 활용할 수 있는 프레임워크입니다: 하네스는 명확한 책임과 평가 기준을 가진 다섯 가지 하위 시스템으로 구성됩니다. - -## 비유로 시작하기 - -문서도 없고, 코드에 주석도 없고, 테스트 실행 방법도 아무도 알려주지 않고, CI 설정은 어딘가에 묻혀 있는 프로젝트에 갑자기 투입된 신규 엔지니어를 상상해보십시오. 좋은 코드를 작성할 수 있을까요? 충분히 똑똑하고 인내심이 있다면 가능할 수도 있습니다. 하지만 "문제를 해결하는 것"보다 "이 프로젝트가 뭔지 파악하는 것"에 엄청난 시간을 쏟게 됩니다. - -AI 에이전트도 정확히 같은 상황에 놓입니다. 그리고 더 나쁩니다 — 여러분은 적어도 동료에게 물어볼 수 있습니다. 에이전트는 여러분이 앞에 놓아준 파일과 실행할 수 있는 명령어만 볼 수 있습니다. 누군가를 톡 치면서 "이 프로젝트가 어떤 버전의 ORM을 사용해요?"라고 물을 수 없습니다. - -OpenAI는 핵심 원칙을 "저장소(repo)가 명세(spec)다"로 정의합니다 — 필요한 모든 컨텍스트(context)는 저장소에 있어야 하며, 구조화된 지시 파일, 명시적 검증(verification) 명령어, 명확한 디렉터리 구성을 통해 전달되어야 합니다. Anthropic의 장기 실행 에이전트 문서는 상태(state) 지속성, 명시적 복구 경로, 구조화된 진행 추적을 강조합니다. 두 회사는 서로 다른 측면에 집중하지만 같은 말을 합니다: **모델 외부의 엔지니어링 인프라 전체가 모델의 역량이 실제로 얼마나 발휘되는지를 결정합니다.** - -이미 알고 있는 도구들을 살펴보십시오: - -**Claude Code**는 하네스 사고를 구현합니다. 저장소에서 `CLAUDE.md`를 읽고(레시피 선반), 셸 명령어를 실행할 수 있으며(칼 랙), 로컬 환경에서 실행되고(가스레인지), 세션 기록을 유지하며(준비 스테이션), 테스트를 실행하고 결과를 볼 수 있습니다(품질 확인 창). 하지만 테스트 실행 방법을 알려주지 않으면 품질 확인 창이 고장납니다 — 요리가 완전히 익었는지 아무도 알 수 없습니다. - -**Cursor**도 유사한 논리를 따릅니다. `.cursorrules` 파일이 레시피 선반이고, 터미널이 칼 랙이며, 프로젝트 구조와 린트 설정을 읽는 것이 가스레인지입니다. 하지만 Cursor의 상태 관리는 상대적으로 취약합니다 — IDE를 닫고 다시 열면 이전 컨텍스트가 사라집니다. - -**Codex**(OpenAI의 코딩 에이전트)는 git 워크트리(worktree)를 사용하여 각 작업의 런타임 환경을 격리하고, 로컬 관찰 가능성 스택(로그, 메트릭, 트레이스)과 결합하여 모든 변경 사항이 독립적인 환경에서 검증됩니다. `AGENTS.md`와 명확한 검증 명령어가 있는 저장소에서는 "맨손" 저장소보다 훨씬 뛰어난 성능을 발휘합니다. - -**AutoGPT**는 경고의 사례입니다 — 구조화된 상태 관리의 부재로 장시간 작업에서 컨텍스트가 축적되고, 정밀한 피드백 메커니즘의 부재로 에이전트가 루프에 빠집니다. 많은 사람들이 AutoGPT가 "작동하지 않는다"고 말하지만, 실제로는 AutoGPT의 하네스가 작동하지 않는 것입니다 — 고장난 가스레인지에 주방장을 배치하면 아무리 좋은 재료도 요리가 되지 않습니다. - -## 핵심 개념 - -- **하네스(harness)란 무엇인가**: 모델 가중치 외부의 엔지니어링 인프라 전체. OpenAI는 엔지니어의 핵심 업무를 세 가지로 정리합니다: 환경 설계, 의도 표현, 피드백 루프 구축. Anthropic은 자사의 Claude Agent SDK를 "범용 에이전트 하네스"라고 부릅니다. -- **저장소는 단일 진실 원천(SoR)이다**: 에이전트가 볼 수 없는 것은 실질적으로 존재하지 않습니다. OpenAI는 저장소를 "시스템 오브 레코드(SoR, system of record)"로 취급합니다 — 모든 필요한 컨텍스트는 구조화된 파일과 명확한 디렉터리 구성을 통해 저장소에 있어야 합니다. -- **지도를 줘라, 매뉴얼이 아니라**: OpenAI의 경험 — `AGENTS.md`는 백과사전이 아니라 디렉터리 페이지여야 합니다. 100줄 정도가 적당합니다. 맞지 않으면 `docs/` 디렉터리로 분리하여 에이전트가 필요할 때 읽도록 하십시오. -- **제약하되, 마이크로매니지하지 마라**: 좋은 하네스는 실행 가능한 규칙으로 에이전트를 제약하지, 지시를 하나하나 열거하지 않습니다. OpenAI는 "불변성을 강제하되, 구현을 마이크로매니지하지 말라"고 말합니다. Anthropic은 에이전트가 자신의 작업을 자신만만하게 칭찬한다는 것을 발견했으며, 해결책은 "일하는 사람"과 "확인하는 사람"을 분리하는 것입니다. -- **컴포넌트를 하나씩 제거하라**: 각 하네스 컴포넌트의 한계 기여도를 정량화하려면 하나씩 제거하고 어떤 제거가 가장 큰 성능 저하를 유발하는지 확인하십시오. Anthropic은 이 방법을 사용했으며, 모델이 강해질수록 일부 컴포넌트는 더 이상 핵심적이지 않지만 새로운 컴포넌트가 항상 등장한다는 것을 발견했습니다. - -## 5개 하위 시스템 하네스 모델 - -주방 비유로 돌아가봅시다. 완전한 주방에는 다섯 가지 기능 구역이 있고, 하네스에도 다섯 가지 하위 시스템이 있습니다: - -```mermaid -flowchart LR - Rules["프로젝트 규칙
AGENTS.md / CLAUDE.md"] --> Agent["AI 에이전트"] - State["진행 상황 및 git
PROGRESS.md / 커밋"] --> Agent - Agent --> Tools["도구
셸 / 파일 / 테스트"] - Tools --> Env["런타임
의존성 / 서비스 / 버전"] - Env --> Checks["검사 결과
테스트 / 린트 / 빌드"] - Checks --> Agent -``` - -**지시 하위 시스템(레시피 선반)**: `AGENTS.md`(또는 `CLAUDE.md`)를 만들어 다음을 포함하십시오: 프로젝트 개요와 목적(한 문장), 기술 스택과 버전(Python 3.11, FastAPI 0.100+, PostgreSQL 15), 첫 실행 명령어(`make setup`, `make test`), 협상 불가능한 HARD 제약("모든 API는 OAuth 2.0을 사용해야 함"), 더 자세한 문서 링크. - -**도구 하위 시스템(칼 랙)**: 에이전트가 충분한 도구 접근권을 갖도록 하십시오. "보안"을 위해 셸을 비활성화하지 마십시오 — 에이전트가 `pip install`조차 실행할 수 없다면 어떻게 작업하겠습니까? 하지만 모든 것을 열어두지도 마십시오 — 최소 권한 원칙을 따르십시오. - -**환경 하위 시스템(가스레인지)**: 환경 상태가 자기 서술적이도록 만드십시오. 의존성 잠금을 위해 `pyproject.toml` 또는 `package.json`을 사용하고, 런타임 버전을 위해 `.nvmrc` 또는 `.python-version`을, 재현성을 위해 Docker 또는 devcontainers를 사용하십시오. - -**상태(state) 하위 시스템(준비 스테이션)**: 장시간 작업에는 진행 추적이 필요합니다. 간단한 `PROGRESS.md` 파일을 사용하여 기록하십시오: 완료된 것, 진행 중인 것, 차단된 것. 매 세션 종료 전에 업데이트하고, 다음 세션 시작 시 읽으십시오. - -**피드백 하위 시스템(품질 확인 창)**: 이것이 투자 대비 효과가 가장 높은 하위 시스템입니다. `AGENTS.md`에 검증 명령어를 명시적으로 나열하십시오: -``` -검증 명령어: -- 테스트: pytest tests/ -x -- 타입 검사: mypy src/ --strict -- 린트: ruff check src/ -- 전체 검증: make check (위 모두 포함) -``` - -하위 시스템 하나라도 빠지면 주방의 기능 구역 하나가 없는 것과 같습니다 — 요리는 할 수 있지만 항상 불편합니다. - -**하네스 컴포넌트 가치 정량화**: "등압 모델 제어(isometric model control)"를 사용하십시오. 모델을 고정하고 하위 시스템을 하나씩 제거하면서 어떤 제거가 가장 큰 성능 저하를 유발하는지 측정하십시오. 가장 큰 하락은 해당 작업에서 한계 기여도가 가장 큰 컴포넌트를 보여주지만, 그것만으로 병목을 증명하지는 않습니다. 병목을 찾으려면 실패 기록과 귀인을 함께 보십시오. 작업이 불명확했는지, 컨텍스트가 부족했는지, 환경이 재현 불가능했는지, 검증 피드백이 없었는지, 상태 관리가 끊겼는지를 확인해야 합니다. - -## 한 팀의 실제 이야기 - -한 팀이 TypeScript + React 프론트엔드 앱(약 20,000줄)에 GPT-4o를 사용했습니다. 필수적으로 주방 장비를 하나씩 추가하는 4단계를 거쳤습니다: - -**1단계 — 빈 주방**: README에 기본 프로젝트 설명만 있음. 5번 실행 중 1번 성공(20%). 주요 실패: 잘못된 패키지 매니저 선택(npm vs yarn), 컴포넌트 명명 컨벤션 미준수, 테스트 실행 불가. - -**2단계 — 레시피 선반 설치**: 기술 스택 버전, 명명 컨벤션, 주요 아키텍처 결정이 담긴 `AGENTS.md` 추가. 성공률 60%로 상승. 남은 실패는 주로 환경 문제와 검증 누락. - -**3단계 — 품질 확인 창 개방**: `AGENTS.md`에 검증 명령어 나열: `yarn test && yarn lint && yarn build`. 성공률 80%로 상승. - -**4단계 — 준비 스테이션 준비**: 에이전트가 매 실행마다 완료된 작업과 미완료 작업을 기록하는 진행 파일 템플릿 도입. 성공률 80-100%로 안정화. - -4번의 반복, 모델은 전혀 바꾸지 않았고, 성공률은 20%에서 거의 100%로. 그것이 하네스 엔지니어링의 힘입니다. 더 비싼 재료를 구매한 것이 아닙니다 — 주방을 제대로 정리한 것입니다. - -## 핵심 정리 - -- 하네스 = 지시 + 도구 + 환경 + 상태(state) + 피드백. 다섯 가지 하위 시스템, 주방의 다섯 가지 기능 구역처럼 — 모두 필수입니다. -- 모델 가중치가 아니라면 모두 하네스입니다. 하네스가 모델 역량이 얼마나 발휘되는지를 결정합니다. -- 다섯 가지 하위 시스템 중 피드백 하위 시스템이 일반적으로 가장 낮은 투자로 가장 높은 수익을 냅니다. 검증 명령어를 먼저 맞추십시오 — 품질 확인 창이 가장 가치 있는 업그레이드입니다. -- "등압 모델 제어"를 사용하여 각 하위 시스템의 한계 기여도를 정량화하십시오. 직감에 의존하지 말고, 실패 기록과 귀인으로 실제 병목을 찾으십시오. -- 하네스는 코드처럼 부패합니다. 정기적으로 감사하고 기술 부채를 갚듯 하네스 부채를 갚으십시오. - -## 더 읽을거리 - -- [OpenAI: Harness Engineering](https://openai.com/index/harness-engineering/) -- [Anthropic: Effective Harnesses for Long-Running Agents](https://www.anthropic.com/engineering/effective-harnesses-for-long-running-agents) -- [HumanLayer: Harness Engineering for Coding Agents](https://humanlayer.dev/articles/harness-engineering-for-coding-agents/) -- [SWE-agent: Agent-Computer Interfaces](https://github.com/princeton-nlp/SWE-agent) -- [Thoughtworks: Harness Engineering on Technology Radar](https://www.thoughtworks.com/radar) - -## 연습 문제 - -1. **5개 하위 시스템 하네스 감사**: AI 에이전트를 사용하는 프로젝트를 가져와 5개 하위 시스템 프레임워크를 사용하여 완전한 감사를 수행하십시오. 각 하위 시스템에 1-5점을 매기십시오. 점수가 가장 낮은 하위 시스템을 찾아 30분을 개선에 투자하고, 에이전트 성능의 변화를 관찰하십시오. - -2. **등압 모델 제어 실험**: 모델 하나와 도전적인 작업 하나를 선택하십시오. 지시(AGENTS.md 삭제), 피드백(검증 명령어 미제공), 상태(진행 파일 없음)를 순차적으로 제거하되 한 번에 하나씩만 제거하고 성능 저하를 측정하십시오. 결과를 기반으로 프로젝트에서 하위 시스템 중요도를 순위 매기십시오. - -3. **어포던스 분석**: 프로젝트에서 에이전트가 "하고 싶지만 할 수 없는" 시나리오(예: 매개변수화된 쿼리를 사용해야 하지만 프로젝트의 ORM 패턴을 모르는 경우)를 찾으십시오. 이것이 실행의 간극(Gulf of Execution, 방법을 모름)인지 아니면 평가의 간극(Gulf of Evaluation, 맞는지를 모름)인지 분석한 다음, 이를 해소할 하네스 개선 방안을 설계하십시오. diff --git a/courses/learn-harness-engineering/docs/ko/lectures/lecture-03-why-the-repository-must-become-the-system-of-record/code/index.md b/courses/learn-harness-engineering/docs/ko/lectures/lecture-03-why-the-repository-must-become-the-system-of-record/code/index.md deleted file mode 100644 index 8eafba56..00000000 --- a/courses/learn-harness-engineering/docs/ko/lectures/lecture-03-why-the-repository-must-become-the-system-of-record/code/index.md +++ /dev/null @@ -1,7 +0,0 @@ -# 강의 03 코드 예제 - -이 폴더는 다음 예제에 사용됩니다. - -- 에이전트(agent)가 읽을 수 있는 저장소(repository) 구조 -- 시스템 오브 레코드(SoR)로서의 문서 -- 잘못된 지식 배치 vs 올바른 지식 배치 diff --git a/courses/learn-harness-engineering/docs/ko/lectures/lecture-03-why-the-repository-must-become-the-system-of-record/code/repo-knowledge-layout.txt b/courses/learn-harness-engineering/docs/ko/lectures/lecture-03-why-the-repository-must-become-the-system-of-record/code/repo-knowledge-layout.txt deleted file mode 100644 index 45315dac..00000000 --- a/courses/learn-harness-engineering/docs/ko/lectures/lecture-03-why-the-repository-must-become-the-system-of-record/code/repo-knowledge-layout.txt +++ /dev/null @@ -1,13 +0,0 @@ -AGENTS.md -docs/ - ARCHITECTURE.md - PRODUCT.md - RELIABILITY.md - references/ - electron.md - sqlite.md - plans/ - active/ - completed/ -src/ -scripts/ diff --git a/courses/learn-harness-engineering/docs/ko/lectures/lecture-03-why-the-repository-must-become-the-system-of-record/code/repo-reader.ts b/courses/learn-harness-engineering/docs/ko/lectures/lecture-03-why-the-repository-must-become-the-system-of-record/code/repo-reader.ts deleted file mode 100644 index 46f6a62a..00000000 --- a/courses/learn-harness-engineering/docs/ko/lectures/lecture-03-why-the-repository-must-become-the-system-of-record/code/repo-reader.ts +++ /dev/null @@ -1,189 +0,0 @@ -/** - * repo-reader.ts - * - * Reads a directory structure and scores it on discoverability. - * Checks for: AGENTS.md, docs/, architecture docs, feature tracking, - * handoff files, and other signals of a repository that serves as the - * system of record. - * - * Usage: - * npx tsx docs/lectures/lecture-03.../code/repo-reader.ts [path] - * (defaults to current working directory if no path given) - * - * Run: npx tsx docs/lectures/lecture-03-why-the-repository-must-become-the-system-of-record/code/repo-reader.ts - */ - -import * as fs from "node:fs"; -import * as path from "node:path"; - -// --------------------------------------------------------------------------- -// Scoring criteria -// --------------------------------------------------------------------------- - -interface Check { - name: string; - description: string; - maxPoints: number; - check: (dir: string) => { points: number; found: string[]; missing: string[] }; -} - -const checks: Check[] = [ - { - name: "AGENTS.md / CLAUDE.md", - description: "Agent-readable instruction file at repo root", - maxPoints: 15, - check: (dir) => { - const candidates = ["AGENTS.md", "CLAUDE.md", ".claude/CLAUDE.md"]; - const found = candidates.filter((f) => fs.existsSync(path.join(dir, f))); - return { points: found.length > 0 ? 15 : 0, found, missing: found.length > 0 ? [] : candidates }; - }, - }, - { - name: "Documentation directory", - description: "Dedicated docs/ or documentation/ directory", - maxPoints: 10, - check: (dir) => { - const candidates = ["docs", "documentation", "doc"]; - const found = candidates.filter((f) => { - const p = path.join(dir, f); - return fs.existsSync(p) && fs.statSync(p).isDirectory(); - }); - return { points: found.length > 0 ? 10 : 0, found, missing: found.length > 0 ? [] : ["docs/"] }; - }, - }, - { - name: "Architecture documentation", - description: "Files describing system architecture", - maxPoints: 15, - check: (dir) => { - const patterns = [ - "architecture.md", "ARCHITECTURE.md", "docs/architecture.md", - "docs/architecture/", "design.md", "DESIGN.md", - ]; - const found = patterns.filter((f) => fs.existsSync(path.join(dir, f))); - return { points: found.length > 0 ? 15 : 0, found, missing: found.length > 0 ? [] : ["architecture.md or docs/architecture/"] }; - }, - }, - { - name: "Feature tracking", - description: "Feature list or task tracking file", - maxPoints: 15, - check: (dir) => { - const patterns = [ - "feature_list.json", "features.md", "FEATURES.md", - "docs/features.md", "tasks.json", "TODO.md", - ]; - const found = patterns.filter((f) => fs.existsSync(path.join(dir, f))); - return { points: found.length > 0 ? 15 : 0, found, missing: found.length > 0 ? [] : ["feature_list.json or features.md"] }; - }, - }, - { - name: "Handoff / session continuity", - description: "Files for multi-session continuity", - maxPoints: 15, - check: (dir) => { - const patterns = [ - "HANDOFF.md", "handoff.md", "SESSION_NOTES.md", - "docs/handoff.md", ".handoff", "PROGRESS.md", - ]; - const found = patterns.filter((f) => fs.existsSync(path.join(dir, f))); - return { points: found.length > 0 ? 15 : 0, found, missing: found.length > 0 ? [] : ["HANDOFF.md or PROGRESS.md"] }; - }, - }, - { - name: "Testing structure", - description: "Test directory or test configuration", - maxPoints: 10, - check: (dir) => { - const patterns = ["test", "tests", "__tests__", "spec"]; - const found = patterns.filter((f) => { - const p = path.join(dir, f); - return fs.existsSync(p) && fs.statSync(p).isDirectory(); - }); - return { points: found.length > 0 ? 10 : 0, found, missing: found.length > 0 ? [] : ["test/ or tests/"] }; - }, - }, - { - name: "Configuration files", - description: "Project config (package.json, tsconfig, etc.)", - maxPoints: 10, - check: (dir) => { - const patterns = ["package.json", "tsconfig.json", "pyproject.toml", "Cargo.toml", "go.mod"]; - const found = patterns.filter((f) => fs.existsSync(path.join(dir, f))); - return { points: found.length > 0 ? 10 : 0, found, missing: found.length > 0 ? [] : ["package.json or equivalent"] }; - }, - }, - { - name: "README", - description: "Root README file", - maxPoints: 10, - check: (dir) => { - const patterns = ["README.md", "README.rst", "README.txt", "README"]; - const found = patterns.filter((f) => fs.existsSync(path.join(dir, f))); - return { points: found.length > 0 ? 10 : 0, found, missing: found.length > 0 ? [] : ["README.md"] }; - }, - }, -]; - -// --------------------------------------------------------------------------- -// Report generation -// --------------------------------------------------------------------------- - -function pad(s: string, len: number): string { - return s.length >= len ? s : s + " ".repeat(len - s.length); -} - -function scoreRepo(targetDir: string): void { - const resolved = path.resolve(targetDir); - - if (!fs.existsSync(resolved)) { - console.error(` Error: Directory not found: ${resolved}`); - process.exit(1); - } - - console.log("\n" + "=".repeat(80)); - console.log(" REPOSITORY DISCOVERABILITY SCORE"); - console.log("=".repeat(80)); - console.log(` Target: ${resolved}\n`); - - const header = `| ${pad("Criterion", 30)}| ${pad("Points", 8)}| ${pad("Status", 10)}| Details`; - const sep = `|${"-".repeat(32)}|${"-".repeat(10)}|${"-".repeat(12)}|${"-".repeat(30)}`; - console.log(header); - console.log(sep); - - let totalScore = 0; - let maxScore = 0; - - for (const check of checks) { - const result = check.check(resolved); - totalScore += result.points; - maxScore += check.maxPoints; - const status = result.points > 0 ? "PASS" : "FAIL"; - const detail = result.found.length > 0 ? result.found.join(", ") : result.missing[0]; - - console.log(`| ${pad(check.name, 30)}| ${pad(result.points + "/" + check.maxPoints, 8)}| ${pad(status, 10)}| ${detail}`); - } - - // Final score - console.log("\n" + "-".repeat(80)); - console.log(` TOTAL SCORE: ${totalScore} / ${maxScore} (${Math.round((totalScore / maxScore) * 100)}%)`); - - // Grade - const pct = totalScore / maxScore; - let grade: string; - if (pct >= 0.9) grade = "A -- Repository is a strong system of record"; - else if (pct >= 0.7) grade = "B -- Good foundation, minor gaps"; - else if (pct >= 0.5) grade = "C -- Partial structure, significant gaps"; - else if (pct >= 0.3) grade = "D -- Minimal structure, hard for agents to navigate"; - else grade = "F -- Repository lacks discoverability signals"; - - console.log(` GRADE: ${grade}`); - console.log("=".repeat(80) + "\n"); -} - -// --------------------------------------------------------------------------- -// Main -// --------------------------------------------------------------------------- - -const target = process.argv[2] || process.cwd(); -scoreRepo(target); diff --git a/courses/learn-harness-engineering/docs/ko/lectures/lecture-03-why-the-repository-must-become-the-system-of-record/code/system-of-record-checklist.md b/courses/learn-harness-engineering/docs/ko/lectures/lecture-03-why-the-repository-must-become-the-system-of-record/code/system-of-record-checklist.md deleted file mode 100644 index 5e06ed23..00000000 --- a/courses/learn-harness-engineering/docs/ko/lectures/lecture-03-why-the-repository-must-become-the-system-of-record/code/system-of-record-checklist.md +++ /dev/null @@ -1,11 +0,0 @@ -# 시스템 오브 레코드 체크리스트 - -새 에이전트(agent)가 저장소(repository)만으로 다음을 발견할 수 있는가? - -- 어떤 제품을 만들고 있는가? -- 앱이 사용자를 위해 무엇을 해야 하는가? -- 코드베이스는 어떻게 구성되어 있는가? -- 앱은 어떻게 시작하는가? -- 상태는 어떻게 확인하는가? -- 현재 어떤 작업이 진행 중인가? -- 어떤 품질 기준이 중요한가? diff --git a/courses/learn-harness-engineering/docs/ko/lectures/lecture-03-why-the-repository-must-become-the-system-of-record/index.md b/courses/learn-harness-engineering/docs/ko/lectures/lecture-03-why-the-repository-must-become-the-system-of-record/index.md deleted file mode 100644 index b4286ab8..00000000 --- a/courses/learn-harness-engineering/docs/ko/lectures/lecture-03-why-the-repository-must-become-the-system-of-record/index.md +++ /dev/null @@ -1,137 +0,0 @@ -[中文版本 →](../../../zh/lectures/lecture-03-why-the-repository-must-become-the-system-of-record/) - -> 코드 예제: [code/](https://github.com/walkinglabs/learn-harness-engineering/blob/main/docs/en/lectures/lecture-03-why-the-repository-must-become-the-system-of-record/code/) -> 실습 프로젝트: [Project 02. Agent-readable workspace](./../../projects/project-02-agent-readable-workspace/index.md) - -# 강의 03. 저장소를 단일 진실 원천으로 만들어라 - -팀의 아키텍처 결정이 Confluence, Slack, Jira, 그리고 몇몇 시니어 엔지니어의 머릿속에 흩어져 있습니다. 사람에게는 이것이 겨우 통합니다 — 동료에게 물어보고, 채팅 기록을 검색하고, 문서를 뒤지면 됩니다. 도저히 안 되면 휴게실에서 누군가를 붙잡을 수도 있습니다. 하지만 AI 에이전트(agent)에게는, 저장소(repository)에 없는 정보는 단순히 존재하지 않습니다. - -이것은 과장이 아닙니다. 에이전트의 입력이 실제로 무엇인지 생각해보십시오: 시스템 프롬프트와 작업 설명, 저장소의 파일 내용, 그리고 도구 실행 결과. 그뿐입니다. 슬랙 기록, Jira 티켓, Confluence 페이지, 금요일 오후 동료와 커피 한 잔 하며 나눈 아키텍처 결정 — 에이전트는 이 중 어느 것도 볼 수 없습니다. "누군가에게 물어보거나" "채팅 기록을 검색"할 수 없습니다. 저장소 안에 갇힌 엔지니어입니다 — 외부에 있는 것은 아무것도 모릅니다. - -그렇다면 질문은 이것입니다: 이 엔지니어에게 좋은 지도를 줄 건가요? - -## 지도에 무엇이 있어야 하는가 - -OpenAI는 이를 명확히 말합니다: **저장소에 없는 정보는 에이전트에게 존재하지 않습니다.** 그들은 이를 "저장소를 명세(spec)로" 원칙이라고 부릅니다 — 저장소 자체가 최고 권위의 명세 문서입니다. - -Anthropic의 장기 실행 에이전트 문서도 이를 반향합니다: 영속적인 상태(state)는 장기 작업 연속성의 필요 조건입니다. 세션 간 지식 복구 가능성이 작업 성공률을 직접 결정합니다. 그리고 이 상태는 저장소에 존재해야 합니다 — 에이전트가 가진 유일하게 안정적이고 접근 가능한 저장소이기 때문입니다. - -"우리 팀은 규모가 작아서 지식이 모두의 머릿속에 있는데 잘 돌아가고 있어"라고 생각할 수 있습니다. 사람에게는 그렇습니다. 하지만 에이전트를 사용한다면 이 사실을 받아들여야 합니다: 에이전트는 사람에게 물어볼 수 없습니다. 알아야 하는 모든 것은 적어서 찾을 수 있는 곳에 두어야 합니다. - -이것은 "더 많은 문서를 작성하는 것"에 관한 것이 아닙니다. "결정 정보를 올바른 장소에 두는 것"에 관한 것입니다. `src/api/` 디렉터리의 50줄짜리 `ARCHITECTURE.md`는 아무도 유지보수하지 않는 Confluence의 500페이지 설계 문서보다 만 배 유용합니다. 책상에 붙여 놓은 손으로 그린 사무실 지도와 파일 캐비닛에 잠겨 있는 아름다운 건축 청사진의 차이와 같습니다 — 전자는 필요할 때 바로 거기 있지만, 후자는 기술적으로 우수하더라도 현장에서는 쓸모가 없습니다. - -## 지식 가시성 - -```mermaid -flowchart LR - Slack["슬랙의 규칙"] --> Write["저장소 파일에 작성
AGENTS.md / ARCHITECTURE.md / PROGRESS.md"] - Confluence["Confluence의 규칙"] --> Write - Heads["사람들 머릿속의 규칙"] --> Write - Jira["Jira 티켓의 규칙"] --> Write - Write --> Repo["저장소 파일"] - Repo --> Agent["새 에이전트 세션
저장소를 직접 읽음"] - Warning["저장소에 없는 규칙은
에이전트가 볼 수 없음"] --> Agent -``` - -지도가 충분히 좋은지 테스트하는 방법은? "콜드 스타트 테스트(cold-start test)"를 실행하십시오: 저장소 내용만을 사용하여 완전히 새로운 에이전트 세션을 열고, 다섯 가지 기본 질문에 답할 수 있는지 확인하십시오: - -```mermaid -flowchart TB - Q1["이 시스템이 무엇인가?"] --> A1["AGENTS.md / README"] - Q2["어떻게 구성되어 있는가?"] --> A2["ARCHITECTURE.md / 모듈 문서"] - Q3["어떻게 실행하는가?"] --> A3["Makefile / init.sh / 패키지 스크립트"] - Q4["어떻게 검증하는가?"] --> A4["테스트, 린트, 검사 명령어"] - Q5["지금 어디에 있는가?"] --> A5["PROGRESS.md / 기능 목록 / git 기록"] - - A1 --> Ready["새 세션이 사람에게 묻지 않고
작업을 시작할 수 있음"] - A2 --> Ready - A3 --> Ready - A4 --> Ready - A5 --> Ready -``` - -답할 수 없다면 지도에 빈 곳이 있습니다. 지도에 빈 곳이 있으면 에이전트는 추측합니다 — 잘못된 추측은 버그가 되고, 과도한 추측은 컨텍스트(context)를 낭비합니다. 그리고 모든 새 세션에서 다시 추측합니다. 추측의 비용은 항상 처음부터 제대로 지도를 그리는 비용보다 높습니다. - -## 핵심 개념 - -- **지식 가시성 격차(Knowledge Visibility Gap)**: 저장소에 없는 전체 프로젝트 지식의 비율. 격차가 클수록 에이전트의 실패율이 높습니다. 이 프로젝트에 대해 얼마나 많은 암묵적 지식이 머릿속에 있는지 헤아려보십시오. 그 중 얼마나 저장소에 들어갔는지 확인하십시오 — 그 차이가 여러분의 가시성 격차입니다. -- **시스템 오브 레코드(SoR, System of Record)**: 프로젝트 결정, 아키텍처 제약, 실행 상태, 검증 기준에 대한 권위 있는 출처로서의 코드 저장소. 저장소가 최종 발언권을 가지며, 다른 어느 곳도 인정되지 않습니다. "도로 폐쇄"를 표시한 지도처럼 — 그 길로 가지 않습니다. 하지만 그 정보가 장씨의 머릿속에만 있다면 매번 장씨에게 물어봐야 합니다. -- **콜드 스타트 테스트(Cold-Start Test)**: 위의 다섯 가지 질문. 몇 개나 답할 수 있는가가 지도가 얼마나 완전한가를 나타냅니다. -- **발견 비용(Discovery Cost)**: 에이전트가 저장소에서 핵심 정보를 찾는 데 소비하는 컨텍스트 예산의 양. 정보가 숨어있을수록 발견 비용이 높아지고, 실제 작업에 남는 예산이 줄어듭니다. 중요한 정보를 열 개의 디렉터리 아래 README에 숨기는 것은 지하실 금고에 소화기를 잠그는 것과 같습니다 — 존재하지만 필요할 때 찾을 수 없습니다. -- **지식 부패율(Knowledge Decay Rate)**: 단위 시간당 낡아지는 지식 항목의 비율. 문서가 코드와 동기화되지 않는 것이 가장 큰 적입니다 — 문서가 없는 것보다 더 나쁩니다. -- **ACID 유추**: 데이터베이스 트랜잭션 원칙(원자성, 일관성, 격리성, 내구성)을 에이전트 상태 관리에 적용하는 것. 아래에서 자세히 설명합니다. - -## 좋은 지도를 그리는 방법 - -**원칙 1: 지식은 코드 옆에 있어야 합니다.** API 엔드포인트 인증에 관한 규칙은 거대한 전역 문서 안에 묻혀 있는 것이 아니라 API 코드 옆에 있어야 합니다. 각 모듈 디렉터리에 해당 모듈의 책임, 인터페이스, 특별 제약을 설명하는 짧은 문서를 두십시오. 도서관 선반 레이블처럼 — 역사책을 원하면 "역사" 표시 선반으로 바로 갑니다. 전체 도서관을 뒤질 필요가 없습니다. - -**원칙 2: 표준화된 진입 파일을 사용하십시오.** `AGENTS.md`(또는 `CLAUDE.md`)는 에이전트의 "랜딩 페이지"입니다. 모든 정보를 담을 필요는 없지만 에이전트가 세 가지 질문에 빠르게 답할 수 있도록 해야 합니다: "이 프로젝트가 무엇인가", "어떻게 실행하는가", "어떻게 검증하는가". 50-100줄이면 충분합니다. - -**원칙 3: 최소하되 완전하게.** 모든 지식은 명확한 사용 사례가 있어야 합니다. 규칙을 제거해도 에이전트의 결정 품질에 영향을 주지 않는다면 그 규칙은 존재하지 않아야 합니다. 하지만 콜드 스타트 테스트의 모든 질문에는 답이 있어야 합니다. 이것은 섬세한 균형입니다 — 너무 많지도, 너무 적지도 않게, 딱 적당하게. - -**원칙 4: 코드와 함께 업데이트하십시오.** 지식 업데이트를 코드 변경에 바인딩하십시오. 가장 간단한 방법: 아키텍처 문서를 해당 모듈 디렉터리에 두십시오. 코드를 수정할 때 자연스럽게 문서를 보게 됩니다. 코드 변경 후 CI에서 문서 업데이트가 필요한지 확인하도록 알릴 수 있습니다. - -**구체적인 저장소 구조**: - -``` -project/ -├── AGENTS.md # 진입점: 프로젝트 개요, 실행 명령어, HARD 제약 -├── src/ -│ ├── api/ -│ │ ├── ARCHITECTURE.md # API 레이어 아키텍처 결정 -│ │ └── ... -│ ├── db/ -│ │ ├── CONSTRAINTS.md # 데이터베이스 작업 HARD 제약 -│ │ └── ... -│ └── ... -├── PROGRESS.md # 현재 진행 상황: 완료, 진행 중, 차단됨 -└── Makefile # 표준화된 명령어: setup, test, lint, check -``` - -## ACID 원칙으로 에이전트 상태 관리하기 - -이 유추는 데이터베이스 트랜잭션 관리에서 비롯됩니다 — 과도하게 복잡하게 만드는 것 같지만, 실제로는 매우 실용적인 프레임워크를 제공합니다: - -- **원자성(Atomicity)**: 각 "논리적 작업"(예: "새 엔드포인트 추가 및 테스트 업데이트")은 하나의 git 커밋을 받습니다. 중간에 실패하면 `git stash`로 롤백합니다. 전부 아니면 아무것도 — "반쯤 완료된" 상태는 없습니다. -- **일관성(Consistency)**: "일관된 상태" 검증 조건을 정의하십시오 — 모든 테스트 통과, 린트 오류 없음. 에이전트는 각 작업 후 검증을 실행합니다. 일관성 없는 중간 상태는 커밋되지 않습니다. 은행 송금처럼 — 입금 없이 출금할 수 없습니다. -- **격리성(Isolation)**: 여러 에이전트가 동시에 작업할 때 상태 파일이 경쟁 조건을 피하도록 설계하십시오. 간단한 방법: 각 에이전트가 자체 진행 파일을 사용하거나, 격리를 위해 git 브랜치를 사용하십시오. 두 명의 주방장이 같은 냄비를 동시에 간을 맞출 수 없습니다 — 짜게 되면 누가 책임을 지나요? -- **내구성(Durability)**: 중요한 프로젝트 지식은 git으로 추적되는 파일에 있습니다. 임시 상태는 세션 메모리에 있어도 되지만, 세션 간 지식은 파일에 영속화해야 합니다. 머릿속에 있는 것은 인정되지 않습니다 — 종이에 있는 것만 인정됩니다. - -## 실제 변환 사례 - -약 30개의 마이크로서비스로 구성된 이커머스 플랫폼을 유지보수하는 팀이 있었습니다. 아키텍처 결정(서비스 간 통신 프로토콜, 데이터 일관성 전략, API 버전 관리 규칙)이 다음에 흩어져 있었습니다: Confluence(부분적으로 낡음), Slack(검색하기 어려움), 몇몇 시니어 엔지니어의 머릿속(확장 불가), 산발적인 코드 주석(체계적이지 않음). - -AI 에이전트를 도입한 후 70%의 작업에 사람의 개입이 필요했습니다. 거의 모든 실패에는 에이전트가 "모두가 알지만 아무도 적지 않은" 암묵적 제약을 위반하는 것이 포함되었습니다. 아무도 "점심 주문은 단체 채팅에 올려야 해"라고 알려주지 않은 새 직원과 같습니다 — 잘못 추측하고, 혼나지만, 혼난 후에도 아무도 규칙을 알려주지 않습니다. - -팀은 변환을 실행했습니다: -1. 저장소 루트에 `AGENTS.md`를 만들어 프로젝트 개요, 기술 스택 버전, 전역 HARD 제약 포함 -2. 각 마이크로서비스 디렉터리에 책임, 인터페이스, 의존성을 설명하는 `ARCHITECTURE.md` 추가 -3. 명시적인 "MUST/MUST NOT" 언어로 HARD 제약을 담은 중앙 집중식 `CONSTRAINTS.md` 생성 -4. 각 서비스 디렉터리에 현재 작업 상태를 추적하는 `PROGRESS.md` 추가 - -변환 후: 같은 에이전트가 콜드 스타트에서 모든 주요 프로젝트 질문에 답할 수 있게 되었고, 작업 완료 품질이 크게 향상되었습니다. - -## 핵심 정리 - -- 저장소에 없는 지식은 에이전트에게 존재하지 않습니다. 중요한 결정을 저장소에 두는 것이 가장 기본적인 하네스 투자입니다 — 길을 잃지 않도록 좋은 지도를 그리십시오. -- "콜드 스타트 테스트"를 사용하여 저장소 품질을 평가하십시오: 새 세션이 저장소 내용만으로 다섯 가지 기본 질문에 답할 수 있는가? -- 지식은 코드 가까이에, 최소하되 완전하게, 코드와 함께 업데이트되어야 합니다. 더 많은 문서를 작성하는 것이 아니라 정보를 올바른 장소에 두는 것입니다. -- ACID 원칙을 에이전트 상태에 사용하십시오: 원자적 커밋, 일관성 검증, 동시성 격리, 중요 지식의 내구성. -- 지식 부패가 가장 큰 적입니다. 코드와 동기화되지 않는 문서는 문서가 없는 것보다 더 위험합니다 — 에이전트를 맞는 방향이라고 생각하면서 잘못된 방향으로 보냅니다. - -## 더 읽을거리 - -- [OpenAI: Harness Engineering](https://openai.com/index/harness-engineering/) -- [Anthropic: Effective Harnesses for Long-Running Agents](https://www.anthropic.com/engineering/effective-harnesses-for-long-running-agents) -- [Infrastructure as Code — Martin Fowler](https://martinfowler.com/bliki/InfrastructureAsCode.html) -- [ADR: Architecture Decision Records](https://adr.github.io/) -- [The Twelve-Factor App](https://12factor.net/) - -## 연습 문제 - -1. **콜드 스타트 테스트**: 프로젝트에서 완전히 새로운 에이전트 세션을 여십시오(구두 컨텍스트 없음, 저장소 내용만). 다섯 가지 질문을 하십시오: 이 시스템이 무엇인가? 어떻게 구성되어 있는가? 어떻게 실행하는가? 어떻게 검증하는가? 현재 진행 상황은? 답하지 못하는 것을 기록한 다음, 에이전트가 답할 수 있을 때까지 저장소를 개선하십시오. - -2. **지식 외재화 정량화**: 프로젝트의 개발 작업에 중요한 모든 결정과 제약을 나열하십시오. 각각을 저장소 내부 또는 외부로 표시하십시오. 지식 가시성 격차(저장소 외부 비율)를 계산하십시오. 이를 10% 미만으로 낮추는 계획을 세우십시오. - -3. **ACID 평가**: 이 강의의 ACID 유추를 사용하여 프로젝트의 상태 관리를 평가하십시오. 원자성 — 에이전트 작업을 깔끔하게 롤백할 수 있는가? 일관성 — "일관된 상태" 검증이 있는가? 격리성 — 동시 에이전트들이 서로 간섭하는가? 내구성 — 모든 세션 간 지식이 영속화되는가? diff --git a/courses/learn-harness-engineering/docs/ko/lectures/lecture-04-why-one-giant-instruction-file-fails/code/AGENTS-short.md b/courses/learn-harness-engineering/docs/ko/lectures/lecture-04-why-one-giant-instruction-file-fails/code/AGENTS-short.md deleted file mode 100644 index 5ba6bb63..00000000 --- a/courses/learn-harness-engineering/docs/ko/lectures/lecture-04-why-one-giant-instruction-file-fails/code/AGENTS-short.md +++ /dev/null @@ -1,14 +0,0 @@ -# AGENTS.md - -## 시작하기 - -- `docs/ARCHITECTURE.md`를 읽으세요 -- `docs/PRODUCT.md`를 읽으세요 -- `npm run dev`로 앱을 시작하세요 -- 작업 완료로 표시하기 전에 `npm run check`를 실행하세요 - -## 하드 규칙 - -- `docs/ARCHITECTURE.md`를 읽지 않고 Electron main/preload/renderer 경계를 변경하지 마세요 -- 검증 없이 기능을 완료로 표시하지 마세요 -- 다음 세션을 위해 깨끗한 상태를 남기세요 diff --git a/courses/learn-harness-engineering/docs/ko/lectures/lecture-04-why-one-giant-instruction-file-fails/code/anti-patterns.md b/courses/learn-harness-engineering/docs/ko/lectures/lecture-04-why-one-giant-instruction-file-fails/code/anti-patterns.md deleted file mode 100644 index 3c9afe44..00000000 --- a/courses/learn-harness-engineering/docs/ko/lectures/lecture-04-why-one-giant-instruction-file-fails/code/anti-patterns.md +++ /dev/null @@ -1,7 +0,0 @@ -# 명령 파일 안티패턴 - -- 저장소의 모든 지식을 하나의 파일에 넣기 -- 여러 곳에 동일한 규칙을 반복하기 -- 아무도 감사하지 않는 오래된 규칙을 유지하기 -- 거의 적용되지 않는 지나치게 구체적인 조건부 명령 작성하기 -- 긴 도구 설명서를 시작 컨텍스트에 포함하기 diff --git a/courses/learn-harness-engineering/docs/ko/lectures/lecture-04-why-one-giant-instruction-file-fails/code/index.md b/courses/learn-harness-engineering/docs/ko/lectures/lecture-04-why-one-giant-instruction-file-fails/code/index.md deleted file mode 100644 index 1738dfff..00000000 --- a/courses/learn-harness-engineering/docs/ko/lectures/lecture-04-why-one-giant-instruction-file-fails/code/index.md +++ /dev/null @@ -1,7 +0,0 @@ -# 강의 04 코드 예제 - -이 폴더는 다음 예제들을 위해 사용합니다. - -- 단일 파일에 모든 내용을 담은 명령 파일 -- 짧은 진입점(entrypoint) 파일 -- 점진적 공개(progressive disclosure) 패턴 diff --git a/courses/learn-harness-engineering/docs/ko/lectures/lecture-04-why-one-giant-instruction-file-fails/code/split-vs-monolithic.ts b/courses/learn-harness-engineering/docs/ko/lectures/lecture-04-why-one-giant-instruction-file-fails/code/split-vs-monolithic.ts deleted file mode 100644 index 59a96328..00000000 --- a/courses/learn-harness-engineering/docs/ko/lectures/lecture-04-why-one-giant-instruction-file-fails/code/split-vs-monolithic.ts +++ /dev/null @@ -1,209 +0,0 @@ -/** - * split-vs-monolithic.ts - * - * Creates a monolithic instruction file (~200 lines) and then shows how - * splitting into 4 focused files dramatically reduces the context needed - * for any single query. Simulates an "agent" searching for a specific rule - * and measures how many lines it must read in each approach. - * - * Run: npx tsx docs/lectures/lecture-04-why-one-giant-instruction-file-fails/code/split-vs-monolithic.ts - */ - -// --------------------------------------------------------------------------- -// Simulated monolithic instruction file (200 lines of rules) -// --------------------------------------------------------------------------- - -const monolithicInstructions: { lineNumber: number; section: string; content: string }[] = []; - -// Section 1: Project Overview (lines 1-50) -for (let i = 1; i <= 50; i++) { - monolithicInstructions.push({ - lineNumber: i, - section: "Project Overview", - content: i === 25 ? "This project uses React 18 with TypeScript strict mode." : `Overview detail line ${i}`, - }); -} - -// Section 2: Code Style Rules (lines 51-100) -for (let i = 51; i <= 100; i++) { - monolithicInstructions.push({ - lineNumber: i, - section: "Code Style", - content: - i === 72 - ? "RULE: Always use explicit return types on exported functions." - : i === 78 - ? "RULE: Use const assertions for immutable arrays." - : `Style rule detail line ${i}`, - }); -} - -// Section 3: Testing Standards (lines 101-150) -for (let i = 101; i <= 150; i++) { - monolithicInstructions.push({ - lineNumber: i, - section: "Testing", - content: - i === 120 - ? "RULE: Every new endpoint must have integration tests." - : i === 135 - ? "RULE: Test files must mirror the source file structure." - : `Testing detail line ${i}`, - }); -} - -// Section 4: Deployment Rules (lines 151-200) -for (let i = 151; i <= 200; i++) { - monolithicInstructions.push({ - lineNumber: i, - section: "Deployment", - content: - i === 175 - ? "RULE: Never deploy on Fridays. Deploy window is Tue-Thu 10am-3pm." - : `Deployment detail line ${i}`, - }); -} - -// --------------------------------------------------------------------------- -// Split instruction files (4 focused files) -// --------------------------------------------------------------------------- - -const splitInstructions: Record = { - "01-project-overview.md": monolithicInstructions.filter((l) => l.section === "Project Overview"), - "02-code-style.md": monolithicInstructions.filter((l) => l.section === "Code Style"), - "03-testing.md": monolithicInstructions.filter((l) => l.section === "Testing"), - "04-deployment.md": monolithicInstructions.filter((l) => l.section === "Deployment"), -}; - -// --------------------------------------------------------------------------- -// Simulated queries -- the agent needs to find specific rules -// --------------------------------------------------------------------------- - -interface Query { - description: string; - targetRule: string; - relevantSection: string; -} - -const queries: Query[] = [ - { - description: "Find the rule about return types", - targetRule: "explicit return types", - relevantSection: "Code Style", - }, - { - description: "Find the deployment window rule", - targetRule: "deploy on Fridays", - relevantSection: "Deployment", - }, - { - description: "Find the integration test rule", - targetRule: "integration tests", - relevantSection: "Testing", - }, - { - description: "Find the test file structure rule", - targetRule: "mirror the source file", - relevantSection: "Testing", - }, -]; - -// --------------------------------------------------------------------------- -// Search simulation -// --------------------------------------------------------------------------- - -function searchMonolithic(query: Query): { linesRead: number; found: boolean } { - // Agent must scan from the top, reading each line until it finds the rule. - // In the worst case it reads all lines. - let linesRead = 0; - let found = false; - - for (const line of monolithicInstructions) { - linesRead++; - if (line.content.toLowerCase().includes(query.targetRule.toLowerCase())) { - found = true; - break; - } - } - - return { linesRead, found }; -} - -function searchSplit(query: Query): { linesRead: number; found: boolean; fileAccessed: string } { - // Agent knows which file to look in based on the section. - // It only reads lines from that one file. - const fileMap: Record = { - "Project Overview": "01-project-overview.md", - "Code Style": "02-code-style.md", - Testing: "03-testing.md", - Deployment: "04-deployment.md", - }; - - const targetFile = fileMap[query.relevantSection]; - const lines = splitInstructions[targetFile]; - let linesRead = 0; - let found = false; - - for (const line of lines) { - linesRead++; - if (line.content.toLowerCase().includes(query.targetRule.toLowerCase())) { - found = true; - break; - } - } - - return { linesRead, found, fileAccessed: targetFile }; -} - -// --------------------------------------------------------------------------- -// Reporting -// --------------------------------------------------------------------------- - -function pad(s: string, len: number): string { - return s.length >= len ? s : s + " ".repeat(len - s.length); -} - -function run(): void { - console.log("\n" + "=".repeat(90)); - console.log(" MONOLITHIC vs SPLIT INSTRUCTION FILES"); - console.log("=".repeat(90)); - - console.log("\n Monolithic file: 1 file, " + monolithicInstructions.length + " lines total"); - console.log(" Split files: 4 files, ~" + Math.round(monolithicInstructions.length / 4) + " lines each\n"); - - const header = `| ${pad("Query", 42)}| ${pad("Monolithic (lines)", 20)}| ${pad("Split (lines)", 15)}| ${pad("File Accessed", 22)}| Savings`; - const sep = `|${"-".repeat(44)}|${"-".repeat(22)}|${"-".repeat(17)}|${"-".repeat(24)}|${"-".repeat(10)}`; - console.log(header); - console.log(sep); - - let totalMono = 0; - let totalSplit = 0; - - for (const q of queries) { - const mono = searchMonolithic(q); - const split = searchSplit(q); - totalMono += mono.linesRead; - totalSplit += split.linesRead; - - const savings = Math.round(((mono.linesRead - split.linesRead) / mono.linesRead) * 100); - console.log( - `| ${pad(q.description, 42)}| ${pad(String(mono.linesRead), 20)}| ${pad(String(split.linesRead), 15)}| ${pad(split.fileAccessed, 22)}| ${savings}%` - ); - } - - console.log(sep); - const avgMono = Math.round(totalMono / queries.length); - const avgSplit = Math.round(totalSplit / queries.length); - console.log( - `| ${pad("AVERAGE", 42)}| ${pad(String(avgMono), 20)}| ${pad(String(avgSplit), 15)}| ${pad("(targeted file)", 22)}| ${Math.round(((avgMono - avgSplit) / avgMono) * 100)}%` - ); - - console.log("\n" + "=".repeat(90)); - console.log(" KEY INSIGHT"); - console.log("=".repeat(90)); - console.log(" With a monolithic file, the agent must scan up to " + monolithicInstructions.length + " lines for every query."); - console.log(" With split files, it reads only the relevant " + Math.round(monolithicInstructions.length / 4) + "-line file."); - console.log(" This means less context window usage, fewer hallucinations, and faster execution.\n"); -} - -run(); diff --git a/courses/learn-harness-engineering/docs/ko/lectures/lecture-04-why-one-giant-instruction-file-fails/index.md b/courses/learn-harness-engineering/docs/ko/lectures/lecture-04-why-one-giant-instruction-file-fails/index.md deleted file mode 100644 index a8e0fa1a..00000000 --- a/courses/learn-harness-engineering/docs/ko/lectures/lecture-04-why-one-giant-instruction-file-fails/index.md +++ /dev/null @@ -1,132 +0,0 @@ -[中文版本 →](../../../zh/lectures/lecture-04-why-one-giant-instruction-file-fails/) - -> 코드 예제: [code/](https://github.com/walkinglabs/learn-harness-engineering/blob/main/docs/en/lectures/lecture-04-why-one-giant-instruction-file-fails/code/) -> 실습 프로젝트: [Project 02. 에이전트가 읽을 수 있는 작업 공간](./../../projects/project-02-agent-readable-workspace/index.md) - -# 강의 04. 명령 파일을 여러 파일로 분산하라 - -하네스 엔지니어링(harness engineering)에 진지하게 임하기 시작했습니다. `AGENTS.md`를 만들고, 떠오르는 모든 규칙과 제약과 교훈을 그 안에 빼곡히 담았습니다. 한 달이 지나자 파일이 300줄로 불었고, 두 달 뒤엔 450줄, 세 달 뒤엔 600줄이 됐습니다. 그런데 에이전트(agent) 성능이 오히려 나빠지고 있다는 것을 알게 됩니다. 간단한 버그 수정을 할 때도 에이전트가 불필요한 배포 명령을 처리하느라 컨텍스트(context)를 낭비하고, 300번째 줄에 묻혀 있는 중요한 보안 제약은 완전히 무시되며, 서로 모순된 코드 스타일 규칙 세 개를 매번 임의로 골라 따릅니다. - -이것이 "거대한 명령 파일" 함정입니다. 여행 가방을 과도하게 채우는 것과 같습니다. 모든 물건이 필요해 보여서 계속 욱여넣다 보면 지퍼가 터질 지경이 됩니다. 속옷 한 장을 찾으려면 가방 전체를 비워야 합니다. 가득 찬 가방을 들고 다니지만, 실제로 꺼내 쓰는 물건은 그 중 3분의 1도 안 됩니다. - -## 근본 원인: 악순환 - -가장 흔한 악순환은 이렇게 진행됩니다. 에이전트가 실수를 하면, "이걸 막을 규칙을 추가하자"라는 생각에 AGENTS.md에 규칙을 넣습니다. 잠시 효과가 있습니다. 에이전트가 다른 실수를 하면, 또 규칙을 추가합니다. 이 과정을 반복하다 보면 파일은 걷잡을 수 없이 커집니다. - -이것은 여러분의 잘못이 아닙니다. "문제가 생기면 규칙을 추가한다"는 반응은 매우 자연스럽습니다. 외출할 때마다 "혹시 몰라서" 가방에 물건을 하나씩 더 챙기는 것과 같습니다. 하지만 누적 효과는 치명적입니다. 구체적으로 무엇이 잘못되는지 살펴보겠습니다. - -**컨텍스트 예산이 잠식됩니다.** 에이전트의 컨텍스트 윈도(context window)는 유한합니다. 에이전트에게 200K 토큰 윈도가 있다고 가정하면, 비대해진 명령 파일이 10~20K 토큰을 먹어치울 수 있습니다. 아직 여유가 있어 보이나요? 하지만 복잡한 작업은 수십 개의 소스 파일을 읽어야 하고, 도구 실행 결과도 컨텍스트를 차지하며, 대화 기록도 쌓입니다. 에이전트가 코드를 이해해야 할 때쯤이면 예산은 이미 빠듯합니다. "혹시 몰라서" 챙긴 물건들로 가득 찬 가방에 노트북을 넣을 자리가 없는 것과 같습니다. - -**중간에서 길을 잃습니다(Lost in the Middle).** "Lost in the Middle" 논문(Liu et al., 2023)은 LLM이 긴 텍스트의 중간에 있는 정보를 처음이나 끝에 있는 정보보다 훨씬 덜 효과적으로 활용한다는 것을 명확히 증명했습니다. AGENTS.md가 600줄이고 300번째 줄에 "모든 데이터베이스 쿼리는 매개변수화된 쿼리를 사용해야 한다"는 보안 하드 제약이 있다면, 그것은 중간에 묻혀 있어 에이전트가 거의 확실히 무시할 것입니다. 과도하게 채운 여행 가방 바닥의 선크림과 같습니다. 있다는 건 알지만 세 번을 뒤져도 못 찾아서 결국 새로 하나 삽니다. - -**우선순위 충돌이 발생합니다.** 파일은 협상 불가능한 하드 제약("절대 eval()을 사용하지 말 것"), 중요한 설계 지침("함수형 스타일을 선호할 것"), 그리고 특정 역사적 교훈("지난주에 WebSocket 메모리 누수를 고쳤으니 유사 패턴을 주의할 것")을 뒤섞어 놓습니다. 이 세 규칙의 중요도는 완전히 다르지만 파일 안에서는 동일하게 보입니다. 에이전트가 이를 구별할 신뢰할 만한 신호가 없습니다. 여행 가방 안에서 여권과 충전 케이블이 뒤엉켜 있는 것처럼, 어느 쪽이 더 긴급한지 알 수 없습니다. - -**유지보수 붕괴가 옵니다.** 큰 파일은 본질적으로 유지보수하기 어렵습니다. 오래된 명령은 좀처럼 삭제되지 않습니다. 삭제의 결과가 불확실하기 때문입니다("어쩌면 다른 규칙이 이것에 의존하고 있을지도?"). 반면 새 명령을 추가하는 것은 공짜처럼 느껴집니다. 결과적으로 파일은 오로지 커지기만 하고 줄어들지 않으며, 신호 대 잡음비(SNR)는 지속적으로 하락합니다. 이것은 소프트웨어의 기술 부채 축적과 정확히 같습니다. - -**모순이 쌓입니다.** 서로 다른 시점에 추가된 명령들이 서로 충돌하기 시작합니다. 한쪽은 "TypeScript strict 모드를 사용하라"고 하고, 다른 쪽은 "일부 레거시 파일은 any 타입을 허용한다"고 합니다. 에이전트는 매번 하나를 임의로 골라 따릅니다. 엄마가 "따뜻하게 입어라", 아빠가 "너무 두껍게 입지 마라"라고 하는 상황에서 문 앞에 서서 누구 말을 들어야 할지 모르는 것과 같습니다. - -## 핵심 개념 - -- **명령 비대화(Instruction Bloat)**: 명령 파일이 컨텍스트 윈도의 10~15%를 초과하면 코드 읽기와 작업 추론에 쓸 예산을 잠식하기 시작합니다. 600줄짜리 `AGENTS.md`는 10,000~20,000 토큰을 소비할 수 있으며, 이는 에이전트가 시작하기도 전에 128K 윈도의 8~15%가 소진되는 것입니다. -- **중간 손실 효과(Lost in the Middle Effect)**: Liu et al.의 2023년 연구는 LLM이 긴 텍스트의 중간 정보를 처음이나 끝 정보보다 훨씬 덜 효과적으로 활용한다는 것을 증명했습니다. 600줄 파일의 300번째 줄에 묻힌 중요한 제약은 사실상 무시될 가능성이 매우 높습니다. -- **명령 신호 대 잡음비(SNR)**: 파일 안의 명령 중 현재 작업과 관련된 비율입니다. 버그 수정 중에 배포 명령 50줄을 읽어야 한다면, 그것은 낮은 SNR입니다. -- **라우팅 파일(Routing File)**: 모든 것을 담는 것이 아니라 에이전트를 더 상세한 문서로 안내하는 것이 핵심 기능인 짧은 진입 파일. 50~200줄이면 충분합니다. -- **점진적 공개(Progressive Disclosure)**: 먼저 개요 정보를 제공하고, 필요할 때 상세 정보를 제공합니다. 좋은 하네스 설계는 좋은 UI 설계와 같습니다. 모든 옵션을 한 번에 사용자에게 쏟아붓지 않습니다. -- **우선순위 모호성(Priority Ambiguity)**: 모든 명령이 같은 형식과 위치에 나타나면, 에이전트는 협상 불가능한 하드 제약과 권장적인 소프트 가이드라인을 구별할 수 없습니다. - -## 명령 아키텍처 - -```mermaid -flowchart LR - Mono["600줄짜리 단일 AGENTS.md"] --> MonoLoad["간단한 버그 수정에도
배포 규칙과 오래된 메모를 모두 읽어야 함"] - MonoLoad --> MonoRisk["중간에 묻힌 중요 규칙은
쉽게 놓칠 수 있음"] - - Router["짧은 AGENTS.md"] --> Topics["이 작업에 필요한 경우에만
API / DB / 테스트 문서를 로드"] - Topics --> RoutedResult["코드 읽기와 검증에
더 많은 컨텍스트가 남음"] -``` - -```mermaid -flowchart TB - File["600줄짜리 명령 파일"] --> Top["상단 섹션
빠른 시작 + 하드 제약"] - File --> Mid["중간 섹션
300번째 줄의 중요한 보안 규칙"] - File --> Bot["하단 섹션
파일 끝의 명시적 체크리스트"] - Top --> Seen["높은 회상 가능성"] - Bot --> Seen - Mid --> Missed["희석되거나 놓칠 가능성 높음"] -``` - -## 분산하는 방법 - -핵심 원칙: 자주 필요한 정보는 손에 닿는 곳에, 가끔 필요한 정보는 치워두고, 결코 쓰지 않을 정보는 버립니다. - -진입 파일인 `AGENTS.md`는 50~200줄로 유지하며, 가장 자주 사용하는 항목만 담습니다. 프로젝트 개요(한두 문장), 첫 실행 명령(`make setup && make test`), 전역 하드 제약(협상 불가능한 규칙 최대 15개), 그리고 주제 문서 링크(한 줄 설명 + 적용 조건)입니다. - -```markdown -# AGENTS.md - -## 프로젝트 개요 -Python 3.11 FastAPI 백엔드, PostgreSQL 15 데이터베이스. - -## 빠른 시작 -- 설치: `make setup` -- 테스트: `make test` -- 전체 검증: `make check` - -## 하드 제약 -- 모든 API는 OAuth 2.0 인증을 사용해야 함 -- 모든 데이터베이스 쿼리는 SQLAlchemy 2.0 문법을 사용해야 함 -- 모든 PR은 pytest + mypy --strict + ruff check를 통과해야 함 - -## 주제 문서 -- [API 설계 패턴](docs/api-patterns.md) — 엔드포인트 추가 시 필독 -- [데이터베이스 규칙](docs/database-rules.md) — 데이터베이스 작업 수정 시 필독 -- [테스트 표준](docs/testing-standards.md) — 테스트 작성 시 참조 -``` - -각 주제 문서는 50~150줄이며, `docs/` 디렉터리 또는 해당 모듈 옆에 주제별로 정리됩니다. 에이전트는 필요할 때만 읽습니다. 여행 가방 안의 패킹 큐브와 같습니다. 속옷은 한 큐브에, 세면도구는 다른 큐브에, 충전기는 세 번째 큐브에. 전체 가방을 비우지 않아도 원하는 것을 찾을 수 있습니다. - -일부 정보는 코드 안에 직접 배치하는 것이 더 낫습니다. 타입 정의, 인터페이스 주석, 설정 파일의 설명 등입니다. 에이전트는 코드를 읽을 때 자연스럽게 이것들을 보게 되므로, 명령 파일에 중복할 필요가 없습니다. - -모든 명령에는 출처("이 규칙은 왜 추가됐는가?"), 적용 조건("이 규칙은 언제 필요한가?"), 만료 조건("어떤 상황에서 이 규칙을 제거할 수 있는가?")이 있어야 합니다. 정기적으로 감사하고, 오래되거나 중복되거나 모순된 항목을 제거합니다. 코드 의존성을 관리하듯 명령을 관리하세요. 사용하지 않는 의존성은 삭제해야 합니다. 그렇지 않으면 시스템이 느려질 뿐입니다. - -명령이 반드시 진입 파일에 있어야 한다면, 맨 위나 맨 아래에 두세요. 절대 중간에 두지 마세요. "중간 손실" 효과는 LLM이 양 끝의 정보를 중간보다 훨씬 더 잘 활용한다는 것을 알려줍니다. 하지만 더 나은 접근법은 명령을 주제 문서로 옮겨 필요할 때만 로드하는 것입니다. - -OpenAI와 Anthropic 모두 분산 접근 방식을 암묵적으로 지지합니다. OpenAI는 진입 파일이 "짧고 라우팅 중심"이어야 한다고 말하고, Anthropic은 장기 실행 에이전트 제어 정보가 "간결하고 높은 우선순위"여야 한다고 말합니다. 둘 다 같은 말을 하고 있습니다. 모든 것을 하나의 파일에 구겨 넣지 말라는 것입니다. 여행 가방도 정리가 필요합니다. 무작정 욱여넣는 것이 아니라. - -## 실제 사례 - -한 SaaS 팀의 `AGENTS.md`가 50줄에서 600줄로 불었습니다. 내용은 기술 스택 버전, 코딩 표준, 역사적 버그 수정 메모, API 사용 가이드, 배포 절차, 팀원들의 개인 선호도가 뒤섞여 있었습니다. 여행 가방이 터질 지경이었습니다. - -에이전트 성능이 눈에 띄게 저하되기 시작했습니다. 간단한 버그 수정 중에도 에이전트가 관련 없는 배포 명령을 처리하느라 많은 컨텍스트를 낭비했고, "모든 데이터베이스 쿼리는 매개변수화된 쿼리를 사용해야 한다"는 보안 제약이 300번째 줄에 묻혀 자주 무시됐으며, 서로 모순된 코드 스타일 규칙 세 개가 에이전트의 임의적인 행동을 유발했습니다. - -팀은 "여행 가방 재정리"를 실행했습니다. -1. `AGENTS.md`를 80줄로 줄임: 프로젝트 개요, 실행 명령, 전역 하드 제약 15개만 남김 -2. 주제 문서 생성: `docs/api-patterns.md`(120줄), `docs/database-rules.md`(60줄), `docs/testing-standards.md`(80줄) -3. 라우팅 파일에 주제 문서 링크 추가 -4. 역사적 메모는 테스트 케이스로 전환하거나 삭제 - -리팩터링 후: 동일 작업 세트 성공률이 45%에서 72%로 올랐습니다. 보안 제약 준수율은 60%에서 95%로 상승했는데, 이는 파일 중간에서 라우팅 파일 상단으로 이동해 더 이상 "중간에서 길을 잃지" 않게 됐기 때문입니다. - -## 핵심 정리 - -- "규칙 추가"는 단기 진통제이고 장기 독약입니다. 규칙을 추가하기 전에 먼저 물어보세요: 이게 주제 문서에 더 적합하지 않을까요? 가방에 물건을 계속 욱여넣지 마세요. -- 진입 파일은 라우터이지 백과사전이 아닙니다. 50~200줄에 개요, 하드 제약, 링크만 담으세요. -- "중간 손실" 효과를 활용하세요: 중요한 정보는 위나 아래에, 덜 중요한 정보는 주제 문서로 이동합니다. -- 명령 비대화를 기술 부채처럼 관리하세요. 정기적인 감사, 모든 명령에 출처·적용 조건·만료 조건이 필요합니다. -- 분산 후 SNR이 개선되고 에이전트는 관련 없는 명령을 처리하는 대신 실제 작업에 더 많은 컨텍스트 예산을 씁니다. - -## 더 읽어보기 - -- [OpenAI: Harness Engineering](https://openai.com/index/harness-engineering/) -- [Anthropic: Effective Harnesses for Long-Running Agents](https://www.anthropic.com/engineering/effective-harnesses-for-long-running-agents) -- [Lost in the Middle: How Language Models Use Long Contexts](https://arxiv.org/abs/2307.03172) -- [HumanLayer: Harness Engineering for Coding Agents](https://humanlayer.dev/articles/harness-engineering-for-coding-agents/) -- [Nielsen Norman Group: Progressive Disclosure](https://www.nngroup.com/articles/progressive-disclosure/) - -## 연습 문제 - -1. **SNR 감사**: 현재 진입 명령 파일을 가져와 모든 명령 항목을 나열하세요. 흔한 작업 유형 5가지를 골라 각 명령이 해당 작업과 관련 있는지 표시하세요. 각 작업 유형별 SNR을 계산하세요. 대부분의 작업에서 잡음인 명령은 주제 문서로 이동해야 합니다. - -2. **점진적 공개 리팩터**: 300줄 이상의 명령 파일이 있다면 다음으로 분리하세요: (a) 100줄 미만의 라우팅 파일, (b) 3~5개의 주제 문서. 분리 전후에 동일한 작업 세트(최소 5개)를 실행하고 성공률을 비교하세요. - -3. **중간 손실 검증**: 긴 명령 파일에서 중요한 제약을 각각 맨 위, 중간, 맨 아래에 배치하고 동일한 작업 세트를 실행하세요(위치당 최소 5회). 준수율에 차이가 있는지 확인하세요. 위치 효과가 얼마나 강력한지 놀랄 수도 있습니다. diff --git a/courses/learn-harness-engineering/docs/ko/lectures/lecture-05-why-long-running-tasks-lose-continuity/code/continuity-checklist.md b/courses/learn-harness-engineering/docs/ko/lectures/lecture-05-why-long-running-tasks-lose-continuity/code/continuity-checklist.md deleted file mode 100644 index 1c8ab44f..00000000 --- a/courses/learn-harness-engineering/docs/ko/lectures/lecture-05-why-long-running-tasks-lose-continuity/code/continuity-checklist.md +++ /dev/null @@ -1,6 +0,0 @@ -# 연속성(continuity) 체크리스트 - -- 새로운 에이전트가 5분 이내에 최근 작업을 파악할 수 있습니까? -- 현재의 안정적인 시작 경로가 문서화되어 있습니까? -- 미완료 작업이 명확히 식별되어 있습니까? -- 이전 채팅 로그를 읽지 않고도 다음으로 가장 좋은 작업이 보입니까? diff --git a/courses/learn-harness-engineering/docs/ko/lectures/lecture-05-why-long-running-tasks-lose-continuity/code/index.md b/courses/learn-harness-engineering/docs/ko/lectures/lecture-05-why-long-running-tasks-lose-continuity/code/index.md deleted file mode 100644 index 59b9f9d9..00000000 --- a/courses/learn-harness-engineering/docs/ko/lectures/lecture-05-why-long-running-tasks-lose-continuity/code/index.md +++ /dev/null @@ -1,7 +0,0 @@ -# 강의 05 코드 예제 - -이 폴더는 다음 예제들을 위해 사용합니다. - -- 멀티 세션 작업의 연속성이 끊긴 사례 -- 연속성 산출물(continuity artifacts)이 누락된 경우 -- 연속성 복구 패턴 diff --git a/courses/learn-harness-engineering/docs/ko/lectures/lecture-05-why-long-running-tasks-lose-continuity/code/session-handoff.md b/courses/learn-harness-engineering/docs/ko/lectures/lecture-05-why-long-running-tasks-lose-continuity/code/session-handoff.md deleted file mode 100644 index c6b33705..00000000 --- a/courses/learn-harness-engineering/docs/ko/lectures/lecture-05-why-long-running-tasks-lose-continuity/code/session-handoff.md +++ /dev/null @@ -1,17 +0,0 @@ -# 세션 핸드오프(Session Handoff) 예제 - -## 완료된 항목 - -- 마크다운 가져오기 지원 추가 -- 렌더러에 기본 문서 목록 추가 - -## 깨지거나 미검증된 항목 - -- `.md` 가져오기는 성공하지만 큰 `.txt` 파일은 실패함 -- 앱은 시작되지만 상세 보기가 아직 연결되지 않음 - -## 다음으로 가장 좋은 단계 - -- `.txt` 가져오기 경로 수정 -- 가져오기 엔드투엔드 검증 -- 이후 문서 상세 패널 추가 diff --git a/courses/learn-harness-engineering/docs/ko/lectures/lecture-05-why-long-running-tasks-lose-continuity/code/session-simulator.ts b/courses/learn-harness-engineering/docs/ko/lectures/lecture-05-why-long-running-tasks-lose-continuity/code/session-simulator.ts deleted file mode 100644 index 69f9c3e2..00000000 --- a/courses/learn-harness-engineering/docs/ko/lectures/lecture-05-why-long-running-tasks-lose-continuity/code/session-simulator.ts +++ /dev/null @@ -1,162 +0,0 @@ -/** - * session-simulator.ts - * - * Simulates two sessions working on a multi-step task. - * Run 1: No handoff file -- Session B starts from scratch and duplicates work. - * Run 2: With handoff file -- Session B picks up where Session A left off. - * - * Run: npx tsx docs/lectures/lecture-05-why-long-running-tasks-lose-continuity/code/session-simulator.ts - */ - -// --------------------------------------------------------------------------- -// Types -// --------------------------------------------------------------------------- - -interface TaskStep { - id: number; - name: string; - durationMs: number; -} - -interface SessionResult { - session: string; - stepsCompleted: number; - totalDurationMs: number; - duplicatedSteps: number; - output: string[]; -} - -// --------------------------------------------------------------------------- -// Task definition -// --------------------------------------------------------------------------- - -const taskSteps: TaskStep[] = [ - { id: 1, name: "Read project structure", durationMs: 50 }, - { id: 2, name: "Understand existing auth module", durationMs: 80 }, - { id: 3, name: "Design new search endpoint", durationMs: 60 }, - { id: 4, name: "Implement search endpoint", durationMs: 100 }, - { id: 5, name: "Write integration tests", durationMs: 70 }, - { id: 6, name: "Update documentation", durationMs: 40 }, -]; - -// --------------------------------------------------------------------------- -// Simulated session executor -// --------------------------------------------------------------------------- - -/** Simulate a session doing work. */ -function runSession( - sessionName: string, - startStep: number, - endStep: number -): SessionResult { - const output: string[] = []; - let totalDuration = 0; - - for (let i = startStep; i <= endStep; i++) { - const step = taskSteps.find((s) => s.id === i)!; - totalDuration += step.durationMs; - output.push(` [${sessionName}] Step ${step.id}: ${step.name} (${step.durationMs}ms)`); - } - - return { - session: sessionName, - stepsCompleted: endStep - startStep + 1, - totalDurationMs: totalDuration, - duplicatedSteps: 0, - output, - }; -} - -// --------------------------------------------------------------------------- -// Run 1: No handoff file -// --------------------------------------------------------------------------- - -function simulateNoHandoff(): { sessionA: SessionResult; sessionB: SessionResult } { - // Session A does steps 1-3 before timing out - const sessionA = runSession("Session A", 1, 3); - - // Session B has no context, starts from scratch - const sessionB = runSession("Session B", 1, 6); - sessionB.duplicatedSteps = 3; // Redoes steps 1-3 that A already did - - return { sessionA, sessionB }; -} - -// --------------------------------------------------------------------------- -// Run 2: With handoff file -// --------------------------------------------------------------------------- - -function simulateWithHandoff(): { sessionA: SessionResult; sessionB: SessionResult } { - // Session A does steps 1-3 and writes a handoff file - const sessionA = runSession("Session A", 1, 3); - - // Session B reads handoff, starts from step 4 - const sessionB = runSession("Session B", 4, 6); - sessionB.duplicatedSteps = 0; - - return { sessionA, sessionB }; -} - -// --------------------------------------------------------------------------- -// Reporting -// --------------------------------------------------------------------------- - -function pad(s: string, len: number): string { - return s.length >= len ? s : s + " ".repeat(len - s.length); -} - -function printRun(title: string, result: { sessionA: SessionResult; sessionB: SessionResult }): void { - console.log("\n" + "=".repeat(80)); - console.log(` ${title}`); - console.log("=".repeat(80)); - - console.log("\n Session A output:"); - for (const line of result.sessionA.output) { - console.log(line); - } - - console.log("\n Session B output:"); - for (const line of result.sessionB.output) { - console.log(line); - } - - console.log("\n Session B notes:"); - if (result.sessionB.duplicatedSteps > 0) { - console.log(` - Redid ${result.sessionB.duplicatedSteps} steps that Session A already completed`); - console.log(` - Wasted ${result.sessionB.totalDurationMs - (taskSteps.slice(3).reduce((s, t) => s + t.durationMs, 0))}ms on duplicate work`); - } else { - console.log(" - Read handoff file, continued from step 4"); - console.log(" - No duplicate work performed"); - } -} - -function printComparison(): void { - const noHandoff = simulateNoHandoff(); - const withHandoff = simulateWithHandoff(); - - printRun("RUN 1: NO HANDOFF FILE", noHandoff); - printRun("RUN 2: WITH HANDOFF FILE", withHandoff); - - // Comparison table - console.log("\n" + "=".repeat(80)); - console.log(" COMPARISON TABLE"); - console.log("=".repeat(80) + "\n"); - - const header = `| ${pad("Metric", 35)}| ${pad("No Handoff", 15)}| ${pad("With Handoff", 15)}|`; - const sep = `|${"-".repeat(37)}|${"-".repeat(17)}|${"-".repeat(17)}|`; - console.log(header); - console.log(sep); - - const noTotal = noHandoff.sessionA.totalDurationMs + noHandoff.sessionB.totalDurationMs; - const withTotal = withHandoff.sessionA.totalDurationMs + withHandoff.sessionB.totalDurationMs; - - console.log(`| ${pad("Session A steps completed", 35)}| ${pad(String(noHandoff.sessionA.stepsCompleted), 15)}| ${pad(String(withHandoff.sessionA.stepsCompleted), 15)}|`); - console.log(`| ${pad("Session B steps completed", 35)}| ${pad(String(noHandoff.sessionB.stepsCompleted), 15)}| ${pad(String(withHandoff.sessionB.stepsCompleted), 15)}|`); - console.log(`| ${pad("Duplicated steps", 35)}| ${pad(String(noHandoff.sessionB.duplicatedSteps), 15)}| ${pad(String(withHandoff.sessionB.duplicatedSteps), 15)}|`); - console.log(`| ${pad("Total work (ms)", 35)}| ${pad(String(noTotal), 15)}| ${pad(String(withTotal), 15)}|`); - console.log(`| ${pad("Time saved by handoff", 35)}| ${pad("-", 15)}| ${pad(String(noTotal - withTotal) + "ms", 15)}|`); - - console.log("\n A handoff file eliminates duplicate work and ensures continuity across sessions.\n"); -} - -printComparison(); diff --git a/courses/learn-harness-engineering/docs/ko/lectures/lecture-05-why-long-running-tasks-lose-continuity/index.md b/courses/learn-harness-engineering/docs/ko/lectures/lecture-05-why-long-running-tasks-lose-continuity/index.md deleted file mode 100644 index 345c6849..00000000 --- a/courses/learn-harness-engineering/docs/ko/lectures/lecture-05-why-long-running-tasks-lose-continuity/index.md +++ /dev/null @@ -1,180 +0,0 @@ -[中文版本 →](../../../zh/lectures/lecture-05-why-long-running-tasks-lose-continuity/) - -> 코드 예제: [code/](https://github.com/walkinglabs/learn-harness-engineering/blob/main/docs/en/lectures/lecture-05-why-long-running-tasks-lose-continuity/code/) -> 실습 프로젝트: [Project 03. 멀티 세션 연속성](./../../projects/project-03-multi-session-continuity/index.md) - -# 강의 05. 세션을 넘어 컨텍스트를 살아있게 유지하라 - -Claude Code에게 완전한 기능을 구현하도록 요청합니다. 30분 동안 실행되며 대부분의 작업을 완료하지만 컨텍스트(context)가 거의 소진됩니다. 계속하기 위해 새 세션을 시작하면, 지난번에 어떤 결정이 내려졌는지, 왜 옵션 A가 아닌 옵션 B가 선택됐는지, 어떤 파일이 이미 수정됐는지, 테스트 상태가 어떤지를 기억하지 못한다는 것을 발견합니다. 프로젝트를 재탐색하는 데 15분을 쓰고, 이전 접근법과 일관성이 없을 수도 있습니다. - -매일 아침 일어나면 모든 것을 잊어버리는 장인을 상상해 보세요. 건설 현장 전체를 다시 파악해야 합니다. 어떤 벽이 반쯤 지어졌는지, 왜 파란 벽돌이 아닌 빨간 벽돌을 선택했는지, 배관 공사가 어디까지 진행됐는지를요. 더 나쁜 경우, 어제 이미 설치된 창문을 그것이 완료됐다는 것을 기억하지 못해서 뜯어낼 수도 있습니다. - -이것이 바로 장기 작업에서 AI 코딩 에이전트(agent)가 처한 딜레마입니다. 이 강의는 에이전트가 장기 작업 중에 "블랙아웃"되는 이유와, 구조화된 상태(state) 영속화가 에이전트를 신뢰할 수 있는 일일 일지를 갖춘 장인으로 만드는 방법을 설명합니다. 여전히 건망증이 있지만, 일지가 모든 것을 기억합니다. - -## 컨텍스트 윈도는 무한하지 않습니다 - -컨텍스트 윈도는 유한합니다. 이것은 모델 업그레이드로 해결할 수 없는 문제입니다. 윈도 크기가 1M 토큰으로 커지더라도 복잡한 작업은 결국 소진될 것입니다. 에이전트는 단순히 코드를 생성하는 것이 아닙니다. 코드베이스를 이해하고, 자신의 결정 이력을 추적하고, 도구 출력을 처리하고, 대화 컨텍스트를 유지합니다. 이 모든 정보는 윈도 확장보다 빠르게 증가합니다. - -더 깊은 문제가 있습니다. 에이전트가 생성하는 정보가 균등하게 중요하지 않다는 것입니다. 중간 추론 단계에는 결정의 "왜"가 담겨 있습니다. 왜 옵션 A가 아닌 옵션 B를 선택했는지, 왜 이 라이브러리를 저 라이브러리 대신 사용했는지, 왜 특정 최적화를 건너뛰었는지를요. 최종 출력에는 "무엇"만 담겨 있습니다. 코드 자체입니다. 컴팩션(compaction) 전략은 보통 후자를 보존하지만 전자를 잃습니다. 다음 세션은 코드를 보지만 왜 그렇게 작성됐는지 알지 못하며, 의도적인 설계 결정을 "최적화"해 버릴 수도 있습니다. - -Anthropic은 자신들의 장기 실행 에이전트 연구에서 흥미로운 사실을 발견했습니다. 에이전트가 컨텍스트가 소진되고 있다고 느끼면 "조기 수렴" 행동을 보인다는 것입니다. 현재 작업을 서둘러 마무리하고, 검증 단계를 건너뛰거나, 최적의 해결책 대신 간단한 해결책을 선택합니다. 시험에서 시간이 얼마 남지 않았다는 것을 깨닫고 나머지 객관식 문제에 급하게 임의로 답을 채워넣는 것과 같습니다. Anthropic은 이를 "컨텍스트 불안(context anxiety)"이라고 부릅니다. - -## 세션 연속성(continuity) 흐름 - -연속성 산출물(continuity artifacts)이 없으면 모든 새 세션은 재앙입니다. - -```mermaid -flowchart LR - S1["세션 1
기능이 절반 완성"] --> End1["컨텍스트 거의 소진
세션 종료"] - End1 --> S2["세션 2 새로 시작"] - S2 --> Guess["폴더 재탐색, 테스트 재실행,
코드가 왜 이렇게 작성됐는지 추측"] - Guess --> Drift["작업이 반복되고
복구가 느림"] -``` - -연속성 산출물이 있으면 새 세션이 빠르게 이어받을 수 있습니다. - -```mermaid -flowchart LR - Work["세션 1 작업"] --> Progress["PROGRESS.md
완료 / 진행 중 / 다음 단계"] - Work --> Decisions["DECISIONS.md
왜 이 접근법을 선택했는지"] - Work --> Verify["검증 메모
어떤 테스트가 통과하고 실패하는지"] - Work --> Commit["Git 체크포인트
정확한 저장소 상태"] - - Progress --> Rebuild["세션 2 재구축"] - Decisions --> Rebuild - Verify --> Rebuild - Commit --> Rebuild - - Rebuild --> Resume["새 세션이 빠르게 이어받음"] -``` - -## 핵심 개념 - -- **컨텍스트 윈도는 유한합니다**: 어떤 윈도 크기가 주장되더라도(128K, 200K, 1M), 긴 작업은 결국 소진됩니다. 소진 후에는 컴팩션(정보 손실) 또는 리셋(새 세션)이 필요합니다. 둘 다 무언가를 잃습니다. -- **연속성 산출물**: 새 세션이 이전 세션이 중단한 곳에서 명확하게 재개할 수 있도록 해주는 영속화된 상태 파일입니다. 기본 형태: 진행 로그 + 검증(verification) 기록 + 다음 행동. 그 장인의 일지입니다. -- **재구축 비용(Rebuild cost)**: 새 세션이 실행 가능한 상태에 도달하는 데 필요한 시간. 좋은 하네스는 재구축 비용을 15분에서 3분으로 줄일 수 있습니다. -- **드리프트(Drift)**: 에이전트의 이해와 코드 저장소(repository)의 실제 상태 사이의 간극. 모든 세션 경계는 드리프트를 유발하며, 통제하지 않으면 누적됩니다. -- **컨텍스트 불안(Context anxiety)**: Anthropic이 관찰한 현상으로, 에이전트가 인식된 컨텍스트 한계에 접근하면 조기 수렴 행동을 보이며 정보 손실을 피하기 위해 작업을 일찍 마무리합니다. 비합리적인 자원 불안입니다. -- **컴팩션 vs 리셋**: 컴팩션은 동일 세션 내에서 초기 대화를 요약합니다("무엇"은 유지하지만 "왜"를 잃을 수 있음). 리셋은 새 세션을 열어 영속화된 상태에서 재구축합니다(깔끔하지만 산출물 완전성에 의존함). - -## 연속성이 깨지면 어떤 일이 벌어지나 - -이전 세션은 상당한 컨텍스트 예산을 사용해 세 가지 접근법을 분석하고 옵션 B를 선택했습니다. 이번 세션의 에이전트는 그 분석을 모르고 불완전한 정보를 바탕으로 다시 결정을 내릴 수 있습니다. 왜 빨간 벽돌이 선택됐는지 기억하지 못하는 건망증 장인이 오늘 파란 벽돌을 보고 더 예쁘다고 생각해 어제 쌓은 벽을 허물고 다시 짓는 것과 같습니다. - -더 나쁜 것은 중복 작업입니다. 에이전트가 특정 작업이 이미 완료됐는지 확인할 수 없어 다시 합니다. 또는 더 나쁜 경우, 절반만 하다가 기존 구현과의 충돌을 발견하고 다시 작업해야 합니다. 건설 현장에서 두 팀이 동시에 같은 벽을 지을 수 없습니다. 하지만 진행 기록이 없으면 새 팀은 누군가가 이미 그것을 하고 있다는 것을 알 수 없습니다. - -여러 세션에 걸쳐 구현 방향이 원래 요구사항에서 조용히 벗어났을 수도 있습니다. 각 새 세션은 프로젝트 목표에 대해 약간 다른 이해를 가집니다. 전화 전달 게임처럼, 열 명이 메시지를 전달한 후 "커피 한 잔 가져다 줘"가 "커피 머신을 사줘"가 될 수 있습니다. - -검증 격차도 있습니다. 이전 세션의 검증 결과(어떤 테스트가 통과하고, 실패하고, 왜 실패하는지)가 기록되지 않았습니다. 새 세션은 현재 상태를 이해하기 위해 모든 검증을 다시 실행해야 합니다. 매 세션마다 처음부터 진단하고, 매번 귀중한 컨텍스트를 낭비합니다. - -OpenAI와 Anthropic 모두 자신들의 문서에서 구조화된 상태 영속화를 강조합니다. OpenAI의 하네스 엔지니어링 기사는 저장소를 "운영 기록"으로 취급합니다. 모든 작업의 결과는 저장소에 추적 가능한 증거를 남겨야 합니다. Anthropic의 장기 실행 에이전트 문서는 "핸드오프(handoff) 파일"을 명시적으로 권장합니다. 현재 상태, 알려진 이슈, 다음 행동을 담은 구조화된 문서입니다. - -## 건망증 장인을 위한 일지 - -핵심 접근법: **에이전트를 건망증이 있는 뛰어난 엔지니어처럼 취급하세요.** "퇴근" 전에 다음 "교대" 에이전트가 빠르게 이어받을 수 있도록 중요한 정보를 적어두어야 합니다. - -**도구 1: 진행 파일(PROGRESS.md).** 가장 기본적인 연속성 산출물, 일지의 핵심입니다. - -```markdown -# 프로젝트 진행 상황 - -## 현재 상태 -- 최신 커밋: abc1234 (feat: add user preferences endpoint) -- 테스트 상태: 42/43 통과 (test_pagination_edge_case 실패) -- 린트: 통과 - -## 완료된 항목 -- [x] 사용자 모델 및 데이터베이스 마이그레이션 -- [x] 기본 CRUD 엔드포인트 -- [x] 인증 미들웨어 통합 - -## 진행 중 -- [ ] 페이지네이션 기능 (90% - 엣지 케이스 테스트 실패 중) - -## 알려진 이슈 -- test_pagination_edge_case가 빈 결과 세트에서 500을 반환함 -- 삭제된 사용자가 목록에 표시돼야 하는지 확인 필요 - -## 다음 단계 -1. 페이지네이션 엣지 케이스 버그 수정 -2. "삭제된 사용자 포함" 쿼리 매개변수 추가 -3. API 문서 업데이트 -``` - -**도구 2: 결정 로그(DECISIONS.md).** 중요한 설계 결정과 이유를 기록합니다. 상세한 설계 문서가 필요하지 않습니다. "어떤 결정, 왜, 언제"만 있으면 됩니다. 일지의 메모입니다. - -```markdown -# 설계 결정 - -## 2024-01-15: 사용자 선호도 캐싱에 Redis 사용 -- 이유: 읽기 빈도 높음 (모든 API 호출), 데이터 크기 작음 -- 거부된 대안: PostgreSQL 구체화 뷰 (변경 빈도가 높아 유지보수 비용이 수지에 맞지 않음) -- 제약: 캐시 TTL 5분, 쓰기 시 능동적 무효화 -``` - -**도구 3: Git 커밋을 체크포인트로.** 각 원자적 작업 단위 완료 후 커밋합니다. 커밋 메시지는 무엇을 했고 왜 했는지를 설명해야 합니다. 이것은 무료로 자동으로 버전이 관리되는 상태 스냅샷입니다. - -**도구 4: init.sh 또는 하네스 초기화 흐름.** `AGENTS.md`에 "출근"과 "퇴근" 루틴을 명시합니다. - -```markdown -## 세션 시작 시 (출근) -1. PROGRESS.md를 읽어 현재 상태 파악 -2. DECISIONS.md를 읽어 중요 결정 파악 -3. make check를 실행해 저장소가 일관된 상태인지 확인 -4. PROGRESS.md의 "다음 단계" 섹션에서 이어서 진행 - -## 세션 종료 전 (퇴근) -1. PROGRESS.md 업데이트 -2. make check를 실행해 일관된 상태 확인 -3. 완료된 모든 작업 커밋 -``` - -**혼합 전략**: 모든 작업에 컨텍스트 리셋이 필요하지 않습니다. 짧은 작업(30분 이하)은 한 세션 내에서 완료할 수 있습니다. 긴 작업(세션에 걸쳐 있는)은 연속성을 위해 진행 파일과 결정 로그를 사용해야 합니다. 결정 기준: 작업에 윈도의 60% 이상이 필요하다면 핸드오프(handoff) 준비를 시작하세요. - -### 컨텍스트 불안 깊이 보기 - -Anthropic의 2026년 3월 연구는 컨텍스트 불안의 구체적인 발현을 추가로 밝혔습니다. Sonnet 4.5에서 컨텍스트가 윈도 한계에 접근하면 에이전트가 강한 "조기 수렴" 행동을 보입니다. 시험에서 시간이 거의 다 됐다는 것을 깨닫고 객관식 문제에 급하게 임의 답을 채워넣는 것과 같습니다. - -두 가지 전략으로 이에 대처합니다. - -**컴팩션**: 같은 세션 내에서 초기 대화를 요약합니다. 장점: 연속성을 유지하며 에이전트가 "무엇"을 볼 수 있습니다. 단점: "왜"는 요약에서 자주 사라집니다. 왜 옵션 B가 옵션 A 대신 선택됐는지, 왜 특정 최적화가 건너뛰어졌는지요. 더 결정적으로, 컴팩션은 컨텍스트 불안을 없애지 않습니다. 에이전트는 컨텍스트가 한때 컸다는 것을 알고, 심리적으로 여전히 종결을 서두르는 경향이 있습니다. - -**컨텍스트 리셋**: 컨텍스트를 완전히 지우고, 새 세션을 열고, 영속화된 산출물에서 재구축합니다. 장점: 깨끗한 정신 상태로 새 세션에는 "시간이 촉박하다"는 불안이 없습니다. 단점: 핸드오프 산출물의 완전성에 의존합니다. 일지에 중요한 정보가 빠져 있으면 새 세션이 잘못된 방향으로 시간을 낭비할 수 있습니다. - -Anthropic의 실제 데이터: Sonnet 4.5에서 컨텍스트 불안이 심각해서 컴팩션만으로는 충분하지 않으며, 컨텍스트 리셋이 하네스 설계의 중요한 구성 요소가 됩니다. 하지만 Opus 4.5에서는 이 행동이 크게 줄어들어 컴팩션으로 리셋에 의존하지 않고 컨텍스트를 관리할 수 있습니다. 이것이 의미하는 바: **하네스 설계는 대상 모델에 대한 구체적인 이해가 필요하며, 일률적인 템플릿이 아닙니다.** - -> 출처: [Anthropic: Harness design for long-running application development](https://www.anthropic.com/engineering/harness-design-long-running-apps) - -## 실제 사례 - -에이전트에게 사용자 인증이 있는 블로그 시스템 구현을 맡겼습니다. 기능 포인트 12개, 예상 5세션이 필요합니다. - -**일지 없는 기준선**: 세션 1에서 사용자 모델과 기본 라우트를 구현했습니다. 세션 2는 에이전트가 인증 미들웨어의 인터페이스 계약을 기억하지 못해 이전 설계 의도를 추론하는 데 약 15분을 썼습니다. 세션 3에서는 누적된 드리프트로 에이전트가 이미 완료된 기능을 다시 구현하기 시작했습니다. 세션 5에서는 저장소에 중복 코드가 많았지만 핵심 인증 기능은 여전히 엔드투엔드 테스트를 통과하지 못했습니다. 12개 기능 포인트 중 7개만 완료됐고, 3개는 숨겨진 정확성 문제가 있었습니다. 일지를 쓰지 않는 장인처럼, 5일째 건설 현장은 혼돈입니다. 어떤 벽은 두 번 지어졌고, 지어져야 했던 어떤 벽은 시작도 안 됐습니다. - -**일지 있는 경우**: 진행 파일, 결정 로그, 검증 기록, git 체크포인트를 사용했습니다. 각 세션 종료 시 상태 보고서가 자동으로 업데이트됐습니다. 세션 2의 재구축 비용이 약 3분으로 줄었습니다. 세션 5에서 12개 기능 포인트 모두 완료되고 검증됐습니다. - -정량적 비교: 재구축 시간이 약 78% 감소했고, 기능 완료율은 58%에서 100%로, 숨겨진 결함율은 43%에서 8%로 낮아졌습니다. 장인은 여전히 건망증이 있지만, 일지 덕분에 매일 어제가 멈춘 곳에서 시작합니다. 처음부터가 아니라. - -## 핵심 정리 - -- 컨텍스트 윈도는 유한한 자원입니다. 긴 작업은 세션에 걸쳐 이어지고, 세션은 정보를 잃습니다. 매일 잊어버리는 장인처럼, 이것은 객관적인 현실입니다. -- 해결책은 더 큰 윈도가 아닙니다. 더 나은 상태 영속화입니다. 진행 파일 + 결정 로그 + git 체크포인트로 건망증 장인에게 신뢰할 수 있는 일지를 제공하세요. -- 에이전트를 건망증 있는 엔지니어처럼 취급하세요. "퇴근" 전에 무엇을 했는지, 왜 했는지, 다음은 무엇인지를 적어두세요. -- 재구축 비용이 핵심 지표입니다. 좋은 하네스는 새 세션이 3분 이내에 실행 가능한 상태에 도달하도록 해야 합니다. -- 혼합 전략: 짧은 작업은 세션 내에서, 긴 작업은 구조화된 산출물로 연속성을 유지합니다. - -## 더 읽어보기 - -- [Anthropic: Effective Harnesses for Long-Running Agents](https://www.anthropic.com/engineering/effective-harnesses-for-long-running-agents) -- [OpenAI: Harness Engineering](https://openai.com/index/harness-engineering/) -- [Lost in the Middle: How Language Models Use Long Contexts](https://arxiv.org/abs/2307.03172) -- [Claude Code Documentation](https://docs.anthropic.com/en/docs/claude-code) -- [HumanLayer: Harness Engineering for Coding Agents](https://humanlayer.dev/articles/harness-engineering-for-coding-agents/) - -## 연습 문제 - -1. **연속성 손실 측정**: 최소 3세션이 필요한 개발 작업을 고르세요. 연속성 산출물을 제공하지 않고, 각 세션 시작 시 에이전트가 "지난번에 무슨 일이 있었는지" 파악하는 데 얼마나 많은 컨텍스트를 소비하는지 기록하세요. 각 세션 후 진행 파일을 만들고 다음 세션이 그것에서 시작하게 하세요. 진행 파일 있을 때와 없을 때의 재구축 비용을 비교하세요. - -2. **핸드오프 템플릿 설계**: 네 개의 필드를 가진 최소 핸드오프(handoff) 템플릿을 설계하세요: 저장소 상태(커밋 해시), 실행 상태(테스트 통과율), 차단 항목, 다음 행동. 완전히 새로운 에이전트 세션이 오직 이 템플릿만을 사용해 프로젝트 상태를 복원하게 하세요. 복원 중 만난 모호함을 기록하고, 템플릿을 개선하세요. - -3. **혼합 전략 실험**: 5세션짜리 개발 작업에서 세 가지 전략을 비교하세요: (a) 항상 새 세션 시작 + 진행 파일, (b) 한 세션에서 최대한 많이 하기(컨텍스트 컴팩션), (c) 혼합 전략(짧은 작업은 세션 내, 긴 작업은 세션 간 + 진행 파일). 재구축 시간, 기능 완료율, 결정 일관성을 비교하세요. diff --git a/courses/learn-harness-engineering/docs/ko/lectures/lecture-06-why-initialization-needs-its-own-phase/code/index.md b/courses/learn-harness-engineering/docs/ko/lectures/lecture-06-why-initialization-needs-its-own-phase/code/index.md deleted file mode 100644 index 4af83470..00000000 --- a/courses/learn-harness-engineering/docs/ko/lectures/lecture-06-why-initialization-needs-its-own-phase/code/index.md +++ /dev/null @@ -1,8 +0,0 @@ -# 강의 06 코드 예제 - -이 폴더는 다음 예제들을 위해 사용합니다. - -- 초기화(initializer) 출력물 -- init 스크립트 -- 진행 파일 -- 첫 실행 스캐폴딩 diff --git a/courses/learn-harness-engineering/docs/ko/lectures/lecture-06-why-initialization-needs-its-own-phase/code/init-check.ts b/courses/learn-harness-engineering/docs/ko/lectures/lecture-06-why-initialization-needs-its-own-phase/code/init-check.ts deleted file mode 100644 index 52dcd985..00000000 --- a/courses/learn-harness-engineering/docs/ko/lectures/lecture-06-why-initialization-needs-its-own-phase/code/init-check.ts +++ /dev/null @@ -1,296 +0,0 @@ -/** - * init-check.ts - * - * Programmatically checks initialization prerequisites: - * - Node.js version - * - Dependencies installed (node_modules exists) - * - Data directory exists - * - Config files present - * - * Simulates running with and without an explicit init phase, - * showing how missing prerequisites cause silent failures later. - * - * Run: npx tsx docs/lectures/lecture-06-why-initialization-needs-its-own-phase/code/init-check.ts - */ - -import * as fs from "node:fs"; -import * as path from "node:path"; -import * as childProcess from "node:child_process"; - -// --------------------------------------------------------------------------- -// Types -// --------------------------------------------------------------------------- - -interface CheckItem { - name: string; - category: string; - check: () => { pass: boolean; detail: string }; - impactIfMissing: string; -} - -interface CheckResult { - name: string; - category: string; - passed: boolean; - detail: string; - impactIfMissing: string; -} - -// --------------------------------------------------------------------------- -// Checks -// --------------------------------------------------------------------------- - -function createChecks(targetDir: string): CheckItem[] { - return [ - { - name: "Node.js version >= 18", - category: "Runtime", - check: () => { - try { - const version = process.version; // e.g. "v20.11.0" - const major = parseInt(version.replace("v", "").split(".")[0], 10); - return { - pass: major >= 18, - detail: `Detected: ${version} (need >= v18)`, - }; - } catch { - return { pass: false, detail: "Could not detect Node.js version" }; - } - }, - impactIfMissing: "TypeScript features and built-in APIs unavailable", - }, - { - name: "package.json exists", - category: "Config", - check: () => { - const p = path.join(targetDir, "package.json"); - const exists = fs.existsSync(p); - return { - pass: exists, - detail: exists ? `Found: ${p}` : "Not found in target directory", - }; - }, - impactIfMissing: "Cannot install dependencies or run scripts", - }, - { - name: "Dependencies installed (node_modules)", - category: "Dependencies", - check: () => { - const p = path.join(targetDir, "node_modules"); - const exists = fs.existsSync(p); - return { - pass: exists, - detail: exists ? `Found: ${p}` : "node_modules/ not found -- run npm install", - }; - }, - impactIfMissing: "All imports fail at runtime", - }, - { - name: "TypeScript available", - category: "Toolchain", - check: () => { - try { - const result = childProcess.execSync("npx tsc --version", { - encoding: "utf-8", - timeout: 10000, - stdio: ["pipe", "pipe", "pipe"], - }).trim(); - return { pass: true, detail: `Detected: ${result}` }; - } catch { - return { pass: false, detail: "TypeScript not available via npx" }; - } - }, - impactIfMissing: "Cannot compile TypeScript files", - }, - { - name: "tsconfig.json exists", - category: "Config", - check: () => { - const p = path.join(targetDir, "tsconfig.json"); - const exists = fs.existsSync(p); - return { - pass: exists, - detail: exists ? `Found: ${p}` : "Not found", - }; - }, - impactIfMissing: "TypeScript compiler uses defaults, may not match project needs", - }, - { - name: "Source directory exists", - category: "Structure", - check: () => { - const candidates = ["src", "lib", "app"]; - const found = candidates.filter((d) => { - const p = path.join(targetDir, d); - return fs.existsSync(p) && fs.statSync(p).isDirectory(); - }); - return { - pass: found.length > 0, - detail: found.length > 0 ? `Found: ${found.join(", ")}` : "No src/ lib/ or app/ directory found", - }; - }, - impactIfMissing: "Agent cannot locate source files to modify", - }, - { - name: "Test directory exists", - category: "Structure", - check: () => { - const candidates = ["test", "tests", "__tests__", "spec"]; - const found = candidates.filter((d) => { - const p = path.join(targetDir, d); - return fs.existsSync(p) && fs.statSync(p).isDirectory(); - }); - return { - pass: found.length > 0, - detail: found.length > 0 ? `Found: ${found.join(", ")}` : "No test directory found", - }; - }, - impactIfMissing: "Agent cannot find or run existing tests", - }, - { - name: "Git repository initialized", - category: "Version Control", - check: () => { - const gitDir = path.join(targetDir, ".git"); - const exists = fs.existsSync(gitDir); - return { - pass: exists, - detail: exists ? "Git repository detected" : "No .git directory found", - }; - }, - impactIfMissing: "No rollback capability, no change history", - }, - ]; -} - -// --------------------------------------------------------------------------- -// Simulation: with and without init phase -// --------------------------------------------------------------------------- - -interface SimResult { - scenario: string; - checksRun: boolean; - failuresBeforeWork: number; - workAttempted: boolean; - workSucceeded: boolean; - timeWastedMs: number; -} - -function simulateWithoutInit(): SimResult { - // Agent skips init, goes straight to work. - // Encounters failures one at a time as it hits missing prerequisites. - const checks = createChecks(process.cwd()); - let failures = 0; - let timeWasted = 0; - - for (const check of checks) { - const result = check.check(); - if (!result.pass) { - failures++; - timeWasted += 200; // Agent spends time discovering each missing piece - } - } - - return { - scenario: "NO INIT PHASE", - checksRun: false, - failuresBeforeWork: 0, // Didn't check upfront - workAttempted: true, - workSucceeded: failures === 0, - timeWastedMs: timeWasted, - }; -} - -function simulateWithInit(): SimResult { - // Agent runs init phase first, discovers all issues upfront. - const checks = createChecks(process.cwd()); - let failures = 0; - - for (const check of checks) { - if (!check.check().pass) { - failures++; - } - } - - return { - scenario: "WITH INIT PHASE", - checksRun: true, - failuresBeforeWork: failures, - workAttempted: failures === 0, - workSucceeded: failures === 0, - timeWastedMs: 0, - }; -} - -// --------------------------------------------------------------------------- -// Reporting -// --------------------------------------------------------------------------- - -function pad(s: string, len: number): string { - return s.length >= len ? s : s + " ".repeat(len - s.length); -} - -function run(): void { - const targetDir = process.cwd(); - console.log("\n" + "=".repeat(80)); - console.log(" INITIALIZATION PREREQUISITE CHECK"); - console.log("=".repeat(80)); - console.log(` Target: ${targetDir}\n`); - - const checks = createChecks(targetDir); - const results: CheckResult[] = checks.map((c) => { - const r = c.check(); - return { - name: c.name, - category: c.category, - passed: r.pass, - detail: r.detail, - impactIfMissing: c.impactIfMissing, - }; - }); - - // Detailed report - const header = `| ${pad("Check", 35)}| ${pad("Category", 12)}| ${pad("Status", 6)}| Detail`; - const sep = `|${"-".repeat(37)}|${"-".repeat(14)}|${"-".repeat(8)}|${"-".repeat(40)}`; - console.log(header); - console.log(sep); - - for (const r of results) { - const status = r.passed ? "PASS" : "FAIL"; - console.log(`| ${pad(r.name, 35)}| ${pad(r.category, 12)}| ${pad(status, 6)}| ${r.detail}`); - } - - const passCount = results.filter((r) => r.passed).length; - const failCount = results.filter((r) => !r.passed).length; - - console.log("\n" + "-".repeat(80)); - console.log(` Results: ${passCount} passed, ${failCount} failed out of ${results.length} checks`); - - if (failCount > 0) { - console.log("\n FAILED CHECKS -- Impact if not resolved:"); - for (const r of results.filter((r) => !r.passed)) { - console.log(` - ${r.name}: ${r.impactIfMissing}`); - } - } - - // Comparison: with vs without init - const noInit = simulateWithoutInit(); - const withInit = simulateWithInit(); - - console.log("\n" + "=".repeat(80)); - console.log(" INIT PHASE COMPARISON"); - console.log("=".repeat(80) + "\n"); - - const cHeader = `| ${pad("Metric", 35)}| ${pad("No Init Phase", 18)}| ${pad("With Init Phase", 18)}|`; - const cSep = `|${"-".repeat(37)}|${"-".repeat(20)}|${"-".repeat(20)}|`; - console.log(cHeader); - console.log(cSep); - console.log(`| ${pad("Prerequisites checked upfront", 35)}| ${pad("No", 18)}| ${pad("Yes", 18)}|`); - console.log(`| ${pad("Work attempted despite issues", 35)}| ${pad(String(noInit.workAttempted), 18)}| ${pad(String(withInit.workAttempted), 18)}|`); - console.log(`| ${pad("Time wasted discovering issues late", 35)}| ${pad(noInit.timeWastedMs + "ms", 18)}| ${pad(withInit.timeWastedMs + "ms", 18)}|`); - console.log(`| ${pad("Work succeeded", 35)}| ${pad(String(noInit.workSucceeded), 18)}| ${pad(String(withInit.workSucceeded), 18)}|`); - - console.log("\n An explicit init phase catches problems before they waste time.\n"); -} - -run(); diff --git a/courses/learn-harness-engineering/docs/ko/lectures/lecture-06-why-initialization-needs-its-own-phase/code/init.sh b/courses/learn-harness-engineering/docs/ko/lectures/lecture-06-why-initialization-needs-its-own-phase/code/init.sh deleted file mode 100644 index 2c6e1b0c..00000000 --- a/courses/learn-harness-engineering/docs/ko/lectures/lecture-06-why-initialization-needs-its-own-phase/code/init.sh +++ /dev/null @@ -1,10 +0,0 @@ -#!/usr/bin/env bash -set -euo pipefail - -echo "[init] installing dependencies" -npm install - -echo "[init] starting the docs site is optional" -echo "[init] use npm run docs:dev for course docs" - -echo "[init] project-specific startup would go here" diff --git a/courses/learn-harness-engineering/docs/ko/lectures/lecture-06-why-initialization-needs-its-own-phase/code/initializer-output-checklist.md b/courses/learn-harness-engineering/docs/ko/lectures/lecture-06-why-initialization-needs-its-own-phase/code/initializer-output-checklist.md deleted file mode 100644 index b43c368d..00000000 --- a/courses/learn-harness-engineering/docs/ko/lectures/lecture-06-why-initialization-needs-its-own-phase/code/initializer-output-checklist.md +++ /dev/null @@ -1,7 +0,0 @@ -# 초기화(Initializer) 출력 체크리스트 - -- 표준 시작 명령이 있습니까? -- 표준 검증 명령이 있습니까? -- 첫 번째 진행 산출물이 있습니까? -- 안정적인 첫 번째 커밋이 있습니까? -- 이후 실행을 위한 가시적인 기능 표면이 있습니까? diff --git a/courses/learn-harness-engineering/docs/ko/lectures/lecture-06-why-initialization-needs-its-own-phase/index.md b/courses/learn-harness-engineering/docs/ko/lectures/lecture-06-why-initialization-needs-its-own-phase/index.md deleted file mode 100644 index 28617726..00000000 --- a/courses/learn-harness-engineering/docs/ko/lectures/lecture-06-why-initialization-needs-its-own-phase/index.md +++ /dev/null @@ -1,157 +0,0 @@ -[中文版本 →](../../../zh/lectures/lecture-06-why-initialization-needs-its-own-phase/) - -> 코드 예제: [code/](https://github.com/walkinglabs/learn-harness-engineering/blob/main/docs/en/lectures/lecture-06-why-initialization-needs-its-own-phase/code/) -> 실습 프로젝트: [Project 03. 멀티 세션 연속성](./../../projects/project-03-multi-session-continuity/index.md) - -# 강의 06. 모든 에이전트 세션 전에 초기화하라 - -새 에이전트(agent) 세션을 시작하고 "검색 기능을 추가해줘"라고 말합니다. 에이전트가 바로 코딩에 뛰어듭니다. 훌륭한 열정입니다. 20분 후 테스트 프레임워크가 제대로 설정되지 않았다는 것을 발견하고, 그것을 수정하는 데 10분을 더 씁니다. 그러다 데이터베이스 마이그레이션 스크립트 형식이 잘못됐다는 것을 알게 되어 또 수정합니다. 검색 기능은 결국 추가되지만, 전체 세션이 비효율적이었습니다. 대부분의 시간이 검색 기능을 작성하는 것이 아닌 "이 프로젝트가 어떻게 작동하는지 파악하기"에 사용됐습니다. - -더 나은 접근법은 이렇습니다. 에이전트가 작업을 시작하기 전에, 별도의 단계를 사용해 기본 환경을 준비하고, 검증 명령이 통과하도록 하고, 프로젝트 구조를 이해합니다. 집을 짓는 것과 같습니다. 기초를 붓고 동시에 벽을 세우지 않습니다. 그렇게 하면 기초가 굳기 전에 벽이 올라가고, 건물 전체를 허물고 다시 시작해야 합니다. 먼저 기초를 붓고, 굳힌 다음, 벽을 세웁니다. 깔끔하고 효율적입니다. - -이 강의는 초기화(initialization)가 구현과 혼합되지 않고 왜 별도의 단계여야 하는지 설명합니다. - -## 기초와 벽: 근본적으로 다른 두 가지 작업 - -초기화와 구현은 완전히 다른 최적화 목표를 가집니다. 구현 단계는 검증된 기능의 수량과 품질을 극대화하는 것을 최적화합니다. 초기화(initialization) 단계는 이후 모든 구현의 신뢰성과 효율성을 극대화하는 것을 최적화합니다. - -초기화와 구현을 혼합하면 에이전트는 다중 목표 최적화 문제에 직면합니다. 동시에 인프라를 구축하고 기능 코드를 작성합니다. 명시적인 우선순위 설정 없이는 에이전트가 자연스럽게 코드 작성을 선호합니다(직접 보이는 출력이기 때문에). 인프라는 희생됩니다(그 가치는 이후 세션에서만 나타나기 때문에). 건설 팀에게 동시에 기초를 붓고 벽을 세우라고 하는 것과 같습니다. 팀은 벽이 보이고 보여줄 수 있으니 서두를 것입니다. 하지만 기초가 나쁜 집은 나중에 체계적인 문제를 가집니다. - -## 초기화 생명주기 - -```mermaid -flowchart TB - subgraph Wrong["혼합 세션 (잘못된 방식)"] - W1["즉시 기능 작업 시작"] --> W2["작업 중간에 환경·테스트 누락 발견"] - W2 --> W3["미검증 코드가 누적됨"] - W3 --> W4["다음 세션은 프로젝트 상태를 다시 발견해야 함"] - end - - subgraph Right["전용 초기화 (올바른 방식)"] - R1["세션 1: 환경 실행 가능"] --> R2["예제 테스트 통과"] - R2 --> R3["부트스트랩 계약 + 작업 목록 작성"] - R3 --> R4["깨끗한 체크포인트 커밋"] - R4 --> R5["이후 세션은 검증된 작업에서 직접 시작"] - end -``` - -## 혼합했을 때 어떤 일이 벌어지나 - -가장 직접적인 문제: 기초가 제대로 굳지 않습니다. 에이전트가 기능 코드에 80%의 노력을 쓰고 인프라 설정에 20%만 임시로 씁니다. 테스트 프레임워크가 설정됐지만 검증되지 않았고, 린트 규칙이 설정됐지만 너무 느슨하고, 진행 파일이 만들어지지 않았습니다. 이 결함은 첫 번째 세션에서 명확하지 않습니다(에이전트가 무엇을 했는지 여전히 기억하기 때문에). 하지만 두 번째 세션에서 표면화됩니다. 새 에이전트는 실행 방법, 테스트 방법, 상황이 어떤지를 알지 못합니다. 기초가 부실하면 건물이 흔들립니다. - -더 숨겨진 비용은 "미검증 누적"입니다. 테스트 프레임워크가 설정되기 전에 작성된 기능 코드는 검증 없는 코드입니다. 마침내 그 코드에 테스트를 추가하러 돌아갈 때, 처음부터 설계가 잘못됐다는 것을 발견할 수도 있습니다. 알았더라면 다르게 구현했을 것입니다. 젖은 콘크리트 위에 타일을 붙이는 것과 같습니다. 바닥이 평평하지 않다는 것을 발견하면 모든 타일을 들어내고 다시 해야 합니다. - -세션 예산도 낭비됩니다. 초기화 작업(환경 설정, 테스트 구성, 프로젝트 구조 이해)이 상당한 예산을 소비해 실제 기능 구현에 남는 것이 적습니다. 결과: 첫 번째 세션에서 기능의 절반만 완료되고, 두 번째 세션은 프로젝트를 다시 이해하는 것부터 시작해야 합니다. 기초에 예산을 썼지만 기초도 단단하지 않습니다. 어떤 목표도 달성하지 못합니다. - -가장 쉽게 간과하는 문제는 암묵적 가정의 지뢰밭입니다. 에이전트가 초기화 중에 내리는 결정들(어떤 테스트 프레임워크, 디렉터리 구성 방식, 의존성 관리)이 명시적으로 기록되지 않으면 이후 세션이 이러한 선택을 이해할 수 없습니다. 더 나쁜 경우, 이후 세션이 상충되는 선택을 할 수도 있습니다. 첫 번째 건설 팀이 콘크리트 기초를 사용했는데 두 번째 팀은 그것을 모르고 그 위에 나무 말뚝을 박습니다. 기초가 갈라집니다. - -Anthropic의 장기 실행 애플리케이션 개발 연구는 초기화와 구현을 분리할 것을 명시적으로 권장합니다. 그들의 실험 데이터: 전용 초기화 단계를 사용한 프로젝트는 멀티 세션 시나리오에서 혼합 접근법 대비 기능 완료율이 31% 높았습니다. 핵심 통찰은 초기화 단계에 투자한 시간이 이후 3~4세션에서 완전히 회수된다는 것입니다. 기초가 견고할수록 벽이 더 빨리 올라갑니다. - -OpenAI의 Codex 하네스 엔지니어링 가이드도 "저장소(repository)를 운영 기록으로" 원칙을 강조합니다. 첫 번째 실행에서 명확한 운영 구조를 확립하거나, 모든 새 세션이 프로젝트 컨벤션을 다시 추론해야 합니다. - -## 핵심 개념 - -- **초기화 단계(Initialization Phase)**: 에이전트 생명주기의 첫 번째 단계로, 기능 구현이 없고 이후 모든 구현 단계의 전제 조건을 확립하는 것만 합니다. 출력은 코드가 아니라 인프라입니다. -- **부트스트랩 계약(Bootstrap Contract)**: 새로운 에이전트 세션이 프로젝트를 명확하게 운영할 수 있는 조건입니다. 시작할 수 있고, 테스트할 수 있고, 진행 상황을 볼 수 있고, 다음 단계를 파악할 수 있어야 합니다. 네 가지 조건이 모두 필요합니다. -- **콜드 스타트(Cold Start) vs 웜 스타트(Warm Start)**: 콜드 스타트는 에이전트가 프로젝트 구조를 추측해야 하는 빈 디렉터리에서 시작하는 것입니다. 웜 스타트는 인프라가 이미 갖춰진 템플릿이나 기존 프로젝트에서 시작하는 것입니다. 웜 스타트가 콜드 스타트보다 훨씬 우수합니다. 물과 전기가 들어오는 현장에서 작업을 시작하는 것과 황량한 땅에서 시작하는 것의 차이입니다. -- **핸드오프 준비성(Handoff Readiness)**: 어느 순간이든 프로젝트가 새로운 에이전트가 인계받을 수 있는 상태인 것. 구두 설명이 필요 없고, 저장소 내용만으로 충분합니다. -- **첫 번째 검증까지의 시간(Time to First Verification)**: 프로젝트 시작부터 첫 번째 기능 포인트가 검증을 통과할 때까지의 시간. 초기화 효율성을 측정하는 핵심 지표입니다. -- **다운스트림 사용성(Downstream Usability)**: 초기화 품질을 측정하는 가장 좋은 방법. 암묵적 지식에 의존하지 않고 성공적으로 작업을 실행할 수 있는 이후 세션의 비율. - -## 초기화를 올바르게 하는 방법 - -**초기화를 전용 단계로 취급하세요.** 첫 번째 세션은 초기화만 합니다. 비즈니스 기능 코드는 전혀 없습니다. 초기화가 생성하는 것: - -**1. 실행 가능한 환경.** 프로젝트가 시작되고, 의존성이 설치되고, 환경 문제가 없습니다. 기초를 부었고, 균열이 없습니다. - -**2. 검증 가능한 테스트 프레임워크.** 최소한 하나의 예제 테스트가 통과합니다. 이것은 테스트 프레임워크 자체가 제대로 설정됐다는 것을 증명합니다. 기초 위에 기둥을 세워 무게를 지탱할 수 있음을 증명하는 것과 같습니다. - -**3. 부트스트랩 계약 문서.** 이후 세션에 다음을 알려주는 명확한 문서: -```markdown -# 초기화 계약 - -## 시작 명령 -- 의존성 설치: `make setup` -- 개발 서버 시작: `make dev` -- 테스트 실행: `make test` -- 전체 검증: `make check` - -## 현재 상태 -- 모든 의존성 설치 및 잠금 -- 테스트 프레임워크 설정됨 (Vitest + React Testing Library) -- 예제 테스트 통과 (1/1) -- 린트 규칙 설정됨 (ESLint + Prettier) - -## 프로젝트 구조 -- src/ — 소스 코드 -- src/components/ — React 컴포넌트 -- src/api/ — API 클라이언트 -- tests/ — 테스트 파일 -``` - -**4. 작업 분해.** 전체 프로젝트를 순서가 있는 작업 목록으로 분리하고, 각 작업에 명확한 인수 기준을 부여합니다. -```markdown -# 작업 분해 - -## 작업 1: 사용자 인증 기본 -- JWT 인증 미들웨어 구현 -- 로그인/회원가입 엔드포인트 추가 -- 인수 기준: pytest tests/test_auth.py 모두 통과 - -## 작업 2: 사용자 프로필 페이지 -- 사용자 프로필 CRUD 구현 -- 프로필 편집 폼 추가 -- 인수 기준: pytest tests/test_profile.py 모두 통과 - -## 작업 3: 검색 기능 -- ... -``` - -**5. Git 커밋을 체크포인트로.** 초기화가 완료되면 깨끗한 체크포인트를 커밋합니다. 이후 모든 작업은 이 체크포인트에서 시작합니다. - -**웜 스타트 전략**: 빈 디렉터리에서 시작하지 마세요. 프로젝트 템플릿(create-react-app, fastapi-template 등)을 사용해 표준 디렉터리 구조, 의존성 설정, 테스트 프레임워크를 미리 설정하세요. 일반적인 초기화 단계를 템플릿에 굽고, 프로젝트별 초기화 작업만 남깁니다. 물과 전기가 들어오는 현장에서 작업을 시작하는 것과 같습니다. 황량한 땅에서 시작하는 것보다 만 배 낫습니다. - -**초기화 완료 기준**: "얼마나 많은 코드를 작성했는가"가 아니라, 부트스트랩 계약의 네 가지 조건이 충족됐는지 여부입니다. 시작할 수 있고, 테스트할 수 있고, 진행 상황을 볼 수 있고, 다음 단계를 파악할 수 있어야 합니다. 이 체크리스트로 초기화를 검증하세요: - -```markdown -## 초기화 인수 체크리스트 -- [ ] `make setup`이 처음부터 성공함 -- [ ] `make test`에 최소한 하나의 통과하는 테스트가 있음 -- [ ] 새 에이전트 세션이 저장소 내용만으로 "실행 방법"과 "테스트 방법"을 답할 수 있음 -- [ ] 최소 3개의 작업이 있는 작업 분해 파일이 존재함 -- [ ] 모든 것이 git에 커밋됨 -``` - -## 실제 사례 - -React 프론트엔드 프로젝트에 대한 두 가지 초기화 접근법: - -**혼합 접근법 (기초를 부으면서 동시에 벽을 세우기)**: 에이전트가 세션 1에서 동시에 프로젝트 스캐폴딩을 만들고 첫 번째 기능을 구현했습니다. 세션 종료 시 저장소에는 실행 가능한 코드가 있었지만: 명시적인 시작/테스트 명령 문서가 없었고, 진행 추적 파일도 없었고, 작업 분해도 없었습니다. 세션 2는 프로젝트 구조, 테스트 프레임워크, 빌드 프로세스를 추론하는 데 약 20분을 썼습니다. 새 건설 팀이 현장에 도착해서 기초가 얼마나 됐는지, 배관 공사가 어디까지 됐는지 알지 못해 하나씩 구멍을 파가며 확인해야 하는 것과 같습니다. - -**전용 초기화 (기초 먼저)**: 세션 1에서 초기화만 했습니다. 템플릿에서 디렉터리 구조를 만들고, 테스트 프레임워크(Vitest + React Testing Library)를 설정하고, 하나의 예제 테스트를 작성하고 검증하고, 부트스트랩 계약 문서와 작업 분해 파일을 만들고, 초기 체크포인트를 커밋했습니다. 세션 2의 재구축 시간은 3분 미만이었고, 작업 목록에서 바로 작업을 시작했습니다. 팀이 도착해서 청사진을 한번 보고 어디서 이어받아야 하는지 정확히 아는 것과 같습니다. - -전체 프로젝트 주기 비교: 혼합 접근법의 총 재구축 시간(모든 세션 합산)은 전용 초기화 접근법보다 약 60% 더 많았습니다. 초기화에 쓴 추가 20분이 이후 세션에서 여러 배로 회수됐습니다. 견고한 기초가 벽을 더 빨리 올라가게 하는 것과 같습니다. 느린 것이 빠른 것입니다. - -## 핵심 정리 - -- 초기화와 구현은 최적화 목표가 다릅니다. 섞으면 둘 다 끌어내립니다. 기초를 먼저 붓고, 그 다음 벽을 세우세요. -- 초기화의 출력은 코드가 아닙니다. 인프라입니다: 실행 가능한 환경, 검증 가능한 테스트, 부트스트랩 계약, 작업 분해. -- 부트스트랩 계약의 네 가지 조건으로 초기화를 검증하세요: 시작할 수 있고, 테스트할 수 있고, 진행 상황을 볼 수 있고, 다음 단계를 파악할 수 있어야 합니다. -- 웜 스타트가 콜드 스타트보다 낫습니다. 프로젝트 템플릿을 사용해 표준화된 인프라를 미리 설정하세요. -- 초기화에 투자한 시간은 이후 3~4세션에서 완전히 회수됩니다. 이것은 추가 비용이 아닙니다. 선행 투자입니다. 기초가 견고할수록 건물이 더 빨리 올라갑니다. - -## 더 읽어보기 - -- [Anthropic: Effective Harnesses for Long-Running Agents](https://www.anthropic.com/engineering/effective-harnesses-for-long-running-agents) -- [OpenAI: Harness Engineering](https://openai.com/index/harness-engineering/) -- [HumanLayer: Harness Engineering for Coding Agents](https://humanlayer.dev/articles/harness-engineering-for-coding-agents/) -- [Infrastructure as Code — Martin Fowler](https://martinfowler.com/bliki/InfrastructureAsCode.html) -- [SWE-agent: Agent-Computer Interfaces](https://github.com/princeton-nlp/SWE-agent) - -## 연습 문제 - -1. **부트스트랩 계약 설계**: 개발 중인 프로젝트에 대한 완전한 부트스트랩 계약을 작성하세요. 그런 다음 완전히 새로운 에이전트 세션을 열고, 저장소 내용만 보여주고(구두 컨텍스트 없이), 프로젝트를 시작하고, 테스트를 실행하고, 현재 진행 상황을 이해하게 하세요. 마주치는 모든 문제를 기록하세요. 각각이 부트스트랩 계약의 누락된 조항에 해당합니다. - -2. **비교 실험**: 적당히 복잡한 새 프로젝트를 고르세요. 접근법 A: 에이전트가 초기화와 첫 번째 구현을 동시에 하게 합니다. 접근법 B: 한 세션을 전용 초기화에 쓰고, 세션 2에서 구현을 시작합니다. 4세션 후 비교: 첫 번째 검증까지의 시간, 재구축 비용, 기능 완료율. - -3. **초기화 인수 체크리스트**: 프로젝트를 위한 초기화 인수 체크리스트를 설계하세요. 새로운 에이전트 세션이 각 체크리스트 항목을 실행하고 어떤 것이 통과하고 실패하는지 기록하게 하세요. 실패하는 항목이 하네스를 강화해야 하는 부분입니다. diff --git a/courses/learn-harness-engineering/docs/ko/lectures/lecture-07-why-agents-overreach-and-under-finish/code/index.md b/courses/learn-harness-engineering/docs/ko/lectures/lecture-07-why-agents-overreach-and-under-finish/code/index.md deleted file mode 100644 index dc98ec01..00000000 --- a/courses/learn-harness-engineering/docs/ko/lectures/lecture-07-why-agents-overreach-and-under-finish/code/index.md +++ /dev/null @@ -1,8 +0,0 @@ -# 강의 07 코드 예제 - -이 폴더는 다음 예제들을 위한 공간입니다: - -- 원샷 실패(one-shot failures) 사례 -- 지나치게 큰 작업 프롬프트 -- 점진적 작업 형태 조정 -- 구조화된 기능 범위 표면(feature surface) diff --git a/courses/learn-harness-engineering/docs/ko/lectures/lecture-07-why-agents-overreach-and-under-finish/code/next-task-template.md b/courses/learn-harness-engineering/docs/ko/lectures/lecture-07-why-agents-overreach-and-under-finish/code/next-task-template.md deleted file mode 100644 index 59f8c7e4..00000000 --- a/courses/learn-harness-engineering/docs/ko/lectures/lecture-07-why-agents-overreach-and-under-finish/code/next-task-template.md +++ /dev/null @@ -1,6 +0,0 @@ -# 다음 작업 템플릿 - -- 현재 가장 높은 우선순위의 기능: -- 이 기능이 다음에 오는 이유: -- 통과로 간주되는 조건: -- 이 단계에서 변경해서는 안 되는 것: diff --git a/courses/learn-harness-engineering/docs/ko/lectures/lecture-07-why-agents-overreach-and-under-finish/code/scope-surface-example.md b/courses/learn-harness-engineering/docs/ko/lectures/lecture-07-why-agents-overreach-and-under-finish/code/scope-surface-example.md deleted file mode 100644 index 1883be28..00000000 --- a/courses/learn-harness-engineering/docs/ko/lectures/lecture-07-why-agents-overreach-and-under-finish/code/scope-surface-example.md +++ /dev/null @@ -1,17 +0,0 @@ -# 범위 표면(Scope Surface) 예제 - -작업: - -- Electron 지식 앱에 인덱싱 기능 추가 - -나쁜 범위 형태: - -- "인덱싱 구현" - -더 나은 범위 형태: - -- 가져온 문서 파싱 -- 문서를 청크(chunk)로 분할 -- 청크 메타데이터 영속화 -- UI에서 인덱싱 상태 노출 -- 재인덱싱 액션 추가 diff --git a/courses/learn-harness-engineering/docs/ko/lectures/lecture-07-why-agents-overreach-and-under-finish/code/scope-tracker.ts b/courses/learn-harness-engineering/docs/ko/lectures/lecture-07-why-agents-overreach-and-under-finish/code/scope-tracker.ts deleted file mode 100644 index c587a1dc..00000000 --- a/courses/learn-harness-engineering/docs/ko/lectures/lecture-07-why-agents-overreach-and-under-finish/code/scope-tracker.ts +++ /dev/null @@ -1,151 +0,0 @@ -/** - * scope-tracker.ts - * - * Reads a feature list and a change log. Enforces single-active-feature - * policy. Given a log of changes, flags any changes outside the active - * feature scope. Demonstrates how scope drift happens and how the tracker - * catches it. - * - * Run: npx tsx docs/lectures/lecture-07-why-agents-overreach-and-under-finish/code/scope-tracker.ts - */ - -// --------------------------------------------------------------------------- -// Types -// --------------------------------------------------------------------------- - -interface Feature { - id: string; - name: string; - status: "active" | "pending" | "done"; -} - -interface ChangeLogEntry { - step: number; - file: string; - description: string; - featureId: string; // The feature this change claims to belong to -} - -// --------------------------------------------------------------------------- -// Sample data -// --------------------------------------------------------------------------- - -const features: Feature[] = [ - { id: "F-001", name: "Search endpoint", status: "active" }, - { id: "F-002", name: "Delete endpoint", status: "pending" }, - { id: "F-003", name: "Rate limiting", status: "pending" }, - { id: "F-004", name: "User dashboard", status: "pending" }, -]; - -// A realistic change log where the agent gradually drifts from the active feature -const changeLog: ChangeLogEntry[] = [ - { step: 1, file: "src/routes/search.ts", description: "Add search route handler", featureId: "F-001" }, - { step: 2, file: "src/routes/search.ts", description: "Add query parameter validation", featureId: "F-001" }, - { step: 3, file: "src/routes/search.ts", description: "Add search results pagination", featureId: "F-001" }, - { step: 4, file: "src/routes/delete.ts", description: "Add delete route handler", featureId: "F-002" }, // DRIFT - { step: 5, file: "src/middleware/rate-limit.ts", description: "Add rate limiter middleware", featureId: "F-003" }, // DRIFT - { step: 6, file: "src/routes/search.ts", description: "Integrate rate limiter into search", featureId: "F-001" }, - { step: 7, file: "src/dashboard/ui.tsx", description: "Create dashboard layout component", featureId: "F-004" }, // DRIFT - { step: 8, file: "src/routes/search.ts", description: "Add search response formatting", featureId: "F-001" }, - { step: 9, file: "src/routes/delete.ts", description: "Add delete confirmation logic", featureId: "F-002" }, // DRIFT - { step: 10, file: "src/routes/search.ts", description: "Add search tests", featureId: "F-001" }, -]; - -// --------------------------------------------------------------------------- -// Scope tracker -// --------------------------------------------------------------------------- - -interface ScopeCheckResult { - step: number; - file: string; - description: string; - featureId: string; - inScope: boolean; - activeFeature: string; -} - -function trackScope( - featureList: Feature[], - changes: ChangeLogEntry[] -): ScopeCheckResult[] { - const activeFeatures = featureList.filter((f) => f.status === "active"); - const activeIds = new Set(activeFeatures.map((f) => f.id)); - const activeNames = activeFeatures.map((f) => f.name).join(", "); - - return changes.map((change) => ({ - step: change.step, - file: change.file, - description: change.description, - featureId: change.featureId, - inScope: activeIds.has(change.featureId), - activeFeature: activeNames, - })); -} - -// --------------------------------------------------------------------------- -// Reporting -// --------------------------------------------------------------------------- - -function pad(s: string, len: number): string { - return s.length >= len ? s : s + " ".repeat(len - s.length); -} - -function run(): void { - const results = trackScope(features, changeLog); - - console.log("\n" + "=".repeat(100)); - console.log(" SCOPE TRACKER -- Single Active Feature Enforcement"); - console.log("=".repeat(100)); - - console.log("\n Active feature: " + features.filter((f) => f.status === "active").map((f) => `${f.id} (${f.name})`).join(", ")); - console.log(" Pending features: " + features.filter((f) => f.status === "pending").map((f) => `${f.id} (${f.name})`).join(", ")); - - // Detailed change log - console.log("\n" + "-".repeat(100)); - const header = `| ${pad("Step", 5)}| ${pad("File", 35)}| ${pad("Description", 40)}| ${pad("Feature", 8)}| ${pad("In Scope", 10)}|`; - const sep = `|${"-".repeat(7)}|${"-".repeat(37)}|${"-".repeat(42)}|${"-".repeat(10)}|${"-".repeat(12)}|`; - console.log(header); - console.log(sep); - - let inScopeCount = 0; - let driftCount = 0; - - for (const r of results) { - const scopeLabel = r.inScope ? "OK" : "DRIFT"; - if (r.inScope) inScopeCount++; - else driftCount++; - - const marker = r.inScope ? " " : ">>"; - console.log(`${marker}| ${pad(String(r.step), 5)}| ${pad(r.file, 35)}| ${pad(r.description, 40)}| ${pad(r.featureId, 8)}| ${pad(scopeLabel, 10)}|`); - } - - // Summary - console.log("\n" + "=".repeat(100)); - console.log(" SCOPE DRIFT SUMMARY"); - console.log("=".repeat(100) + "\n"); - - const sHeader = `| ${pad("Metric", 40)}| ${pad("Value", 15)}|`; - const sSep = `|${"-".repeat(42)}|${"-".repeat(17)}|`; - console.log(sHeader); - console.log(sSep); - console.log(`| ${pad("Total changes", 40)}| ${pad(String(results.length), 15)}|`); - console.log(`| ${pad("Changes within active scope (F-001)", 40)}| ${pad(String(inScopeCount), 15)}|`); - console.log(`| ${pad("Changes outside active scope (DRIFT)", 40)}| ${pad(String(driftCount), 15)}|`); - console.log(`| ${pad("Features touched (total)", 40)}| ${pad(String(new Set(results.map((r) => r.featureId)).size), 15)}|`); - - // Drift detail - const driftFeatures = [...new Set(results.filter((r) => !r.inScope).map((r) => r.featureId))]; - if (driftFeatures.length > 0) { - console.log("\n DRIFTED FEATURES:"); - for (const fid of driftFeatures) { - const feat = features.find((f) => f.id === fid); - const driftChanges = results.filter((r) => r.featureId === fid); - console.log(` ${fid} (${feat?.name}): ${driftChanges.length} unauthorized changes`); - } - } - - console.log("\n Without a scope tracker, the agent silently worked on " + driftFeatures.length + " unrelated features."); - console.log(" The tracker catches this drift and enforces the single-active-feature policy.\n"); -} - -run(); diff --git a/courses/learn-harness-engineering/docs/ko/lectures/lecture-07-why-agents-overreach-and-under-finish/index.md b/courses/learn-harness-engineering/docs/ko/lectures/lecture-07-why-agents-overreach-and-under-finish/index.md deleted file mode 100644 index ce138f3b..00000000 --- a/courses/learn-harness-engineering/docs/ko/lectures/lecture-07-why-agents-overreach-and-under-finish/index.md +++ /dev/null @@ -1,134 +0,0 @@ -[中文版本 →](../../../zh/lectures/lecture-07-why-agents-overreach-and-under-finish/) - -> 코드 예제: [code/](https://github.com/walkinglabs/learn-harness-engineering/blob/main/docs/en/lectures/lecture-07-why-agents-overreach-and-under-finish/code/) -> 실습 프로젝트: [프로젝트 04. 런타임 피드백과 범위(scope) 제어](./../../projects/project-04-incremental-indexing/index.md) - -# 강의 07. 에이전트에게 명확한 작업 경계를 그어 주어야 합니다 - -Claude Code에게 "이 프로젝트에 사용자 인증을 추가해 줘"라고 지시하면, 에이전트(agent)는 데이터베이스 스키마를 수정하고, 라우트를 작성하고, 프론트엔드 컴포넌트를 변경하고 — 그 김에 오류 처리 미들웨어까지 리팩터링하기 시작합니다. 두 시간 후 확인해 보면: 수정된 파일 12개, 새로운 코드 800줄, 그리고 단 하나의 기능도 엔드투엔드로 동작하지 않습니다. - -"한 번에 너무 많이 베어 물다" — 이 표현은 AI 에이전트에게 특히 잘 들어맞습니다. 에이전트는 "조금 더 해두자"는 충동을 타고납니다. 관련된 것들을 보면 그냥 처리해 버립니다. 마치 간장 한 병 사러 마트에 갔다가 카트 가득 채워 나오는 사람처럼요. 문제는, 사람이 너무 많이 사면 돈만 낭비하지만, 에이전트가 너무 많은 것을 동시에 하면 어느 것 하나도 제대로 마무리되지 않는다는 점입니다. - -Anthropic의 "장시간 실행 에이전트를 위한 효과적인 하네스(harness)" 엔지니어링 블로그는 명확하게 밝힙니다: 프롬프트가 지나치게 넓으면, 에이전트는 "한 가지를 먼저 끝내기"보다 "여러 가지를 동시에 시작"하는 경향이 있다고. OpenAI의 Codex 엔지니어링 실천 사례에서도 같은 현상이 관찰되었습니다 — 명시적인 범위 제어가 없는 작업은 완료율이 급락합니다. 이것은 모델의 문제가 아닙니다 — 하네스의 문제입니다. 경계를 그어주지 않은 것입니다. - -## 주의력은 유한한 자원입니다 - -이것은 비유가 아니라 수학입니다. 에이전트의 컨텍스트(context) 용량을 C라 하고, k개의 작업을 동시에 활성화한다고 가정하면, 각 작업에는 평균 C/k의 추론 자원이 할당됩니다. C/k가 단일 작업을 완료하는 데 필요한 최소 임계값 아래로 떨어지면, 어느 것도 완성되지 않습니다. 위가 한 개인데 만두 열 개를 한꺼번에 욱여넣으면 다 소화되지 않고, 열 개 모두 체하게 됩니다. - -Claude Code의 실제 동작이 이를 잘 보여줍니다. "사용자 등록 기능을 추가해 줘"라고 요청하면 다음과 같이 진행될 수 있습니다: - -1. User 모델 생성 -2. 등록 라우트 작성 -3. 이메일 인증이 필요하다는 것을 알아채고 메일 서비스 추가 -4. 비밀번호 해싱이 필요하다는 것을 확인하고 bcrypt 도입 -5. 오류 처리가 일관성이 없다는 것을 발견하고 글로벌 오류 미들웨어 리팩터링 -6. 테스트 파일 구조가 지저분하다는 것을 보고 디렉터리 재구성 - -여섯 단계 후, 하나도 완성되지 않았습니다. 엔드투엔드 검증(verification)은 없고, 미완성 코드 간의 복잡한 결합만 남으며, 다음에 이어받을 세션은 완전히 길을 잃습니다. 마치 여섯 가지 요리를 동시에 하는 사람처럼 — 모든 요리가 냄비 안에 있지만 접시에 담긴 것은 하나도 없습니다. 다 타버립니다. - -Anthropic의 실험 데이터가 이를 직접 뒷받침합니다: "작은 다음 단계" 전략(WIP=1에 해당)을 사용한 에이전트는 넓은 프롬프트를 사용한 에이전트보다 작업 완료율이 37% 높습니다. 더 흥미로운 점은, 에이전트가 생성한 코드 줄 수와 실제 기능 완료 수가 약한 음의 상관관계를 보인다는 것입니다 — 코드를 많이 작성할수록 완성된 기능은 적습니다. 데이터로 증명된 과욕(overreach)입니다. - -## WIP=1 워크플로우 - -```mermaid -flowchart LR - Queue["기능 대기열"] --> Pick["정확히 하나의 작업 선택"] - Pick --> Active["활성 항목 하나만 유지"] - Active --> Verify["엔드투엔드 검증 실행"] - Verify -->|통과| Commit["커밋하고 다음 작업 잠금 해제"] - Verify -->|실패| Active - Commit --> Queue -``` - -```mermaid -flowchart TB - Budget["사용 가능한 추론 예산 = C"] --> One["WIP = 1
작업당 C / 1"] - Budget --> Many["WIP = 5
작업당 C / 5"] - - One --> Finish["하나의 기능이 통과 상태에 도달"] - Many --> Partial["다섯 개의 부분 구현"] - Partial --> VCR["낮은 검증 완료율(VCR)
다음 세션에서 높은 재작업 비율"] -``` - -## 핵심 개념 - -- **과욕(Overreach)**: 에이전트가 단일 세션에서 최적보다 많은 작업을 활성화하는 것입니다. 정량화 가능합니다 — 5개 기능을 작업했지만 엔드투엔드를 통과한 것이 0개이면 과욕입니다. -- **미완성(Under-finish)**: 활성화된 전체 작업 중 엔드투엔드 검증을 통과한 작업의 비율이 임계값 아래로 떨어지는 상태입니다. 코드는 작성했지만 테스트가 통과하지 않으면 미완성입니다. -- **WIP 제한(Work-in-Progress Limit)**: 칸반(Kanban) 방법론에서 온 개념입니다. 핵심 아이디어: 동시에 진행 중인 작업 수를 제한합니다. 에이전트에게는 WIP=1이 가장 안전한 기본값입니다 — 하나를 끝낸 후 다음을 시작합니다. 뷔페처럼 — 접시를 쌓지 말고, 한 접시를 다 먹은 후 다음 접시를 가져오십시오. 칸반은 제조업에서 비롯된 작업 흐름 관리 방법론으로, 재공품(WIP) 제한을 통해 흐름 효율을 극대화하는 것이 핵심 원리입니다. -- **완료 증거(Completion Evidence)**: 작업이 "진행 중"에서 "완료"로 이동하기 위해 만족해야 하는 검증 가능한 조건입니다. 이것 없이는 에이전트가 "코드가 멀쩡해 보인다"를 "동작이 테스트를 통과했다"로 대체합니다. -- **범위 표면(Scope Surface)**: 각 노드가 작업 단위이고 엣지가 의존성인 DAG 구조입니다. 상태는 네 가지로 제한됩니다: not_started, active, blocked, passing. -- **완료 압력(Completion Pressure)**: 하네스가 WIP 제한과 완료 증거 요구사항을 통해 가하는 제약력으로, 에이전트가 새 작업을 시작하기 전에 현재 작업을 마무리하도록 강제합니다. - -## 과욕과 미완성은 공생 관계입니다 - -두 문제는 독립적이지 않습니다 — 서로를 증폭시킵니다. 과욕은 주의력을 희석시키고, 희석된 주의력은 미완성을 야기하며, 남겨진 반완성 코드는 시스템 복잡도를 높이고, 이는 다음 작업에서 더 큰 과욕을 불러옵니다. 악순환입니다. - -칸반 용어로: 리틀의 법칙(Little's Law)은 L = lambda * W임을 알려줍니다. 재공품(WIP) L이 너무 높으면(동시에 너무 많은 것을 하면), 각 작업의 리드 타임 W가 불가피하게 늘어납니다. 에이전트에게 이는 각 기능이 시작에서 검증된 완료까지 더 오래 걸리고 실패 확률이 커짐을 의미합니다. - -이것은 인간 세계에서도 오래된 문제입니다 — Steve McConnell은 『Rapid Development』에서 범위 크리프(scope creep)가 프로젝트 실패의 주요 원인이라고 기록했습니다. 하지만 사람은 적어도 "이 정도면 됐다"는 직관이 있습니다. 에이전트에게는 그것이 없습니다. 다음 아이디어를 생성하는 데 드는 추가 토큰 비용은 거의 0에 가깝습니다 — "여기도 고쳐볼게"라고 쓰는 것은 모델 입장에서 거의 부담이 없지만, 모든 추가 수정은 에이전트의 주의력을 희석시킵니다. 한 접시 추가 비용이 거의 0인 뷔페에서도 위장은 여전히 한 개뿐인 것과 같습니다. - -## 올바른 방법 - -### 1. WIP=1 강제 적용 - -가장 직접적이고 효과적인 방법입니다. 하네스에서 에이전트에게 명시적으로 지시하십시오: **어떤 시점에도 하나의 작업만 "active" 상태일 수 있습니다.** Claude Code의 CLAUDE.md 또는 Codex의 AGENTS.md에 다음과 같이 작성하십시오: - -``` -## 작업 규칙 -- 한 번에 하나의 기능만 작업합니다 -- 현재 기능이 엔드투엔드 검증을 통과한 후에만 다음 기능을 시작합니다 -- 기능 A를 구현하는 동안 기능 B도 "리팩터링"하지 않습니다 -``` - -뷔페에서 먹는 것처럼 — 한 번에 한 접시, 다 먹은 후 다시 가져옵니다. - -### 2. 모든 작업에 명시적인 완료 증거 정의 - -완료는 "코드가 작성됨"이 아니라 "동작 검증이 통과됨"입니다. 기능 목록(feature list)의 모든 항목에는 검증 명령이 필요합니다: - -``` -F01: 사용자 등록 - 검증: curl -X POST /api/register -d '{"email":"test@example.com","password":"123456"}' | jq .status == 201 - 상태: passing -``` - -### 3. 범위 표면 외부화 - -기계 판독 가능한 파일(JSON 또는 Markdown)을 사용하여 모든 작업 상태를 기록하십시오. 새로운 세션은 이 파일을 읽고 즉시 알 수 있습니다: 어떤 작업이 활성 상태인가? 어떤 동작이 완료로 간주되는가? 어떤 검증이 통과했는가? - -### 4. 검증 완료율(VCR) 모니터링 - -하네스는 VCR(Verified Completion Rate) = 검증된 작업 / 활성화된 작업을 지속적으로 추적해야 합니다. VCR < 1.0일 때 새 작업 활성화를 차단합니다. - -## 실제 사례 - -8개의 기능을 가진 REST API 프로젝트, 두 가지 전략 비교: - -**뷔페 모드(제약 없음)**: 에이전트가 세션 1에서 5개의 기능을 동시에 활성화합니다. 12개 파일에 걸쳐 ~800줄을 생성합니다. 엔드투엔드 테스트 통과율: 20% — 사용자 등록만 작동합니다. 나머지 4개 기능: 데이터베이스 스키마는 생성되었지만 유효성 검사 로직이 없고, 라우트는 정의되었지만 잘못된 응답 형식을 반환합니다. 마치 여섯 가지 요리를 동시에 하다가 하나만 겨우 먹을 수 있는 것처럼. 세션 3 종료 시 8개 기능 중 3개만 완성됩니다. - -**단일 접시 모드(WIP=1)**: 에이전트가 세션 1에서 사용자 등록에만 집중합니다. 4개 파일에 ~200줄을 생성합니다. 엔드투엔드 테스트: 100% 통과. 검증된 클린 구현을 커밋합니다. 세션 4 종료 시 8개 기능 중 7개 완성(나머지 1개는 외부 의존성으로 차단됨). - -결과: 총 코드는 적지만(800 vs 1200줄) 더 효과적인 코드. 완료율: 87.5% vs 37.5%. 한 번에 한 입씩 먹으면, 실제로 더 많이 먹을 수 있습니다. - -## 핵심 요점 - -- **WIP=1은 에이전트 하네스의 기본 안전 설정입니다** — 하나를 완료한 후 다음을 시작하십시오. 병렬화하려 하지 마십시오. 한 입에 살찔 수 없습니다. -- **완료 증거는 실행 가능해야 합니다** — "코드가 멀쩡해 보인다"는 해당되지 않습니다. "curl이 201을 반환한다"가 해당됩니다. -- **범위 표면은 파일로 외부화되어야 합니다** — 대화에서만 언급하는 것이 아니라, 저장소의 기계 판독 가능한 형식으로 기록되어야 합니다. -- **과욕과 미완성은 공생 관계입니다** — 하나를 해결하면 다른 하나도 해결됩니다. -- **"적게 하되 끝내는 것"이 항상 "많이 하되 반만 남기는 것"보다 낫습니다** — 에이전트의 코드 줄 수와 기능 완료율은 음의 상관관계를 보입니다. 품질이 항상 양을 이깁니다. - -## 더 읽을거리 - -- [Effective harnesses for long-running agents - Anthropic](https://www.anthropic.com/engineering/effective-harnesses-for-long-running-agents) — Anthropic 엔지니어링 블로그, "작은 다음 단계" 전략에 대한 상세한 논의 -- [Harness Engineering - OpenAI](https://openai.com/index/harness-engineering/) — OpenAI의 하네스 엔지니어링에 대한 포괄적인 다룸 -- [Kanban: Successful Evolutionary Change - David Anderson](https://www.goodreads.com/book/show/1070822.Kanban) — WIP 제한에 관한 고전 자료 -- [Rapid Development - Steve McConnell](https://www.goodreads.com/book/show/125171.Rapid_Development) — 범위 크리프가 프로젝트 실패의 주요 원인이라는 실증적 데이터 - -## 연습 문제 - -1. **작업 원자화**: 넓은 요구사항(예: "사용자 관리 시스템 구현")을 골라 최소 5개의 원자적 작업 단위로 분해하십시오. 각 단위에 대해 다음을 명시하십시오: (a) 단일 동작 설명, (b) 실행 가능한 검증 명령, (c) 의존성. 분해가 WIP=1 제약을 만족하는지 확인하십시오. - -2. **비교 실험**: 동일한 프로젝트를 두 번 실행하십시오 — 한 번은 제약 없이, 한 번은 WIP=1을 강제 적용하여. 비교하십시오: 검증 완료율, 총 코드 줄 수, 유효 코드 비율. - -3. **완료 증거 감사**: 최근 에이전트 실행 결과를 검토하고, 각 코드 변경 사항을 "완료된 동작", "미완성 동작", "스캐폴딩"으로 분류하십시오. 각 미완성 동작에 누락된 검증 명령을 추가하십시오. diff --git a/courses/learn-harness-engineering/docs/ko/lectures/lecture-08-why-feature-lists-are-harness-primitives/code/feature-list-validator.ts b/courses/learn-harness-engineering/docs/ko/lectures/lecture-08-why-feature-lists-are-harness-primitives/code/feature-list-validator.ts deleted file mode 100644 index 52d6a2b9..00000000 --- a/courses/learn-harness-engineering/docs/ko/lectures/lecture-08-why-feature-lists-are-harness-primitives/code/feature-list-validator.ts +++ /dev/null @@ -1,218 +0,0 @@ -/** - * feature-list-validator.ts - * - * Reads a feature_list.json, validates its schema, and checks for features - * marked "pass" without verification evidence. Outputs a structured report. - * Can run against any project directory that has a feature_list.json. - * - * Usage: - * npx tsx docs/lectures/lecture-08.../code/feature-list-validator.ts [path-to-dir] - * (defaults to the directory containing this script) - * - * Run: npx tsx docs/lectures/lecture-08-why-feature-lists-are-harness-primitives/code/feature-list-validator.ts - */ - -import * as fs from "node:fs"; -import * as path from "node:path"; - -// --------------------------------------------------------------------------- -// Types -// --------------------------------------------------------------------------- - -interface FeatureEntry { - id?: string; - category?: string; - description?: string; - verification?: string[]; - passes?: boolean; - // Allow unknown fields for flexibility - [key: string]: unknown; -} - -interface ValidationResult { - featureId: string; - schemaValid: boolean; - schemaErrors: string[]; - hasVerification: boolean; - markedPassWithoutEvidence: boolean; - passes: boolean; - verificationCount: number; -} - -// --------------------------------------------------------------------------- -// Schema validation -// --------------------------------------------------------------------------- - -function validateSchema(entry: FeatureEntry, index: number): string[] { - const errors: string[] = []; - const label = entry.id ?? `entry ${index + 1}`; - - if (!entry.id || typeof entry.id !== "string") { - errors.push(`[${label}] Missing or invalid 'id' field`); - } - if (!entry.category || typeof entry.category !== "string") { - errors.push(`[${label}] Missing or invalid 'category' field`); - } - if (!entry.description || typeof entry.description !== "string") { - errors.push(`[${label}] Missing or invalid 'description' field`); - } - if (entry.verification !== undefined && !Array.isArray(entry.verification)) { - errors.push(`[${label}] 'verification' must be an array if present`); - } - if (entry.passes !== undefined && typeof entry.passes !== "boolean") { - errors.push(`[${label}] 'passes' must be a boolean if present`); - } - - return errors; -} - -// --------------------------------------------------------------------------- -// Evidence validation -// --------------------------------------------------------------------------- - -function checkEvidence(entry: FeatureEntry): { - hasVerification: boolean; - markedPassWithoutEvidence: boolean; -} { - const hasVerification = Array.isArray(entry.verification) && entry.verification.length > 0; - const markedPassWithoutEvidence = entry.passes === true && !hasVerification; - - return { hasVerification, markedPassWithoutEvidence }; -} - -// --------------------------------------------------------------------------- -// Process feature list -// --------------------------------------------------------------------------- - -function processFeatureList(entries: FeatureEntry[]): ValidationResult[] { - return entries.map((entry, index) => { - const schemaErrors = validateSchema(entry, index); - const evidence = checkEvidence(entry); - - return { - featureId: (entry.id as string) ?? `entry-${index + 1}`, - schemaValid: schemaErrors.length === 0, - schemaErrors, - hasVerification: evidence.hasVerification, - markedPassWithoutEvidence: evidence.markedPassWithoutEvidence, - passes: entry.passes === true, - verificationCount: Array.isArray(entry.verification) ? entry.verification.length : 0, - }; - }); -} - -// --------------------------------------------------------------------------- -// Reporting -// --------------------------------------------------------------------------- - -function pad(s: string, len: number): string { - return s.length >= len ? s : s + " ".repeat(len - s.length); -} - -function run(): void { - // Resolve target directory - const scriptDir = path.dirname(new URL(import.meta.url).pathname); - const targetDir = process.argv[2] - ? path.resolve(process.argv[2]) - : scriptDir; - - const filePath = path.join(targetDir, "feature_list.json"); - - console.log("\n" + "=".repeat(90)); - console.log(" FEATURE LIST VALIDATOR"); - console.log("=".repeat(90)); - console.log(` Reading: ${filePath}\n`); - - if (!fs.existsSync(filePath)) { - console.error(` ERROR: feature_list.json not found at ${filePath}`); - console.error(" Usage: npx tsx feature-list-validator.ts [path-to-directory-containing-feature_list.json]\n"); - process.exit(1); - } - - let entries: FeatureEntry[]; - try { - const raw = fs.readFileSync(filePath, "utf-8"); - entries = JSON.parse(raw); - } catch (err) { - console.error(` ERROR: Could not parse feature_list.json: ${err}`); - process.exit(1); - } - - if (!Array.isArray(entries)) { - console.error(" ERROR: feature_list.json must contain a JSON array at the top level."); - process.exit(1); - } - - // For demo purposes, also validate an extended test set - const demoEntries: FeatureEntry[] = [ - ...entries, - { - id: "qna-002", - category: "import", - description: "User can import a PDF document.", - verification: ["Upload a PDF file", "Verify it appears in the document list"], - passes: true, - }, - { - id: "qna-003", - category: "grounded_qa", - description: "System hallucination rate is below 5%.", - verification: [], // Empty -- no evidence - passes: true, // Marked as pass WITHOUT evidence - }, - { - id: "missing-fields", - // Missing 'category' and 'description' - passes: true, - } as FeatureEntry, - ]; - - const results = processFeatureList(demoEntries); - - // Print report - const header = `| ${pad("Feature ID", 14)}| ${pad("Schema", 8)}| ${pad("Passes", 7)}| ${pad("Verifications", 14)}| ${pad("Evidence?", 12)}| Notes`; - const sep = `|${"-".repeat(16)}|${"-".repeat(10)}|${"-".repeat(9)}|${"-".repeat(16)}|${"-".repeat(14)}|${"-".repeat(30)}`; - console.log(header); - console.log(sep); - - for (const r of results) { - const schemaLabel = r.schemaValid ? "OK" : "INVALID"; - const passesLabel = r.passes ? "PASS" : "FAIL"; - const evidenceLabel = r.hasVerification ? "Present" : "MISSING"; - const notes = r.markedPassWithoutEvidence - ? "FLAGGED: passes without evidence!" - : r.schemaErrors.length > 0 - ? r.schemaErrors[0] - : ""; - - const marker = r.markedPassWithoutEvidence ? ">>" : r.schemaValid ? " " : "!!"; - console.log( - `${marker}| ${pad(r.featureId, 14)}| ${pad(schemaLabel, 8)}| ${pad(passesLabel, 7)}| ${pad(String(r.verificationCount), 14)}| ${pad(evidenceLabel, 12)}| ${notes}` - ); - } - - // Summary - const total = results.length; - const schemaOk = results.filter((r) => r.schemaValid).length; - const passing = results.filter((r) => r.passes).length; - const flagged = results.filter((r) => r.markedPassWithoutEvidence).length; - const withEvidence = results.filter((r) => r.hasVerification).length; - - console.log("\n" + "-".repeat(90)); - console.log(" SUMMARY"); - console.log("-".repeat(90)); - console.log(` Total features: ${total}`); - console.log(` Schema valid: ${schemaOk}/${total}`); - console.log(` Marked as passing: ${passing}/${total}`); - console.log(` With verification evidence: ${withEvidence}/${total}`); - console.log(` Flagged (pass without evidence): ${flagged}`); - - if (flagged > 0) { - console.log(`\n WARNING: ${flagged} feature(s) marked as "pass" without any verification evidence.`); - console.log(" These features need verification before they can be trusted.\n"); - } else { - console.log("\n All passing features have verification evidence. Feature list is healthy.\n"); - } -} - -run(); diff --git a/courses/learn-harness-engineering/docs/ko/lectures/lecture-08-why-feature-lists-are-harness-primitives/code/feature_list.json b/courses/learn-harness-engineering/docs/ko/lectures/lecture-08-why-feature-lists-are-harness-primitives/code/feature_list.json deleted file mode 100644 index 8b973e31..00000000 --- a/courses/learn-harness-engineering/docs/ko/lectures/lecture-08-why-feature-lists-are-harness-primitives/code/feature_list.json +++ /dev/null @@ -1,15 +0,0 @@ -[ - { - "id": "qna-001", - "category": "grounded_qa", - "description": "User can ask a question about an imported document and receive an answer with visible citations.", - "verification": [ - "Import a markdown document", - "Open the Q&A panel", - "Ask a question about known content", - "Verify the answer is returned", - "Verify one or more citations are displayed" - ], - "passes": false - } -] diff --git a/courses/learn-harness-engineering/docs/ko/lectures/lecture-08-why-feature-lists-are-harness-primitives/code/index.md b/courses/learn-harness-engineering/docs/ko/lectures/lecture-08-why-feature-lists-are-harness-primitives/code/index.md deleted file mode 100644 index c47a0035..00000000 --- a/courses/learn-harness-engineering/docs/ko/lectures/lecture-08-why-feature-lists-are-harness-primitives/code/index.md +++ /dev/null @@ -1,8 +0,0 @@ -# 강의 08 코드 예제 - -이 폴더는 다음 예제들을 위한 공간입니다: - -- 패스 상태 게이팅(pass-state gating) -- 엔드투엔드 검증(end-to-end verification) -- 약한 완료 기준 vs 강한 완료 기준 -- 평가자 루프(evaluator-loop) 예제 diff --git a/courses/learn-harness-engineering/docs/ko/lectures/lecture-08-why-feature-lists-are-harness-primitives/code/pass-gate-policy.md b/courses/learn-harness-engineering/docs/ko/lectures/lecture-08-why-feature-lists-are-harness-primitives/code/pass-gate-policy.md deleted file mode 100644 index 954f0250..00000000 --- a/courses/learn-harness-engineering/docs/ko/lectures/lecture-08-why-feature-lists-are-harness-primitives/code/pass-gate-policy.md +++ /dev/null @@ -1,8 +0,0 @@ -# 패스 게이트 정책(Pass Gate Policy) - -기능이 `passes: false`에서 `passes: true`로 이동하려면 다음 조건을 모두 만족해야 합니다: - -- 예상된 워크플로우가 실행(exercise)되었을 것 -- 성공의 증거가 기록되었을 것 -- 테스트된 경로에 차단하는 오류가 없을 것 -- 구현이 앱을 손상되거나 모호한 상태로 두지 않을 것 diff --git a/courses/learn-harness-engineering/docs/ko/lectures/lecture-08-why-feature-lists-are-harness-primitives/index.md b/courses/learn-harness-engineering/docs/ko/lectures/lecture-08-why-feature-lists-are-harness-primitives/index.md deleted file mode 100644 index 77d1940d..00000000 --- a/courses/learn-harness-engineering/docs/ko/lectures/lecture-08-why-feature-lists-are-harness-primitives/index.md +++ /dev/null @@ -1,137 +0,0 @@ -[中文版本 →](../../../zh/lectures/lecture-08-why-feature-lists-are-harness-primitives/) - -> 코드 예제: [code/](https://github.com/walkinglabs/learn-harness-engineering/blob/main/docs/en/lectures/lecture-08-why-feature-lists-are-harness-primitives/code/) -> 실습 프로젝트: [프로젝트 04. 런타임 피드백과 범위 제어](./../../projects/project-04-incremental-indexing/index.md) - -# 강의 08. 기능 목록으로 에이전트의 행동을 제약하십시오 - -에이전트에게 전자상거래 사이트를 구축하라고 요청합니다. 완성 후 에이전트는 "완료"라고 말합니다. 코드를 살펴보니 — 사용자 인증은 작동하지만 장바구니의 결제 버튼은 아무것도 하지 않고, 결제 흐름은 전혀 연결되어 있지 않습니다. 문제는: "완료"가 무엇을 의미하는지 전혀 알려주지 않았기 때문에, 에이전트는 자신만의 기준을 사용했습니다 — "코드를 많이 작성했고 꽤 완성된 것처럼 보인다." - -기능 목록(feature list)은 많은 사람들의 눈에 그저 메모처럼 보입니다 — 잊지 않으려고 적어두고 나서 옆으로 치워두는 것. 하지만 하네스(harness) 세계에서 기능 목록은 인간을 위한 메모가 아닙니다 — 전체 하네스의 근간(backbone)입니다. 스케줄러(scheduler)는 이것을 바탕으로 작업을 선택하고, 검증기(verifier)는 이것을 바탕으로 완료를 판단하며, 핸드오프(handoff) 보고기는 이것을 바탕으로 요약본을 생성합니다. 근간이 부러지면 온 몸이 마비됩니다. - -Anthropic과 OpenAI 모두 강조합니다: **산출물(artifact)은 외부화되어야 합니다.** 기능 상태는 구조화되지 않은 대화 텍스트가 아니라, 저장소의 기계 판독 가능한 파일에 존재해야 합니다. - -## 에이전트는 "완료"의 의미를 모릅니다 - -Claude Code도 Codex도 "완료"가 무엇을 의미하는지 자동으로 알지 못합니다. "장바구니 기능을 추가해 줘"라고 하면, 모델의 해석은 "Cart 컴포넌트와 addToCart 메서드를 작성하는 것"일 수 있습니다. 하지만 당신이 의미한 것은 "사용자가 상품을 탐색하고, 장바구니에 추가하고, 결제를 완료할 수 있는 엔드투엔드 흐름"이었습니다. 이 이해의 간극은 기능 목록 없이는 지속됩니다. 에이전트는 자신만의 암묵적인 기준을 사용합니다 — 보통 "코드에 명백한 문법 오류가 없다"입니다. 당신이 필요한 것은 엔드투엔드 동작 검증입니다. 친구에게 과일을 사달라고 하면서 "과일 좀 사와"라고만 했는데 레몬을 가져오는 것처럼. 친구의 과일과 당신의 과일은 같은 과일이 아닙니다. - -다음과 같은 일반적인 진행 메모를 살펴보십시오: - -``` -사용자 인증 완료, 장바구니 거의 완료, 결제는 아직 필요 -``` -새로운 에이전트 세션이 이 메모에서 다음 질문들에 답할 수 있을까요? "거의 완료"가 무엇을 의미하는지? 장바구니가 통과한 테스트는 무엇인지? 결제를 막는 것이 무엇인지? 모든 답은 "아무도 모른다"입니다. 의사에게 "배가 아픈데, 요즘엔 괜찮아요"라고 하는 것과 같습니다 — 어떤 약을 처방할 수 있을까요? - -결과: 새 세션이 프로젝트 상태를 추론하는 데 20분을 소비하고, 이미 완료된 기능을 재구현할 수도 있습니다. Anthropic의 엔지니어링 데이터에 따르면 좋은 진행 기록은 세션 시작 진단 시간을 60-80% 줄입니다. - -## 기능 상태 기계 - -```mermaid -flowchart LR - Feature["하나의 기능 행"] --> Behavior["동작 설명
예: POST /cart/items가 201 반환"] - Feature --> Check["검증 명령
실행할 정확한 확인 방법"] - Feature --> State["상태
not_started / active / blocked / passing"] - - Behavior --> Complete["세 필드가 모두 있어야만
기능 행이 사용 가능"] - Check --> Complete - State --> Complete -``` - -```mermaid -flowchart LR - List["feature_list.json / features.md"] --> Scheduler["다음 not_started 항목 선택"] - Scheduler --> Agent["에이전트가 해당 항목 하나 작업"] - Agent --> Verifier["해당 항목의 검증 명령 실행"] - Verifier -->|통과| Passing["passing으로 표시하고
증거 기록"] - Verifier -->|실패| Active["active 유지"] - Verifier -->|의존성 문제| Blocked["blocked로 표시"] - Passing --> Handoff["핸드오프 메모와
현재 진행상황 업데이트"] - Active --> Agent -``` - -## 핵심 개념 - -- **기능 목록은 하네스 프리미티브(primitive)입니다**: "선택적 계획 도구"가 아니라, 다른 모든 하네스 구성 요소가 의존하는 기반 데이터 구조입니다. 데이터베이스 테이블 구조처럼 — "기본 키는 건너뛰자"고 말할 수 없습니다. "프리미티브"란 시스템에서 더 이상 분해할 수 없는 기본 구성 요소를 의미하며, 다른 모든 추상화가 이 위에 구축됩니다. -- **삼중 구조**: 각 기능 항목은 `(동작 설명, 검증 명령, 현재 상태)` 삼중 구조입니다. 어느 한 요소가 빠지면 항목이 불완전합니다. -- **상태 기계 모델**: 각 기능 항목은 네 가지 상태를 가집니다 — `not_started`, `active`, `blocked`, `passing`. 상태 전환은 에이전트가 자유롭게 변경하는 것이 아니라 하네스가 제어합니다. -- **패스 상태 게이팅(Pass-state gating)**: 기능이 `active`에서 `passing`으로 이동하는 유일한 방법은 검증 명령이 성공적으로 실행되는 것입니다. 이것은 되돌릴 수 없습니다 — 한 번 `passing`이 되면 되돌아갈 수 없습니다. 시험에 합격하면 합격한 것이고, 점수를 소급하여 변경할 수 없는 것처럼요. -- **단일 진실 원천(Single source of truth)**: "무엇을 해야 하는가"에 관한 모든 정보는 하나의 기능 목록에서 파생되어야 합니다. 기능 목록과 대화 기록 간에 모순이 없어야 합니다. -- **역압력(Back-pressure)**: 아직 통과하지 못한 기능의 수는 하네스가 에이전트에게 가하는 압력입니다. 압력이 0이면 = 프로젝트 완료. - -## 기능 목록이 "프리미티브"여야 하는 이유 - -문서는 인간이 읽기 위한 것이고, 프리미티브는 시스템이 실행하기 위한 것입니다. 문서는 무시될 수 있지만, 프리미티브는 우회될 수 없습니다. - -데이터베이스 트리거 제약과 애플리케이션 레이어 검사를 생각해 보십시오: 전자는 데이터베이스 엔진이 강제하며, 어떤 SQL도 건너뛸 수 없습니다. 후자는 애플리케이션 코드의 정확성에 의존하며 우연히 우회될 수 있습니다. 하네스 프리미티브로서의 기능 목록은 구체적으로 네 가지 하네스 구성 요소를 담당합니다: - -1. **스케줄러(Scheduler)**: 상태를 읽고, 다음 `not_started` 기능을 선택합니다. 공장 생산 계획 시스템처럼요. -2. **검증기(Verifier)**: 검증 명령을 실행하고, 상태 전환을 허용할지 결정합니다. 품질 검사처럼요. -3. **핸드오프 보고기(Handoff reporter)**: 기능 목록에서 자동으로 세션 핸드오프 요약을 생성합니다. 자동 교대 근무 보고서처럼요. -4. **진행 추적기(Progress tracker)**: 상태 분포를 집계하고, 프로젝트 건강 지표를 제공합니다. 대시보드처럼요. - -## 올바른 방법 - -### 1. 최소한의 기능 목록 형식 정의 - -복잡한 시스템이 필요하지 않습니다 — 구조화된 Markdown 또는 JSON 파일이면 충분합니다. 핵심은 모든 항목이 삼중 구조를 가져야 한다는 것입니다: - -```json -{ - "id": "F03", - "behavior": "POST /cart/items with {product_id, quantity} returns 201", - "verification": "curl -X POST http://localhost:3000/api/cart/items -H 'Content-Type: application/json' -d '{\"product_id\":1,\"quantity\":2}' | jq .status == 201", - "state": "passing", - "evidence": "commit abc123, test output log" -} -``` - -### 2. 하네스가 상태 전환을 제어하도록 하기 - -에이전트는 기능의 상태를 `passing`으로 직접 변경할 수 없습니다. 검증 요청만 제출할 수 있으며, 하네스가 검증 명령을 실행하고 전환을 허용할지 결정합니다. 이것이 "패스 상태 게이팅"입니다. - -### 3. CLAUDE.md에 규칙 작성 - -``` -## 기능 목록 규칙 -- 기능 목록 파일: /docs/features.md -- 한 번에 하나의 기능만 활성화 -- passing으로 표시하기 전에 검증 명령이 통과해야 함 -- 기능 목록 상태를 직접 수정하지 마십시오 — 검증 스크립트가 자동으로 업데이트합니다 -``` - -### 4. 세분화 수준 조정 - -각 기능 항목은 "하나의 세션 내에서 완료 가능"한 범위여야 합니다. 너무 넓으면 완료되지 않고, 너무 좁으면 관리 오버헤드가 늘어납니다. "사용자가 장바구니에 항목을 추가할 수 있다"는 적절한 세분화입니다. "장바구니 구현"은 너무 넓습니다. "Cart 모델에 name 필드 생성"은 너무 좁습니다. 스테이크 자르는 것처럼 — 통째로도 아니고, 간 고기도 아닌 적절한 한 입 크기. - -## 실제 사례 - -10개의 기능을 가진 전자상거래 플랫폼. 두 가지 추적 방법 비교: - -**메모 모드**: 에이전트가 구조화되지 않은 메모를 사용합니다. 3개의 세션 후 메모는 "사용자 인증과 상품 목록은 완료, 장바구니는 거의 완료지만 버그 있음, 결제는 시작 안 됨"이 됩니다. 새 세션은 상태를 추론하는 데 20분이 필요하고, 결국 완료된 기능을 재구현합니다. 장보기 목록에 "우유, 빵, 그 물건"이라고 적혀 있으면 — 마트에서도 여전히 무엇을 사야 할지 모르는 것처럼. - -**근간 모드**: 모든 기능이 명확한 상태와 검증 명령을 가집니다. 새 세션은 기능 목록을 읽고 3분 만에 알 수 있습니다: F01-F05는 `passing`, F06은 `active`, F07-F10은 `not_started`. F06에서 직접 이어받고, 재작업 없이 진행합니다. - -정량화된 결과: 구조화된 기능 목록을 사용하는 프로젝트는 자유 형식 추적에 비해 기능 완료율이 45% 높으며, 중복 구현이 전혀 없습니다. - -## 핵심 요점 - -- **기능 목록은 하네스의 근간입니다**, 인간을 위한 메모가 아닙니다. 스케줄러, 검증기, 핸드오프 보고기 모두 이에 의존합니다. -- **모든 기능 항목은 삼중 구조를 가져야 합니다**: 동작 설명 + 검증 명령 + 현재 상태. 하나의 요소가 빠지면 불완전합니다 — 다리 하나 없는 세발 의자처럼. -- **상태 전환은 하네스가 제어합니다** — 에이전트는 혼자서 상태를 변경할 수 없습니다. 검증 통과 = 유일한 업그레이드 경로. -- **기능 목록은 프로젝트의 단일 진실 원천입니다** — "무엇을 해야 하는가"에 관한 모든 정보는 하나의 목록에서 파생됩니다. -- **세분화 수준을 "하나의 세션 내에서 완료 가능"으로 조정하십시오.** - -## 더 읽을거리 - -- [Building Effective Agents - Anthropic](https://www.anthropic.com/research/building-effective-agents) — 기능 목록을 에이전트 범위 제어를 위한 "핵심 데이터 구조"로 명시적으로 식별 -- [Harness Engineering - OpenAI](https://openai.com/index/harness-engineering/) — "산출물 외부화" 원칙 강조 -- [Design by Contract - Bertrand Meyer](https://www.goodreads.com/book/show/130439.Object_Oriented_Software_Construction) — 계약 설계 원칙, 기능 목록의 이론적 기반 -- [How Google Tests Software](https://www.goodreads.com/book/show/13563030-how-google-tests-software) — 테스트 피라미드와 동작 명세 엔지니어링 실천 - -## 연습 문제 - -1. **기능 목록 설계**: 최소한의 기능 목록 JSON 스키마를 정의하십시오. 포함 항목: id, 동작 설명, 검증 명령, 현재 상태, 증거 참조. 이를 사용하여 5개의 기능을 가진 실제 프로젝트를 설명하십시오. - -2. **검증 엄격성 비교**: 3개의 기능을 선택하고 "느슨한" 검증(예: "코드에 문법 오류가 없음")과 "엄격한" 검증(예: "엔드투엔드 테스트 통과") 모두를 설계하십시오. 각 방법에서의 위양성 비율을 비교하십시오. - -3. **단일 진실 원천 원칙 감사**: 기존 에이전트 프로젝트를 검토하고 기능 목록과 모순되는 범위 정보(대화 내 암묵적 요구사항, 코드의 TODO 댓글 등)를 찾아보십시오. 모든 정보를 기능 목록으로 통합하는 계획을 설계하십시오. diff --git a/courses/learn-harness-engineering/docs/ko/lectures/lecture-09-why-agents-declare-victory-too-early/code/clean-state-checklist.md b/courses/learn-harness-engineering/docs/ko/lectures/lecture-09-why-agents-declare-victory-too-early/code/clean-state-checklist.md deleted file mode 100644 index 68984e56..00000000 --- a/courses/learn-harness-engineering/docs/ko/lectures/lecture-09-why-agents-declare-victory-too-early/code/clean-state-checklist.md +++ /dev/null @@ -1,6 +0,0 @@ -# 클린 상태 체크리스트(Clean State Checklist) - -- 수동 수리 없이 앱이 시작됩니다 -- 현재 진행 상황이 기록되어 있습니다 -- 문서화되지 않은 반완성 가져오기(import) 또는 인덱싱 단계가 남아 있지 않습니다 -- 다음 에이전트(agent)가 표준 시작 및 검증(verification) 경로를 즉시 실행할 수 있습니다 diff --git a/courses/learn-harness-engineering/docs/ko/lectures/lecture-09-why-agents-declare-victory-too-early/code/index.md b/courses/learn-harness-engineering/docs/ko/lectures/lecture-09-why-agents-declare-victory-too-early/code/index.md deleted file mode 100644 index c48c9263..00000000 --- a/courses/learn-harness-engineering/docs/ko/lectures/lecture-09-why-agents-declare-victory-too-early/code/index.md +++ /dev/null @@ -1,8 +0,0 @@ -# 강의 09 코드 예제 - -이 폴더는 다음 예제들을 위한 공간입니다: - -- 피드백으로서의 로그(logs as feedback) -- 런타임 상태 가시성(runtime-state visibility) -- 클린 상태(clean state) 확인 -- 복구(recovery) 예제 diff --git a/courses/learn-harness-engineering/docs/ko/lectures/lecture-09-why-agents-declare-victory-too-early/code/victory-detector.ts b/courses/learn-harness-engineering/docs/ko/lectures/lecture-09-why-agents-declare-victory-too-early/code/victory-detector.ts deleted file mode 100644 index fad1a3ba..00000000 --- a/courses/learn-harness-engineering/docs/ko/lectures/lecture-09-why-agents-declare-victory-too-early/code/victory-detector.ts +++ /dev/null @@ -1,241 +0,0 @@ -/** - * victory-detector.ts - * - * Simulates an agent that claims task completion, then checks the claimed - * state against actual verification. Outputs: claimed vs actual, highlighting - * gaps between what the agent said and what's really true. - * - * Run: npx tsx docs/lectures/lecture-09-why-agents-declare-victory-too-early/code/victory-detector.ts - */ - -// --------------------------------------------------------------------------- -// Types -// --------------------------------------------------------------------------- - -interface Task { - name: string; - claimedComplete: boolean; - checks: VerificationCheck[]; -} - -interface VerificationCheck { - description: string; - claimed: boolean; // What the agent says - actual: boolean; // What is actually true - severity: "critical" | "warning"; -} - -// --------------------------------------------------------------------------- -// Simulated tasks with claimed vs actual states -// --------------------------------------------------------------------------- - -const tasks: Task[] = [ - { - name: "Add search endpoint", - claimedComplete: true, - checks: [ - { - description: "Route handler exists", - claimed: true, - actual: true, - severity: "critical", - }, - { - description: "Authentication middleware applied", - claimed: true, - actual: false, // Agent forgot auth - severity: "critical", - }, - { - description: "Input validation added", - claimed: true, - actual: true, - severity: "critical", - }, - { - description: "Unit tests written", - claimed: true, - actual: false, // Agent skipped tests - severity: "critical", - }, - { - description: "Integration tests pass", - claimed: true, - actual: false, // No tests to run - severity: "critical", - }, - { - description: "Documentation updated", - claimed: true, - actual: false, // Agent skipped docs - severity: "warning", - }, - ], - }, - { - name: "Fix pagination bug", - claimedComplete: true, - checks: [ - { - description: "Bug reproduction case confirmed", - claimed: true, - actual: true, - severity: "critical", - }, - { - description: "Root cause identified", - claimed: true, - actual: true, - severity: "critical", - }, - { - description: "Fix applied", - claimed: true, - actual: true, - severity: "critical", - }, - { - description: "Edge cases tested", - claimed: true, - actual: false, // Only happy path tested - severity: "warning", - }, - { - description: "Regression tests added", - claimed: true, - actual: false, - severity: "critical", - }, - ], - }, - { - name: "Refactor auth module", - claimedComplete: true, - checks: [ - { - description: "Old code removed", - claimed: true, - actual: false, // Old code still present - severity: "warning", - }, - { - description: "All existing tests still pass", - claimed: true, - actual: false, // Two tests broke - severity: "critical", - }, - { - description: "New auth flow works", - claimed: true, - actual: true, - severity: "critical", - }, - { - description: "Migration guide written", - claimed: true, - actual: false, - severity: "warning", - }, - ], - }, -]; - -// --------------------------------------------------------------------------- -// Verification -// --------------------------------------------------------------------------- - -interface TaskVerification { - taskName: string; - claimedComplete: boolean; - actuallyComplete: boolean; - totalChecks: number; - claimedPassing: number; - actualPassing: number; - gaps: { description: string; severity: string }[]; -} - -function verifyTask(task: Task): TaskVerification { - const claimedPassing = task.checks.filter((c) => c.claimed).length; - const actualPassing = task.checks.filter((c) => c.actual).length; - const gaps = task.checks.filter((c) => c.claimed && !c.actual); - - return { - taskName: task.name, - claimedComplete: task.claimedComplete, - actuallyComplete: gaps.length === 0, - totalChecks: task.checks.length, - claimedPassing, - actualPassing, - gaps: gaps.map((g) => ({ description: g.description, severity: g.severity })), - }; -} - -// --------------------------------------------------------------------------- -// Reporting -// --------------------------------------------------------------------------- - -function pad(s: string, len: number): string { - return s.length >= len ? s : s + " ".repeat(len - s.length); -} - -function run(): void { - console.log("\n" + "=".repeat(90)); - console.log(" VICTORY DETECTOR -- Claimed vs Actual Verification"); - console.log("=".repeat(90)); - - const verifications = tasks.map(verifyTask); - - for (const v of verifications) { - console.log("\n Task: " + v.taskName); - console.log(" " + "-".repeat(70)); - console.log(` Agent claimed: ${v.claimedComplete ? "COMPLETE" : "NOT COMPLETE"}`); - console.log(` Actually is: ${v.actuallyComplete ? "COMPLETE" : "INCOMPLETE"}`); - - if (!v.actuallyComplete) { - console.log(` MISMATCH: Agent declared victory but ${v.gaps.length} check(s) failed!`); - } - - // Detailed checks for this task - const task = tasks.find((t) => t.name === v.taskName)!; - console.log("\n Check detail:"); - const header = ` | ${pad("Check", 40)}| ${pad("Claimed", 9)}| ${pad("Actual", 9)}| ${pad("Gap?", 6)}|`; - const sep = ` |${"-".repeat(42)}|${"-".repeat(11)}|${"-".repeat(11)}|${"-".repeat(8)}|`; - console.log(header); - console.log(sep); - - for (const check of task.checks) { - const claimedLabel = check.claimed ? "PASS" : "FAIL"; - const actualLabel = check.actual ? "PASS" : "FAIL"; - const gapLabel = check.claimed && !check.actual ? "GAP" : ""; - const marker = gapLabel ? ">>" : " "; - console.log(`${marker}| ${pad(check.description, 40)}| ${pad(claimedLabel, 9)}| ${pad(actualLabel, 9)}| ${pad(gapLabel, 6)}|`); - } - } - - // Overall summary - console.log("\n" + "=".repeat(90)); - console.log(" OVERALL SUMMARY"); - console.log("=".repeat(90) + "\n"); - - const sHeader = `| ${pad("Task", 28)}| ${pad("Claimed", 10)}| ${pad("Actual", 10)}| ${pad("Gaps", 6)}| ${pad("Accuracy", 10)}|`; - const sSep = `|${"-".repeat(30)}|${"-".repeat(12)}|${"-".repeat(12)}|${"-".repeat(8)}|${"-".repeat(12)}|`; - console.log(sHeader); - console.log(sSep); - - for (const v of verifications) { - const accuracy = Math.round((v.actualPassing / v.totalChecks) * 100); - const cLabel = v.claimedComplete ? "Done" : "Pending"; - const aLabel = v.actuallyComplete ? "Done" : "Incomplete"; - console.log(`| ${pad(v.taskName, 28)}| ${pad(cLabel, 10)}| ${pad(aLabel, 10)}| ${pad(String(v.gaps.length), 6)}| ${pad(accuracy + "%", 10)}|`); - } - - const totalGaps = verifications.reduce((s, v) => s + v.gaps.length, 0); - const falseVictories = verifications.filter((v) => v.claimedComplete && !v.actuallyComplete).length; - - console.log("\n False victories: " + falseVictories + " of " + verifications.length + " tasks"); - console.log(" Total undetected gaps: " + totalGaps); - console.log("\n The agent declared 'done' on every task, but verification reveals " + totalGaps + " unmet criteria."); - console.log(" Without explicit verification, premature victory declarations go unchecked.\n"); -} - -run(); diff --git a/courses/learn-harness-engineering/docs/ko/lectures/lecture-09-why-agents-declare-victory-too-early/index.md b/courses/learn-harness-engineering/docs/ko/lectures/lecture-09-why-agents-declare-victory-too-early/index.md deleted file mode 100644 index e611693e..00000000 --- a/courses/learn-harness-engineering/docs/ko/lectures/lecture-09-why-agents-declare-victory-too-early/index.md +++ /dev/null @@ -1,145 +0,0 @@ -[中文版本 →](../../../zh/lectures/lecture-09-why-agents-declare-victory-too-early/) - -> 이 강의의 코드 예제: [code/](https://github.com/walkinglabs/learn-harness-engineering/blob/main/docs/en/lectures/lecture-09-why-agents-declare-victory-too-early/code/) -> 실습 프로젝트: [프로젝트 05. 에이전트가 자신의 작업을 스스로 검증하게 하기](./../../projects/project-05-grounded-qa-verification/index.md) - -# 강의 9. 에이전트가 너무 일찍 완료를 선언하지 못하도록 방지하기 - -에이전트에게 "비밀번호 재설정" 기능을 구현하라고 요청합니다. 에이전트는 데이터베이스 스키마를 수정하고, API 엔드포인트를 작성하고, 이메일 템플릿을 추가하고, 단위 테스트를 실행합니다(모두 통과). 그런 다음 자신 있게 "완료되었습니다"라고 알립니다. 실제로 실행해 보면 — 비밀번호 재설정 링크를 보낼 수 없고(이메일 서비스 설정 누락), 데이터베이스 마이그레이션이 중간에 실패하며(스키마 불일치), 엔드투엔드 흐름은 단 한 번도 실행되지 않았습니다. - -이 느낌이 낯설지 않을 것입니다 — 시험지를 가득 채우고 자신 있게 가장 먼저 제출했는데, 성적이 나왔을 때 낙제하는 경우와 같습니다. 시험지가 가득 찼다고 해서 답이 맞는 것은 아닙니다. - -이것은 단발성 사건이 아닙니다. 2017년 Guo et al.의 ICML 고전 논문이 증명했습니다: **현대 신경망은 체계적으로 과도한 자신감을 보입니다** — 모델이 보고하는 신뢰도는 실제 정확도보다 유의미하게 높습니다. AI 코딩 에이전트(agent)에게도 동일하게 적용됩니다: 에이전트는 완료되었다고 "느끼지만", 실제로는 아직 한참 멀었습니다. 여러분의 하네스(harness)는 에이전트의 "느낌"을 외부화된 실행 기반 검증(verification)으로 대체해야 합니다. - -## 미끄러운 경사면 - -조기 완료 선언은 거의 항상 같은 패턴을 따릅니다: 코드가 괜찮아 보입니다 — 문법이 올바르고, 로직이 합리적으로 보이며, 정적 분석에서 명백한 오류가 없습니다. 하지만 하네스가 포괄적인 실행 검증을 강제하지 않기 때문에, 에이전트는 실제 실행을 건너뛰거나 부분적인 테스트만 수행합니다. 단위 테스트는 실행하지만 통합 테스트는 건너뜁니다. 테스트는 실행하지만 커버리지를 확인하지 않습니다. 결국 "코드가 멀쩡해 보인다"가 "기능이 완료되었다"의 증거로 사용됩니다. 그리고 시험지가 제출됩니다. - -정보는 모든 단계에서 손실됩니다. 작업 명세에서 코드 구현을 거쳐 런타임 동작에 이르기까지, 모든 변환은 편향을 유발할 수 있으며, 건너뛴 모든 검증은 정보 비대칭을 악화시킵니다. - -## 3단계 종료 검사 - -```mermaid -flowchart LR - Claim["에이전트: 완료됨"] --> L1["먼저 실행
lint / typecheck"] - L1 --> L2["다음으로 실행
테스트 및 시작 확인"] - L2 --> L3["마지막으로 실행
완전한 사용자 흐름"] - L3 --> Done["세 가지 모두 통과해야 완료"] -``` - -```mermaid -flowchart LR - A["코드가 작성됨
단위 테스트 통과"] --> B["하지만 앱이 실제로 시작되지 않음
전체 흐름이 실행된 적 없음"] - B --> C["설정, DB, 외부 서비스 문제가
모두 숨겨져 있음"] - C --> D["그래서 에이전트가 너무 일찍 완료 선언"] -``` - -## 핵심 개념 - -- **조기 완료 선언(Premature Completion Declaration)**: 에이전트가 작업이 완료되었다고 주장하지만, 아직 충족되지 않은 정확성 명세가 존재하는 상태입니다. 핵심 문제: 에이전트는 코드 수준의 로컬 신뢰도를 기반으로 판단하지만, 시스템 수준의 정확성은 전역적 검증을 필요로 합니다. -- **신뢰도 보정 편향(Confidence Calibration Bias)**: 에이전트가 자체 보고한 완료 신뢰도와 실제 완료 품질 사이의 체계적인 간극입니다. 복잡한 다중 파일 작업의 경우 이 편향은 유의미하게 양(+)입니다 — 에이전트는 실제 성과보다 항상 더 자신 있어합니다. 시험 후 항상 점수를 과대 평가하는 학생처럼요. -- **종료 기준(Termination Criteria)**: 하네스에서 정의된 명확하고 실행 가능한 판단 조건의 집합입니다. 에이전트는 완료를 선언하기 전에 모든 조건을 만족해야 합니다. "완료"가 주관적 판단에서 객관적 결정으로 바뀝니다. -- **검증-유효성 검사 이중 게이트(Verification-Validation Dual Gate)**: 첫 번째 검증 레이어는 "코드가 명세된 동작을 올바르게 구현했는가"를 확인하고, 두 번째 유효성 검사 레이어는 "시스템 수준의 동작이 엔드투엔드 요구사항을 충족하는가"를 확인합니다. 완료로 간주되려면 두 가지 모두 통과해야 합니다. -- **런타임 피드백 신호(Runtime Feedback Signals)**: 프로그램 실행에서 나오는 로그, 프로세스 상태, 상태 확인. 하네스가 완료 품질을 판단하기 위한 객관적 근거입니다. -- **완료 우선순위 제약(Completion Priority Constraint)**: 먼저 기능적 정확성을 검증하고, 그다음 성능을 다루고, 마지막으로 스타일을 다룹니다. 핵심 기능이 검증되기 전까지 리팩터링은 금지입니다. - -## 단위 테스트 통과 != 작업 완료 - -이것이 가장 흔한 함정이자 가장 위험한 것입니다. 에이전트가 코드를 작성하고, 단위 테스트를 실행하고, 모두 통과했으며, "완료"라고 했습니다. 하지만 단위 테스트의 설계 철학 — 테스트 대상을 격리하고 의존성을 모킹(mocking)하는 것 — 이 바로 단위 테스트가 컴포넌트 간 문제를 감지할 수 없게 하는 이유입니다: - -**인터페이스 불일치**: 렌더 프로세스가 preload 스크립트에 전달하는 파일 경로는 상대 경로이지만, preload 스크립트는 절대 경로를 기대합니다. 각각의 단위 테스트는 모두 모킹을 사용했고 통과했습니다. 문제는 엔드투엔드 테스트 중에만 발견됩니다. 밴드의 모든 음악가가 각자 연습할 때는 완벽하지만, 함께 연주할 때 음이 맞지 않는다는 것을 깨닫는 것처럼. - -**상태 전파 오류**: 데이터베이스 마이그레이션이 테이블 스키마를 변경했지만, ORM 캐싱 레이어는 여전히 이전 스키마의 캐시 항목을 보유하고 있습니다. 단위 테스트는 매번 새로운 모킹 환경을 제공하므로 이 교차 레이어 상태 불일치가 드러나지 않습니다. - -**환경 의존성**: 코드는 테스트 환경(모든 것이 모킹됨)에서는 올바르게 동작하지만, 설정 차이, 네트워크 지연, 또는 서비스 불가용성으로 인해 실제 환경에서 실패합니다. 연습실에서는 완벽하게 노래하지만, 무대에서는 음향 장비 문제에 직면하는 것처럼. - -### "그 김에 리팩터링"은 완료 판단에 독입니다 - -Claude Code에는 흔한 행동 패턴이 있습니다: 핵심 기능이 검증을 통과하기 전에 코드를 리팩터링하고, 성능을 최적화하고, 스타일을 개선하기 시작합니다. Knuth의 인용문 "조기 최적화는 만악의 근원"은 에이전트 시나리오에서 새로운 의미를 가집니다 — 리팩터링은 검증된 코드와 미검증 코드 사이의 경계를 변경하여, 이전에 암묵적으로 올바랐던 코드 경로를 잠재적으로 깨뜨릴 수 있습니다. 수학 서술형 문제를 다 풀기 전에 객관식 답안을 더 예쁘게 옮겨 적는 것과 같습니다 — 시간 낭비일 뿐만 아니라 잘못 옮길 수도 있습니다. - -### 자기 평가의 체계적 편향 - -Anthropic은 2026년 연구에서 더 깊은 실패 패턴을 발견했습니다: **에이전트에게 자신의 작업을 평가하도록 요청하면, 인간 관찰자가 품질이 명백히 기준에 미달한다고 볼 때조차 체계적으로 지나치게 긍정적인 평가를 제공합니다.** 학생에게 자신의 시험을 채점하도록 하는 것과 같습니다 — 그들은 항상 자신의 답안에 특히 관대할 것입니다. - -이 문제는 주관적인 작업(예: 디자인 미학)에서 특히 심각합니다 — "레이아웃이 정교한가"는 판단의 문제이며, 에이전트는 신뢰할 수 있게 긍정적으로 치우칩니다. 심지어 검증 가능한 결과가 있는 작업에서도 에이전트의 성과는 잘못된 판단에 의해 방해받을 수 있습니다. - -해결책은 에이전트를 "더 객관적으로" 만드는 것이 아닙니다 — 생성과 평가를 담당하는 동일한 모델은 본질적으로 자신에게 관대한 경향이 있습니다. **해결책은 "작업자"와 "검사자"를 분리하는 것입니다.** 학생이 자신의 시험을 채점해서는 안 되는 것처럼 — 독립적인 채점자가 필요합니다. - -"까다롭게" 튜닝된 독립적인 평가 에이전트는 생성 에이전트가 스스로를 평가하는 것보다 훨씬 효과적입니다. Anthropic의 실험 데이터: - -| 아키텍처 | 실행 시간 | 비용 | 핵심 기능 작동 여부 | -|----------|-----------|------|---------------------| -| 단일 에이전트(bare run) | 20분 | $9 | 아니오(게임 개체가 입력에 반응 없음) | -| 세 에이전트(planner + generator + evaluator) | 6시간 | $200 | 예(게임이 완전히 플레이 가능) | - -이것은 정확히 같은 모델(Opus 4.5)에 정확히 같은 프롬프트("2D 레트로 게임 에디터 구축")입니다. 유일한 차이점은 하네스입니다 — "bare 실행"에서 "플래너가 요구사항 확장 → 생성기가 기능별 구현 → 평가기가 Playwright를 사용하여 실제 클릭 테스트 수행"으로 변경된 것입니다. - -> 출처: [Anthropic: 장시간 애플리케이션 개발을 위한 하네스 설계](https://www.anthropic.com/engineering/harness-design-long-running-apps) - -## 조기 제출을 방지하는 방법 - -### 1. 종료 판단 외부화 - -완료 판단은 에이전트 자신이 내려서는 안 됩니다. 하네스는 런타임 신호를 입력으로 사용하여 독립적으로 종료 유효성 검사를 실행해야 하며, 에이전트의 신뢰도를 사용해서는 안 됩니다. `CLAUDE.md`에 다음과 같이 명확하게 작성하십시오: - -``` -## 완료의 정의 -- 기능 완료 = 엔드투엔드 검증 통과, "코드가 작성됨"이 아님 -- 필요한 검증 레벨: - 1. 단위 테스트 통과 - 2. 통합 테스트 통과 - 3. 엔드투엔드 흐름 검증 통과 -- 레벨 1이 실패하면 레벨 2로 진행하지 않습니다 -- 레벨 2가 실패하면 레벨 3으로 진행하지 않습니다 -``` - -### 2. 3단계 종료 유효성 검사 구축 - -- **레이어 1: 문법 및 정적 분석**. 비용이 가장 낮고 정보가 가장 적지만, 반드시 통과해야 합니다. 이것이 최소한의 확인입니다 — 다른 것을 보기 전에 철자를 올바르게 써야 합니다. -- **레이어 2: 런타임 동작 검증**. 테스트 실행, 앱 시작 확인, 핵심 경로 유효성 검사. 이것이 완료의 핵심 증거입니다. 작성하는 것만으로는 충분하지 않습니다. 실행되어야 합니다. -- **레이어 3: 시스템 수준 확인**. 엔드투엔드 테스트, 통합 유효성 검사, 사용자 시나리오 시뮬레이션. 조기 선언에 대한 최후의 방어선입니다. 실행하는 것만으로는 충분하지 않습니다. 올바르게 실행되어야 합니다. - -### 3. 에이전트를 위한 좋은 "빨간 펜 수정" 설계 - -OpenAI는 Codex 실천 과정에서 특히 효과적인 패턴을 소개했습니다: **에이전트에 대한 오류 메시지에는 수정 지침이 포함되어야 합니다**. 게으른 채점자처럼 큰 빨간 X만 그리지 말고, 좋은 선생님처럼 "여기를 이렇게 바꿔야 합니다"라고 여백에 써주십시오. `"테스트 실패"`를 사용하지 말고, `"테스트 실패: POST /api/reset-password가 500을 반환했습니다. 환경 변수에 이메일 서비스 설정이 있는지 확인하십시오. 템플릿 파일은 templates/reset-email.html에 있어야 합니다."`를 사용하십시오. 이러한 구체적이고 실행 가능한 피드백은 에이전트가 인간의 개입 없이 스스로 수정할 수 있게 해줍니다. - -### 4. 런타임 신호 수집 - -효과적인 런타임 신호에는 다음이 포함됩니다: -- 애플리케이션이 성공적으로 시작되어 준비 상태에 도달했는가? -- 핵심 기능 경로가 런타임에 성공적으로 실행되었는가? -- 데이터베이스 쓰기, 파일 작업 및 기타 부작용이 올바른가? -- 임시 리소스가 정리되었는가? - -## 실제 사례 - -**작업**: 사용자 비밀번호 재설정 기능 구현. 데이터베이스 작업, 이메일 전송, API 엔드포인트 수정이 포함됩니다. - -**조기 제출 경로**: 에이전트가 데이터베이스 스키마를 수정하고, API 엔드포인트를 작성하고, 이메일 템플릿을 추가하고, 단위 테스트를 실행하고(통과), 완료를 선언합니다. 시험지가 가득 찼습니다. - -**실제 감점 항목**: (1) 엔드투엔드 흐름 미테스트 — 재설정 링크의 실제 전송 및 검증이 확인된 적 없음. (2) 데이터베이스 마이그레이션이 부분 실행 후 실패하여 스키마 불일치 발생. (3) 대상 환경에서 이메일 서비스 설정 누락. - -**하네스 개입**: 종료 유효성 검사 강제 실행 — (1) 전체 앱을 시작하여 재설정 엔드포인트 접근성 확인; (2) 전체 재설정 흐름 실행; (3) 데이터베이스 상태 일관성 검증. 모든 결함이 세션 내에서 발견되어, 이후 수정 비용의 5-10배를 절약했습니다. 독립적인 채점자가 실제 문제를 발견했습니다. - -## 핵심 요점 - -- **에이전트는 체계적으로 과도한 자신감을 보입니다** — 신뢰도 보정 편향은 객관적 현실입니다. 시험지를 가득 채운다고 답이 맞는 것은 아닙니다. -- **완료 판단은 외부화되어야 합니다** — 하네스가 독립적으로 검증하며, 에이전트의 "느낌"을 신뢰하지 마십시오. 학생이 자신의 시험을 채점할 수 없습니다. -- **3단계 유효성 검사가 모두 필수입니다** — 문법 통과, 동작 통과, 시스템 통과, 단계별로 진행합니다. -- **오류 메시지는 좋은 선생님의 빨간 펜 수정과 같아야 합니다** — 에이전트가 스스로 수정할 수 있도록 구체적인 수정 단계를 포함하십시오. -- **핵심 기능이 검증되기 전에는 리팩터링하지 마십시오** — 완료 우선순위 제약이 조기 최적화를 방지하는 핵심입니다. - -## 더 읽을거리 - -- [On Calibration of Modern Neural Networks - Guo et al.](https://arxiv.org/abs/1706.04599) — 현대 심층 신경망이 체계적으로 과도한 자신감을 보임을 증명 -- [Building Effective Agents - Anthropic](https://www.anthropic.com/research/building-effective-agents) — 완료 판단에서 런타임 증거의 중요한 역할 -- [Harness Engineering - OpenAI](https://openai.com/index/harness-engineering/) — 조기 완료 선언이 에이전트의 주요 실패 모드 중 하나 -- [The Art of Software Testing - Myers](https://www.goodreads.com/book/show/137543.The_Art_of_Software_Testing) — 테스트 방법 계층 구조와 효과성에 관한 고전 참고서 - -## 연습 문제 - -1. **종료 유효성 검사 함수 설계**: 데이터베이스 마이그레이션과 API 수정이 포함된 작업에 대한 완전한 종료 유효성 검사를 설계하십시오. 필요한 런타임 신호와 각 신호의 통과/실패 기준을 나열하십시오. 실제 작업에 실행하고 어떤 숨겨진 문제가 발견되는지 기록하십시오. - -2. **보정 편향 측정**: 10가지 다른 유형의 코딩 작업을 선택하고, 에이전트의 자체 보고 완료 신뢰도와 실제 완료 품질을 기록하십시오. 편향 값을 계산하고 작업 복잡성과의 관계를 분석하십시오. - -3. **다층 방어 실험**: 동일한 작업 세트에 세 가지 구성을 실행하십시오 — (a) 정적 분석만, (b) 단위 테스트 추가, (c) 완전한 3단계 유효성 검사. 조기 완료 선언의 비율과 발견되지 않은 결함의 수를 비교하십시오. diff --git a/courses/learn-harness-engineering/docs/ko/lectures/lecture-10-why-end-to-end-testing-changes-results/code/architecture-rules.md b/courses/learn-harness-engineering/docs/ko/lectures/lecture-10-why-end-to-end-testing-changes-results/code/architecture-rules.md deleted file mode 100644 index 44adc631..00000000 --- a/courses/learn-harness-engineering/docs/ko/lectures/lecture-10-why-end-to-end-testing-changes-results/code/architecture-rules.md +++ /dev/null @@ -1,6 +0,0 @@ -# Electron 아키텍처 규칙 - -- 렌더러 코드는 파일시스템에 직접 접근할 수 없습니다. -- 프리로드(preload)는 렌더러와 Electron 메인 간의 유일한 브릿지입니다. -- 검색 및 인덱싱 로직은 UI 컴포넌트가 아닌 서비스 모듈에 있어야 합니다. -- 로깅은 구조화되어야 하며 서비스 경계에서 방출되어야 합니다. diff --git a/courses/learn-harness-engineering/docs/ko/lectures/lecture-10-why-end-to-end-testing-changes-results/code/e2e-runner.ts b/courses/learn-harness-engineering/docs/ko/lectures/lecture-10-why-end-to-end-testing-changes-results/code/e2e-runner.ts deleted file mode 100644 index 24284165..00000000 --- a/courses/learn-harness-engineering/docs/ko/lectures/lecture-10-why-end-to-end-testing-changes-results/code/e2e-runner.ts +++ /dev/null @@ -1,253 +0,0 @@ -/** - * e2e-runner.ts - * - * A minimal E2E test harness. Defines test cases as user action sequences - * (import doc -> index -> ask question -> verify citation). Simulates - * running them and shows the difference between "unit tests pass" and - * "full pipeline works". - * - * Run: npx tsx docs/lectures/lecture-10-why-end-to-end-testing-changes-results/code/e2e-runner.ts - */ - -// --------------------------------------------------------------------------- -// Types -// --------------------------------------------------------------------------- - -interface PipelineStep { - name: string; - unitTestPasses: boolean; - // Simulated actual behavior in the pipeline - actualBehavior: "works" | "fails" | "partial"; - failureReason?: string; -} - -interface TestCase { - name: string; - steps: PipelineStep[]; -} - -interface TestResult { - testCase: string; - unitTestsPassed: number; - unitTestsTotal: number; - unitTestResult: "PASS" | "FAIL"; - e2eResult: "PASS" | "FAIL"; - e2eFailureStep?: string; - e2eFailureReason?: string; -} - -// --------------------------------------------------------------------------- -// Test cases -- realistic scenarios where unit tests pass but E2E fails -// --------------------------------------------------------------------------- - -const testCases: TestCase[] = [ - { - name: "Import document and ask question", - steps: [ - { - name: "Parse uploaded document", - unitTestPasses: true, - actualBehavior: "works", - }, - { - name: "Store document chunks", - unitTestPasses: true, - actualBehavior: "works", - }, - { - name: "Index chunks for retrieval", - unitTestPasses: true, - actualBehavior: "partial", // Indexes but with wrong embedding dimensions - failureReason: "Embedding dimension mismatch between indexer and retriever", - }, - { - name: "Retrieve relevant chunks", - unitTestPasses: true, // Unit test uses mock data with correct dimensions - actualBehavior: "fails", - failureReason: "Empty results due to dimension mismatch from previous step", - }, - { - name: "Generate answer with citations", - unitTestPasses: true, // Unit test provides pre-retrieved chunks - actualBehavior: "fails", - failureReason: "No chunks retrieved, so answer has no citations", - }, - ], - }, - { - name: "Delete document and verify removal", - steps: [ - { - name: "Find document by ID", - unitTestPasses: true, - actualBehavior: "works", - }, - { - name: "Delete document record", - unitTestPasses: true, - actualBehavior: "works", - }, - { - name: "Remove indexed chunks", - unitTestPasses: true, - actualBehavior: "fails", // Orphaned chunks remain in the index - failureReason: "Index cleanup query timed out, chunks remain orphaned", - }, - { - name: "Verify document not in search results", - unitTestPasses: true, // Unit test mocks the search - actualBehavior: "fails", - failureReason: "Orphaned chunks from previous step still appear in results", - }, - ], - }, - { - name: "Multi-user concurrent access", - steps: [ - { - name: "User A imports document", - unitTestPasses: true, - actualBehavior: "works", - }, - { - name: "User B imports document", - unitTestPasses: true, - actualBehavior: "works", - }, - { - name: "User A queries their document", - unitTestPasses: true, - actualBehavior: "partial", // Cross-contamination of results - failureReason: "No user-scoping on retrieval, returns chunks from User B's doc", - }, - { - name: "Verify only User A's results returned", - unitTestPasses: true, - actualBehavior: "fails", - failureReason: "Results include documents from other users", - }, - ], - }, -]; - -// --------------------------------------------------------------------------- -// Run tests -// --------------------------------------------------------------------------- - -function runUnitTests(tc: TestCase): { passed: number; total: number } { - const passed = tc.steps.filter((s) => s.unitTestPasses).length; - return { passed, total: tc.steps.length }; -} - -function runE2ETest(tc: TestCase): { pass: boolean; failureStep?: string; failureReason?: string } { - // Pipeline: if any step actually fails, the whole E2E fails - for (const step of tc.steps) { - if (step.actualBehavior === "fails") { - return { - pass: false, - failureStep: step.name, - failureReason: step.failureReason ?? "Unknown failure", - }; - } - // "partial" means the step technically completes but creates problems downstream - // We let it continue but track it - } - // Check if any step was "partial" (which may cause downstream issues) - const partialSteps = tc.steps.filter((s) => s.actualBehavior === "partial"); - if (partialSteps.length > 0) { - // The partial steps may or may not cause overall failure - // In our simulation, partial steps always lead to failure downstream - // unless there's an explicit "fails" step that already caught it - // This case means all steps were either "works" or "partial" - return { - pass: false, - failureStep: partialSteps[partialSteps.length - 1].name, - failureReason: partialSteps[partialSteps.length - 1].failureReason ?? "Partial completion", - }; - } - - return { pass: true }; -} - -// --------------------------------------------------------------------------- -// Reporting -// --------------------------------------------------------------------------- - -function pad(s: string, len: number): string { - return s.length >= len ? s : s + " ".repeat(len - s.length); -} - -function run(): void { - console.log("\n" + "=".repeat(95)); - console.log(" E2E TEST RUNNER -- Unit Tests vs Full Pipeline"); - console.log("=".repeat(95)); - - const results: TestResult[] = testCases.map((tc) => { - const unit = runUnitTests(tc); - const e2e = runE2ETest(tc); - - return { - testCase: tc.name, - unitTestsPassed: unit.passed, - unitTestsTotal: unit.total, - unitTestResult: unit.passed === unit.total ? "PASS" : "FAIL", - e2eResult: e2e.pass ? "PASS" : "FAIL", - e2eFailureStep: e2e.failureStep, - e2eFailureReason: e2e.failureReason, - }; - }); - - // Per-test-case detail - for (let i = 0; i < testCases.length; i++) { - const tc = testCases[i]; - const r = results[i]; - - console.log("\n Test Case: " + tc.name); - console.log(" " + "-".repeat(70)); - - const stepHeader = ` | ${pad("Step", 40)}| ${pad("Unit Test", 11)}| ${pad("E2E Actual", 12)}|`; - const stepSep = ` |${"-".repeat(42)}|${"-".repeat(13)}|${"-".repeat(14)}|`; - console.log(stepHeader); - console.log(stepSep); - - for (const step of tc.steps) { - const utLabel = step.unitTestPasses ? "PASS" : "FAIL"; - let e2eLabel: string; - if (step.actualBehavior === "works") e2eLabel = "PASS"; - else if (step.actualBehavior === "partial") e2eLabel = "PARTIAL*"; - else e2eLabel = "FAIL"; - - const marker = step.actualBehavior !== "works" ? ">>" : " "; - console.log(`${marker}| ${pad(step.name, 40)}| ${pad(utLabel, 11)}| ${pad(e2eLabel, 12)}|`); - } - - if (r.e2eFailureStep) { - console.log(`\n E2E Failure at: ${r.e2eFailureStep}`); - console.log(` Reason: ${r.e2eFailureReason}`); - } - } - - // Summary comparison - console.log("\n" + "=".repeat(95)); - console.log(" COMPARISON: Unit Tests vs E2E Tests"); - console.log("=".repeat(95) + "\n"); - - const header = `| ${pad("Test Case", 35)}| ${pad("Unit Tests", 15)}| ${pad("E2E Result", 12)}| Discrepancy`; - const sep = `|${"-".repeat(37)}|${"-".repeat(17)}|${"-".repeat(14)}|${"-".repeat(30)}`; - console.log(header); - console.log(sep); - - for (const r of results) { - const utLabel = `${r.unitTestsPassed}/${r.unitTestsTotal} ${r.unitTestResult}`; - const discrepancy = r.unitTestResult === "PASS" && r.e2eResult === "FAIL"; - const discLabel = discrepancy ? "UNIT PASS BUT E2E FAIL" : "Consistent"; - - console.log(`| ${pad(r.testCase, 35)}| ${pad(utLabel, 15)}| ${pad(r.e2eResult, 12)}| ${discLabel}`); - } - - const falseConfidence = results.filter((r) => r.unitTestResult === "PASS" && r.e2eResult === "FAIL").length; - console.log("\n False confidence count: " + falseConfidence + " of " + results.length); - console.log(" Unit tests pass but E2E reveals integration failures that units cannot catch.\n"); -} - -run(); diff --git a/courses/learn-harness-engineering/docs/ko/lectures/lecture-10-why-end-to-end-testing-changes-results/code/index.md b/courses/learn-harness-engineering/docs/ko/lectures/lecture-10-why-end-to-end-testing-changes-results/code/index.md deleted file mode 100644 index 5f3f44ea..00000000 --- a/courses/learn-harness-engineering/docs/ko/lectures/lecture-10-why-end-to-end-testing-changes-results/code/index.md +++ /dev/null @@ -1,8 +0,0 @@ -# 강의 10 코드 - -이 폴더는 다음 항목의 예제에 사용합니다. - -- 아키텍처 제약(architecture constraints) -- 구조적 테스트(structural tests) -- 불변성 검사(taste invariants) -- 수정 지향 린트 메시지(remediation-oriented lint messages) diff --git a/courses/learn-harness-engineering/docs/ko/lectures/lecture-10-why-end-to-end-testing-changes-results/code/review-feedback-to-rule.md b/courses/learn-harness-engineering/docs/ko/lectures/lecture-10-why-end-to-end-testing-changes-results/code/review-feedback-to-rule.md deleted file mode 100644 index ab79481e..00000000 --- a/courses/learn-harness-engineering/docs/ko/lectures/lecture-10-why-end-to-end-testing-changes-results/code/review-feedback-to-rule.md +++ /dev/null @@ -1,10 +0,0 @@ -# 예제: 리뷰 피드백을 규칙으로 전환하기 - -반복되는 리뷰 코멘트: - -> 렌더러에서 파일시스템 유틸리티를 직접 호출하지 마세요. 프리로드 브릿지를 사용하세요. - -승격된 하네스 규칙: - -- 렌더러 코드에서 `fs` 사용을 방지하는 린트 또는 임포트 규칙 추가 -- 프리로드 경계를 설명하는 수정 텍스트 추가 diff --git a/courses/learn-harness-engineering/docs/ko/lectures/lecture-10-why-end-to-end-testing-changes-results/index.md b/courses/learn-harness-engineering/docs/ko/lectures/lecture-10-why-end-to-end-testing-changes-results/index.md deleted file mode 100644 index b4a0e41c..00000000 --- a/courses/learn-harness-engineering/docs/ko/lectures/lecture-10-why-end-to-end-testing-changes-results/index.md +++ /dev/null @@ -1,158 +0,0 @@ -[English Version →](../../../en/lectures/lecture-10-why-end-to-end-testing-changes-results/) - -> 이 강의의 코드 예제: [code/](https://github.com/walkinglabs/learn-harness-engineering/blob/main/docs/en/lectures/lecture-10-why-end-to-end-testing-changes-results/code/) -> 실습 프로젝트: [Project 05. 에이전트가 자신의 작업을 스스로 검증하게 하기](./../../projects/project-05-grounded-qa-verification/index.md) - -# 강의 10. 엔드투엔드 테스트(end-to-end testing)만이 진정한 검증이다 - -에이전트에게 Electron 앱에 파일 내보내기 기능을 추가해 달라고 합니다. 에이전트는 렌더 프로세스 컴포넌트, 프리로드 스크립트, 서비스 계층 로직을 작성합니다. 각 컴포넌트에 대한 단위 테스트(unit test)는 완벽하게 통과합니다. 에이전트는 "완료됐습니다"라고 말합니다. 실제로 내보내기 버튼을 클릭하면—파일 경로 형식이 잘못되어 있고, 진행 표시줄이 업데이트되지 않으며, 대용량 파일 내보내기 시 메모리 누수가 발생합니다. 다섯 개의 컴포넌트 경계 결함이 있었는데, 단위 테스트는 하나도 잡아내지 못한 것입니다. - -마치 합창 리허설에서 각 성부가 개별적으로 연습할 때는 완벽하게 들리지만, 함께 노래하면 소프라노가 베이스보다 반 박자 빠르고, 반주는 주 선율보다 반음 낮은 것과 같습니다. 각 파트는 개별적으로는 "정확"하지만, 전체적으로는 엇나갑니다. - -Google의 테스팅 피라미드(Testing Pyramid)는 이렇게 말합니다. 대량의 단위 테스트가 기반이지만, 거기서 멈추면 컴포넌트 간 상호작용 문제를 체계적으로 놓치게 됩니다. AI 코딩 에이전트(coding agent)에게는 이 문제가 더욱 심각합니다—에이전트는 가장 빠른 테스트만 실행하고 완료를 선언하는 경향이 있기 때문입니다. **엔드투엔드 테스트만이 시스템 수준의 결함이 존재하지 않음을 증명할 수 있습니다.** - -## 단위 테스트의 맹점 - -단위 테스트의 설계 철학은 격리(isolation)입니다—의존성을 모킹(mocking)하고 테스트 대상 단위에만 집중하는 것입니다. 이 철학이 단위 테스트를 빠르고 정밀하게 만들지만, 동시에 체계적인 맹점을 만들어 냅니다. 합창 리허설에서 각 성부가 헤드폰을 끼고 연습하는 것과 같습니다—자신에게는 괜찮게 들리지만, 함께 모였을 때야 비로소 문제가 드러납니다. - -**인터페이스 불일치**: 렌더 프로세스가 프리로드 스크립트에 전달하는 파일 경로는 상대 경로이지만, 프리로드 스크립트는 절대 경로를 기대합니다. 각각의 단위 테스트는 모두 모킹을 사용해 통과했습니다. 엔드투엔드 흐름이 실행될 때야 비로소 문제가 발견됩니다—마치 두 성부가 개별적으로 연습하면서 괜찮다고 느끼다가, 앙상블에서 한쪽은 4/4박자로, 다른 쪽은 3/4박자로 노래하고 있다는 것을 알게 되는 것처럼. - -**상태 전파 오류(State Propagation Errors)**: 데이터베이스 마이그레이션이 테이블 스키마를 변경했지만, ORM 캐싱 계층은 여전히 이전 스키마에 대한 캐시 항목을 보유하고 있습니다. 단위 테스트는 매번 완전히 새로운 모킹 환경을 제공하므로, 이러한 계층 간 상태 불일치를 드러내지 않습니다. 노래 가사를 바꿨는데 누군가가 아직도 이전 버전으로 노래하는 것과 같습니다. - -**리소스 수명 주기 문제**: 파일 핸들, 데이터베이스 연결, 네트워크 소켓의 획득 및 해제는 여러 컴포넌트에 걸쳐 있습니다. 단위 테스트는 각 테스트를 위해 독립적인 리소스를 생성하고 소멸시키므로, 리소스 경합이나 누수를 드러내지 못합니다. 리허설 중에 각 성부가 마이크를 번갈아 사용하지만, 무대에서 모두 함께 올라갔을 때 마이크가 부족한 것과 같습니다. - -**환경 의존성**: 코드는 테스트 환경(모든 것이 모킹된 곳)에서는 올바르게 동작하지만, 설정 차이, 네트워크 지연, 또는 서비스 불가용성으로 인해 실제 환경에서는 실패합니다. 리허설실에서는 완벽하게 노래했지만, 야외 페스티벌에서 오디오 피드백과 바람 간섭을 만나는 것과 같습니다. - -## 엔드투엔드 테스트는 결과만 바꾸는 것이 아니라 행동을 바꾼다 - -많은 사람들이 깨닫지 못하는 것이 있습니다. 에이전트가 자신의 작업이 엔드투엔드 테스트를 받게 된다는 것을 알면, 코딩 행동이 바뀐다는 점입니다. - -1. **컴포넌트 상호작용 고려**: 코드를 작성하면서 "이 인터페이스가 업스트림과 어떻게 연결되는가"를 생각하게 됩니다. 단순히 하나의 함수에만 집중하지 않습니다. 마치 결국 함께 노래하게 될 것을 알기에, 연습 중에도 다른 성부에 귀를 기울이는 것처럼. -2. **아키텍처 경계 준수**: 아키텍처 제약이 있는 시스템에서 엔드투엔드 테스트는 에이전트가 경계 규칙을 준수하도록 강제합니다. 악보에 "여기서 크레셴도(crescendo)"라고 표시된 것처럼, 따라야 합니다. -3. **오류 경로 처리**: 엔드투엔드 테스트는 보통 실패 시나리오를 포함하므로, 에이전트가 예외 처리를 고려하게 만듭니다. 리허설 중에 "마이크가 갑자기 꺼지면 어떻게 할까"를 시뮬레이션하여, 실제 상황에서 어떻게 대처할지 알게 되는 것처럼. - -## 테스팅 피라미드와 리뷰 피드백 승격 - -```mermaid -flowchart TB - subgraph Unit["단위 테스트는 격리된 부분만 검사합니다"] - U1["렌더러 테스트"] - U2["프리로드 테스트"] - U3["서비스 테스트"] - end - - subgraph E2E["E2E는 실제 시스템을 통과합니다"] - R["렌더러 버튼 클릭"] --> P["프리로드 브릿지"] - P --> S["서비스 계층"] - S --> F["파일 시스템 / OS"] - F --> Result["실제 내보낸 파일"] - end -``` - -```mermaid -flowchart LR - Review["리뷰 피드백:
렌더러에서 fs를 직접 import할 수 없음"] --> Rule["직접 fs import 검사 추가"] - Rule --> Message["오류 메시지에 에이전트에게
파일 접근을 프리로드로 이동하라고 알림"] - Message --> Harness["이 검사를 하네스에 추가"] - Harness --> Stronger["다음에는 즉시 실패하게 됨"] -``` - -Codex 엔지니어링 실천에서 OpenAI는 강조합니다. **에이전트를 위해 작성된 오류 메시지에는 수정 지침이 포함되어야 합니다.** `"렌더러에서 직접 파일시스템 접근"`이라고만 쓰지 말고, `"렌더러에서 직접 파일시스템 접근. 모든 파일 작업은 프리로드 브릿지를 통해야 합니다. 이 호출을 preload/file-ops.ts로 이동하고 window.api를 통해 호출하세요."`라고 써야 합니다. 이것이 아키텍처 규칙을 자동 수정 루프로 만듭니다. 지휘자가 단순히 "그거 틀렸어"라고 말하지 않고, "여기서 반 박자 빠르게 나왔어, 알토 리듬을 들어봐, 32마디에서 들어와"라고 말하는 것처럼. - -## 핵심 개념 - -- **컴포넌트 경계 결함(Component Boundary Defects)**: 컴포넌트 A와 B가 각자의 단위 테스트를 통과하지만, 상호작용이 잘못된 동작을 만들어냅니다. 이것이 엔드투엔드 테스트가 가장 잘 잡아내는 문제 유형입니다—개별적으로는 정확하지만 함께하면 엇나가는 합창 성부처럼. -- **테스팅 적합성 기울기(Testing Adequacy Gradient)**: 단위 테스트가 잡는 결함 <= 통합 테스트가 잡는 결함 <= 엔드투엔드 테스트가 잡는 결함. 각 계층이 올라갈수록 탐지 능력이 증가합니다. -- **아키텍처 경계 강제 규칙(Architectural Boundary Enforcement Rules)**: 아키텍처 문서의 규칙(예: "렌더 프로세스는 파일 시스템에 직접 접근할 수 없다")을 실행 가능한 자동화된 검사로 전환합니다. "문서에 적힌"에서 "CI에서 실행되는"으로. -- **리뷰 피드백 승격(Review Feedback Promotion)**: 반복되는 코드 리뷰 코멘트를 자동화된 테스트로 전환합니다. 반복되는 문제가 발견될 때마다 규칙을 추가하면, 하네스(harness)가 자동으로 강화됩니다. 지휘자가 흔한 리허설 실수를 워밍업 연습으로 바꾸는 것처럼—다음에 같은 실수를 하면, 연습 자체가 지휘자가 한 마디 할 필요 없이 그것을 드러냅니다. -- **에이전트 지향 오류 메시지(Agent-Oriented Error Messages)**: 실패 메시지는 단순히 "무엇이 잘못됐는지"를 명시해서는 안 되고, 에이전트에게 정확히 어떻게 수정하는지도 알려줘야 합니다. 이것이 테스트 실패를 자기 수정 피드백 루프로 만듭니다. - -## 어떻게 해야 하는가 - -### 0. 아키텍처 경계를 먼저 정의하고, 그 다음에 E2E 테스트를 작성하라 - -엔드투엔드 테스트의 전제 조건은 명확한 시스템 경계입니다. 아키텍처가 스파게티 더미라면, 엔드투엔드 테스트는 그저 "이 스파게티가 실행된다"는 것만 증명할 뿐, 설계 의도가 위반된 곳을 알려주지 않습니다. 성부 분리조차 안 된 합창단처럼—아무리 리허설을 해도 좋아지지 않습니다. - -OpenAI의 경험: **에이전트가 생성한 코드베이스에는 아키텍처 제약이 팀이 커질 때 고려할 것이 아니라, 1일차에 확립되어야 하는 초기 전제 조건이어야 합니다.** 이유는 간단합니다—에이전트는 저장소의 기존 패턴을 복사하는 경향이 있고, 그 패턴이 고르지 않거나 차선이어도 마찬가지입니다. 아키텍처 제약 없이는 에이전트가 매 세션마다 더 많은 편차를 도입합니다. - -OpenAI는 "계층화된 도메인 아키텍처(Layered Domain Architecture)"를 채택했습니다—각 비즈니스 도메인은 고정된 계층으로 구분됩니다: Types → Config → Repo → Service → Runtime → UI. 의존성은 엄격하게 앞으로만 흐르고, 도메인 간 관심사는 명시적인 Provider 인터페이스를 통해 들어옵니다. 다른 의존성은 금지되며 커스텀 린팅(linting)으로 기계적으로 강제됩니다. - -핵심 원칙: **불변성을 강제하되, 구현을 세세하게 관리하지 않는다.** 예를 들어, "데이터는 경계에서 파싱된다"고 요구하되, 어떤 라이브러리를 사용할지는 지시하지 않습니다. 오류 메시지에는 수정 지침이 포함되어야 합니다—단순히 "위반"이라고 말하는 것이 아니라, 에이전트에게 정확히 어떻게 변경해야 하는지 알려줘야 합니다. - -> 출처: [OpenAI: Harness engineering: leveraging Codex in an agent-first world](https://openai.com/index/harness-engineering/) - -### 1. 하네스에 엔드투엔드 계층이 반드시 포함되어야 한다 - -검증 흐름에서 명시적으로 만드세요. 컴포넌트 간 변경이 포함된 작업의 경우, 엔드투엔드 테스트 통과가 완료의 전제 조건입니다. - -``` -## 검증 계층 구조 -- 레벨 1: 단위 테스트 (반드시 통과) -- 레벨 2: 통합 테스트 (반드시 통과) -- 레벨 3: 엔드투엔드 테스트 (크로스 컴포넌트 변경 시 반드시 통과) -- 필수 레벨 건너뛰기 = 완료 아님 -``` - -### 2. 아키텍처 규칙을 실행 가능한 검사로 전환하라 - -모든 아키텍처 제약에는 대응하는 테스트 또는 린트 규칙이 있어야 합니다. - -```bash -# 렌더 프로세스가 Node.js API를 직접 호출하는지 검사 -grep -r "require('fs')" src/renderer/ && exit 1 || echo "OK: no direct fs access in renderer" -``` - -### 3. 에이전트 지향 오류 메시지를 설계하라 - -실패 메시지에는 세 가지 요소가 포함되어야 합니다. 무엇이 잘못됐는지, 왜인지, 어떻게 수정하는지. - -``` -ERROR: Found direct import of 'fs' in src/renderer/App.tsx:12 -WHY: Renderer process has no access to Node.js APIs for security -FIX: Move file operations to src/preload/file-ops.ts and call via window.api.readFile() -``` - -### 4. 리뷰 피드백 승격 프로세스를 수립하라 - -코드 리뷰 중에 새로운 유형의 에이전트 오류가 발견될 때마다, 자동화된 검사로 전환하세요. 한 달 후에는 하네스가 한 달 전보다 크게 강화되어 있을 것입니다. 합창의 리허설 노트처럼—매 리허설에서 발견된 문제를 기록해두면, 다음 리허설 전에 확인할 수 있습니다. 시간이 지날수록 흔한 오류는 줄어들고, 음악은 더 조화로워집니다. - -## 실제 사례 - -**작업**: Electron 앱에 파일 내보내기 기능 구현. 렌더 프로세스 UI, 프리로드 스크립트 파일시스템 프록시, 서비스 계층 데이터 변환이 포함됩니다. - -**각 성부 개별 연습 (단위 테스트 통과)**: 렌더 컴포넌트 테스트 (통과, 파일 작업 모킹), 프리로드 스크립트 테스트 (통과, 파일시스템 모킹), 서비스 계층 테스트 (통과, 데이터 소스 모킹). 에이전트는 완료를 선언합니다. - -**함께 노래하기 (엔드투엔드 테스트로 드러난 결함)**: - -| 결함 | 설명 | 단위 테스트 | E2E | -|------|------|-----------|-----| -| 인터페이스 불일치 | 파일 경로 형식 불일치 | 놓침 | 잡음 | -| 상태 전파 | 내보내기 진행 상황이 IPC를 통해 UI로 전달되지 않음 | 놓침 | 잡음 | -| 리소스 누수 | 대용량 파일 내보내기 핸들 미해제 | 놓침 | 잡음 | -| 권한 문제 | 패키징된 환경에서 권한 차이 | 놓침 | 잡음 | -| 오류 전파 | 서비스 계층 예외가 UI 계층까지 도달하지 않음 | 놓침 | 잡음 | - -5개의 결함 모두 엔드투엔드 테스트로 잡혔고, 단위 테스트는 하나도 잡지 못했습니다. 비용은 테스트 시간이 2초에서 15초로 늘어난 것뿐이었습니다—에이전트 워크플로에서는 완전히 수용 가능합니다. 각 파트가 개별적으로 아무리 잘 노래해도, 전체 앙상블 리허설을 이길 수는 없습니다. - -## 핵심 정리 - -- **단위 테스트는 컴포넌트 경계 결함에 체계적으로 맹목적입니다**—격리 설계 자체가 상호작용 문제를 탐지하지 못하게 합니다. 모두가 정확하게 노래해도 합창이 엇나갈 수 있습니다. -- **엔드투엔드 테스트는 결함을 탐지할 뿐만 아니라 에이전트의 코딩 행동을 바꿉니다**—통합과 경계에 더 집중하게 만듭니다. -- **아키텍처 규칙은 실행 가능해야 합니다**—읽히기를 기다리는 문서에 쓰이는 것이 아니라, 모든 커밋에서 자동으로 검사되어야 합니다. -- **오류 메시지는 에이전트를 위해 설계되어야 합니다**—"어떻게 수정하는지"에 대한 구체적인 단계를 포함해 자기 수정 루프를 형성해야 합니다. -- **리뷰 피드백 승격이 하네스를 자동으로 강화합니다**—포착된 결함의 각 카테고리가 영구적인 방어선이 됩니다. - -## 더 읽을거리 - -- [How Google Tests Software - Whittaker et al.](https://www.goodreads.com/book/show/13563030-how-google-tests-software) — 테스팅 피라미드 모델의 고전적 원천 -- [Harness Engineering - OpenAI](https://openai.com/index/harness-engineering/) — 아키텍처 제약의 자동화된 실행을 위한 엔지니어링 실천 -- [Chaos Engineering - Netflix (Basiri et al.)](https://ieeexplore.ieee.org/document/7466237) — 시스템 복원력 검증을 위한 능동적 장애 주입 -- [QuickCheck - Claessen & Hughes](https://www.cs.tufts.edu/~nr/cs257/archive/john-hughes/quick.pdf) — 예제 테스트와 형식 검증 사이에 위치한 속성 테스팅 방법론 - -## 연습 문제 - -1. **크로스 컴포넌트 결함 탐지**: 최소 세 개의 컴포넌트가 관련된 수정 작업을 선택하세요. 먼저 단위 테스트만 실행하고 결과를 기록한 다음, 엔드투엔드 테스트를 실행합니다. 추가로 발견된 각 결함이 어떤 유형의 계층 간 상호작용 문제에 해당하는지 분석하세요. - -2. **아키텍처 규칙 자동화**: 프로젝트에서 아키텍처 제약을 선택하고 실행 가능한 검사(에이전트 지향 오류 메시지 포함)로 전환하세요. 하네스에 통합하고 기준선 작업으로 효과를 검증하세요. - -3. **리뷰 피드백 승격**: 코드 리뷰 히스토리에서 반복되는 코멘트 유형을 찾아 5단계 프로세스를 사용해 자동화된 검사로 전환하세요. 승격 전후에 문제의 빈도를 비교하세요. diff --git a/courses/learn-harness-engineering/docs/ko/lectures/lecture-11-why-observability-belongs-inside-the-harness/code/evaluator-rubric.md b/courses/learn-harness-engineering/docs/ko/lectures/lecture-11-why-observability-belongs-inside-the-harness/code/evaluator-rubric.md deleted file mode 100644 index d640fc53..00000000 --- a/courses/learn-harness-engineering/docs/ko/lectures/lecture-11-why-observability-belongs-inside-the-harness/code/evaluator-rubric.md +++ /dev/null @@ -1,8 +0,0 @@ -# 평가자 루브릭 예제 - -각 차원에 대해 1-5 점수를 사용합니다. - -- 근거(Grounding): 답변이 가져온 출처에 명확하게 연결되어 있는가? -- 인용 품질(Citation quality): 출처 참조가 눈에 보이고 구체적인가? -- 기능성(Functionality): 사용자가 질의응답 흐름을 완료할 수 있는가? -- 제품 일관성(Product coherence): 워크플로가 통합된 것처럼 느껴지는가? diff --git a/courses/learn-harness-engineering/docs/ko/lectures/lecture-11-why-observability-belongs-inside-the-harness/code/index.md b/courses/learn-harness-engineering/docs/ko/lectures/lecture-11-why-observability-belongs-inside-the-harness/code/index.md deleted file mode 100644 index 1df92708..00000000 --- a/courses/learn-harness-engineering/docs/ko/lectures/lecture-11-why-observability-belongs-inside-the-harness/code/index.md +++ /dev/null @@ -1,8 +0,0 @@ -# 강의 11 코드 - -이 폴더는 다음 항목의 예제에 사용합니다. - -- 계획자 출력(planner outputs) -- 평가자 루브릭(evaluator rubrics) -- 생성자/평가자 루프(generator/evaluator loops) -- 단일 에이전트 대 다중 역할 비교(single-agent vs multi-role comparisons) diff --git a/courses/learn-harness-engineering/docs/ko/lectures/lecture-11-why-observability-belongs-inside-the-harness/code/runtime-logger.ts b/courses/learn-harness-engineering/docs/ko/lectures/lecture-11-why-observability-belongs-inside-the-harness/code/runtime-logger.ts deleted file mode 100644 index 213dc198..00000000 --- a/courses/learn-harness-engineering/docs/ko/lectures/lecture-11-why-observability-belongs-inside-the-harness/code/runtime-logger.ts +++ /dev/null @@ -1,260 +0,0 @@ -/** - * runtime-logger.ts - * - * A structured logging module demo. Shows ad-hoc console.log output vs - * structured JSON log output when diagnosing a failure. Includes a seeded - * failure scenario and demonstrates how structured logs pinpoint the issue - * faster. - * - * Run: npx tsx docs/lectures/lecture-11-why-observability-belongs-inside-the-harness/code/runtime-logger.ts - */ - -// --------------------------------------------------------------------------- -// Types -// --------------------------------------------------------------------------- - -interface StructuredLogEntry { - timestamp: string; - level: "info" | "warn" | "error" | "debug"; - component: string; - action: string; - durationMs?: number; - input?: unknown; - output?: unknown; - error?: string; - correlationId: string; -} - -// --------------------------------------------------------------------------- -// Simulated pipeline with a seeded failure -// --------------------------------------------------------------------------- - -const CORRELATION_ID = "req-" + Math.random().toString(36).slice(2, 8); - -// Simulated stages of a document Q&A pipeline -interface PipelineStage { - component: string; - action: string; - durationMs: number; - success: boolean; - errorMessage?: string; - input?: unknown; - output?: unknown; -} - -function runPipeline(): PipelineStage[] { - return [ - { - component: "DocumentLoader", - action: "parse_upload", - durationMs: 45, - success: true, - input: { filename: "report.pdf", size: "2.3MB" }, - output: { chunks: 47 }, - }, - { - component: "ChunkIndexer", - action: "embed_and_store", - durationMs: 230, - success: true, - input: { chunks: 47 }, - output: { indexed: 47, embeddingDim: 1536 }, - }, - { - component: "QueryRouter", - action: "route_query", - durationMs: 12, - success: true, - input: { query: "What is the revenue target?" }, - output: { routedTo: "RetrievalEngine" }, - }, - { - // SEEDED FAILURE: Retrieval returns 0 results due to dimension mismatch - component: "RetrievalEngine", - action: "semantic_search", - durationMs: 180, - success: false, - errorMessage: "Vector dimension mismatch: query embedding dim=768, index embedding dim=1536", - input: { query: "What is the revenue target?", topK: 5 }, - output: { results: 0 }, - }, - { - component: "AnswerGenerator", - action: "generate_with_citations", - durationMs: 1500, - success: true, // Doesn't crash, but produces a bad answer - input: { context: [], query: "What is the revenue target?" }, - output: { answer: "I could not find relevant information.", citations: 0 }, - }, - ]; -} - -// --------------------------------------------------------------------------- -// Ad-hoc logging (console.log style) -// --------------------------------------------------------------------------- - -function printAdHocLog(stages: PipelineStage[]): void { - console.log("Starting document Q&A pipeline..."); - console.log("User uploaded report.pdf"); - - for (const stage of stages) { - if (stage.success) { - console.log(`${stage.component}: ${stage.action} done (${stage.durationMs}ms)`); - } else { - console.log(`${stage.component}: something went wrong`); - } - } - - console.log("Pipeline finished. Answer: I could not find relevant information."); -} - -// --------------------------------------------------------------------------- -// Structured logging (JSON) -// --------------------------------------------------------------------------- - -function printStructuredLog(stages: PipelineStage[]): StructuredLogEntry[] { - const entries: StructuredLogEntry[] = []; - - for (const stage of stages) { - entries.push({ - timestamp: new Date().toISOString(), - level: stage.success ? "info" : "error", - component: stage.component, - action: stage.action, - durationMs: stage.durationMs, - input: stage.input, - output: stage.output, - error: stage.errorMessage, - correlationId: CORRELATION_ID, - }); - } - - return entries; -} - -// --------------------------------------------------------------------------- -// Diagnose failure from structured logs -// --------------------------------------------------------------------------- - -function diagnoseFromStructured(entries: StructuredLogEntry[]): string[] { - const diagnosis: string[] = []; - - // Find errors - const errors = entries.filter((e) => e.level === "error"); - if (errors.length > 0) { - diagnosis.push("ERRORS FOUND:"); - for (const err of errors) { - diagnosis.push(` - ${err.component}.${err.action}: ${err.error}`); - } - } - - // Check for latency spikes - const slowStages = entries.filter((e) => (e.durationMs ?? 0) > 1000); - if (slowStages.length > 0) { - diagnosis.push("LATENCY SPIKES:"); - for (const s of slowStages) { - diagnosis.push(` - ${s.component}.${s.action}: ${s.durationMs}ms`); - } - } - - // Check for cascading failures - const emptyOutputs = entries.filter((e) => { - const out = e.output as Record | undefined; - return out && typeof out === "object" && "results" in out && (out.results as number) === 0; - }); - if (emptyOutputs.length > 0) { - diagnosis.push("EMPTY OUTPUTS (possible cascade):"); - for (const e of emptyOutputs) { - diagnosis.push(` - ${e.component}.${e.action}: returned 0 results`); - } - } - - return diagnosis; -} - -// --------------------------------------------------------------------------- -// Reporting -// --------------------------------------------------------------------------- - -function pad(s: string, len: number): string { - return s.length >= len ? s : s + " ".repeat(len - s.length); -} - -function run(): void { - const pipeline = runPipeline(); - - console.log("\n" + "=".repeat(90)); - console.log(" OBSERVABILITY DEMO -- Ad-hoc vs Structured Logging"); - console.log("=".repeat(90)); - - // --- Ad-hoc output --- - console.log("\n" + "-".repeat(90)); - console.log(" SCENARIO A: Ad-hoc console.log output"); - console.log("-".repeat(90) + "\n"); - printAdHocLog(pipeline); - console.log("\n Diagnosis from ad-hoc logs: ??? Hard to tell what went wrong."); - console.log(" The failure message is vague: 'something went wrong'."); - console.log(" No dimensions, no input/output data, no correlation ID."); - - // --- Structured output --- - console.log("\n" + "-".repeat(90)); - console.log(" SCENARIO B: Structured JSON log output"); - console.log("-".repeat(90) + "\n"); - - const structuredEntries = printStructuredLog(pipeline); - - const header = `| ${pad("Timestamp", 26)}| ${pad("Level", 6)}| ${pad("Component", 20)}| ${pad("Action", 25)}| ${pad("Duration", 9)}| Error?`; - const sep = `|${"-".repeat(28)}|${"-".repeat(8)}|${"-".repeat(22)}|${"-".repeat(27)}|${"-".repeat(11)}|${"-".repeat(30)}`; - console.log(header); - console.log(sep); - - for (const entry of structuredEntries) { - const hasError = entry.error ? entry.error.slice(0, 30) : ""; - const marker = entry.level === "error" ? ">>" : " "; - console.log( - `${marker}| ${pad(entry.timestamp, 26)}| ${pad(entry.level, 6)}| ${pad(entry.component, 20)}| ${pad(entry.action, 25)}| ${pad(String(entry.durationMs) + "ms", 9)}| ${hasError}` - ); - } - - // --- Diagnosis --- - console.log("\n" + "-".repeat(90)); - console.log(" AUTOMATED DIAGNOSIS FROM STRUCTURED LOGS"); - console.log("-".repeat(90) + "\n"); - - const errors = structuredEntries.filter((e) => e.level === "error"); - for (const err of errors) { - console.log(` ROOT CAUSE: ${err.component}.${err.action}`); - console.log(` Error: ${err.error}`); - console.log(` Input: ${JSON.stringify(err.input)}`); - console.log(` Output: ${JSON.stringify(err.output)}`); - console.log(` Correlation ID: ${err.correlationId}`); - } - - // Downstream impact - console.log("\n DOWNSTREAM IMPACT:"); - const answerGen = structuredEntries.find((e) => e.component === "AnswerGenerator"); - if (answerGen) { - const out = answerGen.output as Record; - console.log(` AnswerGenerator received empty context (${JSON.stringify(answerGen.input)})`); - console.log(` Produced answer: "${out.answer}" with ${out.citations} citations`); - } - - // Comparison summary - console.log("\n" + "=".repeat(90)); - console.log(" COMPARISON"); - console.log("=".repeat(90) + "\n"); - - const cHeader = `| ${pad("Metric", 35)}| ${pad("Ad-hoc Logs", 18)}| ${pad("Structured Logs", 18)}|`; - const cSep = `|${"-".repeat(37)}|${"-".repeat(20)}|${"-".repeat(20)}|`; - console.log(cHeader); - console.log(cSep); - console.log(`| ${pad("Root cause identifiable", 35)}| ${pad("No", 18)}| ${pad("Yes", 18)}|`); - console.log(`| ${pad("Input/output traceable", 35)}| ${pad("No", 18)}| ${pad("Yes", 18)}|`); - console.log(`| ${pad("Correlation across steps", 35)}| ${pad("No", 18)}| ${pad("Yes", 18)}|`); - console.log(`| ${pad("Machine-parseable", 35)}| ${pad("No", 18)}| ${pad("Yes", 18)}|`); - console.log(`| ${pad("Time to diagnose", 35)}| ${pad("Minutes (manual)", 18)}| ${pad("Seconds (auto)", 18)}|`); - - console.log("\n Structured logging transforms debugging from guesswork into a deterministic lookup.\n"); -} - -run(); diff --git a/courses/learn-harness-engineering/docs/ko/lectures/lecture-11-why-observability-belongs-inside-the-harness/code/sprint-contract.md b/courses/learn-harness-engineering/docs/ko/lectures/lecture-11-why-observability-belongs-inside-the-harness/code/sprint-contract.md deleted file mode 100644 index 49ba615b..00000000 --- a/courses/learn-harness-engineering/docs/ko/lectures/lecture-11-why-observability-belongs-inside-the-harness/code/sprint-contract.md +++ /dev/null @@ -1,12 +0,0 @@ -# 스프린트 계약 예제 - -스프린트 목표: - -- 근거 기반 Q&A 결과에 눈에 보이는 인용 추가 - -완료 기준: - -- 사용자가 질문을 합니다 -- 앱이 답변을 반환합니다 -- 최소 하나의 인용이 표시됩니다 -- 인용을 클릭하면 문서 뷰에서 출처 위치가 열립니다 diff --git a/courses/learn-harness-engineering/docs/ko/lectures/lecture-11-why-observability-belongs-inside-the-harness/index.md b/courses/learn-harness-engineering/docs/ko/lectures/lecture-11-why-observability-belongs-inside-the-harness/index.md deleted file mode 100644 index d1d718a5..00000000 --- a/courses/learn-harness-engineering/docs/ko/lectures/lecture-11-why-observability-belongs-inside-the-harness/index.md +++ /dev/null @@ -1,154 +0,0 @@ -[English Version →](../../../en/lectures/lecture-11-why-observability-belongs-inside-the-harness/) - -> 코드 예제: [code/](https://github.com/walkinglabs/learn-harness-engineering/blob/main/docs/en/lectures/lecture-11-why-observability-belongs-inside-the-harness/code/) -> 실습 프로젝트: [Project 06. 완전한 하네스 (캡스톤)](./../../projects/project-06-runtime-observability-and-debugging/index.md) - -# 강의 11. 에이전트의 런타임을 관측 가능하게 만들어라 - -## 이 강의가 해결하는 문제 - -에이전트에게 기능을 구현하도록 요청합니다. 20분 동안 실행하고, 여러 파일을 수정한 다음, "완료됐지만 두 개의 테스트가 실패하고 있습니다"라고 알립니다. 왜 실패하는지 물으면 "잘 모르겠습니다, 타이밍 문제일 수 있습니다". 어떤 중요 경로를 변경했는지 물으면 "코드를 살펴보겠습니다..." - -이것은 에이전트의 능력 부족 문제가 아닙니다. 하네스가 충분한 관측 가능성(observability)을 제공하지 않는 것입니다. **관측 가능성 없이는 에이전트가 불확실성 속에서 결정을 내리고, 평가는 주관적 판단이 되며, 재시도는 맹목적인 방황이 됩니다.** OpenAI와 Anthropic 모두 신뢰성을 증거 문제로 정의합니다—하네스는 런타임 동작과 평가 신호를 다음 결정을 안내할 수 있는 형태로 노출해야 합니다. - -## 핵심 개념 - -- **런타임 관측 가능성(Runtime observability)**: 로그, 트레이스(trace), 프로세스 이벤트, 상태 확인 등 시스템 수준의 신호입니다. "시스템이 무엇을 했는가"에 대해 답합니다. 분산 시스템의 요청 트레이싱과 유사하게, 에이전트의 각 동작을 컨텍스트와 함께 기록합니다. -- **프로세스 관측 가능성(Process observability)**: 계획, 채점 루브릭(rubric), 인수 기준 등 하네스 결정 산출물에 대한 가시성입니다. "이 변경이 왜 수용되어야 하는가"에 대해 답합니다. -- **태스크 트레이스(Task trace)**: 태스크 시작부터 완료까지의 완전한 결정 경로 기록입니다. 분산 시스템의 요청 트레이싱과 유사하게, 에이전트가 취하는 모든 단계가 컨텍스트와 함께 기록됩니다. -- **스프린트 계약(Sprint contract)**: 코딩 시작 전에 협상되는 단기 합의입니다. 작업 범위, 검증 기준, 제외 사항을 명시합니다. 프로세스 관측 가능성을 위한 핵심 도구입니다. -- **평가자 루브릭(Evaluator rubric)**: 품질 평가를 주관적 판단에서 증거 기반 구조화된 채점으로 변환합니다. 서로 다른 평가자가 동일한 출력에 대해 유사한 결과를 도출하게 합니다. -- **계층화된 관측 가능성(Layered observability)**: 시스템 계층과 프로세스 계층의 관측 가능성을 동시에 설계하고 서로 강화하게 합니다. 런타임 신호는 동작을 설명하고, 프로세스 산출물은 의도를 설명합니다. - -## 계층화된 관측 가능성 - -```mermaid -flowchart LR - Contract["먼저 작업을 적어 두세요
변경할 것 / 변경하지 않을 것 / 통과 기준"] --> Generator["생성기"] - Generator --> Signals["실행 중에 앱 로그, 트레이스,
상태 확인을 수집합니다"] - Contract --> Review["결과를 항목별로 확인합니다
동작 / 테스트 / 경계"] - Signals --> Review - Review --> Verdict["실패한 검사와
수정 위치를 지목합니다"] - Verdict --> Generator -``` - -## 왜 이런 일이 발생하는가 - -### 관측 가능성 부재의 실제 비용 - -하네스에 관측 가능성이 없으면 네 가지 유형의 문제가 체계적으로 나타납니다. - -**"정확함"과 "정확해 보임"을 구별할 수 없다**: 코드 리뷰 중에 함수가 완벽하게 보입니다—올바른 구문, 탄탄한 로직. 그러나 런타임에서 엣지 케이스 처리 오류가 특정 입력에서 잘못된 결과를 만들어냅니다. 실제 실행 경로가 예상에서 벗어났다는 것은 런타임 트레이스만이 드러낼 수 있습니다. - -**평가가 신비주의가 된다**: 채점 루브릭과 인수 기준 없이는 평가자(사람 또는 에이전트)가 암묵적 가정에 의존합니다. 동일한 출력이 서로 다른 평가자로부터 완전히 다른 평가를 받을 수 있습니다. 품질 평가가 재현 불가능해집니다. - -**재시도가 맹목적인 추측이 된다**: 에이전트가 왜 실패했는지 모르면, 재시도 방향이 무작위입니다. 실제 실패 근본 원인을 무시하고 관련 없는 코드 경로를 고치면서 잘못된 방향으로 반복적으로 시도할 수 있습니다. 모든 맹목적 재시도는 토큰과 시간을 소비합니다. - -**세션 핸드오프(handoff) 정보 절벽**: 완료되지 않은 작업이 다음 세션으로 인계될 때, 관측 가능성 부재는 새 세션이 시스템 상태를 처음부터 진단해야 함을 의미합니다. Anthropic의 장시간 실행 에이전트 관찰에 따르면 이러한 중복 진단이 총 세션 시간의 30-50%를 소비할 수 있습니다. - -### 실제 Claude Code 시나리오 - -"계획자-생성자-평가자(planner-generator-evaluator)" 세 가지 역할 워크플로를 사용하는 하네스가 "앱에 다크 모드 추가" 작업을 실행하는 것을 상상해 보세요. - -**관측 가능성 없이**: 계획자가 모호한 설명을 출력합니다. 생성자가 그 모호함을 바탕으로 다크 모드를 구현하지만, 계획자의 암묵적 기대와 맞지 않습니다. 평가자는 자신의 암묵적 기준을 바탕으로 거부하지만, 구체적으로 무엇이 잘못됐는지 명확히 말할 수 없습니다. 생성자는 모호한 거부 이유를 바탕으로 맹목적으로 재시도합니다. 사이클이 3-4번 반복되고, 약 45분이 소요되며, 겨우 수용 가능한 출력이 만들어집니다. - -**완전한 관측 가능성으로**: 계획자가 스프린트 계약을 출력합니다—수정할 컴포넌트, 각각의 검증 기준, 제외 사항(인쇄 스타일 처리 안 함)을 나열합니다. 생성자가 계약에 따라 구현합니다. 런타임 관측 가능성이 각 컴포넌트의 스타일 로딩 및 적용 프로세스를 기록합니다. 평가자가 채점 루브릭을 사용해 차원별로 평가하며, 구체적인 증거를 인용합니다. 한 번의 반복으로 고품질 결과가 나오고, 약 15분이 소요됩니다. - -3배의 효율성 차이. 유일한 변화는 관측 가능성입니다. - -### 에이전트가 이것을 스스로 해결할 수 없는 이유 - -"에이전트가 자체 로그를 출력하면 안 되나요?"라고 생각할 수 있습니다. 문제는 다음과 같습니다. - -1. 에이전트는 자신이 모르는 것을 모릅니다—필요하다고 깨닫지 못하는 신호는 능동적으로 기록하지 않을 것입니다. -2. 로그 형식이 일관되지 않습니다—서로 다른 세션이 서로 다른 로그 형식을 사용하면 체계적인 분석이 불가능합니다. -3. 프로세스 관측 가능성은 로그로 해결할 수 없습니다—스프린트 계약과 채점 루브릭은 하네스 수준의 지원이 필요한 구조화된 산출물입니다. - -## 올바르게 하는 방법 - -### 1. 런타임 신호 수집을 하네스에 구축하라 - -에이전트가 자체 로그를 출력하는 것에 의존하지 마세요. 하네스가 자동으로 이러한 신호를 수집해야 합니다. - -- **애플리케이션 수명 주기**: 시작, 준비, 실행, 종료 단계 상태 -- **기능 경로 실행**: 진입점, 체크포인트, 종료를 포함한 중요 경로 실행 기록 -- **데이터 흐름**: 컴포넌트 간 데이터 흐름 기록 -- **리소스 활용**: 비정상적인 리소스 사용 패턴 (예: 지속적으로 증가하는 메모리) -- **오류 및 예외**: 오류 메시지만이 아닌 전체 오류 컨텍스트 - -### 2. 스프린트 계약을 구현하라 - -각 작업 시작 전에, 생성자와 평가자(동일한 에이전트의 서로 다른 호출일 수 있음)가 계약을 협상합니다. - -```markdown -# 스프린트 계약: 다크 모드 지원 - -## 범위 -- 테마 전환 컴포넌트 수정 -- 전역 CSS 변수 업데이트 -- 다크 모드 테스트 추가 - -## 검증 기준 -- 각 컴포넌트에 대한 시각적 회귀 테스트 통과 -- 주요 흐름 엔드투엔드 테스트 통과 -- 스타일되지 않은 콘텐츠의 플래시(FOUC) 없음 - -## 제외 사항 -- 인쇄 스타일 처리 안 함 -- 써드파티 컴포넌트 다크 모드 처리 안 함 -``` - -### 3. 평가자 루브릭을 수립하라 - -"좋은가 아닌가"를 정량화 가능한 채점으로 전환합니다. - -```markdown -# 채점 루브릭 - -| 차원 | A | B | C | D | -|------|---|---|---|---| -| 코드 정확성 | 모든 테스트 통과 | 주요 흐름 통과 | 부분 통과 | 빌드 실패 | -| 아키텍처 준수 | 완전 준수 | 사소한 편차 | 명백한 편차 | 심각한 위반 | -| 테스트 커버리지 | 주요 + 엣지 케이스 | 주요 흐름만 | 스켈레톤만 | 테스트 없음 | -``` - -### 4. OpenTelemetry로 표준화하라 - -각 하네스 세션에 대한 트레이스(trace)를, 각 작업에 대한 스팬(span)을, 각 검증 단계에 대한 하위 스팬을 생성하세요. 표준 속성을 사용해 주요 정보에 주석을 달 수 있습니다. 이렇게 하면 관측 가능성 데이터가 표준 툴체인(Jaeger, Zipkin)과 통합됩니다. - -## 실제 사례 - -계획자-생성자-평가자 워크플로를 사용하는 하네스가 "다크 모드 지원 추가"를 실행합니다. - -**관측 불가능한 버전**: 3-4번의 맹목적 재시도, 45분, 겨우 수용 가능한 출력. 평가자는 "느낌이 좋지 않습니다"라고 하지만 구체적으로 무엇인지 말할 수 없습니다. 생성자는 잘못된 방향으로 상당한 시간을 낭비합니다. - -**완전히 관측 가능한 버전**: -- 스프린트 계약이 범위, 기준, 제외 사항을 명확히 함 -- 런타임 트레이스가 각 컴포넌트의 스타일 로딩 프로세스를 기록 -- 채점 루브릭이 차원별 구조화된 평가를 제공 -- 한 번의 반복으로 고품질 결과, 15분 - -3배 효율성 향상, 더 안정적인 품질, 재현 가능한 평가. - -## 핵심 정리 - -- **관측 가능성은 하네스 아키텍처의 속성입니다**—나중에 추가되는 기능이 아니라, 설계 중에 고려되어야 하는 핵심 역량입니다. -- **두 관측 가능성 계층이 모두 필수적입니다**—런타임 신호는 "무슨 일이 있었는가"를 설명하고, 프로세스 산출물은 "왜 그렇게 했는가"를 설명합니다. -- **스프린트 계약은 사전 정렬(front-load alignment)을 수행합니다**—"생성자가 구축한 것을 평가자가 예측 가능한 이유로 즉시 거부하는" 상황을 방지합니다. -- **채점 루브릭은 평가를 재현 가능하게 만듭니다**—서로 다른 평가자가 동일한 출력에 대해 유사한 점수를 부여합니다. -- **관측 가능성 부재는 세션 시간의 30-50%를 중복 진단에 낭비합니다.** - -## 더 읽을거리 - -- [Observability Engineering - Charity Majors](https://www.honeycomb.io/blog/observability-engineering-book) — 현대 관측 가능성 엔지니어링의 이론과 실천 프레임워크 -- [Dapper - Google (Sigelman et al.)](https://research.google/pubs/pub36356/) — 대규모 분산 트레이싱의 선구적 실천 -- [Harness Design - Anthropic](https://www.anthropic.com/engineering/harness-design-long-running-apps) — 스프린트 계약과 평가자 루브릭 소개 -- [Site Reliability Engineering - Google](https://sre.google/sre-book/table-of-contents/) — 프로덕션 시스템에서 관측 가능성의 체계적 적용 - -## 연습 문제 - -1. **관측 가능성 격차 분석**: 현재 하네스에서 시스템 계층과 프로세스 계층의 관측 가능성을 감사합니다. 기존 신호로 구별할 수 없는 시스템 상태를 찾아 추가 사항을 제안하세요. - -2. **스프린트 계약 실습**: 실제 작업에 대한 스프린트 계약을 작성하세요. 에이전트가 계약에 따라 실행하도록 하고, 계약 유무에 따른 효율성과 품질을 비교하세요. - -3. **태스크 트레이스 구성**: 완전한 코딩 작업 동안 에이전트 작업의 모든 단계를 기록합니다. OpenTelemetry 시맨틱 컨벤션으로 주석을 달 수 있습니다. 트레이스에서 정보 병목을 분석합니다—어떤 단계가 결정을 위한 충분한 신호 지원이 부족한지 파악하세요. diff --git a/courses/learn-harness-engineering/docs/ko/lectures/lecture-12-why-every-session-must-leave-a-clean-state/code/benchmark-comparison-template.md b/courses/learn-harness-engineering/docs/ko/lectures/lecture-12-why-every-session-must-leave-a-clean-state/code/benchmark-comparison-template.md deleted file mode 100644 index d4b4afec..00000000 --- a/courses/learn-harness-engineering/docs/ko/lectures/lecture-12-why-every-session-must-leave-a-clean-state/code/benchmark-comparison-template.md +++ /dev/null @@ -1,18 +0,0 @@ -# 벤치마크 비교 템플릿 - -하네스 A: - -- 완료율 -- 평균 재시도 횟수 -- 사람 리뷰 전에 발견된 버그 - -하네스 B: - -- 완료율 -- 평균 재시도 횟수 -- 사람 리뷰 전에 발견된 버그 - -해석: - -- 어떤 하네스가 결과를 바꿨는가? -- 어떤 하네스가 결과를 얻는 비용을 바꿨는가? diff --git a/courses/learn-harness-engineering/docs/ko/lectures/lecture-12-why-every-session-must-leave-a-clean-state/code/benchmark-runner.ts b/courses/learn-harness-engineering/docs/ko/lectures/lecture-12-why-every-session-must-leave-a-clean-state/code/benchmark-runner.ts deleted file mode 100644 index 0388cad7..00000000 --- a/courses/learn-harness-engineering/docs/ko/lectures/lecture-12-why-every-session-must-leave-a-clean-state/code/benchmark-runner.ts +++ /dev/null @@ -1,216 +0,0 @@ -/** - * benchmark-runner.ts - * - * Reads a benchmark task definition (JSON array of tasks with pass criteria), - * "executes" each task, records timing and pass/fail, outputs a comparison - * report showing which tasks pass and which fail. - * - * Run: npx tsx docs/lectures/lecture-12-why-every-session-must-leave-a-clean-state/code/benchmark-runner.ts - */ - -// --------------------------------------------------------------------------- -// Types -// --------------------------------------------------------------------------- - -interface BenchmarkTask { - id: string; - name: string; - category: string; - passCriteria: string[]; - expectedDurationMs: number; - // Simulated actual results - actualDurationMs: number; - actualPass: boolean; - failureReason?: string; -} - -interface BenchmarkResult { - id: string; - name: string; - category: string; - criteriaTotal: number; - criteriaPassed: number; - pass: boolean; - expectedMs: number; - actualMs: number; - durationDelta: number; - failureReason?: string; -} - -// --------------------------------------------------------------------------- -// Benchmark task definitions -// --------------------------------------------------------------------------- - -const benchmarkTasks: BenchmarkTask[] = [ - { - id: "bench-001", - name: "Import markdown document", - category: "Document Pipeline", - passCriteria: ["File accepted", "Chunks created", "Metadata stored"], - expectedDurationMs: 100, - actualDurationMs: 95, - actualPass: true, - }, - { - id: "bench-002", - name: "Import PDF document", - category: "Document Pipeline", - passCriteria: ["File accepted", "Text extracted", "Chunks created", "Metadata stored"], - expectedDurationMs: 200, - actualDurationMs: 310, - actualPass: false, - failureReason: "PDF text extraction failed on page 3 -- encoding issue", - }, - { - id: "bench-003", - name: "Ask grounded question", - category: "Q&A Pipeline", - passCriteria: ["Query received", "Relevant chunks retrieved", "Answer generated", "Citations present"], - expectedDurationMs: 500, - actualDurationMs: 480, - actualPass: true, - }, - { - id: "bench-004", - name: "Ask question with no relevant docs", - category: "Q&A Pipeline", - passCriteria: ["Query received", "Graceful 'no results' message", "No hallucinated citations"], - expectedDurationMs: 400, - actualDurationMs: 420, - actualPass: false, - failureReason: "Model hallucinated a citation instead of saying 'no results'", - }, - { - id: "bench-005", - name: "Delete imported document", - category: "Document Pipeline", - passCriteria: ["Document removed from list", "Chunks removed from index", "No orphan data"], - expectedDurationMs: 150, - actualDurationMs: 145, - actualPass: true, - }, - { - id: "bench-006", - name: "Concurrent user access", - category: "Security", - passCriteria: ["Users isolated", "No cross-user data leak", "Performance within SLA"], - expectedDurationMs: 300, - actualDurationMs: 850, - actualPass: false, - failureReason: "Cross-user data leak detected + response time exceeded SLA (850ms > 500ms)", - }, - { - id: "bench-007", - name: "Session continuity after restart", - category: "Reliability", - passCriteria: ["State persisted", "Session recovers", "No data loss"], - expectedDurationMs: 200, - actualDurationMs: 180, - actualPass: true, - }, - { - id: "bench-008", - name: "API rate limiting", - category: "Security", - passCriteria: ["Rate limit enforced", "429 response after limit", "Legitimate traffic unaffected"], - expectedDurationMs: 100, - actualDurationMs: 100, - actualPass: true, - }, -]; - -// --------------------------------------------------------------------------- -// Execution simulation -// --------------------------------------------------------------------------- - -function executeBenchmark(tasks: BenchmarkTask[]): BenchmarkResult[] { - return tasks.map((task) => ({ - id: task.id, - name: task.name, - category: task.category, - criteriaTotal: task.passCriteria.length, - criteriaPassed: task.actualPass ? task.passCriteria.length : Math.floor(task.passCriteria.length * 0.5), - pass: task.actualPass, - expectedMs: task.expectedDurationMs, - actualMs: task.actualDurationMs, - durationDelta: task.actualDurationMs - task.expectedDurationMs, - failureReason: task.failureReason, - })); -} - -// --------------------------------------------------------------------------- -// Reporting -// --------------------------------------------------------------------------- - -function pad(s: string, len: number): string { - return s.length >= len ? s : s + " ".repeat(len - s.length); -} - -function run(): void { - console.log("\n" + "=".repeat(100)); - console.log(" BENCHMARK RUNNER -- Task Execution Report"); - console.log("=".repeat(100)); - - const results = executeBenchmark(benchmarkTasks); - - // Detailed results - const header = `| ${pad("ID", 10)}| ${pad("Task", 35)}| ${pad("Category", 18)}| ${pad("Pass?", 6)}| ${pad("Criteria", 10)}| ${pad("Expected", 10)}| ${pad("Actual", 10)}| ${pad("Delta", 8)}|`; - const sep = `|${"-".repeat(12)}|${"-".repeat(37)}|${"-".repeat(20)}|${"-".repeat(8)}|${"-".repeat(12)}|${"-".repeat(12)}|${"-".repeat(12)}|${"-".repeat(10)}|`; - console.log("\n" + header); - console.log(sep); - - for (const r of results) { - const passLabel = r.pass ? "PASS" : "FAIL"; - const criteriaLabel = `${r.criteriaPassed}/${r.criteriaTotal}`; - const deltaLabel = r.durationDelta >= 0 ? `+${r.durationDelta}ms` : `${r.durationDelta}ms`; - const marker = r.pass ? " " : ">>"; - - console.log( - `${marker}| ${pad(r.id, 10)}| ${pad(r.name, 35)}| ${pad(r.category, 18)}| ${pad(passLabel, 6)}| ${pad(criteriaLabel, 10)}| ${pad(r.expectedMs + "ms", 10)}| ${pad(r.actualMs + "ms", 10)}| ${pad(deltaLabel, 8)}|` - ); - } - - // Failure details - const failures = results.filter((r) => !r.pass); - if (failures.length > 0) { - console.log("\n" + "-".repeat(100)); - console.log(" FAILURE DETAILS"); - console.log("-".repeat(100)); - for (const f of failures) { - console.log(`\n [${f.id}] ${f.name}`); - console.log(` Category: ${f.category}`); - console.log(` Criteria: ${f.criteriaPassed}/${f.criteriaTotal} passed`); - console.log(` Reason: ${f.failureReason ?? "Unknown"}`); - console.log(` Timing: Expected ${f.expectedMs}ms, actual ${f.actualMs}ms (delta: ${f.durationDelta >= 0 ? "+" : ""}${f.durationDelta}ms)`); - } - } - - // Summary - console.log("\n" + "=".repeat(100)); - console.log(" SUMMARY BY CATEGORY"); - console.log("=".repeat(100) + "\n"); - - const categories = [...new Set(results.map((r) => r.category))]; - const catHeader = `| ${pad("Category", 20)}| ${pad("Total", 8)}| ${pad("Passed", 8)}| ${pad("Failed", 8)}| ${pad("Pass Rate", 12)}|`; - const catSep = `|${"-".repeat(22)}|${"-".repeat(10)}|${"-".repeat(10)}|${"-".repeat(10)}|${"-".repeat(14)}|`; - console.log(catHeader); - console.log(catSep); - - for (const cat of categories) { - const catResults = results.filter((r) => r.category === cat); - const catPassed = catResults.filter((r) => r.pass).length; - const catFailed = catResults.filter((r) => !r.pass).length; - const rate = Math.round((catPassed / catResults.length) * 100); - - console.log(`| ${pad(cat, 20)}| ${pad(String(catResults.length), 8)}| ${pad(String(catPassed), 8)}| ${pad(String(catFailed), 8)}| ${pad(rate + "%", 12)}|`); - } - - // Overall - const totalPassed = results.filter((r) => r.pass).length; - const totalFailed = results.filter((r) => !r.pass).length; - console.log("\n" + "-".repeat(100)); - console.log(` OVERALL: ${totalPassed}/${results.length} passed (${Math.round((totalPassed / results.length) * 100)}%), ${totalFailed} failures`); - console.log("=".repeat(100) + "\n"); -} - -run(); diff --git a/courses/learn-harness-engineering/docs/ko/lectures/lecture-12-why-every-session-must-leave-a-clean-state/code/cleanup-loop.md b/courses/learn-harness-engineering/docs/ko/lectures/lecture-12-why-every-session-must-leave-a-clean-state/code/cleanup-loop.md deleted file mode 100644 index 34dee5c2..00000000 --- a/courses/learn-harness-engineering/docs/ko/lectures/lecture-12-why-every-session-must-leave-a-clean-state/code/cleanup-loop.md +++ /dev/null @@ -1,9 +0,0 @@ -# 클린업 루프 예제 - -반복적인 정리 작업: - -- 오래된 문서 스캔 -- 구조적 위반 스캔 -- 품질 등급 업데이트 -- 목표 지향 정리 PR 오픈 -- 정리 후 고정 벤치마크 슬라이스 재실행 diff --git a/courses/learn-harness-engineering/docs/ko/lectures/lecture-12-why-every-session-must-leave-a-clean-state/code/cleanup-scanner.ts b/courses/learn-harness-engineering/docs/ko/lectures/lecture-12-why-every-session-must-leave-a-clean-state/code/cleanup-scanner.ts deleted file mode 100644 index 6b35905c..00000000 --- a/courses/learn-harness-engineering/docs/ko/lectures/lecture-12-why-every-session-must-leave-a-clean-state/code/cleanup-scanner.ts +++ /dev/null @@ -1,336 +0,0 @@ -/** - * cleanup-scanner.ts - * - * Scans a project directory for stale artifacts, dead code, structural - * violations, and outputs a cleanup report. Helps enforce the "clean state - * at end of every session" principle. - * - * Usage: - * npx tsx docs/lectures/lecture-12.../code/cleanup-scanner.ts [path] - * (defaults to current working directory) - * - * Run: npx tsx docs/lectures/lecture-12-why-every-session-must-leave-a-clean-state/code/cleanup-scanner.ts - */ - -import * as fs from "node:fs"; -import * as path from "node:path"; - -// --------------------------------------------------------------------------- -// Types -// --------------------------------------------------------------------------- - -interface ScanResult { - category: string; - check: string; - severity: "critical" | "warning" | "info"; - found: string[]; - description: string; -} - -// --------------------------------------------------------------------------- -// Scanner checks -// --------------------------------------------------------------------------- - -interface ScannerCheck { - category: string; - name: string; - severity: "critical" | "warning" | "info"; - description: string; - scan: (dir: string) => string[]; -} - -function createChecks(): ScannerCheck[] { - return [ - { - category: "Stale Artifacts", - name: "Temporary files (*.tmp, *.bak, *.swp)", - severity: "warning", - description: "Leftover temp files from editing or processing", - scan: (dir) => findFiles(dir, [".tmp", ".bak", ".swp", "~"]), - }, - { - category: "Stale Artifacts", - name: "Debug/log files in source", - severity: "warning", - description: "Log files that should not be in the source tree", - scan: (dir) => { - const found: string[] = []; - const logPatterns = [".log"]; - for (const p of findFiles(dir, logPatterns)) { - // Only flag log files in source directories - if (p.includes("/src/") || p.includes("/lib/") || p.includes("/app/")) { - found.push(p); - } - } - return found; - }, - }, - { - category: "Dead Code", - name: "Unused imports (placeholder detection)", - severity: "info", - description: "Files that may contain unused code (heuristic scan)", - scan: (dir) => { - const found: string[] = []; - const srcDirs = ["src", "lib", "app"]; - for (const srcDir of srcDirs) { - const srcPath = path.join(dir, srcDir); - if (fs.existsSync(srcPath)) { - scanForPatterns(srcPath, ["TODO:", "FIXME:", "HACK:", "XXX:"], found, dir); - } - } - return found; - }, - }, - { - category: "Structural Violations", - name: "Missing .gitignore", - severity: "warning", - description: "No .gitignore file found -- risk of committing build artifacts", - scan: (dir) => fs.existsSync(path.join(dir, ".gitignore")) ? [] : [".gitignore (MISSING)"], - }, - { - category: "Structural Violations", - name: "Build artifacts in source", - severity: "critical", - description: "Compiled output (dist/, build/) mixed with source", - scan: (dir) => { - const found: string[] = []; - // Check if dist/ or build/ exists inside src/ - const dirs = ["src/dist", "src/build", "lib/dist", "lib/build"]; - for (const d of dirs) { - if (fs.existsSync(path.join(dir, d))) { - found.push(d + "/"); - } - } - return found; - }, - }, - { - category: "Structural Violations", - name: "node_modules in source tree", - severity: "critical", - description: "node_modules directory found outside root (nested dependency)", - scan: (dir) => { - const found: string[] = []; - const srcDirs = ["src", "lib", "app", "test", "tests"]; - for (const srcDir of srcDirs) { - const nmPath = path.join(dir, srcDir, "node_modules"); - if (fs.existsSync(nmPath)) { - found.push(srcDir + "/node_modules/"); - } - } - return found; - }, - }, - { - category: "Session Cleanliness", - name: "Uncommitted changes indicator", - severity: "info", - description: "Checks for common artifacts of incomplete sessions", - scan: (dir) => { - const found: string[] = []; - const indicators = [ - "WIP.md", "IN_PROGRESS.md", "scratch.ts", "scratch.js", - "debug.ts", "test-manual.ts", "temp.ts", - ]; - for (const f of indicators) { - if (fs.existsSync(path.join(dir, f))) { - found.push(f); - } - } - return found; - }, - }, - { - category: "Session Cleanliness", - name: "Empty directories", - severity: "info", - description: "Empty directories that may be leftover from incomplete work", - scan: (dir) => { - const found: string[] = []; - const commonDirs = ["src", "lib", "app", "test", "tests", "docs"]; - for (const d of commonDirs) { - const dp = path.join(dir, d); - if (fs.existsSync(dp) && fs.statSync(dp).isDirectory()) { - try { - const contents = fs.readdirSync(dp); - if (contents.length === 0) { - found.push(d + "/"); - } - } catch { - // Skip directories we can't read - } - } - } - return found; - }, - }, - { - category: "Configuration", - name: "Environment files in source", - severity: "critical", - description: ".env files that may contain secrets", - scan: (dir) => { - const found: string[] = []; - const envFiles = [".env", ".env.local", ".env.production", ".env.staging"]; - for (const f of envFiles) { - if (fs.existsSync(path.join(dir, f))) { - found.push(f); - } - } - return found; - }, - }, - ]; -} - -// --------------------------------------------------------------------------- -// Helper functions -// --------------------------------------------------------------------------- - -function findFiles(dir: string, extensions: string[]): string[] { - const found: string[] = []; - - function walk(current: string, depth: number): void { - if (depth > 4) return; // Limit recursion depth - try { - const entries = fs.readdirSync(current, { withFileTypes: true }); - for (const entry of entries) { - // Skip common non-source directories - if (entry.isDirectory() && ["node_modules", ".git", "dist", "build", ".next", "coverage"].includes(entry.name)) { - continue; - } - - const fullPath = path.join(current, entry.name); - const relativePath = path.relative(dir, fullPath); - - if (entry.isDirectory()) { - walk(fullPath, depth + 1); - } else if (entry.isFile()) { - if (extensions.some((ext) => entry.name.endsWith(ext))) { - found.push(relativePath); - } - } - } - } catch { - // Skip directories we can't read - } - } - - walk(dir, 0); - return found; -} - -function scanForPatterns(dir: string, patterns: string[], results: string[], baseDir: string): void { - try { - const entries = fs.readdirSync(dir, { withFileTypes: true }); - for (const entry of entries) { - if (entry.isDirectory()) { - if (!["node_modules", ".git", "dist", "build"].includes(entry.name)) { - scanForPatterns(path.join(dir, entry.name), patterns, results, baseDir); - } - continue; - } - - const fullPath = path.join(dir, entry.name); - if (!entry.name.endsWith(".ts") && !entry.name.endsWith(".tsx") && !entry.name.endsWith(".js")) { - continue; - } - - try { - const content = fs.readFileSync(fullPath, "utf-8"); - const lines = content.split("\n"); - for (let i = 0; i < Math.min(lines.length, 50); i++) { - for (const pattern of patterns) { - if (lines[i].includes(pattern)) { - const relative = path.relative(baseDir, fullPath); - results.push(`${relative}:${i + 1} contains ${pattern}`); - } - } - } - } catch { - // Skip files we can't read - } - } - } catch { - // Skip directories we can't read - } -} - -// --------------------------------------------------------------------------- -// Reporting -// --------------------------------------------------------------------------- - -function pad(s: string, len: number): string { - return s.length >= len ? s : s + " ".repeat(len - s.length); -} - -function run(): void { - const targetDir = process.argv[2] ? path.resolve(process.argv[2]) : process.cwd(); - - console.log("\n" + "=".repeat(90)); - console.log(" CLEANUP SCANNER -- Stale Artifact & Violation Report"); - console.log("=".repeat(90)); - console.log(` Scanning: ${targetDir}\n`); - - const checks = createChecks(); - const results: ScanResult[] = []; - - for (const check of checks) { - const found = check.scan(targetDir); - results.push({ - category: check.category, - check: check.name, - severity: check.severity, - found, - description: check.description, - }); - } - - // Report - const header = `| ${pad("Category", 20)}| ${pad("Check", 40)}| ${pad("Severity", 10)}| ${pad("Count", 6)}| Details`; - const sep = `|${"-".repeat(22)}|${"-".repeat(42)}|${"-".repeat(12)}|${"-".repeat(8)}|${"-".repeat(30)}`; - console.log(header); - console.log(sep); - - for (const r of results) { - const count = r.found.length; - const detail = count > 0 ? r.found.slice(0, 2).join(", ") : "(clean)"; - const marker = count > 0 && r.severity === "critical" ? ">>" : count > 0 ? " * " : " "; - - console.log(`${marker}| ${pad(r.category, 20)}| ${pad(r.check, 40)}| ${pad(r.severity, 10)}| ${pad(String(count), 6)}| ${detail}`); - } - - // Summary - const criticals = results.filter((r) => r.severity === "critical" && r.found.length > 0); - const warnings = results.filter((r) => r.severity === "warning" && r.found.length > 0); - const infos = results.filter((r) => r.severity === "info" && r.found.length > 0); - const clean = results.filter((r) => r.found.length === 0); - - console.log("\n" + "=".repeat(90)); - console.log(" CLEANUP SUMMARY"); - console.log("=".repeat(90) + "\n"); - - console.log(` Critical issues: ${criticals.length}`); - console.log(` Warnings: ${warnings.length}`); - console.log(` Info items: ${infos.length}`); - console.log(` Clean checks: ${clean.length}/${results.length}`); - - if (criticals.length > 0) { - console.log("\n ACTION REQUIRED (Critical):"); - for (const c of criticals) { - console.log(` - ${c.check}: ${c.found.length} item(s) found`); - console.log(` ${c.description}`); - } - } - - const totalIssues = results.reduce((s, r) => s + r.found.length, 0); - if (totalIssues === 0) { - console.log("\n Project is in a clean state. No issues found.\n"); - } else { - console.log(`\n Total issues found: ${totalIssues}. Run this scanner at the end of every session to maintain clean state.\n`); - } -} - -run(); diff --git a/courses/learn-harness-engineering/docs/ko/lectures/lecture-12-why-every-session-must-leave-a-clean-state/code/index.md b/courses/learn-harness-engineering/docs/ko/lectures/lecture-12-why-every-session-must-leave-a-clean-state/code/index.md deleted file mode 100644 index 0dd20be4..00000000 --- a/courses/learn-harness-engineering/docs/ko/lectures/lecture-12-why-every-session-must-leave-a-clean-state/code/index.md +++ /dev/null @@ -1,8 +0,0 @@ -# 강의 12 코드 - -이 폴더는 다음 항목의 예제에 사용합니다. - -- 벤치마크 슬라이스(benchmark slices) -- 정리 작업(cleanup tasks) -- 엔트로피 감소 예제(entropy reduction examples) -- 반복 가능한 하네스 실행(repeatable harness runs) diff --git a/courses/learn-harness-engineering/docs/ko/lectures/lecture-12-why-every-session-must-leave-a-clean-state/index.md b/courses/learn-harness-engineering/docs/ko/lectures/lecture-12-why-every-session-must-leave-a-clean-state/index.md deleted file mode 100644 index ae45c832..00000000 --- a/courses/learn-harness-engineering/docs/ko/lectures/lecture-12-why-every-session-must-leave-a-clean-state/index.md +++ /dev/null @@ -1,186 +0,0 @@ -[English Version →](../../../en/lectures/lecture-12-why-every-session-must-leave-a-clean-state/) - -> 코드 예제: [code/](https://github.com/walkinglabs/learn-harness-engineering/blob/main/docs/en/lectures/lecture-12-why-every-session-must-leave-a-clean-state/code/) -> 실습 프로젝트: [Project 06. 완전한 하네스 (캡스톤)](./../../projects/project-06-runtime-observability-and-debugging/index.md) - -# 강의 12. 모든 세션은 클린 상태(clean state)로 끝나야 한다 - -## 이 강의가 해결하는 문제 - -에이전트가 오후 내내 실행되고, 20개의 파일을 수정하고, 코드를 커밋하고, 세션이 종료됩니다. 다음 에이전트 세션이 시작되고 즉시 발견합니다. 빌드가 깨져 있고, 테스트는 빨간 상태이며, 임시 디버그 파일이 곳곳에 있고, 기능 목록은 업데이트되지 않았으며, 진행 상황이 전혀 불분명합니다. 새 세션은 "지난 세션이 실제로 무엇을 했는가"를 파악하는 데 첫 30분을 씁니다. - -OpenAI와 Anthropic 모두 명확히 밝힙니다. **장기적 신뢰성은 단일 실행의 성공이 아니라 운영 규율에 달려 있습니다.** 세션 종료 시점의 상태 품질이 다음 세션의 효율성을 직접 결정합니다. Git 모범 사례처럼 생각하세요—모든 커밋은 원자적이고 컴파일 가능한 변경이어야 하며, 반쯤 완성된 코드 더미가 되어서는 안 됩니다. - -## 핵심 개념 - -- **클린 상태(Clean state)**: 세션 종료 시 시스템이 다섯 가지 조건을 만족하는 것입니다—빌드 통과, 테스트 통과, 진행 상황 기록됨, 오래된 산출물 없음, 시작 경로 사용 가능. 하나라도 빠지면 세션이 "완료"된 것이 아닙니다. -- **세션 무결성(Session integrity)**: 데이터베이스 트랜잭션과 유사합니다—완전히 커밋하고 클린 상태를 남기거나, 마지막 일관된 상태로 롤백합니다. 중간 지점은 없습니다. -- **품질 문서(Quality document)**: 각 모듈의 품질 등급을 지속적으로 기록하는 활성 산출물입니다. 일회성 평가가 아니라, 코드베이스가 시간이 지남에 따라 강해지는지 약해지는지 보여주는 추적기입니다. -- **클린업 루프(Cleanup loop)**: 코드베이스의 엔트로피(entropy)를 체계적으로 줄이기 위한 정기적인 유지보수 세션입니다. 긴급 수정이 아닌 일상적인 운영입니다. -- **하네스 단순화(Harness simplification)**: 모델 역량이 향상됨에 따라, 더 이상 필요하지 않은 하네스 구성 요소를 주기적으로 제거합니다. 오늘 필수적인 제약이 세 달 후에는 불필요한 오버헤드가 될 수 있습니다. -- **멱등 정리(Idempotent cleanup)**: 정리 작업이 몇 번 실행되든 동일한 결과를 생성합니다. 실패-재시도 시나리오에서도 정리가 안전하게 유지됨을 보장합니다. - -## 클린 상태의 다섯 가지 차원 - -```mermaid -flowchart LR - Work["기능 작업 완료"] --> Build{"빌드 통과?"} - Build -->|yes| Test{"테스트 통과?"} - Build -->|no| Fix["종료 전에 수정"] - Test -->|yes| Record["기능 목록 + 진행 상황 업데이트"] - Test -->|no| Fix - Record --> Cleanup["임시 산출물 / 디버그 코드 제거"] - Cleanup --> Startup{"표준 시작 경로 작동?"} - Startup -->|yes| Clean["클린 핸드오프"] - Startup -->|no| Fix - Fix --> Build -``` - -```mermaid -flowchart LR - Dirty["세션이 종료됨
빨간 테스트 / 임시 파일 / 진행 상황 미업데이트"] --> Diagnose["다음 세션이 먼저
무슨 일이 있었는지 파악해야 함"] - Diagnose --> Fragile["새 작업이 지저분한 저장소에서 시작됨"] - Fragile --> More["더 많은 디버그 파일, 더 많은 깨진 검사,
더 불분명한 진행 상황"] - More --> Dirty - - Clean["세션이 종료됨
초록 테스트 / 업데이트된 진행 상황 / 임시 파일 제거"] --> Fast["다음 세션이 즉시 코딩을 시작할 수 있음"] - Fast --> Stable["먼저 저장소를 구조할 필요 없음"] - Stable --> Clean -``` - -## 왜 이런 일이 발생하는가 - -### 엔트로피 증가가 기본 상태이다 - -소프트웨어 진화의 Lehman 법칙에 따르면 지속적으로 변경되는 시스템은 능동적으로 관리되지 않으면 복잡성이 불가피하게 증가합니다. 이것은 AI 코딩 에이전트에게 특히 사실입니다—매 세션마다 변경 사항을 도입하고, 종료 시 정리하지 않으면 기술 부채가 기하급수적으로 쌓입니다. - -실제 데이터가 이를 말해줍니다. 정리 전략 없이 12주간 에이전트로 개발된 프로젝트: - -- 1주차: 빌드 통과율 100%, 테스트 통과율 100%, 새 세션 시작 5분 -- 4주차: 빌드 95%, 테스트 92%, 시작 15분 -- 8주차: 빌드 82%, 테스트 78%, 시작 35분 -- 12주차: 빌드 68%, 테스트 61%, 시작 60분+ - -정리 전략이 있는 동일한 프로젝트: - -- 1주차: 100%, 100%, 5분 -- 12주차: 97%, 95%, 9분 - -12주 후: 빌드 통과율 29퍼센트포인트 차이, 새 세션 시작 시간 85% 차이. 이것은 이론이 아닙니다—관찰된 차이입니다. - -### 클린 상태의 다섯 가지 차원 - -클린 상태는 단순히 "코드가 컴파일된다"는 것이 아닙니다. 함께 평가되는 다섯 가지 차원입니다. - -**빌드 차원**: 코드가 오류 없이 빌드되는가? 이것이 가장 기본입니다—다음 세션이 빌드 오류를 먼저 수정해야 하는 상황은 없어야 합니다. - -**테스트 차원**: 모든 테스트가 통과하는가? 세션 전에 존재했던 테스트도 포함해서입니다—세션은 기존 기능을 깨뜨리지 않을 책임이 있습니다. 그리고 "내 기계에서는 작동해"가 아니라 CI에서 검증되어야 합니다. - -**진행 상황 차원**: 현재 진행 상황이 기계가 읽을 수 있는 산출물에 기록되어 있는가? 통과 기준이 있는 완료된 하위 작업, 현재 상태가 있는 진행 중이지만 완료되지 않은 하위 작업, 아직 시작되지 않은 하위 작업. 좋은 진행 상황 기록은 세션 시작 진단 시간의 60-80%를 줄입니다. - -**산출물 차원**: 오래되거나 모호한 임시 산출물이 있는가? 디버그 로그, 임시 파일, 주석 처리된 코드, TODO 마커—이 모두가 다음 세션의 인지 부하를 높입니다. - -**시작 차원**: 표준 시작 경로를 사용할 수 있는가? 다음 세션이 수동 개입 없이 작업을 시작할 수 있는가? 환경 초기화, 코드베이스 로딩, 컨텍스트 획득, 작업 선택—이 경로들이 깨져서는 안 됩니다. - -### "나중에 정리"는 절대 정리하지 않는다는 의미다 - -가장 흔한 정신적 함정은 "이번 세션에는 정리할 시간이 없으니 다음에 하겠다"입니다. 그러나 다음 에이전트 세션은 당신이 남겨 놓은 것을 모릅니다—코드 더미와 불확실한 상태를 봅니다. "이 코드의 어떤 부분이 의도적이고 어떤 부분이 임시적인가"를 추론하는 데 상당한 시간을 씁니다. - -더 나쁜 것은, 모든 세션에는 자체 작업 목표가 있습니다. 새 세션은 이전 세션의 혼란을 정리하는 것이 아니라 새 작업을 하러 왔습니다. 혼란을 무시하고 그 위에 새 작업을 시작하여, 혼란 위에 더 많은 혼란을 도입합니다. 이것이 엔트로피의 양의 피드백 루프입니다. - -## 올바르게 하는 방법 - -### 1. 클린 상태를 완료 요건으로 정의하라 - -하네스에서 명시적으로 정의하세요. **세션 완료 = 작업이 검증을 통과함 AND 클린 상태 검사를 통과함.** 둘 중 하나라도 빠지면 세션이 완료된 것이 아닙니다. CLAUDE.md에 작성하세요. - -``` -## 세션 종료 체크리스트 -- [ ] 빌드 통과 (npm run build) -- [ ] 모든 테스트 통과 (npm test) -- [ ] 기능 목록 업데이트됨 -- [ ] 디버그 코드 없음 (console.log, debugger, TODO) -- [ ] 표준 시작 경로 사용 가능 (npm run dev) -``` - -### 2. 이중 모드 정리 전략을 결합하라 - -두 가지 정리 모드를 결합합니다. - -**즉각적 정리 (모든 세션 종료 시)**: 세션 중에 생성된 임시 산출물을 정리하고, 기능 목록 상태를 업데이트하고, 빌드와 테스트가 통과하는지 확인합니다. 이것은 "참조 카운팅" 정리입니다. - -**주기적 정리 (주간)**: 전체 시스템 스캔—누적된 구조적 문제를 처리하고, 품질 문서를 업데이트하고, 드리프트를 탐지하기 위해 벤치마크 테스트를 실행합니다. 이것은 "추적" 정리입니다. - -### 3. 품질 문서를 유지하라 - -품질 문서는 각 모듈을 지속적으로 채점하는 활성 산출물입니다. - -```markdown -# 품질 문서 - -## 사용자 인증 모듈 (품질: A) -- 검증 통과: 예 -- 에이전트 이해 가능: 예 -- 테스트 안정성: 안정 -- 아키텍처 경계: 준수 -- 코드 컨벤션: 따름 - -## 결제 모듈 (품질: C) -- 검증 통과: 부분 (결제 콜백 미테스트) -- 에이전트 이해 가능: 어려움 (로직이 3개 파일에 분산) -- 테스트 안정성: 불안정 (2개의 불안정 테스트) -- 아키텍처 경계: 위반 존재 -- 코드 컨벤션: 부분적으로 따름 -``` - -새 세션은 이 문서를 읽고 어디에 우선순위를 둘지 즉시 알 수 있습니다. 가장 낮은 점수의 모듈을 먼저 수정합니다. - -### 4. 주기적으로 하네스를 단순화하라 - -Anthropic의 중요한 통찰: **모든 하네스 구성 요소는 모델이 스스로 무언가를 안정적으로 할 수 없기 때문에 존재합니다. 그러나 모델이 향상됨에 따라 이러한 가정은 구식이 됩니다.** 세 달 전에 필수적인 제약이 오늘은 불필요한 오버헤드일 수 있습니다. - -권장 실천: 매달 하나의 하네스 구성 요소를 선택하고, 일시적으로 비활성화하고, 벤치마크 작업을 실행합니다. 결과가 저하되지 않으면 영구적으로 제거합니다. 저하되면 복원하거나 더 가벼운 대안으로 교체합니다. - -### 5. 정리 작업은 멱등적이어야 한다 - -정리 스크립트는 반복 실행에도 안전해야 합니다. - -```bash -# 멱등 정리 작업 -rm -f /tmp/debug-*.log # -f는 파일이 없을 때 오류 없음을 보장 -git checkout -- .env.local # 알려진 상태로 복원 -npm run test # 정리가 아무것도 깨뜨리지 않았는지 확인 -``` - -## 실제 사례 - -12주 동안 에이전트로 개발된 Electron 앱으로 두 가지 접근 방식을 비교합니다. - -**정리 전략 없이** (대조군): 12주차, 빌드 통과율 68%, 테스트 통과율 61%, 새 세션 시작 60분+, 오래된 산출물 103개. - -**정리 전략으로** (실험군): 모든 세션 종료 시 완전한 클린 상태 검사 + 주간 클린업 루프. 12주차, 빌드 통과율 97%, 테스트 통과율 95%, 새 세션 시작 9분, 오래된 산출물 11개. - -12주차에 실험군의 빌드 통과율은 29퍼센트포인트 높고, 테스트 통과율은 34포인트 높으며, 새 세션 시작 시간은 85% 낮습니다. - -## 핵심 정리 - -- **클린 상태는 세션 완료의 필요 조건입니다**—선택적 정리 작업이 아니라, "완료의 정의"의 일부입니다. -- **다섯 가지 차원 모두 필요합니다**—빌드, 테스트, 진행 상황, 산출물, 시작—각각 명시적으로 검사되어야 합니다. -- **품질 문서는 코드베이스 건강을 추적 가능하게 만듭니다**—저하되고 있다는 것을 알아야만 수정할 수 있습니다. -- **주기적으로 하네스를 단순화하세요**—모델 역량이 향상됨에 따라, 더 이상 필요 없는 제약을 제거하세요. -- **"나중에 정리"는 절대 정리하지 않는다는 의미입니다**—엔트로피 증가가 기본 상태이며, 능동적인 정리만이 이를 막을 수 있습니다. - -## 더 읽을거리 - -- [Clean Code - Robert C. Martin](https://www.goodreads.com/book/show/3735293-clean-code) — 코드 청결성의 체계적 원칙 -- [Harness Engineering - OpenAI](https://openai.com/index/harness-engineering/) — 핵심 하네스 설계 요건으로서의 재현성 -- [Effective Harnesses - Anthropic](https://www.anthropic.com/engineering/effective-harnesses-for-long-running-agents) — 장기적 신뢰성을 위한 클린 세션 종료의 중요한 역할 -- [Programs, Life Cycles, and Laws of Software Evolution - Lehman](https://ieeexplore.ieee.org/document/1702314) — 능동적 유지보수 없이는 시스템 복잡성이 불가피하게 증가함을 증명하는 소프트웨어 진화 법칙 - -## 연습 문제 - -1. **클린 상태 체크리스트**: 다섯 가지 차원을 모두 포함하는 코드베이스의 세션 종료 체크리스트를 설계하세요. 연속 5개 세션에 적용하고 차원별 위반 사항을 기록하세요. - -2. **벤치마크 비교**: 두 가지 하네스 변형(클린 상태 요건 있음/없음)으로 고정된 작업 세트를 사용하세요. 완료율, 재시도 횟수, 결함 탈출률을 비교하세요. - -3. **하네스 단순화 실습**: 하나의 하네스 구성 요소를 선택하고, 일시적으로 비활성화하고, 벤치마크 작업을 실행하세요. 있을 때와 없을 때의 결과를 비교하세요. 유지할지, 제거할지, 교체할지 결정하세요. diff --git a/courses/learn-harness-engineering/docs/ko/projects/index.md b/courses/learn-harness-engineering/docs/ko/projects/index.md deleted file mode 100644 index 11a17673..00000000 --- a/courses/learn-harness-engineering/docs/ko/projects/index.md +++ /dev/null @@ -1,24 +0,0 @@ -# 프로젝트에 오신 것을 환영합니다 - -이곳은 Learn Harness Engineering의 실습(hands-on) 섹션입니다. 강의를 읽는 것만으로는 충분하지 않습니다. 직접 환경을 구축하고, 서로 다른 규칙 아래에서 Codex, Claude Code 또는 다른 AI 에이전트(agent)가 어떻게 동작하는지 관찰해야 합니다. - -## 프로젝트 개요 - -이 강의는 신뢰할 수 있는 에이전트 작업 환경을 처음부터 구축하는 방법을 가르치는, 단계적으로 심화되는 6개의 실습 프로젝트로 구성되어 있습니다. - -1. **프롬프트 단독 vs. 규칙 우선(Rules-First)**: 프롬프트만 사용하는 경우와 기초 하네스(harness)를 갖춘 경우의 에이전트 성능을 비교합니다. -2. **에이전트 가독성 있는 작업 공간**: 저장소를 AI 친화적으로 구조화하는 방법과 핸드오프(handoff) 메커니즘을 확립하는 방법을 학습합니다. -3. **멀티 세션 연속성(Multi-Session Continuity)**: 에이전트가 세션 간에 작업을 원활하게 재개할 수 있도록 상태(state) 파일과 초기화(initialization) 스크립트를 설계합니다. -4. **런타임 피드백과 범위 제어**: 에이전트가 자신의 코드를 테스트하고 실행 중 오류를 수정할 수 있는 도구를 도입합니다. -5. **자기 검증과 역할 분리**: 환각(hallucination)과 섣부른 완료 선언을 방지하기 위한 독립적인 리뷰 메커니즘을 구축합니다. -6. **완전한 하네스(캡스톤)**: 관찰 가능하고(observable) 완성된 엔드-투-엔드 에이전트 작업 환경을 조립합니다. - -## 진행 방법 - -각 프로젝트 폴더에는 일반적으로 다음이 포함됩니다. - -- `starter/`: 시작 작업 공간입니다. -- `solution/`: 참조 구현체(막히면 참고하세요)입니다. -- 배경 지식과 구체적인 목표를 설명하는 작업 지침입니다. - -`starter/` 디렉터리 안에서 원하는 AI 코딩 에이전트(예: Claude Code, Cursor, Trae)를 사용하여 작업을 완료하시기 바랍니다. diff --git a/courses/learn-harness-engineering/docs/ko/projects/project-01-baseline-vs-minimal-harness/index.md b/courses/learn-harness-engineering/docs/ko/projects/project-01-baseline-vs-minimal-harness/index.md deleted file mode 100644 index d1123c26..00000000 --- a/courses/learn-harness-engineering/docs/ko/projects/project-01-baseline-vs-minimal-harness/index.md +++ /dev/null @@ -1,27 +0,0 @@ -[English Version →](../../../en/projects/project-01-baseline-vs-minimal-harness/) - -> 관련 강의: [강의 01. 강력한 모델이 곧 신뢰할 수 있는 실행을 의미하지는 않습니다](./../../lectures/lecture-01-why-capable-agents-still-fail/index.md) · [강의 02. 하네스가 실제로 의미하는 것](./../../lectures/lecture-02-what-a-harness-actually-is/index.md) -> 템플릿 파일: [templates/](https://github.com/walkinglabs/learn-harness-engineering/blob/main/docs/en/resources/templates/) - -# 프로젝트 01. 프롬프트 단독 vs. 규칙 우선(Rules-First): 차이는 얼마나 클까 - -## 해야 할 일 - -최소한의 Electron 지식 베이스 앱 쉘(shell)을 구축합니다. 왼쪽에 문서 목록이 있고, 오른쪽에 Q&A 패널이 있으며, 로컬 데이터 디렉터리가 포함된 창입니다. 작업 자체는 복잡하지 않습니다. 복잡한 것은 에이전트(agent)가 이 작업을 완료하도록 만드는 방법입니다. - -두 번 실행합니다. 첫 번째: 프롬프트만 사용하고 사전 준비 없이 진행합니다. 두 번째: `AGENTS.md`, `init.sh`, `feature_list.json`을 저장소에 미리 배치한 상태로 진행합니다. 그런 다음 비교합니다. - -이 프로젝트의 핵심은 코드를 작성하는 것이 아닙니다. "규칙을 먼저 준비하는 데 15분을 쓰는 것"과 "에이전트를 그냥 실행시키는 것" 사이의 격차가 얼마나 큰지 파악하는 것입니다. - -에이전트에게 규칙(rule)과 초기화(initialization) 단서를 제공하면 작업 범위를 스스로 파악하고 불필요한 탐색을 줄일 수 있습니다. 이처럼 최소한의 하네스만으로도 에이전트의 출발점과 결과 품질이 크게 달라집니다. - -## 도구 - -- Claude Code 또는 Codex (하나를 선택하여 두 번의 실행 모두 동일하게 사용) -- Git (브랜치 관리 및 비교) -- Node.js + Electron (프로젝트 스택) -- 타이머 (각 실행의 소요 시간 기록) - -## 하네스 메커니즘 - -최소 하네스(minimal harness): `AGENTS.md` + `init.sh` + `feature_list.json` diff --git a/courses/learn-harness-engineering/docs/ko/projects/project-02-agent-readable-workspace/index.md b/courses/learn-harness-engineering/docs/ko/projects/project-02-agent-readable-workspace/index.md deleted file mode 100644 index c34126a0..00000000 --- a/courses/learn-harness-engineering/docs/ko/projects/project-02-agent-readable-workspace/index.md +++ /dev/null @@ -1,24 +0,0 @@ -[English Version →](../../../en/projects/project-02-agent-readable-workspace/) - -> 관련 강의: [강의 03. 저장소를 단일 진실 원천으로 만들기](./../../lectures/lecture-03-why-the-repository-must-become-the-system-of-record/index.md) · [강의 04. 지침을 여러 파일로 분리하기](./../../lectures/lecture-04-why-one-giant-instruction-file-fails/index.md) -> 템플릿 파일: [templates/](https://github.com/walkinglabs/learn-harness-engineering/blob/main/docs/en/resources/templates/) - -# 프로젝트 02. 프로젝트를 읽기 쉽게 만들고 중단된 곳에서 재개하기 - -## 해야 할 일 - -새로운 에이전트(agent)가 프로젝트 구조를 빠르게 파악하고, 현재 진행 상황을 이해하며, 작업을 이어받을 수 있도록 저장소에 "가독성(readability)"을 추가합니다. 구체적으로는 문서 임포트(import), 문서 상세 보기, 로컬 퍼시스턴스(persistence)를 구현하며, 두 세션에 걸쳐 완성합니다. - -두 번 실행합니다. 첫 번째는 아무런 도움 없이, 두 번째는 `ARCHITECTURE.md`, `PRODUCT.md`, `session-handoff.md`를 저장소에 미리 배치한 상태로 진행합니다. - -에이전트는 컨텍스트 윈도(context window)가 초기화되면 이전 작업 흐름을 기억하지 못합니다. 세션 핸드오프(session handoff) 파일을 통해 이전 세션의 상태(state)를 영속화하면, 새 세션의 에이전트도 동일한 진행 지점에서 출발할 수 있습니다. - -## 도구 - -- Claude Code 또는 Codex -- Git -- Node.js + Electron - -## 하네스 메커니즘 - -에이전트 가독성 있는 작업 공간 + 영속 상태 파일(persistent state files) diff --git a/courses/learn-harness-engineering/docs/ko/projects/project-03-multi-session-continuity/index.md b/courses/learn-harness-engineering/docs/ko/projects/project-03-multi-session-continuity/index.md deleted file mode 100644 index 03644741..00000000 --- a/courses/learn-harness-engineering/docs/ko/projects/project-03-multi-session-continuity/index.md +++ /dev/null @@ -1,24 +0,0 @@ -[English Version →](../../../en/projects/project-03-multi-session-continuity/) - -> 관련 강의: [강의 05. 세션 간에 컨텍스트를 유지하기](./../../lectures/lecture-05-why-long-running-tasks-lose-continuity/index.md) · [강의 06. 모든 에이전트 세션 전에 초기화하기](./../../lectures/lecture-06-why-initialization-needs-its-own-phase/index.md) -> 템플릿 파일: [templates/](https://github.com/walkinglabs/learn-harness-engineering/blob/main/docs/en/resources/templates/) - -# 프로젝트 03. 세션 재시작 후에도 에이전트가 계속 작업하도록 만들기 - -## 해야 할 일 - -에이전트(agent)에 범위 제어(scope control)와 검증 게이트(verification gates)를 추가합니다. 문서 청킹(chunking), 메타데이터 추출, 인덱싱 진행 상태 표시, 인용 기반 Q&A 흐름을 구현합니다. `feature_list.json`을 사용하여 기능 상태를 추적합니다. 한 번에 하나의 기능만 처리하고, 검증 증거 없이는 "완료(pass)"로 표시할 수 없습니다. - -장시간 실행되는 작업은 컨텍스트(context) 초기화나 중단으로 인해 연속성(continuity)을 잃기 쉽습니다. 진행 로그(progress log)와 세션 핸드오프를 결합하면 에이전트가 이전에 완료한 항목을 재확인하지 않고 정확한 다음 단계에서 재개할 수 있습니다. - -두 번 실행합니다. 첫 번째는 제약 없이, 두 번째는 엄격한 적용 방식으로 진행합니다. - -## 도구 - -- Claude Code 또는 Codex -- Git -- Node.js + Electron - -## 하네스 메커니즘 - -진행 로그 + 세션 핸드오프 + 멀티 세션 연속성(multi-session continuity) diff --git a/courses/learn-harness-engineering/docs/ko/projects/project-04-incremental-indexing/index.md b/courses/learn-harness-engineering/docs/ko/projects/project-04-incremental-indexing/index.md deleted file mode 100644 index 10917b37..00000000 --- a/courses/learn-harness-engineering/docs/ko/projects/project-04-incremental-indexing/index.md +++ /dev/null @@ -1,24 +0,0 @@ -[English Version →](../../../en/projects/project-04-incremental-indexing/) - -> 관련 강의: [강의 07. 에이전트를 위한 명확한 작업 경계 설정하기](./../../lectures/lecture-07-why-agents-overreach-and-under-finish/index.md) · [강의 08. 기능 목록을 사용하여 에이전트가 하는 일을 제한하기](./../../lectures/lecture-08-why-feature-lists-are-harness-primitives/index.md) -> 템플릿 파일: [templates/](https://github.com/walkinglabs/learn-harness-engineering/blob/main/docs/en/resources/templates/) - -# 프로젝트 04. 런타임 피드백을 사용하여 에이전트 동작 수정하기 - -## 해야 할 일 - -런타임 관찰 가능성(runtime observability, 시작 로그, 임포트/인덱싱 로그, 오류 상태)을 추가하고, 레이어 간 위반(cross-layer violation)을 방지하기 위한 아키텍처 제약(architecture constraints)을 도입합니다. 에이전트가 수정할 수 있도록 런타임 버그를 심어 둡니다. - -에이전트는 로그(log)나 오류 출력이 없으면 내부 오류를 인식하기 어렵고, 존재하지 않는 기능도 "구현되었다"고 잘못 판단할 수 있습니다. 런타임 피드백 루프를 갖추면 에이전트가 실행 결과를 직접 관찰하고 동작을 즉시 수정할 수 있습니다. - -두 번 실행합니다. 첫 번째는 로그나 제약 없이, 두 번째는 적절한 도구와 규칙(rules)을 갖춘 상태로 진행합니다. - -## 도구 - -- Claude Code 또는 Codex -- Git -- Node.js + Electron - -## 하네스 메커니즘 - -런타임 피드백 + 범위 제어 + 증분 인덱싱(incremental indexing) diff --git a/courses/learn-harness-engineering/docs/ko/projects/project-05-grounded-qa-verification/index.md b/courses/learn-harness-engineering/docs/ko/projects/project-05-grounded-qa-verification/index.md deleted file mode 100644 index 1e44149d..00000000 --- a/courses/learn-harness-engineering/docs/ko/projects/project-05-grounded-qa-verification/index.md +++ /dev/null @@ -1,24 +0,0 @@ -[English Version →](../../../en/projects/project-05-grounded-qa-verification/) - -> 관련 강의: [강의 09. 에이전트가 섣불리 완료를 선언하지 않도록 막기](./../../lectures/lecture-09-why-agents-declare-victory-too-early/index.md) · [강의 10. 완전한 파이프라인 실행만이 진정한 검증으로 인정됩니다](./../../lectures/lecture-10-why-end-to-end-testing-changes-results/index.md) -> 템플릿 파일: [templates/](https://github.com/walkinglabs/learn-harness-engineering/blob/main/docs/en/resources/templates/) - -# 프로젝트 05. 에이전트가 자신의 작업을 검증하도록 만들기 - -## 해야 할 일 - -역할 분리(role separation)를 구현합니다. 구현을 담당하는 생성자(generator), 검토를 담당하는 평가자(evaluator), 그리고 선택적으로 플래너(planner)를 만듭니다. 각 역할이 추가될 때마다 효과를 측정하기 위해 세 번 실행합니다. - -에이전트가 구현과 검증을 동시에 담당하면 자신의 오류를 스스로 발견하기 어렵습니다. 독립된 평가자 역할을 분리하면 환각(hallucination)과 섣부른 완료 선언을 방지하고, 근거 기반(grounded) Q&A의 정확도를 높일 수 있습니다. - -실질적인 기능 업그레이드(멀티턴 대화, 인용 패널 재설계, 또는 문서 필터링)를 선택하고 모든 실행에서 동일하게 유지합니다. - -## 도구 - -- Claude Code 또는 Codex -- Git -- Node.js + Electron - -## 하네스 메커니즘 - -자기 검증(self-verification) + 근거 기반 Q&A + 증거 기반 완료(evidence-based completion) diff --git a/courses/learn-harness-engineering/docs/ko/projects/project-06-runtime-observability-and-debugging/index.md b/courses/learn-harness-engineering/docs/ko/projects/project-06-runtime-observability-and-debugging/index.md deleted file mode 100644 index d4ef1749..00000000 --- a/courses/learn-harness-engineering/docs/ko/projects/project-06-runtime-observability-and-debugging/index.md +++ /dev/null @@ -1,27 +0,0 @@ -[English Version →](../../../en/projects/project-06-runtime-observability-and-debugging/) - -> 관련 강의: [강의 11. 에이전트의 런타임을 관찰 가능하게 만들기](./../../lectures/lecture-11-why-observability-belongs-inside-the-harness/index.md) · [강의 12. 모든 세션의 끝에서 깔끔하게 핸드오프하기](./../../lectures/lecture-12-why-every-session-must-leave-a-clean-state/index.md) -> 템플릿 파일: [templates/](https://github.com/walkinglabs/learn-harness-engineering/blob/main/docs/en/resources/templates/) - -# 프로젝트 06. 완전한 에이전트 하네스 구축하기 (캡스톤) - -## 해야 할 일 - -이 프로젝트는 캡스톤(capstone) 프로젝트입니다. 앞선 다섯 개의 프로젝트에서 학습한 모든 것을 조립하고, 전체 벤치마크(benchmark)를 실행한 다음, 품질이 유지 가능한지 확인하기 위해 정리 과정(cleanup pass)을 수행합니다. - -완전한 제품 슬라이스(product slice)를 포괄하는 고정된 멀티 기능 작업 세트를 사용합니다. 문서 임포트, 인덱싱, 인용 기반 Q&A, 런타임 관찰 가능성, 그리고 가독성 있고 재시작 가능한 저장소 상태가 포함됩니다. 먼저 약한 하네스 베이스라인으로 실행하고, 이어서 가장 강력한 하네스로 실행한 다음, 정리 후 재실행합니다. 마지막으로 하네스 절제 실험(harness ablation experiment)을 수행합니다. 구성 요소를 하나씩 제거하면서 실제로 어떤 요소가 중요한지 확인합니다. - -앞선 프로젝트들에서 개별적으로 효과를 확인한 메커니즘들이 함께 작동할 때 어떤 시너지를 만들어 내는지, 그리고 어떤 구성 요소를 제거했을 때 품질이 가장 크게 저하되는지 파악하는 것이 이 캡스톤의 핵심입니다. - -## 도구 - -- Claude Code 또는 Codex -- Git -- Node.js + Electron -- 품질 문서 템플릿(quality document template) -- 평가자 루브릭(evaluator rubric) -- 앞선 다섯 개의 프로젝트에서 축적된 모든 하네스 구성 요소 - -## 하네스 메커니즘 - -완전한 하네스: 모든 메커니즘 + 관찰 가능성 + 절제 연구(ablation study) diff --git a/courses/learn-harness-engineering/docs/ko/resources/index.md b/courses/learn-harness-engineering/docs/ko/resources/index.md deleted file mode 100644 index e3642e00..00000000 --- a/courses/learn-harness-engineering/docs/ko/resources/index.md +++ /dev/null @@ -1,50 +0,0 @@ -# 한국어 리소스 라이브러리 (Resource Library) - -이 폴더는 강의에서 소개한 메서드(method)를 실제 저장소에 바로 복사해 사용할 수 있는 템플릿(template)과 간결한 참고 자료로 구체화한 것입니다. 에이전트(agent)가 여러 세션에 걸쳐 일관성 있게 작동하려면 별도의 설정·상태·범위를 매번 재도출하는 대신, 이 자료들을 기반으로 삼아야 합니다. - -## 언제 사용하는가 - -Codex, Claude Code 또는 다른 코딩 에이전트가 여러 세션에 걸쳐 작업할 때 시작점으로 활용하십시오. - -특히 다음 상황에서 유용합니다. - -- 작업이 여러 세션에 걸쳐 진행될 때 -- 기능(feature)이 많아 반쯤 완성된 채 방치되기 쉬울 때 -- 에이전트가 지나치게 일찍 완료를 선언하는 경향이 있을 때 -- 시작 단계를 매번 새로 발견해야 할 때 - -## 시작하기 - -최소 설정을 위해 먼저 다음 파일부터 시작하십시오. - -- 루트 지침: [`templates/AGENTS.md`](./templates/AGENTS.md) 또는 [`templates/CLAUDE.md`](./templates/CLAUDE.md) -- 기능 상태: [`templates/feature_list.json`](./templates/feature_list.json) -- 진행 로그: [`templates/claude-progress.md`](./templates/claude-progress.md) -- 부트스트랩(bootstrap) 스크립트 참고: `docs/ko/resources/templates/init.sh` - -그다음 다음 파일을 추가하십시오. - -- 세션 핸드오프(session handoff): [`templates/session-handoff.md`](./templates/session-handoff.md) -- 클린 상태(clean state) 체크리스트: [`templates/clean-state-checklist.md`](./templates/clean-state-checklist.md) -- 평가자(evaluator) 루브릭(rubric): [`templates/evaluator-rubric.md`](./templates/evaluator-rubric.md) - -"Harness engineering" 포스트에 나오는 OpenAI 스타일의 완전한 저장소 구조를 원한다면 고급 팩을 사용하십시오. - -- [`openai-advanced/index.md`](./openai-advanced/index.md) - -## 라이브러리 구조 - -- [`templates/`](./templates/index.md): 실제 저장소에 복사할 수 있는 템플릿 -- [`reference/`](./reference/index.md): 메서드 노트, 시작 흐름, 실패 유형 맵 -- [`openai-advanced/`](./openai-advanced/index.md): 고급 저장소 스켈레톤(skeleton), 시스템 오브 레코드(SoR) 문서, 에이전트 우선 거버넌스 템플릿 - -## 권장 최소 팩 - -- `AGENTS.md` 또는 `CLAUDE.md` -- `feature_list.json` -- `claude-progress.md` -- `init.sh` - -이 네 파일만으로도 대부분의 에이전트 워크플로우를 눈에 띄게 안정화할 수 있습니다. - -저장소가 여러 도메인, 활성 계획(plan), 품질 점수, 안정성 정책을 갖춘 장기 시스템으로 발전하면, 최소 팩을 무리하게 늘리는 대신 [`openai-advanced/`](./openai-advanced/index.md) 팩으로 전환하십시오. diff --git a/courses/learn-harness-engineering/docs/ko/resources/openai-advanced/index.md b/courses/learn-harness-engineering/docs/ko/resources/openai-advanced/index.md deleted file mode 100644 index 698f30a5..00000000 --- a/courses/learn-harness-engineering/docs/ko/resources/openai-advanced/index.md +++ /dev/null @@ -1,73 +0,0 @@ -# OpenAI 고급 팩(Advanced Pack) - -이 폴더는 OpenAI의 "Harness engineering: leveraging Codex in an agent-first world" 문서에서 설명하는 보다 견고한 저장소(repository) 구조를 복사해서 바로 사용할 수 있는 스타터 파일로 패키징한 것입니다. - -기본적인 하네스(harness)가 더 이상 충분하지 않고 다음과 같은 것들이 필요할 때 이 팩을 사용하십시오. - -- 라우팅 스타일의 간결한 `AGENTS.md` -- 저장소 내 지속형 시스템 오브 레코드(system-of-record) 문서 -- 활성 및 완료된 실행 계획(execution plan) -- 명시적인 제품, 신뢰성(reliability), 보안(security), 프론트엔드 정책 파일 -- 제품 도메인과 아키텍처 계층별 품질 점수 관리 -- 에이전트(agent) 친화적 참고 자료 폴더 -- 아키텍처, 지식 포착(knowledge capture), 런타임 검증을 위한 표준 작업 절차(SOP) - -## 포함된 스타터 레이아웃 - -[`repo-template/`](./repo-template/index.md) 아래에 있는 스타터 팩은 아래와 같은 구조를 반영합니다. - -```text -AGENTS.md -ARCHITECTURE.md -docs/ -├── design-docs/ -│ ├── index.md -│ └── core-beliefs.md -├── exec-plans/ -│ ├── active/ -│ ├── completed/ -│ └── tech-debt-tracker.md -├── generated/ -│ └── db-schema.md -├── product-specs/ -│ ├── index.md -│ └── new-user-onboarding.md -├── references/ -│ ├── design-system-reference-llms.txt -│ ├── nixpacks-llms.txt -│ └── uv-llms.txt -├── DESIGN.md -├── FRONTEND.md -├── PLANS.md -├── PRODUCT_SENSE.md -├── QUALITY_SCORE.md -├── RELIABILITY.md -└── SECURITY.md -``` - -## 도입 방법 - -1. 저장소가 아직 작다면 최소 팩(minimal pack)부터 시작하십시오. -2. 더 강한 구조가 필요해지면 [`repo-template/`](./repo-template/index.md)의 파일들을 자신의 저장소에 복사하십시오. -3. `AGENTS.md`는 짧게 유지하십시오. 이 파일을 깊이 있는 문서들로의 라우터로 취급하되, 백과사전처럼 쓰지 마십시오. -4. 품질·신뢰성·계획 문서는 별도의 정리 날을 따로 잡지 말고 일상적인 작업의 일부로 업데이트하십시오. -5. 생성된 산출물(artifact)과 외부 참고 자료를 명시적으로 관리하여 에이전트가 채팅 기록에 의존하지 않고 찾을 수 있도록 하십시오. - -## SOP 라이브러리 - -[`sops/`](./sops/index.md) 폴더는 원문 문서의 다이어그램을 단계별 운영 절차(표준 작업 절차, SOP)로 변환한 것입니다. - -- 계층형 도메인 아키텍처(layered domain architecture) 설정 -- 보이지 않는 지식을 저장소에 인코딩하기 -- 로컬 관측 가능성(observability) 스택과 피드백 루프(feedback loop) 워크플로우 -- UI 작업을 위한 Chrome DevTools 검증 루프(validation loop) - -## 설계 원칙 - -- 짧은 진입점, 더 깊이 연결된 문서들 -- 시스템 오브 레코드로서의 저장소 -- 기계적 검사가 기억에 의존하는 규칙보다 낫다 -- 계획과 품질 이력은 코드 옆에 존재한다 -- 정리와 단순화는 일급(first-class) 책임이다 - -이 팩은 의도적으로 견해가 담겨 있지만, 그럼에도 맹목적으로 복사하지 않고 자신의 프로젝트에 맞게 조정해서 사용해야 합니다. diff --git a/courses/learn-harness-engineering/docs/ko/resources/openai-advanced/repo-template/AGENTS.md b/courses/learn-harness-engineering/docs/ko/resources/openai-advanced/repo-template/AGENTS.md deleted file mode 100644 index 6f5dd029..00000000 --- a/courses/learn-harness-engineering/docs/ko/resources/openai-advanced/repo-template/AGENTS.md +++ /dev/null @@ -1,55 +0,0 @@ -# AGENTS.md - -이 저장소는 장기 실행 코딩 에이전트(coding agent) 작업에 최적화되어 있습니다. 이 파일은 짧게 유지하십시오. 이 파일을 시스템 오브 레코드(system-of-record) 문서들로의 라우팅 계층으로 사용하되, 거대한 지시 모음으로 만들지 마십시오. - -## 시작 워크플로우 - -코드를 변경하기 전에: - -1. `pwd`로 저장소 루트를 확인한다. -2. 현재 시스템 지도와 의존성 규칙을 파악하기 위해 `ARCHITECTURE.md`를 읽는다. -3. 어떤 도메인 또는 계층이 가장 취약한지 확인하기 위해 `docs/QUALITY_SCORE.md`를 읽는다. -4. `docs/PLANS.md`를 읽고, 현재 작업 중인 활성 실행 계획(active execution plan)을 연다. -5. `docs/product-specs/`에서 관련 제품 명세(product spec)를 읽는다. -6. 이 저장소의 표준 부트스트랩(bootstrap) 및 검증(verification) 경로를 실행한다. -7. 기준 검증이 실패하면 범위를 추가하기 전에 기준을 먼저 복구한다. - -## 라우팅 지도 - -- `ARCHITECTURE.md`: 도메인 지도, 계층 모델, 의존성 규칙 -- `docs/design-docs/index.md`: 설계 결정(design decision)과 핵심 신념 -- `docs/product-specs/index.md`: 현재 제품 동작과 인수 목표 -- `docs/PLANS.md`: 계획 라이프사이클과 실행 계획 정책 -- `docs/QUALITY_SCORE.md`: 제품 도메인 및 계층 건전성 -- `docs/RELIABILITY.md`: 런타임 신호, 벤치마크, 재시작 기대치 -- `docs/SECURITY.md`: 비밀(secret), 샌드박스, 데이터, 외부 행동 규칙 -- `docs/FRONTEND.md`: UI 제약, 디자인 시스템 규칙, 접근성(accessibility) 검사 - -## 작업 계약 - -- 한 번에 하나의 제한된 계획 또는 기능 슬라이스로 작업한다. -- 코드 검사만으로 작업이 완료됐다고 표시하지 않는다. 실행 가능한 증거(evidence)가 필요하다. -- 동작을 변경하면, 같은 세션 내에서 일치하는 제품·계획·신뢰성 문서를 업데이트한다. -- 반복되는 리뷰 피드백이 있으면 채팅에서 재설명하는 대신 기계적인 규칙·검사·린터로 승격시킨다. -- 생성된 자료는 `docs/generated/`에, 외부 참고 자료는 `docs/references/`에 보관한다. -- 이 파일을 늘리는 것보다 작고 현재의 문서를 추가하는 것을 선호한다. - -## 완료 정의 - -다음 모든 조건이 충족될 때만 변경이 완료된 것으로 간주한다. - -- 목표 동작이 구현되었다 -- 필요한 검증이 실제로 실행되었다 -- 증거가 관련 계획 또는 품질 문서에서 연결되어 있다 -- 영향받는 문서가 현재 상태를 유지한다 -- 저장소가 표준 시작 경로에서 깔끔하게 재시작될 수 있다 - -## 세션 종료 - -세션을 종료하기 전에: - -1. 활성 실행 계획을 업데이트한다. -2. 도메인 또는 계층이 의미 있게 변경된 경우 `docs/QUALITY_SCORE.md`를 업데이트한다. -3. 미뤄진 사항이 있으면 `docs/exec-plans/tech-debt-tracker.md`에 새 기술 부채(tech debt)를 기록한다. -4. 완료된 계획은 적절한 시점에 `docs/exec-plans/completed/`로 이동한다. -5. 명확한 다음 행동이 있는 재시작 가능한 상태로 저장소를 남긴다. diff --git a/courses/learn-harness-engineering/docs/ko/resources/openai-advanced/repo-template/ARCHITECTURE.md b/courses/learn-harness-engineering/docs/ko/resources/openai-advanced/repo-template/ARCHITECTURE.md deleted file mode 100644 index 0f45ea2d..00000000 --- a/courses/learn-harness-engineering/docs/ko/resources/openai-advanced/repo-template/ARCHITECTURE.md +++ /dev/null @@ -1,57 +0,0 @@ -# ARCHITECTURE.md - -이 파일은 시스템의 최상위 지도입니다. 간결하게 유지하고 필요할 때 더 깊은 문서를 가리키도록 합니다. - -아키텍처 문서는 에이전트(agent)가 코드를 변경하기 전에 읽어야 할 시스템 지도 역할을 합니다. 도메인 경계, 계층 모델, 의존성 규칙을 한 곳에 명시함으로써 에이전트가 아드혹(ad-hoc) 아키텍처를 만들지 않도록 방지합니다. - -## 시스템 형태 - -- 제품: `[제품명으로 교체]` -- 주요 사용자 워크플로우: `[주요 워크플로우로 교체]` -- 런타임 표면: `[desktop / web / cli / services / workers]` -- 제품 동작의 진실 원천(source of truth): `docs/product-specs/` - -## 도메인 지도 - -| 도메인 | 목적 | 주요 진입점 | 관련 명세 | -|--------|------|------------|-----------| -| `[domain-a]` | `[소유하는 것]` | `[모듈 / 라우트 / 명령어]` | `[명세 경로]` | -| `[domain-b]` | `[소유하는 것]` | `[모듈 / 라우트 / 명령어]` | `[명세 경로]` | - -## 계층 모델 - -에이전트가 즉흥적인 아키텍처를 발명하지 않도록 고정된 방향성 모델을 사용한다. - -`Types -> Config -> Repo -> Service -> Runtime -> UI` - -횡단 관심사(cross-cutting concern)는 계층을 직접 가로지르는 대신 명시적인 공급자(provider) 또는 어댑터(adapter) 경계를 통해 진입해야 합니다. - -## 의존성 하드 규칙 - -- 하위 계층은 상위 계층에 의존해서는 안 된다. -- UI는 런타임 또는 서비스 계약을 우회해서는 안 된다. -- 데이터 접근은 저장소(repository) 또는 동등한 어댑터를 통해 진입해야 한다. -- 공유 유틸리티는 범용으로 유지하며 도메인 로직을 축적해서는 안 된다. -- 새로운 의존성은 일치하는 계획 또는 설계 문서에서 정당화되어야 한다. - -## 횡단 관심사 인터페이스 - -| 관심사 | 승인된 경계 | 비고 | -|--------|------------|------| -| 로깅 및 트레이싱 | `[공급자 / 유틸리티 경로]` | `[구조화만 허용, 임의 콘솔 사용 금지]` | -| 인증(Auth) | `[공급자 경로]` | `[토큰/세션 규칙]` | -| 외부 API | `[클라이언트 또는 공급자 경로]` | `[속도 제한 / 재시도 지침]` | -| 기능 플래그 | `[플래그 경계]` | `[소유권]` | - -## 현재 핫스팟 - -- `[에이전트가 안전하게 변경하기 가장 어려운 영역]` -- `[경계가 약하거나 테스트가 취약한 영역]` - -## 변경 체크리스트 - -아키텍처 관련 코드를 수정할 때: - -1. 도메인 지도 또는 허용된 경계가 변경된 경우 이 파일을 업데이트한다. -2. 근거가 변경된 경우 `docs/design-docs/`의 관련 설계 문서(design doc)를 업데이트한다. -3. 규칙이 기계적으로 강제되어야 한다면 실행 가능한 검사를 추가하거나 업데이트한다. diff --git a/courses/learn-harness-engineering/docs/ko/resources/openai-advanced/repo-template/docs/DESIGN.md b/courses/learn-harness-engineering/docs/ko/resources/openai-advanced/repo-template/docs/DESIGN.md deleted file mode 100644 index 2eaa42ca..00000000 --- a/courses/learn-harness-engineering/docs/ko/resources/openai-advanced/repo-template/docs/DESIGN.md +++ /dev/null @@ -1,27 +0,0 @@ -# DESIGN.md - -이 파일은 설계 진입점(design entrypoint)입니다. 간결하게 유지하고 `docs/design-docs/` 아래의 더 상세한 파일들로 라우팅하는 데 사용하십시오. - -## 목적 - -단일 채팅, 스프린트, 또는 리뷰어의 기억을 넘어 지속되어야 할 내구적인 제품 및 시스템 설계 결정을 기록합니다. - -설계 문서(design doc)는 단순한 코드 주석이나 PR 설명과 달리, 결정이 내려진 이유와 맥락을 장기적으로 보존하여 미래의 에이전트(agent)와 인간이 동일한 실수를 반복하지 않도록 돕습니다. - -## 읽어야 할 때 - -- 현재 설계 철학이 필요할 때 -- 새로운 패턴을 도입하려 할 때 -- 어떤 설계 결정이 확정되었고 어떤 것이 아직 열려 있는지 알아야 할 때 - -## 규범적 설계 문서 - -- `docs/design-docs/index.md`: 수락됨, 제안됨, 폐기됨 상태의 문서 색인 -- `docs/design-docs/core-beliefs.md`: 프로젝트 전반에 걸친 에이전트 우선(agent-first) 신념 - -## 설계 규칙 - -- 설계 문서는 작고 현재 상태를 유지한다. -- 결정 영역당 하나의 문서를 선호한다. -- 변경이 설계 문서에 의존할 때 계획과 명세에서 설계 문서를 연결한다. -- 설계 규칙이 운영적으로 중요해지면 자동화된 검사로 승격하거나 `ARCHITECTURE.md`를 업데이트한다. diff --git a/courses/learn-harness-engineering/docs/ko/resources/openai-advanced/repo-template/docs/FRONTEND.md b/courses/learn-harness-engineering/docs/ko/resources/openai-advanced/repo-template/docs/FRONTEND.md deleted file mode 100644 index f33b09f8..00000000 --- a/courses/learn-harness-engineering/docs/ko/resources/openai-advanced/repo-template/docs/FRONTEND.md +++ /dev/null @@ -1,25 +0,0 @@ -# FRONTEND.md - -이 파일은 안정적인 프론트엔드 기대치를 정의하여 에이전트(agent)가 예측 불가능하게 UI 패턴을 발명하지 않도록 합니다. - -프론트엔드 정책 문서는 에이전트가 UI 작업 중 임의의 패턴을 선택하지 않도록, 디자인 시스템 규칙·접근성(accessibility) 기준·검증 방식을 미리 명시하는 역할을 합니다. - -## UI 원칙 - -- 참신함보다 명료함을 우선시한다. -- 상호작용 흐름을 발견하기 쉽고 재시작 가능하게 유지한다. -- 일회성 변형보다 소수의 재사용 가능한 컴포넌트를 선호한다. -- 접근성 검사는 마무리 작업이 아닌 일반적인 검증의 일부이다. - -## 가드레일(Guardrail) - -- 디자인 시스템 또는 컴포넌트 라이브러리를 `docs/references/`에 문서화한다. -- 주요 사용자 대면 상태를 기록한다: 빈 상태, 로딩, 성공, 오류, 재시도. -- 복사 문구, 키보드 동작, 시각적 계층 구조를 흐름 전반에 걸쳐 일관되게 유지한다. -- UI 버그를 수정할 때 일치하는 유효성 검사 단계를 추가하거나 업데이트한다. - -## 검증 기대치 - -- 중요한 사용자 여정에 대한 증거를 캡처한다. -- 관련 계획에 브라우저 또는 런타임 유효성 검사(validation) 단계를 기록한다. -- 시각적 회귀(regression)가 잦으면 스크린샷 또는 DOM 검사를 표준화한다. diff --git a/courses/learn-harness-engineering/docs/ko/resources/openai-advanced/repo-template/docs/PLANS.md b/courses/learn-harness-engineering/docs/ko/resources/openai-advanced/repo-template/docs/PLANS.md deleted file mode 100644 index 57ef988d..00000000 --- a/courses/learn-harness-engineering/docs/ko/resources/openai-advanced/repo-template/docs/PLANS.md +++ /dev/null @@ -1,36 +0,0 @@ -# PLANS.md - -이 파일은 실행 계획(execution plan)이 어떻게 생성·업데이트·완료·보관되는지를 정의합니다. - -실행 계획은 단일 세션을 초과하는 작업, 여러 하위 시스템에 걸친 변경, 또는 공개된 결정 사항이 있는 작업을 구조화된 방식으로 추적합니다. 계획을 저장소에 영속화함으로써 에이전트(agent)가 이전 컨텍스트(context) 없이도 작업을 이어받을 수 있습니다. - -## 계획이 필요한 경우 - -다음에 해당할 때 실행 계획을 생성한다. - -- 하나 이상의 세션에 걸치는 작업 -- 하나 이상의 하위 시스템을 변경하는 작업 -- 중요하지 않은 검증 또는 배포 위험이 있는 작업 -- 기록되어야 할 미결 결정이 있는 작업 - -## 계획 위치 - -- `docs/exec-plans/active/`: 현재 작업을 이끄는 계획 -- `docs/exec-plans/completed/`: 향후 에이전트 컨텍스트를 위해 보관된 완료 계획 -- `docs/exec-plans/tech-debt-tracker.md`: 미뤄진 작업과 후속 조치 - -## 최소 계획 섹션 - -- 목표(objective) -- 범위 및 범위 외(scope and out-of-scope) -- 검증 경로(verification path) -- 위험 및 차단 요소(risks and blockers) -- 진행 로그(progress log) -- 미결 결정(open decisions) - -## 운영 규칙 - -- 하나의 활성 계획에는 현재 진행 중인 단계가 명확하게 하나 있어야 한다. -- 작업이 진행되면 계획을 업데이트한다. 정적 산문으로 취급하지 않는다. -- 결정이 구현 방향을 변경하면 계획에 기록한다. -- 완료된 계획은 에이전트가 이전 컨텍스트를 계속 발견할 수 있도록 `completed/`로 이동한다. diff --git a/courses/learn-harness-engineering/docs/ko/resources/openai-advanced/repo-template/docs/PRODUCT_SENSE.md b/courses/learn-harness-engineering/docs/ko/resources/openai-advanced/repo-template/docs/PRODUCT_SENSE.md deleted file mode 100644 index 8ad0e1b5..00000000 --- a/courses/learn-harness-engineering/docs/ko/resources/openai-advanced/repo-template/docs/PRODUCT_SENSE.md +++ /dev/null @@ -1,26 +0,0 @@ -# PRODUCT_SENSE.md - -이 파일은 에이전트(agent)가 코드만으로는 신뢰할 수 있게 추론할 수 없는 지속적인 제품 판단을 담습니다. - -제품 감각(product sense) 문서는 단순한 기능 명세(feature spec)를 넘어, 팀이 어떤 종류의 결정을 어떤 우선순위로 내려야 하는지에 관한 횡단 관심사(cross-cutting) 원칙을 에이전트에게 제공합니다. - -## 제품 핵심 - -- 주요 사용자: `[교체]` -- 해야 할 일(job to be done): `[교체]` -- 제거할 주요 불편함: `[교체]` -- 인수 품질 기준: `[교체]` - -## 제품 규칙 - -- 기능 수보다 사용자에게 보이는 신뢰성(reliability)을 우선시한다. -- 모호한 동작은 추측할 권한이 아닌 명세 공백(spec gap)으로 취급한다. -- 구현이 사용자가 보거나 신뢰하는 것을 변경하면 일치하는 명세를 업데이트한다. -- 구체적인 흐름에는 제품 명세(product spec)를 사용하고, 횡단적인 제품 우선순위에는 이 파일을 사용한다. - -## 금지 패턴 - -- 숨겨진 파괴적 행동 -- 사용자 피드백 없는 무음(無音) 실패 -- 가시적 상태에 대한 불명확한 진실 원천 -- 한 문장으로 설명할 수 없는 기능 diff --git a/courses/learn-harness-engineering/docs/ko/resources/openai-advanced/repo-template/docs/QUALITY_SCORE.md b/courses/learn-harness-engineering/docs/ko/resources/openai-advanced/repo-template/docs/QUALITY_SCORE.md deleted file mode 100644 index b3038bad..00000000 --- a/courses/learn-harness-engineering/docs/ko/resources/openai-advanced/repo-template/docs/QUALITY_SCORE.md +++ /dev/null @@ -1,41 +0,0 @@ -# QUALITY_SCORE.md - -이 문서는 저장소가 시간이 지남에 따라 강해지고 있는지, 아니면 약해지고 있는지를 추적합니다. - -품질 점수(quality score)는 단순한 테스트 커버리지를 넘어, 에이전트 가독성·경계 강제·안정성 측면에서 각 도메인과 계층의 건전성을 주기적으로 평가하는 도구입니다. 이를 통해 부채가 쌓이기 전에 조기에 발견할 수 있습니다. - -## 등급 척도 - -- `A`: 검증됨, 가독성 높음, 안정적, 경계 강제됨 -- `B`: 사소한 공백이 있지만 작동함 -- `C`: 부분적으로 작동함, 주목할 만한 혼란 또는 불안정 -- `D`: 고장남, 안전하지 않음, 또는 구조적으로 불명확함 - -## 제품 도메인 - -| 도메인 | 등급 | 검증 | 에이전트 가독성 | 테스트 안정성 | 주요 공백 | 최종 업데이트 | -|--------|------|------|--------------|------------|---------|------------| -| `[domain-a]` | - | - | - | - | - | - | -| `[domain-b]` | - | - | - | - | - | - | -| `[domain-c]` | - | - | - | - | - | - | - -## 아키텍처 계층 - -| 계층 | 등급 | 경계 강제 | 에이전트 가독성 | 주요 공백 | 최종 업데이트 | -|------|------|----------|--------------|---------|------------| -| Types | - | - | - | - | - | -| Services | - | - | - | - | - | -| Runtime | - | - | - | - | - | -| UI | - | - | - | - | - | - -## 벤치마크 스냅샷 - -| 날짜 | 하네스 변형 | 완료율 | 재시도 | 리뷰 전 결함 | 비고 | -|------|------------|-------|-------|------------|------| -| YYYY-MM-DD | `[baseline / improved / simplified]` | - | - | - | - | - -## 단순화 로그 - -| 날짜 | 제거된 컴포넌트 | 결과 | 결정 | -|------|--------------|------|------| -| YYYY-MM-DD | `[컴포넌트]` | `[degraded / unchanged]` | `[restore / keep removed]` | diff --git a/courses/learn-harness-engineering/docs/ko/resources/openai-advanced/repo-template/docs/RELIABILITY.md b/courses/learn-harness-engineering/docs/ko/resources/openai-advanced/repo-template/docs/RELIABILITY.md deleted file mode 100644 index fff00901..00000000 --- a/courses/learn-harness-engineering/docs/ko/resources/openai-advanced/repo-template/docs/RELIABILITY.md +++ /dev/null @@ -1,34 +0,0 @@ -# RELIABILITY.md - -이 파일은 시스템이 어떻게 건강하고 재시작 가능함을 증명하는지를 정의합니다. - -신뢰성(reliability) 문서는 에이전트(agent)가 기능 구현 후 시스템이 깨끗하게 재시작될 수 있는지, 그리고 런타임 실패가 진단 가능한지를 확인하는 데 필요한 경로와 신호를 제공합니다. 신뢰성을 가드레일로 정의함으로써 기능 추가 시 시스템 안정성이 묵시적으로 저하되는 것을 방지합니다. - -## 표준 경로 - -- 부트스트랩(Bootstrap): `[명령어]` -- 검증(Verification): `[명령어]` -- 앱 또는 서비스 시작: `[명령어]` -- 런타임 디버그 또는 검사: `[명령어]` - -## 필수 런타임 신호 - -- 시작 및 중요 흐름에 대한 구조화된 로그 -- 주요 서비스에 대한 헬스 체크(health check) -- 가능한 경우 느린 경로에 대한 트레이스 또는 타이밍 데이터 -- 복구 가능한 실패에 대한 사용자 가시적 오류 상태 - -## 황금 여정(Golden Journey) - -- `[여정 1]` -- `[여정 2]` -- `[여정 3]` - -각 황금 여정에는 반복 가능한 검증 경로와 명확한 실패 신호가 있어야 합니다. - -## 신뢰성 규칙 - -- 시스템이 이후에 깔끔하게 재시작될 수 없으면 어떤 기능도 완료된 것이 아니다. -- 런타임 실패는 저장소 로컬 신호로 진단 가능해야 한다. -- 반복되는 실패 모드가 나타나면 해당 모드에 대한 벤치마크 또는 가드레일을 추가한다. -- 정리(cleanup)는 신뢰성의 일부이며 별도의 관심사가 아니다. diff --git a/courses/learn-harness-engineering/docs/ko/resources/openai-advanced/repo-template/docs/SECURITY.md b/courses/learn-harness-engineering/docs/ko/resources/openai-advanced/repo-template/docs/SECURITY.md deleted file mode 100644 index 2ba0e32c..00000000 --- a/courses/learn-harness-engineering/docs/ko/resources/openai-advanced/repo-template/docs/SECURITY.md +++ /dev/null @@ -1,29 +0,0 @@ -# SECURITY.md - -이 파일은 에이전트(agent)가 추측해서는 안 되는 보안(security) 및 안전 규칙을 정의합니다. - -보안 정책을 저장소에 명시적으로 기록하면, 에이전트가 비밀 처리·외부 행동 승인·의존성 검토 등에서 임의의 선택을 하지 않고 정해진 경계 내에서 작동하게 됩니다. - -## 비밀(Secret) 및 자격 증명(Credential) - -- 소스 코드나 문서에 절대로 비밀을 하드코딩하지 않는다. -- 승인된 비밀 로딩 경로를 여기에 문서화한다. -- 토큰, API 키, 개인 데이터는 로그와 스크린샷에서 제거(redact)한다. - -## 신뢰할 수 없는 입력 - -- 외부 콘텐츠는 검증될 때까지 신뢰할 수 없는 것으로 취급한다. -- 허용된 가져오기(fetch) 또는 실행 경계를 여기에 기록한다. -- 프롬프트 주입(prompt injection) 또는 명령어 주입(command injection) 위험이 있으면 가드레일(guardrail)을 문서화한다. - -## 외부 행동 - -- 명시적 승인이 필요한 행동을 나열한다. -- 에이전트가 기본적으로 실행해서는 안 되는 운영 또는 파괴적인 명령어를 기록한다. -- 디버깅 및 검증을 위해 샌드박스 안전 워크플로우를 선호한다. - -## 의존성 및 리뷰 규칙 - -- 새로운 의존성은 활성 계획에서 정당화가 필요하다. -- 보안에 민감한 변경은 명시적인 검증 단계가 필요하다. -- 반복되는 보안 리뷰 의견은 구전 지식이 아닌 검사로 전환해야 한다. diff --git a/courses/learn-harness-engineering/docs/ko/resources/openai-advanced/repo-template/docs/design-docs/core-beliefs.md b/courses/learn-harness-engineering/docs/ko/resources/openai-advanced/repo-template/docs/design-docs/core-beliefs.md deleted file mode 100644 index 562b0b72..00000000 --- a/courses/learn-harness-engineering/docs/ko/resources/openai-advanced/repo-template/docs/design-docs/core-beliefs.md +++ /dev/null @@ -1,11 +0,0 @@ -# 핵심 신념(Core Beliefs) - -이 문서는 프로젝트 전반에서 에이전트(agent)와 인간이 공유하는 에이전트 우선(agent-first) 운영 원칙을 정리한 것입니다. 개별 결정보다 상위에 있는 이 신념들은 모호한 상황에서 판단의 기준이 됩니다. - -- 저장소는 에이전트를 위한 시스템 오브 레코드(system-of-record)이다. -- `AGENTS.md`는 라우터이지 백과사전이 아니다. -- 검증 증거가 자신감보다 더 중요하다. -- 많은 반쯤 완성된 작업보다 하나의 제한된 작업이 낫다. -- 반복되는 사람의 피드백은 재사용 가능한 하네스(harness) 규칙이 되어야 한다. -- 정리와 단순화는 출시의 일부이지 후처리가 아니다. -- 에이전트가 저장소에서 사실을 발견할 수 없다면, 그 사실은 운영적으로 사용 불가능한 것으로 취급한다. diff --git a/courses/learn-harness-engineering/docs/ko/resources/openai-advanced/repo-template/docs/design-docs/index.md b/courses/learn-harness-engineering/docs/ko/resources/openai-advanced/repo-template/docs/design-docs/index.md deleted file mode 100644 index 749b4913..00000000 --- a/courses/learn-harness-engineering/docs/ko/resources/openai-advanced/repo-template/docs/design-docs/index.md +++ /dev/null @@ -1,23 +0,0 @@ -# 설계 문서(Design Doc) 색인 - -이 색인을 설계 이력의 발견 가능한 지도로 사용하십시오. - -설계 문서(design doc)는 단순한 구현 설명이 아니라 "왜 이 결정을 내렸는가"에 대한 근거를 담은 문서입니다. 상태(수락됨·제안됨·폐기됨)를 명시적으로 관리함으로써 에이전트(agent)가 유효한 결정과 낡은 결정을 구분할 수 있습니다. - -## 수락됨(Accepted) - -- `core-beliefs.md`: 에이전트 우선(agent-first) 운영 신념과 지속적인 프로젝트 규범 - -## 제안됨(Proposed) - -- `[여기에 새 설계 문서 경로를 추가]` - -## 폐기됨(Deprecated) - -- `[대체 링크와 함께 오래되거나 대체된 설계 문서를 여기로 이동]` - -## 유지보수 규칙 - -- 모든 설계 문서에는 소유자 또는 업데이트 트리거가 있어야 한다. -- 오래된 문서는 폐기 처리하거나 삭제하되, 그냥 방치하지 않는다. -- 활성 실행 계획(execution plan)을 그것이 의존하는 설계 문서에 연결한다. diff --git a/courses/learn-harness-engineering/docs/ko/resources/openai-advanced/repo-template/docs/exec-plans/active/index.md b/courses/learn-harness-engineering/docs/ko/resources/openai-advanced/repo-template/docs/exec-plans/active/index.md deleted file mode 100644 index ec00d8c7..00000000 --- a/courses/learn-harness-engineering/docs/ko/resources/openai-advanced/repo-template/docs/exec-plans/active/index.md +++ /dev/null @@ -1,11 +0,0 @@ -# 활성 계획(Active Plans) - -이 폴더에 활성 실행 계획(active execution plan)을 마크다운 파일 형식으로 하나씩 보관하십시오. - -활성 계획이란 현재 진행 중인 작업을 이끄는 계획으로, 새 에이전트(agent) 세션이 저장소만으로도 작업을 재개할 수 있을 만큼 충분히 최신 상태를 유지해야 합니다. - -권장 파일명 패턴: - -- `YYYY-MM-DD-short-topic.md` - -각 활성 계획은 새로운 에이전트 세션이 저장소만으로 작업을 재개할 수 있을 만큼 충분히 최신 상태를 유지해야 합니다. diff --git a/courses/learn-harness-engineering/docs/ko/resources/openai-advanced/repo-template/docs/exec-plans/completed/index.md b/courses/learn-harness-engineering/docs/ko/resources/openai-advanced/repo-template/docs/exec-plans/completed/index.md deleted file mode 100644 index 12d7f1a6..00000000 --- a/courses/learn-harness-engineering/docs/ko/resources/openai-advanced/repo-template/docs/exec-plans/completed/index.md +++ /dev/null @@ -1,5 +0,0 @@ -# 완료된 계획(Completed Plans) - -완료된 계획은 삭제하지 말고 이 폴더로 이동하십시오. 완료된 계획은 저장소 메모리 표면의 일부로, 이후 에이전트(agent) 실행 시 코드가 현재와 같은 형태가 된 이유를 이해하는 데 도움이 됩니다. - -완료된 계획을 보존함으로써 미래의 에이전트는 과거에 내린 결정의 맥락과 근거를 추적할 수 있으며, 동일한 문제에 대해 이미 검토된 접근 방식을 재탐색하는 낭비를 줄일 수 있습니다. diff --git a/courses/learn-harness-engineering/docs/ko/resources/openai-advanced/repo-template/docs/exec-plans/tech-debt-tracker.md b/courses/learn-harness-engineering/docs/ko/resources/openai-advanced/repo-template/docs/exec-plans/tech-debt-tracker.md deleted file mode 100644 index 111b05e8..00000000 --- a/courses/learn-harness-engineering/docs/ko/resources/openai-advanced/repo-template/docs/exec-plans/tech-debt-tracker.md +++ /dev/null @@ -1,9 +0,0 @@ -# 기술 부채 추적기(Tech Debt Tracker) - -실제로 존재하고, 인지되어 있으며, 의도적으로 미뤄진 기술 부채(tech debt)에 이 파일을 사용하십시오. - -기술 부채란 더 나은 방법이 있음을 알면서도 시간·리스크·우선순위 등의 이유로 의도적으로 선택한 차선책입니다. 이를 명시적으로 추적함으로써 에이전트(agent)와 팀 구성원이 어떤 영역이 개선이 필요한지, 그 이유는 무엇인지를 한눈에 파악할 수 있습니다. - -| 날짜 | 영역 | 부채 | 미룬 이유 | 위험 | 다음 트리거 | -|------|------|------|----------|------|------------| -| YYYY-MM-DD | `[area]` | `[debt]` | `[reason]` | `[risk]` | `[when to revisit]` | diff --git a/courses/learn-harness-engineering/docs/ko/resources/openai-advanced/repo-template/docs/generated/db-schema.md b/courses/learn-harness-engineering/docs/ko/resources/openai-advanced/repo-template/docs/generated/db-schema.md deleted file mode 100644 index e59fe5e8..00000000 --- a/courses/learn-harness-engineering/docs/ko/resources/openai-advanced/repo-template/docs/generated/db-schema.md +++ /dev/null @@ -1,15 +0,0 @@ -# 데이터베이스 스키마(Database Schema) - -이 폴더는 에이전트(agent)가 코드를 역공학(reverse-engineering)하지 않고도 검사할 수 있도록 생성(generated)되거나 파생된 산출물(artifact)을 보관하는 데 사용하십시오. - -생성된 산출물을 저장소에 명시적으로 보관하면, 에이전트가 원본 코드를 분석하지 않고도 현재 스키마나 파생 데이터를 즉시 참조할 수 있어 컨텍스트(context) 소비를 줄이고 분석 속도를 높입니다. - -## 출처 - -- 생성 위치: `[명령어 또는 소스 경로]` -- 마지막 갱신: `YYYY-MM-DD` - -## 비고 - -- 생성된 섹션을 직접 수동으로 편집하지 마십시오. -- 기반 스키마가 변경되면 이 파일을 재생성하십시오. diff --git a/courses/learn-harness-engineering/docs/ko/resources/openai-advanced/repo-template/docs/product-specs/index.md b/courses/learn-harness-engineering/docs/ko/resources/openai-advanced/repo-template/docs/product-specs/index.md deleted file mode 100644 index d44ad943..00000000 --- a/courses/learn-harness-engineering/docs/ko/resources/openai-advanced/repo-template/docs/product-specs/index.md +++ /dev/null @@ -1,15 +0,0 @@ -# 제품 명세(Product Specs) 색인 - -현재 사용자 대면 동작 명세에 이 폴더를 사용하십시오. - -제품 명세(product spec)는 기능의 사용자 가시적 동작과 인수 기준(acceptance criteria)을 정의합니다. 에이전트(agent)가 명세를 참조함으로써 구현이 제품 의도에서 벗어나는 것을 방지합니다. - -## 활성 명세 - -- `new-user-onboarding.md` - -## 규칙 - -- 명세는 사용자 가시적 동작과 인수 기준을 설명해야 한다. -- 구현이 명세에서 벗어나면 같은 세션에서 둘 중 하나를 업데이트한다. -- 새로운 에이전트가 제품 범위를 빠르게 파악할 수 있도록 이 색인을 최신 상태로 유지한다. diff --git a/courses/learn-harness-engineering/docs/ko/resources/openai-advanced/repo-template/docs/product-specs/new-user-onboarding.md b/courses/learn-harness-engineering/docs/ko/resources/openai-advanced/repo-template/docs/product-specs/new-user-onboarding.md deleted file mode 100644 index 648f9dce..00000000 --- a/courses/learn-harness-engineering/docs/ko/resources/openai-advanced/repo-template/docs/product-specs/new-user-onboarding.md +++ /dev/null @@ -1,28 +0,0 @@ -# 신규 사용자 온보딩(New User Onboarding) - -## 목표 - -신규 사용자가 처음 실행 시 어떤 경험을 해야 하는지를 설명합니다. - -온보딩(onboarding)은 새 사용자가 서비스를 처음 접할 때 거치는 진입 흐름입니다. 이 명세는 에이전트(agent)가 온보딩 흐름을 구현하거나 수정할 때 참조하는 단일 진실 원천(single source of truth)입니다. - -## 진입 조건 - -- `[흐름이 시작되기 전 상태]` - -## 사용자 흐름 - -1. `[첫 번째 단계]` -2. `[두 번째 단계]` -3. `[세 번째 단계]` - -## 인수 기준(Acceptance Criteria) - -- `[관찰 가능한 결과]` -- `[관찰 가능한 결과]` -- `[관찰 가능한 결과]` - -## 실패 상태 - -- `[복구 가능한 오류와 사용자 피드백]` -- `[차단된 상태와 대체 경로]` diff --git a/courses/learn-harness-engineering/docs/ko/resources/openai-advanced/repo-template/docs/references/design-system-reference-llms.txt b/courses/learn-harness-engineering/docs/ko/resources/openai-advanced/repo-template/docs/references/design-system-reference-llms.txt deleted file mode 100644 index 6f1c612c..00000000 --- a/courses/learn-harness-engineering/docs/ko/resources/openai-advanced/repo-template/docs/references/design-system-reference-llms.txt +++ /dev/null @@ -1,9 +0,0 @@ -Purpose: store model-friendly reference material for the design system. - -Suggested contents: -- component naming rules -- spacing and typography tokens -- state variants -- accessibility expectations - -Keep this file concise and refresh it when the upstream design system changes. diff --git a/courses/learn-harness-engineering/docs/ko/resources/openai-advanced/repo-template/docs/references/nixpacks-llms.txt b/courses/learn-harness-engineering/docs/ko/resources/openai-advanced/repo-template/docs/references/nixpacks-llms.txt deleted file mode 100644 index c30cc9c2..00000000 --- a/courses/learn-harness-engineering/docs/ko/resources/openai-advanced/repo-template/docs/references/nixpacks-llms.txt +++ /dev/null @@ -1,8 +0,0 @@ -Purpose: store a clean agent-readable extract of the deployment or packaging -rules your repository depends on. - -Suggested contents: -- build entrypoints -- runtime assumptions -- environment variable expectations -- common failure signatures diff --git a/courses/learn-harness-engineering/docs/ko/resources/openai-advanced/repo-template/docs/references/uv-llms.txt b/courses/learn-harness-engineering/docs/ko/resources/openai-advanced/repo-template/docs/references/uv-llms.txt deleted file mode 100644 index f2ceba9a..00000000 --- a/courses/learn-harness-engineering/docs/ko/resources/openai-advanced/repo-template/docs/references/uv-llms.txt +++ /dev/null @@ -1,8 +0,0 @@ -Purpose: store a compact reference for your Python package and environment -workflow when `uv` or similar tooling matters to the repo. - -Suggested contents: -- install and sync commands -- lockfile policy -- virtualenv expectations -- verification commands diff --git a/courses/learn-harness-engineering/docs/ko/resources/openai-advanced/repo-template/index.md b/courses/learn-harness-engineering/docs/ko/resources/openai-advanced/repo-template/index.md deleted file mode 100644 index 9dfd4140..00000000 --- a/courses/learn-harness-engineering/docs/ko/resources/openai-advanced/repo-template/index.md +++ /dev/null @@ -1,23 +0,0 @@ -# 고급 저장소 템플릿(Advanced Repo Template) - -최소한의 하네스(harness)만으로는 부족하고, OpenAI 스타일의 에이전트 우선(agent-first) 문서 체계가 필요할 때 이 스타터를 실제 저장소에 복사하십시오. - -저장소 템플릿(repo template)이란 에이전트와 인간 모두가 프로젝트 컨텍스트를 빠르게 파악하고, 점진적으로 더 깊은 정보를 탐색할 수 있도록 구조화된 스타터 파일 모음입니다. - -## 복사 순서 - -1. `AGENTS.md`와 `ARCHITECTURE.md`를 저장소 루트에 복사한다. -2. 전체 `docs/` 트리를 복사한다. -3. `docs/PRODUCT_SENSE.md`, `docs/QUALITY_SCORE.md`, `docs/RELIABILITY.md`를 가장 먼저 채운다. -4. `docs/exec-plans/active/` 아래에 첫 번째 활성 계획(active plan)을 추가한다. -5. 진입점 파일은 짧게 유지하고 세부 사항은 연결된 문서로 라우팅한다. - -## 이 템플릿이 최적화하는 것 - -- 지속형 저장소 로컬 컨텍스트 -- 하나의 거대한 지시 파일 대신 점진적 공개(progressive disclosure) -- 명시적인 계획 라이프사이클 -- 시간에 따른 품질 추적 -- 에이전트와 사람 모두 읽기 쉬운 경계 - -여기 있는 모든 파일을 스타터로 취급하십시오. 실제로 사용하기 전에 플레이스홀더, 예시, 샘플 명령어를 실제 프로젝트 내용으로 교체하십시오. diff --git a/courses/learn-harness-engineering/docs/ko/resources/openai-advanced/sops/chrome-devtools-validation-loop.md b/courses/learn-harness-engineering/docs/ko/resources/openai-advanced/sops/chrome-devtools-validation-loop.md deleted file mode 100644 index 4231cb06..00000000 --- a/courses/learn-harness-engineering/docs/ko/resources/openai-advanced/sops/chrome-devtools-validation-loop.md +++ /dev/null @@ -1,50 +0,0 @@ -# SOP: Chrome DevTools 검증 루프(Validation Loop) - -UI 작업이 실제 런타임 상호작용에 의존하고, 코드 검사만으로는 부족하며 스크린샷·DOM 상태·콘솔 출력이 더 중요할 때 이 표준 작업 절차(SOP)를 사용하십시오. - -검증 루프(validation loop)란 특정 UI 여정(journey)이 깔끔하게 완료될 때까지 BEFORE/AFTER 상태를 반복적으로 비교하며 문제를 좁혀 나가는 디버그 사이클입니다. - -## 목표 - -UI 유효성 검사(validation)를 에이전트(agent)가 여정이 깔끔해질 때까지 반복 실행할 수 있는 재현 가능한 상호작용 루프로 전환합니다. - -## 핵심 루프 - -1. 대상 페이지 또는 앱 인스턴스를 선택한다. -2. 오래된 콘솔 노이즈를 제거한다. -3. BEFORE 상태를 캡처한다. -4. UI 경로를 실행한다. -5. 상호작용 중 런타임 이벤트를 관찰한다. -6. AFTER 상태를 캡처한다. -7. 수정 사항을 적용하고 필요하면 앱을 재시작한다. -8. 여정이 깔끔해질 때까지 유효성 검사를 재실행한다. - -## 필요한 입력 - -- 안정적인 시작 명령어 -- 재현 가능한 UI 여정 -- DOM, 콘솔, 또는 스크린샷을 스냅샷할 수 있는 수단 -- "깔끔함"의 기준이 되는 규칙 - -## 실행 절차 - -1. 활성 실행 계획(execution plan)에 대상 여정을 기록한다. -2. 성공을 관찰 가능한 항목으로 정의한다: 텍스트 존재 여부, 버튼 활성화, 오류 제거, 콘솔 정상, 요청 성공. -3. 상호작용 전 초기 상태를 스냅샷한다. -4. 한 번에 정확히 하나의 경로만 실행한다. -5. 런타임 이벤트, DOM 변화, 가시적 출력을 기록한다. -6. 여정이 실패하면 가장 작은 책임 계층을 수정하고 재시작한다. -7. 동일한 경로를 재실행하여 BEFORE/AFTER 증거를 비교한다. - -## 깔끔함 기준 - -- 의도한 가시적 상태가 존재한다 -- 예기치 않은 오류가 없다 -- 콘솔 노이즈가 이해되거나 제거되었다 -- 동일한 경로를 재실행하면 동일한 결과가 나온다 - -## 업데이트해야 할 저장소 산출물 - -- 활성 실행 계획 -- `docs/RELIABILITY.md` — 해당 여정이 황금 경로(golden path)가 되는 경우 -- 가시적 동작이 변경된 경우 제품 명세(product spec) diff --git a/courses/learn-harness-engineering/docs/ko/resources/openai-advanced/sops/encode-knowledge-into-repo.md b/courses/learn-harness-engineering/docs/ko/resources/openai-advanced/sops/encode-knowledge-into-repo.md deleted file mode 100644 index 80ea1751..00000000 --- a/courses/learn-harness-engineering/docs/ko/resources/openai-advanced/sops/encode-knowledge-into-repo.md +++ /dev/null @@ -1,44 +0,0 @@ -# SOP: 보이지 않는 지식을 저장소에 인코딩하기 - -중요한 컨텍스트(context)가 여전히 Google Docs, 채팅 스레드, 티켓, 혹은 사람들의 머릿속에만 있을 때 이 표준 작업 절차(SOP)를 사용하십시오. - -에이전트(agent)에게 보이지 않는 지식이란 저장소 어디에도 기록되지 않아 새로운 세션에서는 발견할 수 없는 암묵지를 말합니다. 이를 저장소 로컬 파일로 옮기는 것이 시스템 오브 레코드(system-of-record)로서의 저장소 원칙의 핵심입니다. - -## 목표 - -에이전트에게 보이지 않는 지식을 코드베이스에서 발견 가능하게 만들어, 새 세션이 이전 대화에 의존하지 않고 바로 작업할 수 있게 합니다. - -## 트리거 신호 - -- 에이전트가 시스템 동작 방식을 계속 질문한다. -- "우리가 Slack에서 결정했어요" 또는 "지난 주에 X가 말한 대로 해주세요"라는 말이 나온다. -- 리뷰에서 저장소 내에 기록되지 않은 제품 또는 보안 규칙을 참조한다. -- 새 세션이 이미 해결됐어야 할 탐색 작업을 반복한다. - -## 실행 절차 - -1. 보이지 않는 지식 출처를 목록으로 작성한다: 문서, 채팅, 암묵적인 팀 규칙, 구두 결정. -2. 각 출처에 대해 묻는다: 이것이 아키텍처, 제품 동작, 보안 정책, 신뢰성(reliability) 기대치, 계획 컨텍스트, 또는 참고 자료인가? -3. 해당하는 저장소 산출물(artifact)에 인코딩한다: - - 아키텍처 -> `ARCHITECTURE.md` - - 제품 동작 -> `docs/product-specs/` - - 설계 근거 -> `docs/design-docs/` - - 실행 상태 -> `docs/exec-plans/` - - 반복적인 외부 참고 자료 -> `docs/references/` - - 품질 또는 신뢰성 기대치 -> `docs/QUALITY_SCORE.md` 또는 `docs/RELIABILITY.md` -4. 모호한 서술을 운영 관점에서 유용한 표현으로 교체한다. -5. 오래된 사본을 제거하거나 폐기(deprecate)하여 저장소가 하나의 발견 가능한 진실을 유지하도록 한다. - -## 올바른 인코딩 규칙 - -- 문학적 완결성이 아닌 발견 가능성을 위해 작성한다. -- 명확한 파일명을 가진 짧은 문서를 선호한다. -- 관련 산출물들을 서로 연결한다. -- 회의록이 아닌 지속적인 규칙을 저장한다. -- 결정이 내려진 동일한 세션에서 저장소를 업데이트한다. - -## 완료 정의 - -- 새로운 에이전트가 사람에게 묻지 않고 관련 규칙을 발견할 수 있다. -- 동일한 사실이 여러 모순된 파일에 흩어져 있지 않다. -- 새 산출물은 그것이 관장하는 코드 또는 워크플로우 가까이에 위치한다. diff --git a/courses/learn-harness-engineering/docs/ko/resources/openai-advanced/sops/index.md b/courses/learn-harness-engineering/docs/ko/resources/openai-advanced/sops/index.md deleted file mode 100644 index 1129c5c5..00000000 --- a/courses/learn-harness-engineering/docs/ko/resources/openai-advanced/sops/index.md +++ /dev/null @@ -1,25 +0,0 @@ -# OpenAI 고급 표준 작업 절차(SOP) - -이 표준 작업 절차(SOP, Standard Operating Procedure) 모음은 원문 문서의 운영 패턴을 직접 따르거나 응용할 수 있는 구체적인 실행 지침서(playbook)로 변환한 것입니다. - -SOP란 반복 가능한 작업을 일관되게 실행할 수 있도록 단계별로 정리한 절차 문서로, 팀의 암묵지를 명시적인 지식으로 전환하는 데 핵심 역할을 합니다. - -## 포함된 표준 작업 절차 - -- [`layered-domain-architecture.md`](./layered-domain-architecture.md): - 명시적인 계층과 횡단 관심사(cross-cutting) 경계를 설정한다 -- [`encode-knowledge-into-repo.md`](./encode-knowledge-into-repo.md): - 채팅·문서·메모리에 있는 보이지 않는 지식을 저장소 로컬 파일로 옮긴다 -- [`observability-feedback-loop.md`](./observability-feedback-loop.md): - 에이전트에게 로그·메트릭·트레이스와 반복 가능한 디버그 루프를 제공한다 -- [`chrome-devtools-validation-loop.md`](./chrome-devtools-validation-loop.md): - 브라우저 자동화와 스냅샷을 활용하여 UI 동작을 깔끔해질 때까지 검증한다 - -## 사용 방법 - -1. 현재 병목 지점에 맞는 표준 작업 절차를 선택하십시오. -2. 체크리스트를 사용하여 누락된 산출물이나 툴링을 갖추십시오. -3. 결과로 나온 규칙들을 복사한 `repo-template/` 문서들에 인코딩하십시오. -4. 반복되는 리뷰 의견은 검사·스크립트·가드레일(guardrail)로 전환하십시오. - -이 절차들을 맹목적으로 따르지 않아도 됩니다. 이것들은 하네스(harness)를 더 명료하고, 강제 가능하며, 반복 가능하게 만들기 위한 것입니다. diff --git a/courses/learn-harness-engineering/docs/ko/resources/openai-advanced/sops/layered-domain-architecture.md b/courses/learn-harness-engineering/docs/ko/resources/openai-advanced/sops/layered-domain-architecture.md deleted file mode 100644 index 553fede9..00000000 --- a/courses/learn-harness-engineering/docs/ko/resources/openai-advanced/sops/layered-domain-architecture.md +++ /dev/null @@ -1,49 +0,0 @@ -# SOP: 계층형 도메인 아키텍처(Layered Domain Architecture) - -에이전트(agent)가 경계를 계속 위반하거나, 계층 간에 로직을 중복 작성하거나, 몇 번의 세션 후에 검토하기 어려운 코드를 생성할 때 이 표준 작업 절차(SOP)를 사용하십시오. - -계층형 아키텍처(layered architecture)란 코드베이스를 역할별 계층으로 분리하고, 각 계층 간의 의존성 방향을 명시적으로 정해 에이전트가 구조를 조용히 훼손하지 않고 빠르게 작업할 수 있게 하는 설계 원칙입니다. - -## 목표 - -도메인 경계를 명시적으로 만들어 에이전트가 구조를 조용히 훼손하지 않고 빠르게 이동할 수 있도록 합니다. - -## 목표 모델 - -비즈니스 도메인 내에서는 다음과 같은 방향성 흐름을 선호하십시오. - -`Types -> Config -> Repo -> Service -> Runtime -> UI` - -횡단 관심사(cross-cutting concern)는 명시적인 공급자(provider) 또는 어댑터(adapter)를 통해 진입해야 합니다. 공유 유틸리티는 도메인 외부에 위치하며, 도메인 로직을 축적해서는 안 됩니다. - -## 설정 체크리스트 - -- `ARCHITECTURE.md`에 현재 도메인들을 정의한다. -- `ARCHITECTURE.md`에 허용된 의존성 방향을 기록한다. -- auth, 텔레메트리(telemetry), 외부 API 등 횡단 관심사 인터페이스를 기록한다. -- 현재 가장 문제가 되는 경계 위반에 대한 짧은 노트를 하나 추가한다. -- 린트, 테스트, 또는 스크립트로 기계적으로 강제할 항목을 결정한다. - -## 실행 절차 - -1. 구현 스타일에 손을 대기 전에 코드베이스를 도메인으로 매핑한다. -2. 각 도메인에 대해 허용된 계층 시퀀스를 확인한다. -3. 모든 횡단 관심사를 식별하고 공급자 또는 어댑터를 통해 라우팅한다. -4. 애매한 공유 로직을 소유 도메인으로 이동하거나 진정한 범용 유틸리티로 분리한다. -5. 규칙을 `ARCHITECTURE.md`에 문서화한다. -6. 가장 비용이 큰 위반 사항에 대해 하나의 실행 가능한 가드레일(guardrail)을 추가한다. -7. 변경 후 품질 점수를 업데이트한다. - -## 완료 정의 - -- 새로운 에이전트가 어떤 계층이 변경을 소유하는지 파악할 수 있다. -- UI 코드가 더 이상 저장소나 외부 사이드 이펙트에 직접 접근하지 않는다. -- 횡단 관심사는 명명된 진입점을 갖는다. -- 적어도 하나의 중요한 경계가 기계적으로 강제된다. - -## 업데이트해야 할 저장소 산출물 - -- `ARCHITECTURE.md` -- `docs/QUALITY_SCORE.md` -- `docs/design-docs/` — 근거가 변경된 경우 -- `docs/PLANS.md` 또는 활성 실행 계획(execution plan) diff --git a/courses/learn-harness-engineering/docs/ko/resources/openai-advanced/sops/observability-feedback-loop.md b/courses/learn-harness-engineering/docs/ko/resources/openai-advanced/sops/observability-feedback-loop.md deleted file mode 100644 index 7eaf889f..00000000 --- a/courses/learn-harness-engineering/docs/ko/resources/openai-advanced/sops/observability-feedback-loop.md +++ /dev/null @@ -1,43 +0,0 @@ -# SOP: 관측 가능성 피드백 루프(Observability Feedback Loop) - -디버깅이 느리거나, 에이전트(agent)가 증거 없이 성공을 주장하거나, 런타임 동작이 코드 자체보다 파악하기 어려울 때 이 표준 작업 절차(SOP)를 사용하십시오. - -관측 가능성(observability)이란 로그, 메트릭, 트레이스와 같은 런타임 신호를 통해 시스템 내부 상태를 외부에서 추론할 수 있는 능력을 말합니다. 피드백 루프(feedback loop)는 이러한 신호를 에이전트가 코드 검사뿐 아니라 실행 결과로부터 추론할 수 있도록 반복 가능하게 구성한 것입니다. - -## 목표 - -에이전트에게 로컬 피드백 루프를 제공하여 로그, 메트릭, 트레이스, 실행 가능한 워크로드를 통해 코드 검사만이 아닌 실행 결과로부터 추론할 수 있게 합니다. - -## 최소 스택 - -- 애플리케이션이 구조화된 로그를 내보낸다 -- 애플리케이션이 가능한 경우 메트릭과 트레이스를 내보낸다 -- 로컬 팬아웃(fan-out) 또는 수집 계층 -- 로그, 메트릭, 트레이스에 대한 쿼리 인터페이스 -- 각 변경 후 재실행할 수 있는 반복 가능한 워크로드 또는 사용자 여정 - -## 실행 절차 - -1. 가장 중요한 황금 런타임 여정(golden runtime journey)을 정의한다. -2. 시작 및 핵심 경로에 구조화된 로그를 추가한다. -3. 유용한 경우 지연 시간, 실패 횟수, 큐 깊이에 대한 메트릭을 추가한다. -4. 느리거나 여러 단계로 구성된 흐름에 트레이스 또는 타이밍 마커를 추가한다. -5. 로컬 개발 환경에서 신호를 쿼리 가능하게 만든다. -6. 에이전트에게 재실행할 반복 가능한 워크로드 또는 시나리오를 하나 제공한다. -7. 루프를 강제한다: 쿼리 -> 상관 분석 -> 추론 -> 구현 -> 재시작 -> 재실행 -> 검증. - -## 디버그 세션 체크리스트 - -- 무엇이 실패했는가? -- 어떤 신호가 실패를 증명하는가? -- 어떤 계층이 실패를 소유하는가? -- 수정 후 무엇이 변경되었는가? -- 앱이 깔끔하게 재시작되었는가? -- 재실행 후 동일한 워크로드가 통과했는가? - -## 완료 정의 - -- 에이전트가 런타임 증거로부터 실패 모드를 설명할 수 있다. -- 각 변경 후 동일한 워크로드를 재실행할 수 있다. -- 재시작과 재실행이 일반적인 작업 루프의 일부이다. -- 신뢰성(reliability) 신호가 `docs/RELIABILITY.md`에 문서화되어 있다. diff --git a/courses/learn-harness-engineering/docs/ko/resources/reference/coding-agent-startup-flow.md b/courses/learn-harness-engineering/docs/ko/resources/reference/coding-agent-startup-flow.md deleted file mode 100644 index 4330e297..00000000 --- a/courses/learn-harness-engineering/docs/ko/resources/reference/coding-agent-startup-flow.md +++ /dev/null @@ -1,33 +0,0 @@ -# 코딩 에이전트 시작 흐름 (Coding Agent Startup Flow) - -초기화(initialization)가 완료된 후 모든 세션 시작 시 이 흐름을 사용하십시오. 이 고정된 순서를 따르면 에이전트(agent)가 상태를 재발견하는 데 시간을 낭비하지 않고 즉시 작업에 집중할 수 있습니다. - -## 고정된 시작 템플릿 (Fixed Startup Template) - -1. `pwd`를 실행하여 저장소 루트를 확인합니다. -2. `claude-progress.md`를 읽습니다. -3. `feature_list.json`을 읽습니다. -4. `git log --oneline -5`로 최근 커밋(commit)을 검토합니다. -5. `./init.sh`를 실행합니다. -6. 기준선(baseline) 스모크(smoke) 또는 엔드-투-엔드(end-to-end) 경로를 실행합니다. -7. 기준선이 손상된 경우 먼저 수정합니다. -8. 가장 높은 우선순위의 미완성 기능을 선택합니다. -9. 검증이 완료되거나 명시적으로 차단될 때까지 그 기능만 작업합니다. - -## 이 순서가 중요한 이유 (Why This Order Matters) - -- `pwd`는 잘못된 디렉터리에서 실수로 작업하는 것을 방지합니다. -- 진행 및 기능 파일은 새 편집이 시작되기 전에 지속적인 상태(durable state)를 복구합니다. -- 최근 커밋(commit)은 가장 최근에 변경된 것을 설명합니다. -- `init.sh`는 메모리에 의존하는 대신 시작을 표준화합니다. -- 기준선(baseline) 검증은 새 작업이 이를 숨기기 전에 손상된 시작 상태를 잡아냅니다. - -## 세션 종료 미러 (End-Of-Session Mirror) - -같은 세션은 다음으로 종료되어야 합니다. - -1. 진행 상태 기록 -2. 기능 상태 업데이트 -3. 필요 시 핸드오프(handoff) 작성 -4. 안전한 작업 커밋(commit) -5. 클린(clean)한 재시작 경로 유지 diff --git a/courses/learn-harness-engineering/docs/ko/resources/reference/glossary.md b/courses/learn-harness-engineering/docs/ko/resources/reference/glossary.md deleted file mode 100644 index 0c971877..00000000 --- a/courses/learn-harness-engineering/docs/ko/resources/reference/glossary.md +++ /dev/null @@ -1,120 +0,0 @@ ---- -title: 한국어 용어집 (Glossary) -description: Learn Harness Engineering 한국어판 전반에서 사용되는 핵심 용어의 한·영 대응표와 정의입니다. ---- - -# 한국어 용어집 (Glossary) - -이 문서는 한국어판 전체에서 일관된 번역어를 보장하기 위한 단일 진실 원천(Single Source of Truth)입니다. 각 강의·프로젝트·리소스 문서에서 처음 등장하는 전문 용어는 본 용어집의 표기를 따릅니다. - -## 표기 규칙 (Convention) - -- **첫 등장 형식**: 본문에서 처음 나오는 전문 용어는 `한국어(English)` 형태로 병기합니다. 예: `하네스(harness)`, `에이전트(agent)`. -- **두 번째 이후**: 한국어만 사용하되, 문맥상 모호하면 다시 영어를 병기할 수 있습니다. -- **고유명사·도구명**: `Claude Code`, `Codex`, `VitePress`, `GitHub Actions` 등은 영어 그대로 둡니다. -- **시스템 토큰**: `BLOCKING`, `HARD`, `[CP]`, `@plan-writer`, `Skill("...")` 등 코드·프롬프트에서 의미를 갖는 토큰은 번역하지 않습니다. -- **명령어·파일 경로**: `npm run docs:build`, `docs/.vitepress/config.mts` 등은 영어로 유지합니다. - -## 핵심 개념 (Core Concepts) - -| English | 한국어 | 정의 | -|---------|--------|------| -| Harness | 하네스 | AI 코딩 에이전트가 안정적으로 동작하도록 환경·상태·검증·제어를 묶어서 제공하는 외골격(스캐폴딩) 시스템. 본 강의의 중심 개념. | -| Harness Engineering | 하네스 엔지니어링 | 하네스를 설계·구축·유지보수하는 공학적 실천 분야. | -| Agent | 에이전트 | 자율적으로 작업을 수행하는 AI 소프트웨어 단위. 본 강의에서는 주로 코딩 에이전트(coding agent)를 가리킵니다. | -| Coding Agent | 코딩 에이전트 | 코드 작성·리팩터·테스트 등 개발 작업을 수행하는 AI 에이전트. | -| State | 상태 | 에이전트가 다음 행동을 결정할 때 참조하는 영속 데이터(작업 진행 로그, 기능 목록, git 히스토리, 체크리스트 등). | -| Context | 컨텍스트 | 에이전트가 한 번의 추론에 사용하는 입력 정보의 집합. 모델의 컨텍스트 윈도(context window)에 담긴 내용. | -| Context Window | 컨텍스트 윈도 | LLM이 한 번에 처리할 수 있는 토큰의 최대 길이. | -| Repository as System of Record | 시스템 오브 레코드(SoR)로서의 저장소 | 작업 결과·결정·근거를 모두 git 저장소에 영속화하여 신뢰의 단일 원천으로 삼는 원칙. | - -## 작업·산출물 (Work and Deliverables) - -| English | 한국어 | 정의 | -|---------|--------|------| -| Deliverable | 산출물 | 작업의 결과로 만들어지는 파일·문서·코드 등 검증 가능한 실체. | -| Specification | 명세 | 작업의 입력·동작·출력을 명확히 정의한 문서. `spec`로 줄여 쓰기도 합니다. | -| Plan | 계획 | 명세를 만족시키기 위한 단계별 작업 순서와 책임 배분 문서. | -| TODO Checklist | TODO 체크리스트 | 계획을 실행 가능한 단위 작업으로 분해한 체크박스 목록. | -| Commit | 커밋 | git에서 변경 사항을 영속화하는 단위. | -| Branch | 브랜치 | 독립적인 작업 흐름을 유지하기 위한 git 분기. | -| Worktree | 워크트리 | 같은 저장소에서 여러 작업 흐름을 동시에 진행하기 위한 별도 작업 디렉터리. | -| Pull Request (PR) | 풀 리퀘스트(PR) | 한 브랜치의 변경 사항을 다른 브랜치로 병합 요청하는 GitHub의 협업 단위. | - -## 검증과 품질 (Verification and Quality) - -| English | 한국어 | 정의 | -|---------|--------|------| -| Verification | 검증 | 산출물이 명세를 만족하는지 객관적 증거로 확인하는 절차. | -| Validation | 유효성 검사 | 입력값이 제약을 만족하는지 확인하는 절차. 검증보다 좁은 의미. | -| Critique | 비평(크리틱) | 계획·설계·산출물의 약점을 사전에 발견하기 위한 적대적(adversarial) 검토. | -| Review | 리뷰 | 코드·문서를 동료 또는 에이전트가 읽고 피드백을 남기는 절차. | -| Test | 테스트 | 코드의 동작을 자동으로 확인하는 검증 수단(단위·통합·E2E 등). | -| TDD (Test-Driven Development) | 테스트 주도 개발 | RED→GREEN→IMPROVE 순서로 테스트를 먼저 쓴 뒤 구현하는 방식. | -| Acceptance Criteria | 인수 기준 | 작업이 완료되었다고 인정받기 위해 만족해야 할 객관적 조건. | -| Quality Gate | 품질 게이트 | 다음 단계로 진행하기 전에 통과해야 하는 자동·수동 검사 묶음. | -| Lint | 린트 | 정적 분석으로 스타일·잠재적 오류를 검출하는 도구(예: `ruff`, `eslint`). | -| Type Check | 타입 검사 | 정적 타입 시스템으로 타입 오류를 사전에 감지하는 절차(예: `pyright`, `tsc --noEmit`). | - -## 하네스 구성 요소 (Harness Components) - -| English | 한국어 | 정의 | -|---------|--------|------| -| Skill | 스킬 | 특정 작업 흐름을 묘사한 재사용 가능한 프롬프트 모듈. 에이전트가 필요할 때 로드합니다. | -| Hook | 훅 | 에이전트의 라이프사이클(예: 도구 호출 전/후, 세션 시작) 시점에 실행되는 사용자 정의 스크립트. | -| Rule | 규칙 | 에이전트가 항상 따라야 하는 정책·제약을 모아 둔 마크다운 문서. | -| Subagent / Agent (role) | 서브에이전트 / 역할 에이전트 | 메인 세션이 위임할 수 있는 전문 역할(예: `@api-implement`, `@code-review`). | -| Orchestration | 오케스트레이션 | 여러 에이전트·도구·검사를 정해진 순서로 묶어 실행하는 상위 흐름 제어. | -| Permission | 권한 | 에이전트가 특정 도구·파일·명령을 사용할 수 있는지 제어하는 설정. | -| Preflight | 사전 점검(프리플라이트) | 본 작업을 시작하기 전에 환경·전제 조건이 갖춰졌는지 확인하는 단계. | -| Guardrail | 가드레일 | 에이전트가 위험한 행동을 하지 못하도록 막는 정책·검증 장치. | -| Telemetry | 텔레메트리 | 에이전트 실행 중에 수집하는 측정 지표(이벤트 로그·지연 시간·실패율 등). | - -## 실패와 복구 (Failure and Recovery) - -| English | 한국어 | 정의 | -|---------|--------|------| -| Fail-loud | 실패-크게(fail-loud) | 오류가 발생하면 즉시 눈에 띄게 알리는 정책. 침묵 실패의 반대. | -| Silent Failure | 무음(無音) 실패 | 오류가 났는데 아무런 신호 없이 다음 단계로 진행되는 현상. | -| Continuity | 연속성 | 장시간 작업 중에도 컨텍스트·상태가 유지되어 끊기지 않는 성질. | -| Handoff | 핸드오프 | 한 세션 또는 한 에이전트에서 다음 세션·에이전트로 작업을 인계하는 행위. | -| Session Handoff | 세션 핸드오프 | 컨텍스트가 잘릴 위험이 있을 때 다음 세션이 이어 받을 수 있도록 상태를 영속화하는 패턴. | -| Checkpoint | 체크포인트 | 복구 가능한 진행 지점에서 상태를 영속화한 시점. | -| Rollback | 롤백 | 특정 시점으로 상태를 되돌리는 행위. | - -## 초기화와 컨벤션 (Initialization and Conventions) - -| English | 한국어 | 정의 | -|---------|--------|------| -| Initialization | 초기화 | 새 프로젝트나 작업 환경에서 하네스를 설정하는 단계. | -| Onboarding | 온보딩 | 새 구성원(사람 또는 에이전트)이 프로젝트 컨벤션을 학습·적용하도록 돕는 절차. | -| Convention | 컨벤션 | 팀이 합의한 명명·구조·작업 방식의 표준. | -| Bootstrap | 부트스트랩 | 빈 상태에서 동작 가능한 최소 환경을 갖추는 일. | -| Scaffold | 스캐폴딩 | 작업 시작점이 될 디렉터리·파일 구조를 미리 생성해 주는 도구·패턴. | - -## 도구 및 생태계 (Tools and Ecosystem) - -| English | 한국어(또는 그대로) | 비고 | -|---------|---------------------|------| -| Claude Code | Claude Code | 그대로 사용. Anthropic의 코딩 에이전트 CLI. | -| Codex | Codex | 그대로 사용. OpenAI의 코딩 에이전트 CLI. | -| Cursor | Cursor | 그대로 사용. AI 코드 에디터. | -| VitePress | VitePress | 그대로 사용. 마크다운 기반 정적 사이트 생성기. | -| MCP (Model Context Protocol) | MCP(모델 컨텍스트 프로토콜) | 에이전트가 외부 도구·리소스를 발견·호출하기 위한 표준. | -| LSP (Language Server Protocol) | LSP(언어 서버 프로토콜) | 에디터와 언어 도구가 코드 정보를 주고받는 표준. | -| GitHub Actions | GitHub Actions | 그대로 사용. GitHub의 CI/CD 시스템. | -| LangGraph | LangGraph | 그대로 사용. 상태 그래프 기반 에이전트 프레임워크. | - -## 보존 토큰 (Untranslated Tokens) - -다음 토큰은 한국어판 본문에서도 절대 번역하지 않고 영어 그대로 유지합니다. - -- 시스템 의미 토큰: `BLOCKING`, `HARD`, `[CP]`, `RED`, `GREEN`, `IMPROVE`, `DONE`, `RETRY`, `BLOCKED` -- 에이전트 식별자: `@plan-writer`, `@code-review`, `@doc-writer`, `@ui-implement` 등 모든 `@agent-name` -- 스킬 호출: `Skill("omb-doc")`, `Skill("omb-tdd")` 등 모든 `Skill("...")` 형태 -- 파일 경로·명령어: `docs/.vitepress/config.mts`, `npm run docs:build`, `git commit -m "..."` 등 -- frontmatter 키: `title`, `description`, `layout` 등 `---` 블록 내부의 영어 키 이름 - -## 향후 갱신 (Maintenance) - -강의 번역 중 새 용어가 발견되면 본 문서에 즉시 추가하고, 후속 강의 번역 시 갱신본을 입력으로 사용합니다(계획 Task #16b 참조). diff --git a/courses/learn-harness-engineering/docs/ko/resources/reference/index.md b/courses/learn-harness-engineering/docs/ko/resources/reference/index.md deleted file mode 100644 index eed9fd0d..00000000 --- a/courses/learn-harness-engineering/docs/ko/resources/reference/index.md +++ /dev/null @@ -1,17 +0,0 @@ -# 한국어 참고 자료 (Reference) - -이 노트들은 템플릿(template) 모음을 단순한 파일 더미가 아닌 실제로 작동하는 하네스(harness)로 사용하는 방법을 설명합니다. 각 문서는 특정 실패 유형(failure mode)을 다루며, 함께 읽으면 안정적인 장기 에이전트(agent) 작업 환경을 구축하는 전체 그림을 파악할 수 있습니다. - -## 참고 노트 (Reference Notes) - -- [`method-map.md`](./method-map.md): 장기 실행 중 자주 발생하는 실패 유형을 해당 문제를 가장 먼저 해결하는 산출물(artifact) 또는 정책(policy)에 매핑합니다. -- [`initializer-agent-playbook.md`](./initializer-agent-playbook.md): 기능 작업이 시작되기 전에 초기화 에이전트가 남겨야 할 것들. -- [`coding-agent-startup-flow.md`](./coding-agent-startup-flow.md): 이후 코딩 세션을 위한 고정된 세션 시작 흐름. -- [`prompt-calibration.md`](./prompt-calibration.md): 루트 지침을 비대하고 취약하게 만들지 않으면서 날카롭게 유지하는 방법. - -## 권장 읽기 순서 (Suggested Reading Order) - -1. `method-map.md` -2. `initializer-agent-playbook.md` -3. `coding-agent-startup-flow.md` -4. `prompt-calibration.md` diff --git a/courses/learn-harness-engineering/docs/ko/resources/reference/initializer-agent-playbook.md b/courses/learn-harness-engineering/docs/ko/resources/reference/initializer-agent-playbook.md deleted file mode 100644 index 4eb84230..00000000 --- a/courses/learn-harness-engineering/docs/ko/resources/reference/initializer-agent-playbook.md +++ /dev/null @@ -1,35 +0,0 @@ -# 초기화 에이전트 플레이북 (Initializer Agent Playbook) - -저장소에서 첫 번째 본격적인 세션, 즉 점진적인 기능 작업이 시작되기 전에 이 플레이북(playbook)을 사용하십시오. 초기화(initialization)는 이후 모든 세션이 안정적으로 작동할 수 있는 기반을 마련하는 단계입니다. - -## 목표 (Goal) - -이후 세션이 시작 명령, 현재 상태, 작업 경계를 재도출하지 않고도 동작을 구현할 수 있도록 안정적인 운영 환경(operating surface)을 만드는 것입니다. - -## 필수 산출물 (Required Outputs) - -초기화 에이전트(agent)는 최소한 다음 산출물을 남겨야 합니다. - -- `AGENTS.md` 또는 `CLAUDE.md` 와 같은 루트 지침 파일 -- `feature_list.json` 과 같은 기계 가독 기능 목록 -- `claude-progress.md` 와 같은 지속적인 진행 산출물 -- `init.sh` 와 같은 표준 시작 헬퍼(helper) -- 기준선(baseline) 스캐폴드(scaffold)를 캡처하는 초기 안전 커밋(commit) - -## 체크리스트 (Checklist) - -1. 표준 시작 경로를 정의합니다. -2. 표준 검증(verification) 경로를 정의합니다. -3. 진행 로그(progress log)를 생성하고 시작 상태를 기록합니다. -4. 작업을 상태(status)가 있는 명시적인 기능으로 분해합니다. -5. 첫 번째 클린(clean) 기준선 커밋(commit)을 생성합니다. - -## 성공 테스트 (Success Test) - -이전 채팅 컨텍스트(context) 없는 새 세션이 다음 질문에 답할 수 있어야 합니다. - -- 이 저장소가 무엇을 하는지 -- 어떻게 시작하는지 -- 어떻게 검증(verify)하는지 -- 무엇이 미완성인지 -- 다음으로 최선의 행동은 무엇인지 diff --git a/courses/learn-harness-engineering/docs/ko/resources/reference/method-map.md b/courses/learn-harness-engineering/docs/ko/resources/reference/method-map.md deleted file mode 100644 index 952841aa..00000000 --- a/courses/learn-harness-engineering/docs/ko/resources/reference/method-map.md +++ /dev/null @@ -1,16 +0,0 @@ -# 메서드 맵 (Method Map) - -이 표는 장기 코딩 에이전트(agent) 작업에서 자주 발생하는 실패 유형(failure mode)을, 보통 가장 먼저 이를 해결하는 산출물(artifact) 또는 운영 규칙(operating rule)에 매핑합니다. 새로운 문제를 발견했을 때 전역 지침 파일에 더 많은 텍스트를 덧붙이는 대신, 해당 실패 유형에 직접 대응하는 최소한의 산출물을 추가하는 원칙을 따릅니다. - -| 실패 유형 (Failure mode) | 실제 현상 (What it looks like in practice) | 주요 수정 방법 (Primary fix) | 보조 산출물 (Supporting artifact) | -| --- | --- | --- | --- | -| 콜드 스타트 혼란 (Cold-start confusion) | 새 세션이 대부분의 시간을 설정 및 상태를 재발견하는 데 사용함 | 저장소를 시스템 오브 레코드(system of record)로 만들기 | `claude-progress.md` | -| 범위 확산 (Scope sprawl) | 에이전트가 여러 기능을 시작하지만 어느 것도 깔끔하게 마무리하지 못함 | 활성 범위 제한 | `feature_list.json` | -| 섣부른 완료 선언 (Premature completion) | 에이전트가 코드 편집 후, 실행 가능한 증거(runnable proof) 없이 완료를 주장함 | 완료를 증거에 연결 | `clean-state-checklist.md` | -| 취약한 시작 (Fragile startup) | 모든 세션이 프로젝트를 부팅하는 방법을 다시 배움 | 설정 및 검증 표준화 | `init.sh` | -| 약한 핸드오프(Weak handoff) | 다음 세션이 무엇이 검증되었고, 손상되었고, 다음에 무엇을 할지 알 수 없음 | 명시적인 핸드오프(handoff)로 종료 | `session-handoff.md` | -| 주관적 리뷰 (Subjective review) | 리뷰 품질이 취향이나 기억에 의존함 | 고정된 카테고리로 산출물 점수화 | `evaluator-rubric.md` | - -## 운영 원칙 (Operating Principle) - -관찰된 실패 유형(failure mode)을 직접 해결하는 최소한의 산출물을 추가하십시오. 하나의 전역 지침 파일에 더 많은 텍스트를 덤핑(dumping)하여 모든 신뢰성 문제를 해결하려 하지 마십시오. diff --git a/courses/learn-harness-engineering/docs/ko/resources/reference/prompt-calibration.md b/courses/learn-harness-engineering/docs/ko/resources/reference/prompt-calibration.md deleted file mode 100644 index 32c7b1ad..00000000 --- a/courses/learn-harness-engineering/docs/ko/resources/reference/prompt-calibration.md +++ /dev/null @@ -1,23 +0,0 @@ -# 프롬프트 보정 (Prompt Calibration) - -루트 지침(root instructions)은 가능한 모든 행동을 나열하는 것이 아니라 운영 프레임(operating frame)을 정의해야 합니다. 파일이 지나치게 커지면 에이전트(agent)가 중요한 정보를 찾기 어려워지고, 유지보수도 까다로워집니다. - -## 루트 파일에 유지할 내용 (Keep In The Root File) - -- 저장소 목적과 범위 -- 시작 경로(startup path) -- 검증(verification) 경로 -- 타협 불가 제약(non-negotiable constraints) -- 필수 상태 산출물(required state artifacts) -- 세션 종료 규칙 - -## 루트 파일에서 이동할 내용 (Move Out Of The Root File) - -- 오래된 히스토리(history) 엣지 케이스(edge cases) -- 주제별 구현 세부 사항 -- 코드 근처에 있어야 할 로컬 아키텍처(architecture) 노트 -- 하나의 서브시스템(subsystem)에만 적용되는 예시 - -## 작업 규칙 (Working Rule) - -루트 파일은 새 세션이 빠르게 방향을 잡도록 도와야 합니다. 파일이 과거 모든 실패 사례를 덤핑(dumping)하는 장소가 되고 있다면, 세부 내용을 더 작은 문서로 분리하고 대신 링크를 사용하십시오. diff --git a/courses/learn-harness-engineering/docs/ko/resources/templates/AGENTS.md b/courses/learn-harness-engineering/docs/ko/resources/templates/AGENTS.md deleted file mode 100644 index 1aac2c4f..00000000 --- a/courses/learn-harness-engineering/docs/ko/resources/templates/AGENTS.md +++ /dev/null @@ -1,52 +0,0 @@ -# AGENTS.md - -이 파일은 하네스(harness) 리소스 라이브러리에 포함된 샘플 템플릿입니다. 실제 프로젝트에 복사한 뒤 프로젝트 고유의 명령어와 경로로 수정하여 사용하십시오. 에이전트(agent)가 세션을 시작할 때 가장 먼저 읽는 파일로, 안정적인 장기 작업의 토대를 제공합니다. - -이 저장소는 장기 코딩 에이전트 작업을 위해 설계되었습니다. 목표는 코드 산출물(output)을 극대화하는 것이 아닙니다. 목표는 다음 세션이 추측 없이 계속할 수 있도록 저장소를 남겨 두는 것입니다. - -## 시작 워크플로우 (Startup Workflow) - -코드를 작성하기 전에: - -1. `pwd`로 작업 디렉터리를 확인합니다. -2. `claude-progress.md`를 읽어 최신 검증(verified) 상태와 다음 단계를 확인합니다. -3. `feature_list.json`을 읽어 가장 높은 우선순위의 미완성 기능을 선택합니다. -4. `git log --oneline -5`로 최근 커밋(commit)을 검토합니다. -5. `./init.sh`를 실행합니다. -6. 새 작업을 시작하기 전에 필요한 스모크(smoke) 또는 엔드-투-엔드(end-to-end) 검증을 실행합니다. - -기준선(baseline) 검증이 이미 실패 중인 경우, 먼저 이를 수정하십시오. 망가진 시작 상태 위에 새 기능 작업을 쌓지 마십시오. - -## 작업 규칙 (Working Rules) - -- 한 번에 하나의 기능만 작업합니다. -- 코드가 추가되었다고 기능을 완료로 표시하지 않습니다. -- 차단(blocker)이 좁은 지원 수정을 강제하지 않는 한, 선택한 기능 범위 내에서 변경 사항을 유지합니다. -- 구현 중에 검증 규칙을 조용히 변경하지 않습니다. -- 채팅 요약보다 지속적인 저장소 산출물(durable repo artifacts)을 선호합니다. - -## 필수 산출물 (Required Artifacts) - -- `feature_list.json`: 기능 상태의 단일 진실 원천(single source of truth) -- `claude-progress.md`: 세션 로그 및 현재 검증 상태 -- `init.sh`: 표준 시작 및 검증 경로 -- `session-handoff.md`: 대규모 세션을 위한 선택적 간결 핸드오프(handoff) - -## 완료 정의 (Definition Of Done) - -다음 조건이 모두 충족될 때만 기능이 완료됩니다. - -- 목표 동작이 구현됨 -- 필요한 검증이 실제로 실행됨 -- 증거(evidence)가 `feature_list.json` 또는 `claude-progress.md`에 기록됨 -- 저장소가 표준 시작 경로에서 재시작 가능한 상태를 유지함 - -## 세션 종료 (End Of Session) - -세션을 종료하기 전에: - -1. `claude-progress.md`를 업데이트합니다. -2. `feature_list.json`을 업데이트합니다. -3. 미해결된 위험 또는 차단 항목을 기록합니다. -4. 작업이 안전한 상태가 되면 설명적인 메시지로 커밋합니다. -5. 다음 세션이 즉시 `./init.sh`를 실행할 수 있도록 저장소를 충분히 클린(clean)한 상태로 남겨 둡니다. diff --git a/courses/learn-harness-engineering/docs/ko/resources/templates/CLAUDE.md b/courses/learn-harness-engineering/docs/ko/resources/templates/CLAUDE.md deleted file mode 100644 index fdb4fc8b..00000000 --- a/courses/learn-harness-engineering/docs/ko/resources/templates/CLAUDE.md +++ /dev/null @@ -1,45 +0,0 @@ -# CLAUDE.md - -이 파일은 하네스(harness) 리소스 라이브러리에 포함된 샘플 템플릿입니다. 실제 프로젝트에 복사한 뒤 프로젝트 고유의 명령어와 경로로 수정하여 사용하십시오. `AGENTS.md`와 구조는 동일하며 Claude Code의 지침 스타일에 맞게 형식화된 버전입니다. - -여러분은 장기 구현 작업을 위해 설계된 저장소에서 작업하고 있습니다. 속도보다 신뢰성 있는 완료, 세션 간 연속성(continuity), 명시적인 검증(verification)을 우선하십시오. - -## 운영 루프 (Operating Loop) - -모든 세션 시작 시: - -1. `pwd`를 실행하여 예상된 저장소 루트에 있는지 확인합니다. -2. `claude-progress.md`를 읽습니다. -3. `feature_list.json`을 읽습니다. -4. `git log --oneline -5`로 최근 커밋(commit)을 검토합니다. -5. `./init.sh`를 실행합니다. -6. 기준선(baseline) 스모크(smoke) 또는 엔드-투-엔드(end-to-end) 경로가 이미 손상되었는지 확인합니다. - -그런 다음 미완성 기능 하나를 정확히 선택하고, 검증이 완료되거나 차단되었다고 명시적으로 문서화할 때까지 그 기능만 작업하십시오. - -## 규칙 (Rules) - -- 한 번에 하나의 활성 기능만. -- 실행 가능한 증거(evidence) 없이 완료를 주장하지 않습니다. -- 미완성 작업을 숨기기 위해 기능 목록을 재작성하지 않습니다. -- 작업이 완료된 것처럼 보이게 하기 위해 테스트를 제거하거나 약화시키지 않습니다. -- 시스템 오브 레코드(system of record)로서 저장소 산출물(repository artifacts)을 사용합니다. - -## 필수 파일 (Required Files) - -- `feature_list.json` -- `claude-progress.md` -- `init.sh` -- `session-handoff.md` — 간결한 핸드오프(handoff)가 유용할 때 - -## 완료 게이트 (Completion Gate) - -필요한 검증이 성공하고 결과가 기록된 후에만 기능을 `passing`으로 이동할 수 있습니다. - -## 중단하기 전에 (Before You Stop) - -1. 진행 로그(progress log)를 업데이트합니다. -2. 기능 상태(feature state)를 업데이트합니다. -3. 여전히 손상되거나 미검증된 것을 기록합니다. -4. 저장소가 재개 가능한 상태가 되면 커밋합니다. -5. 다음 세션을 위한 클린(clean)한 재시작 경로를 남겨 둡니다. diff --git a/courses/learn-harness-engineering/docs/ko/resources/templates/claude-progress.md b/courses/learn-harness-engineering/docs/ko/resources/templates/claude-progress.md deleted file mode 100644 index 7584eaaa..00000000 --- a/courses/learn-harness-engineering/docs/ko/resources/templates/claude-progress.md +++ /dev/null @@ -1,37 +0,0 @@ -# 진행 로그 (Progress Log) - -에이전트(agent)가 세션마다 이 파일에 기록하고, 새 세션은 이 파일을 가장 먼저 읽어 현재 상태와 다음 단계를 파악합니다. 이 파일은 프로젝트의 단일 진실 원천(single source of truth)입니다. - -## 현재 검증된 상태 (Current Verified State) - -- 저장소 루트 (Repository root): -- 표준 시작 경로 (Standard startup path): -- 표준 검증 경로 (Standard verification path): -- 현재 최우선 미완료 기능 (Current highest-priority unfinished feature): -- 현재 차단 요인 (Current blocker): - -## 세션 로그 (Session Log) - -### 세션 001 - -- 날짜 (Date): -- 목표 (Goal): -- 완료 항목 (Completed): -- 검증 실행 (Verification run): -- 수집된 증거 (Evidence captured): -- 커밋 (Commits): -- 업데이트된 파일 또는 산출물 (Files or artifacts updated): -- 알려진 위험 또는 미해결 이슈 (Known risk or unresolved issue): -- 다음 최적 단계 (Next best step): - -### 세션 002 - -- 날짜 (Date): -- 목표 (Goal): -- 완료 항목 (Completed): -- 검증 실행 (Verification run): -- 수집된 증거 (Evidence captured): -- 커밋 (Commits): -- 업데이트된 파일 또는 산출물 (Files or artifacts updated): -- 알려진 위험 또는 미해결 이슈 (Known risk or unresolved issue): -- 다음 최적 단계 (Next best step): diff --git a/courses/learn-harness-engineering/docs/ko/resources/templates/clean-state-checklist.md b/courses/learn-harness-engineering/docs/ko/resources/templates/clean-state-checklist.md deleted file mode 100644 index 6eb52f3c..00000000 --- a/courses/learn-harness-engineering/docs/ko/resources/templates/clean-state-checklist.md +++ /dev/null @@ -1,10 +0,0 @@ -# 클린 상태 체크리스트 (Clean State Checklist) - -세션 종료 전에 이 체크리스트를 실행하여 다음 세션이 깨끗한 클린 상태(clean state)에서 시작할 수 있도록 저장소를 정돈하십시오. 에이전트(agent)도 세션 종료 루틴의 일부로 이 항목들을 점검해야 합니다. - -- [ ] 표준 시작 경로가 여전히 작동합니다. -- [ ] 표준 검증(verification) 경로가 여전히 실행됩니다. -- [ ] 현재 진행 상태가 진행 로그(progress log)에 기록되어 있습니다. -- [ ] 기능 상태가 실제로 통과(passing)된 것과 미검증된 것을 반영합니다. -- [ ] 미완성 단계가 문서화되지 않은 채 남아 있지 않습니다. -- [ ] 다음 세션이 수동 수정 없이 계속할 수 있습니다. diff --git a/courses/learn-harness-engineering/docs/ko/resources/templates/evaluator-rubric.md b/courses/learn-harness-engineering/docs/ko/resources/templates/evaluator-rubric.md deleted file mode 100644 index 0ccbee17..00000000 --- a/courses/learn-harness-engineering/docs/ko/resources/templates/evaluator-rubric.md +++ /dev/null @@ -1,24 +0,0 @@ -# 평가자 루브릭 (Evaluator Rubric) - -구현 후, 최종 수락 전에 이 루브릭(rubric)을 사용하십시오. 에이전트(agent)는 자기 평가에 약한 경향이 있으므로, 이 점수표(scorecard)를 반복적으로 보정(calibration)하여 인간의 판단과 일치하도록 만들어야 합니다. - -| 범주 (Category) | 질문 (Question) | 점수 (0-2) | 비고 (Notes) | -| --- | --- | --- | --- | -| 정확성 (Correctness) | 구현된 동작이 요청된 기능과 일치하는가? | | | -| 검증 (Verification) | 필요한 검사가 증거(evidence)와 함께 실제로 실행되었는가? | | | -| 범위 규율 (Scope discipline) | 세션이 선택된 기능 범위 내에 머물렀는가? | | | -| 신뢰성 (Reliability) | 결과가 재시작 또는 재실행 후에도 수정 없이 유지되는가? | | | -| 유지보수성 (Maintainability) | 코드와 문서가 다음 세션에서 충분히 이해 가능한가? | | | -| 핸드오프 준비성 (Handoff readiness) | 새 세션이 저장소 산출물(repo artifacts)만으로 작업을 계속할 수 있는가? | | | - -## 판정 (Verdict) - -- Accept -- Revise -- Block - -## 필수 후속 조치 (Required Follow-Up) - -- Missing evidence: -- Required fixes: -- Next review trigger: diff --git a/courses/learn-harness-engineering/docs/ko/resources/templates/feature_list.json b/courses/learn-harness-engineering/docs/ko/resources/templates/feature_list.json deleted file mode 100644 index 138adc6a..00000000 --- a/courses/learn-harness-engineering/docs/ko/resources/templates/feature_list.json +++ /dev/null @@ -1,64 +0,0 @@ -{ - "project": "replace-with-project-name", - "last_updated": "YYYY-MM-DD", - "rules": { - "single_active_feature": true, - "passing_requires_evidence": true, - "do_not_skip_verification": true - }, - "status_legend": { - "not_started": "Work has not begun.", - "in_progress": "The feature is the current active task.", - "blocked": "Work cannot continue until a documented blocker is resolved.", - "passing": "Required verification has passed and evidence is recorded." - }, - "features": [ - { - "id": "chat-001", - "priority": 1, - "area": "chat", - "title": "Create a new conversation", - "user_visible_behavior": "A user can click New Chat and see a fresh empty conversation.", - "status": "not_started", - "verification": [ - "Open the app.", - "Click New Chat.", - "Verify a new conversation appears in the sidebar.", - "Verify the main panel shows an empty conversation state." - ], - "evidence": [], - "notes": "" - }, - { - "id": "chat-002", - "priority": 2, - "area": "chat", - "title": "Send a message in the current conversation", - "user_visible_behavior": "A user can submit a message and see it appear in the active thread.", - "status": "not_started", - "verification": [ - "Open an existing conversation.", - "Type a message into the input.", - "Submit the message.", - "Verify the new message appears in the thread." - ], - "evidence": [], - "notes": "" - }, - { - "id": "chat-003", - "priority": 3, - "area": "chat", - "title": "Persist the active conversation list", - "user_visible_behavior": "A user sees previously created conversations after restarting the app.", - "status": "not_started", - "verification": [ - "Create two conversations.", - "Restart the app.", - "Verify both conversations still appear in the sidebar." - ], - "evidence": [], - "notes": "" - } - ] -} diff --git a/courses/learn-harness-engineering/docs/ko/resources/templates/index.md b/courses/learn-harness-engineering/docs/ko/resources/templates/index.md deleted file mode 100644 index d0d92d94..00000000 --- a/courses/learn-harness-engineering/docs/ko/resources/templates/index.md +++ /dev/null @@ -1,226 +0,0 @@ -# 템플릿 가이드 (Template Guide) - -이 템플릿들은 여러분의 프로젝트에 바로 복사해 사용할 수 있도록 준비된 것입니다. 각 파일은 에이전트(agent) 워크플로우의 특정 목적을 위해 설계되었습니다. 프로젝트의 명령어, 경로, 기능 이름, 검증 단계에 맞게 내용을 수정하십시오. - -## 시작 방법 - -먼저 프로젝트 루트에 다음 네 파일을 복사하십시오. - -1. `AGENTS.md` 또는 `CLAUDE.md` -2. `init.sh` -3. `claude-progress.md` -4. `feature_list.json` - -프로젝트가 성장함에 따라 나머지 파일도 추가하십시오. - ---- - -## AGENTS.md - -루트 지침 파일입니다. 에이전트가 세션을 시작할 때 가장 먼저 읽는 파일로, 코드를 작성하기 전에 해야 할 일, 작업 방식, 마무리 방법 등 운영 규칙을 정의합니다. - -**사용 방법:** - -- 프로젝트 루트 디렉터리에 복사하십시오. -- 시작 워크플로우 단계를 실제 프로젝트 경로와 명령어로 교체하십시오. -- 팀의 컨벤션(convention)에 맞게 작업 규칙을 조정하십시오. -- "완료 정의(Definition of Done)" 섹션은 가장 중요한 부분이므로 반드시 유지하십시오. - -**에이전트에게 제공하는 역할:** - -- 작업 시작 전에 진행 상태와 기능(feature) 상태를 먼저 읽도록 지시합니다. -- 한 번에 하나의 기능만 작업하도록 강제합니다. -- 완료 표시 전에 증거(evidence)를 요구합니다. -- 세션 종료 시 저장소를 클린 상태(clean state)로 마무리하는 방법을 정의합니다. - -Codex 또는 다른 에이전트에는 `AGENTS.md`를 사용하십시오. Claude Code와 함께 작업하는 경우 `CLAUDE.md`를 사용하십시오. 구조는 동일하며 Claude의 지침 스타일에 맞게 형식화된 것입니다. - -## init.sh - -시작 스크립트입니다. 의존성 설치, 검증(verification), 시작 명령 출력을 한 번에 실행합니다. - -**사용 방법:** - -- 프로젝트 루트에 복사하십시오. -- 상단의 다음 세 변수를 편집하십시오. - - `INSTALL_CMD` — 의존성 설치 명령 (예: `npm install`, `pip install -r requirements.txt`) - - `VERIFY_CMD` — 기본 검증 명령 (예: `npm test`, `pytest`) - - `START_CMD` — 개발 서버 시작 명령 (예: `npm run dev`) -- 실행 권한을 부여하십시오: `chmod +x init.sh` - -**동작:** - -1. 현재 디렉터리를 출력합니다(올바른 위치에서 실행되고 있는지 확인). -2. 의존성을 설치합니다. -3. 검증 명령을 실행합니다. -4. 시작 명령을 출력합니다(`RUN_START_COMMAND=1`이 설정된 경우 직접 실행). - -검증이 실패하면 에이전트는 다른 작업을 시작하기 전에 기준선(baseline)을 먼저 수정해야 합니다. - -## claude-progress.md - -진행 로그(progress log)입니다. 모든 세션은 이 파일에 기록하고, 새 세션은 이 파일을 가장 먼저 읽습니다. - -**사용 방법:** - -- 프로젝트 루트에 복사하십시오. -- "현재 검증된 상태(Current Verified State)" 섹션을 프로젝트 정보로 채우십시오. -- 각 세션 후에 세션 레코드를 업데이트하십시오. - -**각 필드의 의미:** - -- **현재 검증된 상태(Current Verified State)** — 프로젝트의 현재 상태를 나타내는 단일 진실 원천 - - `Repository root directory` — 프로젝트 위치 - - `Standard startup path` — 프로젝트를 실행하는 명령 - - `Standard verification path` — 테스트 실행 명령 - - `Highest priority unfinished feature` — 다음 세션에서 작업할 항목 - - `Current blocker` — 막혀 있는 항목 -- **세션 레코드(Session Record)** — 세션당 하나의 항목 - - `Goal` — 계획한 작업 - - `Completed` — 실제로 완료된 것 - - `Verification run` — 실행된 테스트 - - `Evidence recorded` — 기록된 증거 - - `Commits` — 커밋된 내용 - - `Known risks` — 손상될 수 있는 것 - - `Next best action` — 다음 세션의 시작점 - -## feature_list.json - -기능 추적기(feature tracker)입니다. 에이전트가 구현해야 할 모든 기능 목록과 상태(status), 검증 단계, 증거를 기계 가독 형식으로 관리합니다. - -**사용 방법:** - -- 프로젝트 루트에 복사하십시오. -- 예시 기능을 실제 기능으로 교체하십시오. -- 각 기능에는 다음이 필요합니다. - - `id` — 짧은 고유 식별자 - - `priority` — 정수, 낮을수록 높은 우선순위 - - `area` — 앱의 어떤 부분인지 (예: "chat", "import", "search") - - `title` — 짧은 설명 - - `user_visible_behavior` — 기능이 작동할 때 사용자가 보게 될 것 - - `status` — `not_started`, `in_progress`, `blocked`, `passing` 중 하나 - - `verification` — 작동 여부를 확인하는 단계별 지침 - - `evidence` — 검증 통과를 기록한 증거 (에이전트가 채움) - - `notes` — 추가 맥락 - -**상태(status) 규칙:** - -- `not_started` — 아직 작업하지 않음 -- `in_progress` — 현재 작업 중인 기능 (한 번에 하나만) -- `blocked` — 문서화된 문제로 진행 불가 -- `passing` — 검증 통과 및 증거 기록됨 - -에이전트는 한 번에 하나의 기능만 `in_progress` 상태여야 합니다. - -## session-handoff.md - -세션 간 간결한 핸드오프(handoff) 노트입니다. 세션이 종료되고 다음 세션이 빠르게 이어 받길 원할 때 사용하십시오. - -**사용 방법:** - -- 프로젝트 루트에 복사하십시오. -- 각 세션 종료 시 채우십시오(에이전트가 채울 수도 있음). - -**각 섹션이 다루는 내용:** - -- **현재 검증된 것(Currently verified)** — 작동이 확인된 것과 실행된 검증 -- **이번 세션의 변경 사항(Changes this session)** — 변경된 코드 또는 인프라 -- **손상되거나 미검증된 것(Still broken or unverified)** — 알려진 문제와 위험 영역 -- **다음으로 최선의 행동(Next best action)** — 다음 세션에서 할 일과 건드리지 말아야 할 것 -- **명령어(Commands)** — 시작, 검증, 디버그 명령 참고용 - -이 파일은 소규모 세션에서는 선택 사항입니다. 세션이 길거나 프로젝트에 여러 활성 영역이 있을 때 중요해집니다. - -## clean-state-checklist.md - -각 세션 종료 전에 실행할 체크리스트입니다. 다음 세션이 클린 상태(clean state)로 시작할 수 있도록 저장소가 좋은 상태인지 확인합니다. - -**사용 방법:** - -- 프로젝트 루트에 복사하십시오. -- 세션을 닫기 전에 확인하십시오. -- 에이전트도 세션 종료 루틴의 일부로 이 항목들을 점검해야 합니다. - -**확인 항목:** - -- 표준 시작 경로가 여전히 작동하는지 -- 표준 검증 경로가 여전히 실행되는지 -- 진행 로그가 업데이트되었는지 -- 기능 목록이 실제 상태를 반영하는지 (허위 `passing` 항목 없음) -- 미완성 작업이 기록되지 않은 채 남아 있지 않은지 -- 다음 세션이 수동 수정 없이 계속할 수 있는지 - -## evaluator-rubric.md - -에이전트 산출물(output) 품질을 검토하기 위한 점수표(scorecard)입니다. 세션 후 또는 프로젝트 마일스톤(milestone)에서 작업이 기준을 충족하는지 평가할 때 사용하십시오. - -**사용 방법:** - -- 프로젝트 루트에 복사하십시오. -- 세션(또는 일련의 세션) 후 여섯 가지 차원에서 에이전트의 작업을 점수화하십시오. -- 각 차원은 0-2점으로 채점합니다. - -**여섯 가지 차원:** - -1. **정확성(Correctness)** — 구현이 목표 동작과 일치하는가? -2. **검증(Verification)** — 필요한 검사가 증거와 함께 실제로 실행되었는가? -3. **범위 규율(Scope discipline)** — 에이전트가 선택한 기능 범위 안에 머물렀는가? -4. **신뢰성(Reliability)** — 결과가 재시작 또는 재실행 후에도 유지되는가? -5. **유지보수성(Maintainability)** — 코드와 문서가 다음 세션에서 충분히 이해 가능한가? -6. **핸드오프 준비성(Handoff readiness)** — 새 세션이 저장소 산출물만으로 계속할 수 있는가? - -**결론 옵션:** - -- Accept — 기준 충족 -- Revise — 수락 전 수정 필요 -- Block — 먼저 해결해야 할 근본적인 문제 있음 - -**중요: 평가자(evaluator)는 보정(calibration)이 필요합니다.** 기본 설정에서 에이전트는 자기 평가를 잘 하지 못합니다. 문제를 발견하고도 스스로 승인하는 방향으로 논리를 전개하는 경향이 있습니다. 다음과 같이 반복적으로 조정해야 합니다. - -1. 완료된 스프린트(sprint)에 평가자(evaluator)를 실행합니다. -2. 점수를 여러분 자신의 인간적 판단과 비교합니다. -3. 차이가 있는 곳에서 루브릭(rubric)의 합격/불합격 기준을 더 구체화합니다. -4. 다시 실행하여 정렬 상태를 확인합니다. -5. 평가자가 인간 리뷰와 일관되게 일치할 때까지 반복합니다. - -3-5회의 보정(calibration) 라운드를 예상하십시오. 각 변경 사항을 기록하여 무엇이 정렬을 개선했는지 추적할 수 있도록 하십시오. - -## quality-document.md - -프로젝트의 각 제품 도메인(domain)과 아키텍처(architecture) 레이어(layer)를 등급화하는 품질 스냅샷(snapshot)입니다. 개별 세션 산출물이 아닌 코드베이스(codebase) 전체의 건전성을 시간에 따라 추적합니다. - -**사용 방법:** - -- 프로젝트 루트에 복사하십시오. -- 세션 시작 전: 코드베이스에서 가장 약한 부분을 파악하기 위해 읽으십시오. -- 세션 후: 변경된 사항에 따라 등급을 업데이트하십시오. -- 시간이 지남에 따라: 스냅샷을 비교하여 어떤 하네스(harness) 변경이 실제로 코드베이스 건전성을 개선했는지 확인하십시오. - -**등급화 항목:** - -- **제품 도메인(Product domains)** (예: 문서 가져오기, Q&A 흐름, 색인화): 각 도메인은 검증 상태, 에이전트 가독성(agent legibility), 테스트 안정성, 주요 격차에 대한 등급(A-D)을 받습니다. -- **아키텍처 레이어(Architectural layers)** (예: 메인 프로세스, preload, renderer, services): 각 레이어는 경계 적용(boundary enforcement)과 에이전트 가독성에 대한 등급을 받습니다. - -**중요성:** - -평가자 루브릭은 개별 에이전트 산출물을 점수화합니다. 품질 문서는 코드베이스 자체를 점수화합니다. 두 문서는 서로 다른 질문에 답합니다. - -- 평가자 루브릭(evaluator rubric): "에이전트가 이번 세션에서 좋은 작업을 했는가?" -- 품질 문서(quality document): "프로젝트가 시간이 지남에 따라 강해지고 있는가, 약해지고 있는가?" - -**업데이트 시점:** - -- 각 중요한 세션 후 -- 벤치마크(benchmark) 비교 전 -- 정리 또는 단순화 작업 후 -- 새 에이전트 또는 모델을 프로젝트에 온보딩(onboarding)할 때 - -**하네스 단순화 연계:** - -품질 문서는 하네스(harness) 단순화도 지원합니다. 모든 하네스 구성 요소는 모델이 할 수 없는 것에 대한 가정을 내포합니다. 모델이 발전함에 따라 이러한 가정은 낡아집니다. 구성 요소가 여전히 필요한지 확인하려면 다음을 수행하십시오. - -1. 품질 문서 스냅샷(snapshot)을 찍습니다. -2. 하네스 구성 요소 하나를 제거합니다. -3. 벤치마크 작업 집합을 실행합니다. -4. 또 다른 스냅샷을 찍습니다. -5. 비교합니다. 등급이 하락하지 않았다면 해당 구성 요소는 오버헤드(overhead)였습니다. 하락했다면 복원합니다. diff --git a/courses/learn-harness-engineering/docs/ko/resources/templates/init.sh b/courses/learn-harness-engineering/docs/ko/resources/templates/init.sh deleted file mode 100755 index d7522d66..00000000 --- a/courses/learn-harness-engineering/docs/ko/resources/templates/init.sh +++ /dev/null @@ -1,29 +0,0 @@ -#!/usr/bin/env bash - -set -euo pipefail - -ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" -cd "$ROOT_DIR" - -# Replace these commands with the correct commands for your repository. -INSTALL_CMD=(npm install) -VERIFY_CMD=(npm test) -START_CMD=(npm run dev) - -echo "==> Working directory: $PWD" -echo "==> Syncing dependencies" -"${INSTALL_CMD[@]}" - -echo "==> Running baseline verification" -"${VERIFY_CMD[@]}" - -echo "==> Startup command" -printf ' %q' "${START_CMD[@]}" -printf '\n' - -if [ "${RUN_START_COMMAND:-0}" = "1" ]; then - echo "==> Starting the app" - exec "${START_CMD[@]}" -fi - -echo "Set RUN_START_COMMAND=1 if you want init.sh to launch the app directly." diff --git a/courses/learn-harness-engineering/docs/ko/resources/templates/quality-document.md b/courses/learn-harness-engineering/docs/ko/resources/templates/quality-document.md deleted file mode 100644 index b1428b9a..00000000 --- a/courses/learn-harness-engineering/docs/ko/resources/templates/quality-document.md +++ /dev/null @@ -1,45 +0,0 @@ -# 품질 문서 (Quality Document) - -각 제품 도메인(product domain)과 아키텍처(architecture) 레이어(layer)에 대한 품질 스냅샷(snapshot)입니다. 에이전트(agent)와 사람 모두 이 문서를 사용하여 코드베이스(codebase)에서 강한 부분과 보완이 필요한 부분을 빠르게 파악할 수 있습니다. - -이 문서는 개별 세션 산출물이 아닌 프로젝트 전체의 건전성을 시간에 따라 추적한다는 점에서 평가자 루브릭(evaluator rubric)과 구분됩니다. - -**업데이트 주기:** 각 중요한 세션 후, 또는 새 작업 단계 시작 전. - -**등급 기준:** - -- **A**: 모든 검증(verification) 통과, 클린(clean) 아키텍처, 에이전트 가독성(agent-legible), 안정적인 테스트 -- **B**: 검증 통과, 대체로 클린, 가독성 또는 테스트 커버리지에 사소한 격차 있음 -- **C**: 부분적으로 작동, 알려진 격차, 일부 코드 영역이 에이전트가 이해하기 어려움 -- **D**: 작동하지 않거나, 주요 구조적 문제 있음 - ---- - -## 제품 도메인 (Product Domains) - -| 도메인 (Domain) | 등급 (Grade) | 검증 (Verification) | 에이전트 가독성 (Agent Legibility) | 테스트 안정성 (Test Stability) | 주요 격차 (Key Gaps) | 마지막 업데이트 (Last Updated) | -|--------|-------|-------------|-----------------|---------------|----------|-------------| -| Document Import | - | - | - | - | - | - | -| Document Management | - | - | - | - | - | - | -| Document Indexing | - | - | - | - | - | - | -| Q&A Flow | - | - | - | - | - | - | -| Grounded Answers | - | - | - | - | - | - | - -## 아키텍처 레이어 (Architectural Layers) - -| 레이어 (Layer) | 등급 (Grade) | 경계 적용 (Boundary Enforcement) | 에이전트 가독성 (Agent Legibility) | 주요 격차 (Key Gaps) | 마지막 업데이트 (Last Updated) | -|-------|-------|---------------------|-----------------|----------|-------------| -| Main Process | - | - | - | - | - | -| Preload | - | - | - | - | - | -| Renderer | - | - | - | - | - | -| Services | - | - | - | - | - | - -## 변경 이력 (Change History) - -### YYYY-MM-DD - -- Changes: -- Domains promoted: -- Demoted: -- New gaps identified: -- Gaps closed: diff --git a/courses/learn-harness-engineering/docs/ko/resources/templates/session-handoff.md b/courses/learn-harness-engineering/docs/ko/resources/templates/session-handoff.md deleted file mode 100644 index 7b4678bd..00000000 --- a/courses/learn-harness-engineering/docs/ko/resources/templates/session-handoff.md +++ /dev/null @@ -1,32 +0,0 @@ -# 세션 핸드오프 (Session Handoff) - -세션이 종료되고 다음 세션이 빠르게 이어 받기 위한 간결한 핸드오프(handoff) 노트입니다. 에이전트(agent)가 세션 종료 시 이 파일을 채우도록 하거나, 사람이 직접 채울 수 있습니다. 세션이 길거나 프로젝트에 여러 활성 영역이 있을 때 특히 중요합니다. - -## 현재 검증된 것 (Verified Now) - -- What is currently working: -- What verification actually ran: - -## 이번 세션의 변경 사항 (Changed This Session) - -- Code or behavior added: -- Infrastructure or harness changes: - -## 손상되거나 미검증된 것 (Broken Or Unverified) - -- Known defect: -- Unverified path: -- Risk for the next session: - -## 다음으로 최선의 행동 (Next Best Step) - -- Highest-priority unfinished feature: -- Why it is next: -- What counts as passing: -- What must not change during that step: - -## 명령어 (Commands) - -- Startup: -- Verification: -- Focused debug command: diff --git a/courses/learn-harness-engineering/docs/ko/skills/index.md b/courses/learn-harness-engineering/docs/ko/skills/index.md deleted file mode 100644 index de69edd9..00000000 --- a/courses/learn-harness-engineering/docs/ko/skills/index.md +++ /dev/null @@ -1,49 +0,0 @@ -# 스킬 (Skills) - -이 디렉터리에는 본 강의와 함께 제공되는 번들(bundled) AI 에이전트(agent) 스킬(skill)이 포함되어 있습니다. 스킬은 AI 코딩 에이전트(Claude Code, Codex, Cursor, Windsurf 등)가 특수한 작업을 수행하기 위해 로드(load)할 수 있는 자기 완결적 프롬프트 템플릿(template)입니다. - -## harness-creator - -AI 코딩 에이전트를 위한 프로덕션(production) 등급의 하네스 엔지니어링(harness engineering) 스킬입니다. 다섯 가지 핵심 하네스(harness) 서브시스템(subsystem)인 지침(instructions), 상태(state), 검증(verification), 범위(scope), 세션 라이프사이클(session lifecycle)을 생성·평가·개선하도록 돕습니다. - -### 기능 (What It Does) - -- **처음부터 하네스 생성** — AGENTS.md, 기능 목록(feature list), 검증(verification) 워크플로우 -- **기존 하네스 개선** — 다섯 가지 서브시스템 평가와 우선순위화된 개선 사항 -- **세션 연속성(continuity) 설계** — 메모리(memory) 영속화, 진행 추적, 핸드오프(handoff) 절차 -- **프로덕션 패턴 적용** — 메모리, 컨텍스트 엔지니어링(context engineering), 도구 안전성, 멀티 에이전트(multi-agent) 조율 - -### 빠른 시작 (Quick Start) - -스킬 파일은 저장소의 [`skills/harness-creator/`](https://github.com/walkinglabs/learn-harness-engineering/tree/main/skills/harness-creator)에 위치합니다. - -Claude Code와 함께 사용하려면 `harness-creator/` 디렉터리를 프로젝트의 스킬 경로에 복사하거나, 에이전트가 SKILL.md 파일을 가리키도록 설정하십시오. - -### 참고 패턴 (Reference Patterns) - -이 스킬에는 6가지 심층 참고 문서가 포함됩니다. - -| 패턴 (Pattern) | 사용 시점 (When to Use) | -|---------|-------------| -| Memory Persistence | 에이전트가 세션 간에 기억을 잃을 때 | -| Context Engineering | 컨텍스트 예산 관리, JIT 로딩 | -| Tool Registry | 도구 안전성, 동시성 제어 | -| Multi-Agent Coordination | 병렬화, 특수화 워크플로우 | -| Lifecycle & Bootstrap | 훅(hook), 백그라운드 작업, 초기화(initialization) | -| Gotchas | 15가지 비자명한 실패 유형과 수정 방법 | - -### 템플릿 (Templates) - -이 스킬에는 바로 사용할 수 있는 템플릿이 포함되어 있습니다. - -- `agents.md` — 작업 규칙이 있는 AGENTS.md 스캐폴드(scaffold) -- `feature-list.json` — JSON Schema와 기능 목록 예시 -- `init.sh` — 표준 초기화(initialization) 스크립트 -- `progress.md` — 세션 진행 로그 템플릿 - -### 이 스킬이 만들어진 방법 (How This Skill Was Built) - -`harness-creator`는 **skill-creator** 방법론을 사용하여 개발되었습니다. 이는 에이전트 스킬을 생성·테스트·반복하기 위한 Anthropic의 공식 메타 스킬입니다. skill-creator는 구조화된 워크플로우(draft → test → evaluate → iterate)를 내장된 평가 러너(eval runner), 채점기(grader), 벤치마크 뷰어(benchmark viewer)와 함께 제공합니다. - -- **skill-creator 소스**: [anthropics/skills — skill-creator](https://github.com/anthropics/skills/tree/main/skills/skill-creator) -- **Claude Code 스킬 문서**: [anthropics/claude-code — plugin-dev/skills](https://github.com/anthropics/claude-code/tree/main/plugins/plugin-dev/skills) diff --git a/courses/learn-harness-engineering/docs/public/screenshots/readme/en-home.png b/courses/learn-harness-engineering/docs/public/screenshots/readme/en-home.png deleted file mode 100644 index 7240a37e5d315d225786c4287170beda7729ef6e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 163315 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
  • {GM;_g{bc%h``jY_F4ke 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%0DaB^b%awoG@N{29jYEnXvEb*HPkT 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 JEMegDe*j^G%Mt(p diff --git a/courses/learn-harness-engineering/docs/public/screenshots/readme/en-lecture-01.png b/courses/learn-harness-engineering/docs/public/screenshots/readme/en-lecture-01.png deleted file mode 100644 index 2ca7567f4440510c4115c8a065e1d41ad07c5918..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 155119 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%0AAEwC(J{!m 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 z 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%0DYs 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

    1igFa~*`?=s)@A@qV`Ya2e_L`p&V4hwS3Uyhp* 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 )%{>lb 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�V&bdM*ZTMAQp8Q?UxlB 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 Rz)ujKNXbd&i$C}Ie*l3Hti%8S diff --git a/courses/learn-harness-engineering/docs/public/screenshots/readme/zh-lecture-01.png b/courses/learn-harness-engineering/docs/public/screenshots/readme/zh-lecture-01.png deleted file mode 100644 index 8d23c9a5bc43d045e068a644f4029a30e6ccb6d4..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 199030 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

    y8I=q@ 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 z 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 diff --git a/courses/learn-harness-engineering/docs/ru/index.md b/courses/learn-harness-engineering/docs/ru/index.md deleted file mode 100644 index 66e916bc..00000000 --- a/courses/learn-harness-engineering/docs/ru/index.md +++ /dev/null @@ -1,73 +0,0 @@ -# Добро пожаловать в Learn Harness Engineering - -Learn Harness Engineering — курс, посвящённый инженерии AI-агентов для кодинга. Мы глубоко изучили и обобщили самые передовые теории и практики Harness Engineering в индустрии. Наши основные источники: -- [OpenAI: Harness engineering: leveraging Codex in an agent-first world](https://openai.com/index/harness-engineering/) -- [Anthropic: Effective harnesses for long-running agents](https://www.anthropic.com/engineering/effective-harnesses-for-long-running-agents) -- [Anthropic: Harness design for long-running application development](https://www.anthropic.com/engineering/harness-design-long-running-apps) -- [Awesome Harness Engineering](https://github.com/walkinglabs/awesome-harness-engineering) - -Через системный дизайн окружения, управление состоянием, верификацию и контроль курс учит, как сделать агентские инструменты вроде Codex и Claude Code по-настоящему надёжными. Он помогает строить фичи, чинить баги и автоматизировать задачи разработки, ограничивая AI-ассистента явными правилами и границами. - -## С чего начать - -Выберите свой путь обучения. Курс разделён на теоретические лекции, практические проекты и готовую к копированию библиотеку материалов. - -

    - -## Основной механизм harness - -Harness не «делает модель умнее» — он создаёт для неё замкнутую **рабочую систему**. Основной поток работы можно понять по этой простой диаграмме: - -```mermaid -graph TD - A["Чёткая цель
    AGENTS.md"] --> B("Инициализация
    init.sh") - B --> C{"Выполнение задач
    AI Agent"} - C -->|Возникла проблема| D["Обратная связь
    CLI / Логи"] - D -->|Автоисправление| C - C -->|Код готов| E{"Проверка и QA
    Тесты"} - E -->|Провал| D - E -->|Пройдено| F["Очистка и передача
    claude-progress.md"] - - classDef primary fill:#D95C41,stroke:#C14E36,color:#fff,font-weight:bold; - classDef process fill:#F4F3EE,stroke:#D1D1D1,color:#1A1A1A; - classDef check fill:#EAE8E1,stroke:#B3B3B3,color:#1A1A1A; - - class A,F primary; - class B,D process; - class C,E check; -``` - -## Что вы узнаете - -Ключевые концепции, которыми вы овладеете: - -
      -
    • Ограничивать поведение агента явными правилами и границами.
    • -
    • Сохранять контекст в длительных задачах между сессиями.
    • -
    • Не давать агенту объявлять успех слишком рано.
    • -
    • Верифицировать работу через сквозные тесты и саморефлексию.
    • -
    • Делать runtime наблюдаемым и отлаживаемым.
    • -
    - -## Дальнейшие шаги - -Когда вы поймёте основы, эти материалы помогут углубиться: - - diff --git a/courses/learn-harness-engineering/docs/ru/lectures/lecture-01-why-capable-agents-still-fail/code/failure-pattern-demo.ts b/courses/learn-harness-engineering/docs/ru/lectures/lecture-01-why-capable-agents-still-fail/code/failure-pattern-demo.ts deleted file mode 100644 index 311e665b..00000000 --- a/courses/learn-harness-engineering/docs/ru/lectures/lecture-01-why-capable-agents-still-fail/code/failure-pattern-demo.ts +++ /dev/null @@ -1,170 +0,0 @@ -/** - * failure-pattern-demo.ts - * - * Simulates the 4-step failure pattern that capable agents fall into: - * 1. Incomplete context - * 2. Locally reasonable changes - * 3. No global verification - * 4. Premature completion - * - * Run: npx tsx docs/lectures/lecture-01-why-capable-agents-still-fail/code/failure-pattern-demo.ts - */ - -// --------------------------------------------------------------------------- -// Types -// --------------------------------------------------------------------------- - -interface StepState { - step: number; - name: string; - contextAvailable: string[]; - contextMissing: string[]; - actionTaken: string; - localOutcome: string; - globalImpact: string; - completed: boolean; -} - -// --------------------------------------------------------------------------- -// Simulated "model" -- a simple decision function that bases its output -// solely on whatever context it has been given. -// --------------------------------------------------------------------------- - -function modelDecide(context: string[], task: string): string { - const has = (s: string) => context.some((c) => c.includes(s)); - - // The task is to "add a search endpoint to the API". - // Correct answer requires knowing about auth middleware and rate limiting. - - if (!has("auth")) { - return "Created new route handler /search without authentication checks"; - } - if (!has("rate-limit")) { - return "Added search route with auth but forgot rate limiting"; - } - if (!has("test-standards")) { - return "Implemented search with auth and rate-limit, but no tests"; - } - return "Fully implemented search endpoint with auth, rate-limit, and tests"; -} - -// --------------------------------------------------------------------------- -// Failure simulation -// --------------------------------------------------------------------------- - -function simulateFailurePattern(): StepState[] { - const steps: StepState[] = []; - - // ---- Step 1: Incomplete Context ---- - const step1Context = ["project structure", "route definitions"]; - const step1Missing = ["auth middleware", "rate-limiting policy", "test standards"]; - const step1Decision = modelDecide(step1Context, "add search endpoint"); - - steps.push({ - step: 1, - name: "Incomplete Context", - contextAvailable: step1Context, - contextMissing: step1Missing, - actionTaken: step1Decision, - localOutcome: "Looks good -- route compiles, returns data", - globalImpact: "Missing auth means unauthenticated access to search", - completed: false, - }); - - // ---- Step 2: Locally Reasonable Changes ---- - // The agent adds auth after a hint, but still lacks other context. - const step2Context = [...step1Context, "auth middleware"]; - const step2Missing = ["rate-limiting policy", "test standards"]; - const step2Decision = modelDecide(step2Context, "add search endpoint"); - - steps.push({ - step: 2, - name: "Locally Reasonable Changes", - contextAvailable: step2Context, - contextMissing: step2Missing, - actionTaken: step2Decision, - localOutcome: "Route has auth -- looks complete locally", - globalImpact: "No rate limiting means the endpoint can be abused", - completed: false, - }); - - // ---- Step 3: No Global Verification ---- - const step3Context = [...step2Context, "rate-limiting policy"]; - const step3Missing = ["test standards"]; - const step3Decision = modelDecide(step3Context, "add search endpoint"); - - steps.push({ - step: 3, - name: "No Global Verification", - contextAvailable: step3Context, - contextMissing: step3Missing, - actionTaken: step3Decision, - localOutcome: "Feature appears fully implemented", - globalImpact: "No tests -- regression risk, violates project standards", - completed: false, - }); - - // ---- Step 4: Premature Completion ---- - steps.push({ - step: 4, - name: "Premature Completion", - contextAvailable: step3Context, - contextMissing: step3Missing, - actionTaken: 'Agent outputs: "Done. Added search endpoint."', - localOutcome: "Agent is satisfied, task marked complete", - globalImpact: "Task is incomplete -- missing tests, no E2E verification", - completed: true, - }); - - return steps; -} - -// --------------------------------------------------------------------------- -// Comparison table -// --------------------------------------------------------------------------- - -function printComparisonTable(steps: StepState[]): void { - console.log("\n" + "=".repeat(90)); - console.log(" FAILURE PATTERN DEMO -- 4 Steps to a Broken Deliverable"); - console.log("=".repeat(90)); - - for (const s of steps) { - console.log(`\n Step ${s.step}: ${s.name}`); - console.log(" " + "-".repeat(60)); - console.log(` Context available : ${s.contextAvailable.join(", ")}`); - console.log(` Context missing : ${s.contextMissing.join(", ") || "(none)"}`); - console.log(` Action taken : ${s.actionTaken}`); - console.log(` Local outcome : ${s.localOutcome}`); - console.log(` Global impact : ${s.globalImpact}`); - console.log(` Marked complete? : ${s.completed ? "YES (premature)" : "No"}`); - } - - // Summary comparison - console.log("\n" + "=".repeat(90)); - console.log(" COMPARISON: What the agent saw vs. what was actually needed"); - console.log("=".repeat(90)); - - const totalRequired = ["project structure", "route definitions", "auth middleware", "rate-limiting policy", "test standards"]; - const finalAvailable = steps[steps.length - 1].contextAvailable; - - const header = "| Criterion | Available | Missing | Status |"; - const sep = "|------------------------|-----------|---------|----------|"; - console.log("\n" + header); - console.log(sep); - - for (const item of totalRequired) { - const avail = finalAvailable.includes(item); - const row = `| ${item.padEnd(23)}| ${(avail ? "Yes" : "No").padEnd(10)}| ${(!avail ? "Yes" : "").padEnd(8)}| ${(avail ? "OK" : "GAP").padEnd(9)}|`; - console.log(row); - } - - const gapCount = totalRequired.filter((i) => !finalAvailable.includes(i)).length; - console.log("\n Result: Agent completed with " + gapCount + " of " + totalRequired.length + " context items missing."); - console.log(" This is the core failure pattern: each step looked reasonable in isolation.\n"); -} - -// --------------------------------------------------------------------------- -// Run -// --------------------------------------------------------------------------- - -printComparisonTable(simulateFailurePattern()); diff --git a/courses/learn-harness-engineering/docs/ru/lectures/lecture-01-why-capable-agents-still-fail/code/failure-signals-checklist.md b/courses/learn-harness-engineering/docs/ru/lectures/lecture-01-why-capable-agents-still-fail/code/failure-signals-checklist.md deleted file mode 100644 index ab35662e..00000000 --- a/courses/learn-harness-engineering/docs/ru/lectures/lecture-01-why-capable-agents-still-fail/code/failure-signals-checklist.md +++ /dev/null @@ -1,10 +0,0 @@ -# Чеклист сигналов о провале - -Используйте этот чеклист при разборе прогона со слабым harness. - -- Спросил ли агент, как запускать приложение, или сделал неверные предположения? -- Создал ли он каталоги или абстракции, не соответствующие задуманному - продукту? -- Остановился ли он после визуальной UI-оболочки без полного рабочего сценария? -- Оставил ли он заметки или артефакты, помогающие следующему прогону продолжить? -- Сможет ли свежая сессия понять, что произошло, меньше чем за пять минут? diff --git a/courses/learn-harness-engineering/docs/ru/lectures/lecture-01-why-capable-agents-still-fail/code/index.md b/courses/learn-harness-engineering/docs/ru/lectures/lecture-01-why-capable-agents-still-fail/code/index.md deleted file mode 100644 index 1d72c048..00000000 --- a/courses/learn-harness-engineering/docs/ru/lectures/lecture-01-why-capable-agents-still-fail/code/index.md +++ /dev/null @@ -1,7 +0,0 @@ -# Код для Лекции 01 - -Используйте эту папку для небольших примеров, демонстрирующих: - -- сильную модель, проваливающуюся в слабой среде -- недоопределённую конфигурацию репозитория -- отсутствие петель обратной связи diff --git a/courses/learn-harness-engineering/docs/ru/lectures/lecture-01-why-capable-agents-still-fail/code/underspecified-task.md b/courses/learn-harness-engineering/docs/ru/lectures/lecture-01-why-capable-agents-still-fail/code/underspecified-task.md deleted file mode 100644 index f1af2148..00000000 --- a/courses/learn-harness-engineering/docs/ru/lectures/lecture-01-why-capable-agents-still-fail/code/underspecified-task.md +++ /dev/null @@ -1,18 +0,0 @@ -# Пример недоопределённой задачи - -Сделай десктоп-приложение базы знаний с AI-ответами на вопросы. - -Ограничения: - -- Не указаны -- Команда запуска не задана -- Структура папок не описана -- Модель данных не определена -- Явных критериев готовности нет - -Типичные исходы такого промпта: - -- агент изобретает структуру на ходу -- приложение может собираться, но запускается нестабильно -- UI может появиться раньше, чем будет рабочий путь ингеста/запросов -- агент часто останавливается после косметического успеха diff --git a/courses/learn-harness-engineering/docs/ru/lectures/lecture-01-why-capable-agents-still-fail/index.md b/courses/learn-harness-engineering/docs/ru/lectures/lecture-01-why-capable-agents-still-fail/index.md deleted file mode 100644 index da3fcf4b..00000000 --- a/courses/learn-harness-engineering/docs/ru/lectures/lecture-01-why-capable-agents-still-fail/index.md +++ /dev/null @@ -1,117 +0,0 @@ -[中文版本 →](../../../zh/lectures/lecture-01-why-capable-agents-still-fail/) - -> Примеры кода: [code/](https://github.com/walkinglabs/learn-harness-engineering/blob/main/docs/en/lectures/lecture-01-why-capable-agents-still-fail/code/) -> Практический проект: [Project 01. Prompt-only vs. rules-first](./../../projects/project-01-baseline-vs-minimal-harness/index.md) - -# Лекция 01. Сильные модели не означают надёжного исполнения - -Считаете себя бывалым в мире AI — есть подписка Claude Pro, ключ к API GPT-4o, цифры с лидерборда SWE-bench выучены наизусть. И вот вы наконец отдаёте AI-агенту реальный проект, переполненные уверенностью. Результат? Он добавляет фичу, но ломает тесты, чинит баг, но создаёт два новых, работает 20 минут и гордо заявляет «готово» — а вы смотрите на код и видите, что это совсем не то, о чём вы просили. - -Первая мысль? «Модель слабовата. Пора апгрейдиться». Стоп. Прежде чем доставать кошелёк, подумайте: возможно, проблема вовсе не в модели. - -Посмотрим на цифры. К концу 2025 года самые сильные кодинг-агенты на SWE-bench Verified выдают примерно 50–60%. И это на тщательно отобранных задачах с понятными описаниями issue и существующими тестами. Перейдите в свою повседневную среду разработки — расплывчатые требования, отсутствие тестов, неявные бизнес-правила, разбросанные повсюду — и эта цифра только падает. - -Но за этими числами скрывается контринтуитивная истина. - -## Один и тот же конь, разные судьбы - -Anthropic провели контролируемый эксперимент. Один и тот же промпт («собери 2D ретро-конструктор игр»), одна и та же модель (Opus 4.5). Первый прогон: голая среда без поддержки — 20 минут, $9, ключевые механики игры просто не работают. Второй прогон: полный harness (архитектура из трёх агентов: planner + generator + evaluator) — 6 часов, $200, в игру можно играть. - -Модель не меняли. Opus 4.5 оставался Opus 4.5. Изменилась упряжь. - -Статья OpenAI 2025 года про harness engineering говорит прямо: Codex в репозитории с хорошим harness переходит из «ненадёжного» в «надёжный». Обратите внимание на формулировку — не «чуть лучше», а качественный сдвиг. Как с породистой лошадью: ездить можно и без подходящей упряжи, но далеко не уедете, быстро не разгонитесь, и упасть — не сюрприз. Harness и есть вся эта упряжь — **всё в инженерной инфраструктуре, что находится за пределами весов модели.** - -## Где на самом деле застревают агенты - -Так что же конкретно идёт не так? - -Самое частое: вы так и не определили задачу чётко. Говорите «добавь поиск», а агент понимает это совсем не так, как вы — поиск чего? Полнотекстовый или структурированный? Пагинация? Подсветка? Вы не уточнили — агент гадает. Угадал — повезло; не угадал — фиксить дороже, чем было бы изначально внятно поставить задачу. Это как зайти в ресторан и сказать повару «я буду рыбу» — будет ли она тушёная, на пару или в горячем горшочке, остаётся на волю случая. - -Даже если вы всё указали, в проекте есть неявные архитектурные конвенции, о которых агент не знает. Ваша команда стандартизировала синтаксис SQLAlchemy 2.0, а агент по умолчанию пишет код 1.x. Все API-эндпоинты должны использовать OAuth 2.0, но это правило существует только в вашей голове и в сообщении в Slack трёхмесячной давности. Агент этого не видит — не то чтобы он не хотел соблюдать, он буквально не знает, что эти правила существуют. - -Среда — тоже ловушка. Неполная dev-среда, отсутствующие зависимости, неправильные версии инструментов. Агент жжёт драгоценное окно контекста на ошибки `pip install` и несовпадения версий Node вместо того, чтобы решать вашу реальную задачу. Как нанять умелого плотника, но забыть дать ему молоток, гвозди и ровный верстак — каким бы талантливым он ни был, работу он не сделает. - -Ещё чаще: попросту нет способа верифицировать. Нет тестов, нет линта, или команды проверки никогда не были донесены до агента. Агент пишет код, смотрит на него, решает что норм, говорит «готово». Это как просить студента сдать домашку, не дав ему ответника — он думает, что всё правильно, а при проверке — куча ошибок. Anthropic заметили любопытное явление: когда агент чувствует, что контекст заканчивается, он торопится закончить, пропускает верификацию и выбирает простое решение вместо оптимального. Они называют это «контекстной тревогой» — тем же, что случается, когда вы понимаете, что время на экзамене на исходе, и начинаете наугад тыкать в оставшихся тестовых вопросах. - -Длинные задачи на несколько сессий — ещё хуже: все находки прошлой сессии теряются, и каждая новая сессия заново исследует структуру проекта и заново разбирается в организации кода. У агентов без персистентного состояния процент провалов резко растёт на задачах длиннее 30 минут. - -## Ключевая терминология - -Когда вы видите эти сценарии вживую, термины перестают быть просто жаргоном: - -- **Capability Gap**: огромная пропасть между производительностью модели на бенчмарках и на реальных задачах. 50–60% pass rate на SWE-bench Verified означает, что почти половина реальных issue не решается. -- **Harness**: всё за пределами модели — инструкции, инструменты, среда, управление состоянием, верификационная обратная связь. Если это не веса модели — это harness. Та самая «упряжь». -- **Harness-Induced Failure**: у модели достаточно способностей, но в среде исполнения есть структурные дефекты. Контролируемый эксперимент Anthropic это уже доказал. -- **Verification Gap**: разрыв между уверенностью агента в своей работе и реальной корректностью. Агент говорит «я закончил», когда не закончил — самый частый паттерн провала. -- **Diagnostic Loop**: выполнить, увидеть провал, отнести его к конкретному слою harness, починить этот слой, выполнить снова. Это ядро методологии harness engineering. -- **Definition of Done**: набор машинно-проверяемых условий — тесты проходят, линт чистый, проверки типов проходят. Без явного definition of done агент придумает свой. - -## Когда что-то ломается — чините harness первым - -Главный принцип: **когда что-то ломается, не меняйте сразу модель — проверьте harness.** Если та же модель справляется со схожими, хорошо структурированными задачами, считайте, что проблема в harness. Это как с поломкой машины — вы же не сразу подозреваете двигатель. Сначала проверяете, не закончился ли бензин. - -Конкретные шаги: - -**Привязывайте каждый провал к конкретному слою.** Не ограничивайтесь «модель тупит». Спросите: задача была размыта? Контекста не хватало? Не было способов верификации? Сопоставьте каждый провал с одним из пяти защитных слоёв: спецификация задачи, предоставление контекста, среда исполнения, верификационная обратная связь, управление состоянием. Это практические диагностические слои, а не шесть терминов из глоссария выше. Выработайте эту привычку — и в ваших логах «модель недостаточно хороша» будет всплывать всё реже. - -**Пишите явный Definition of Done для каждой задачи.** Не «добавь поиск». А: -``` -Критерии готовности: -- Новый эндпоинт GET /api/search?q=xxx -- Поддержка пагинации, по умолчанию 20 элементов -- В результатах подсвеченные сниппеты -- Весь новый код проходит pytest -- Проверка типов проходит (mypy --strict) -``` - -**Заведите файл AGENTS.md.** Положите его в корень репо, чтобы рассказать агенту про стек проекта, архитектурные конвенции и команды верификации. Это первый шаг harness engineering и шаг с самым высоким ROI. Один файл `AGENTS.md` может быть эффективнее апгрейда на более дорогую модель — я не шучу. - -**Постройте диагностическую петлю.** Не относитесь к провалам как к «модель опять тупит». Считайте их сигналами, что в вашем harness есть дефект. На каждом провале определите слой, почините его, никогда больше так не падайте. Через несколько итераций harness крепнет, а производительность агента стабилизируется. Как ремонт дороги — каждая засыпанная яма делает следующий участок ровнее. - -**Измеряйте улучшения.** Ведите простой лог: каждая задача — успех или провал, и какой слой стал причиной провала. Через несколько кругов вы увидите, какой слой — бутылочное горлышко. Туда и направляйте усилия. - -## Эксперимент в миллион строк - -В 2025 году OpenAI провели агрессивный эксперимент: с помощью Codex построить целый внутренний продукт из пустого git-репозитория. Через пять месяцев в репо было примерно миллион строк кода — прикладная логика, инфраструктура, инструментарий, документация, внутренние dev-инструменты — всё сгенерировано агентом. Три инженера управляли Codex, открыли и смержили около 1500 PR. В среднем 3,5 PR на человека в день. - -Ключевое ограничение: **люди не пишут код напрямую.** Это была не показуха — так заставили команду понять, что меняется, когда основная работа инженера — уже не написание кода, а проектирование сред, формулирование намерения и построение петель обратной связи. - -В начале прогресс был медленнее ожидаемого. Не потому что у Codex не хватало способностей, а потому что среда была недостаточно полной — агенту не хватало нужных инструментов, абстракций и внутренних структур, чтобы продвигаться к высокоуровневым целям. Работа инженеров превратилась в: разбиение больших целей на маленькие строительные блоки (дизайн, код, ревью, тест), сборку их агентом, а затем использование этих блоков для разблокирования более сложных задач. Когда что-то ломалось, фиксом почти никогда не было «попробуем сильнее» — было «какой способности агенту не хватает и как сделать её одновременно понятной и исполняемой?». - -Этот эксперимент напрямую подтверждает главный тезис лекции: **одна и та же модель в голой среде и в среде с полным harness даёт принципиально разные результаты.** Модель не менялась. Менялась среда. - -> Источник: [OpenAI: Harness engineering: leveraging Codex in an agent-first world](https://openai.com/index/harness-engineering/) - -## Более приземлённый пример - -Команда использовала Claude Sonnet, чтобы добавить новый API-эндпоинт в среднеразмерное Python-веб-приложение (FastAPI + PostgreSQL + Redis, ~15 000 строк кода). - -Сначала они дали только одну фразу: «добавь эндпоинты пользовательских настроек под `/api/v2/users`». Результат? Агент потратил 40% окна контекста на исследование структуры репо, выдал код, который выглядел разумно, но не следовал паттернам обработки ошибок проекта, использовал старый синтаксис SQLAlchemy и заявил о завершении, в то время как у эндпоинта были рантайм-ошибки. Следующая сессия должна была заново проделать всю разведку. - -Потом они добавили `AGENTS.md` (с описанием архитектуры проекта и версий стека), явные команды верификации (`pytest tests/api/v2/ && python -m mypy src/`) и architecture decision records. Та же модель успешно справилась во всех трёх независимых прогонах, и эффективность контекста выросла примерно на 60%. - -Модель они не меняли. Они меняли harness. - -## Главное - -- Способности модели и надёжность исполнения — разные вещи. Породистой лошади всё равно нужна хорошая упряжь. -- Когда что-то ломается, сначала проверьте harness, потом модель. Менять модель — самый дорогой вариант, и часто это вообще не проблема модели. -- Каждый провал — сигнал: в вашем harness структурный дефект. Найдите, почините. -- Пять защитных слоёв: спецификация задачи, предоставление контекста, среда исполнения, верификационная обратная связь, управление состоянием. Проверяйте систематически, как врач сначала отметает самые частые причины. -- Один файл `AGENTS.md` может оказаться эффективнее апгрейда на более дорогую модель. Серьёзно. - -## Дальнейшее чтение - -- [OpenAI: Harness Engineering — Leveraging Codex in an Agent-First World](https://openai.com/index/harness-engineering/) -- [Anthropic: Effective Harnesses for Long-Running Agents](https://www.anthropic.com/engineering/effective-harnesses-for-long-running-agents) -- [HumanLayer: Skill Issue — Harness Engineering for Coding Agents](https://humanlayer.dev/articles/harness-engineering-for-coding-agents/) -- [SWE-bench Leaderboard](https://www.swebench.com/) -- [Thoughtworks Technology Radar: Harness Engineering](https://www.thoughtworks.com/radar) - -## Упражнения - -1. **Сравнительный эксперимент**: возьмите хорошо знакомую вам кодовую базу и нетривиальную задачу на изменение. Сначала прогоните агента без harness-поддержки и зафиксируйте провалы. Затем добавьте `AGENTS.md` с явными командами верификации и прогоните снова с тем же агентом. Сравните результаты, относя каждый провал к одному из пяти защитных слоёв. - -2. **Замер verification gap**: возьмите 5 кодинг-задач. После каждой зафиксируйте, заявил ли агент о завершении, а затем независимыми тестами проверьте реальную корректность. Посчитайте долю случаев, когда агент говорит «готово», а на самом деле нет — это и есть ваш verification gap. Затем подумайте: какие команды верификации сократили бы эту долю? - -3. **Практика диагностической петли**: найдите в проекте задачу, на которой агент стабильно падает. Прогоните один раз, зафиксируйте провал. Отнесите его к одному из пяти слоёв. Почините слой. Прогоните снова. Повторите три–пять раз, фиксируя улучшения каждый раз. diff --git a/courses/learn-harness-engineering/docs/ru/lectures/lecture-02-what-a-harness-actually-is/code/harness-components.md b/courses/learn-harness-engineering/docs/ru/lectures/lecture-02-what-a-harness-actually-is/code/harness-components.md deleted file mode 100644 index 46efa381..00000000 --- a/courses/learn-harness-engineering/docs/ru/lectures/lecture-02-what-a-harness-actually-is/code/harness-components.md +++ /dev/null @@ -1,21 +0,0 @@ -# Пример компонентов harness - -Для кодинг-агента, работающего в локальном репозитории: - -- Модель: - сама LLM - -- Harness: - - системный промпт - - AGENTS.md - - инструмент bash - - инструменты чтения/записи файлов - - доступ к git - - локальная файловая система - - стартовые скрипты - - команды тестирования - - stop-хуки - - проверки линтера - - петля evaluator - -Если поменять любой из перечисленных компонентов harness — вы меняете эффективного агента. diff --git a/courses/learn-harness-engineering/docs/ru/lectures/lecture-02-what-a-harness-actually-is/code/harness-vs-no-harness.ts b/courses/learn-harness-engineering/docs/ru/lectures/lecture-02-what-a-harness-actually-is/code/harness-vs-no-harness.ts deleted file mode 100644 index d856ba36..00000000 --- a/courses/learn-harness-engineering/docs/ru/lectures/lecture-02-what-a-harness-actually-is/code/harness-vs-no-harness.ts +++ /dev/null @@ -1,180 +0,0 @@ -/** - * harness-vs-no-harness.ts - * - * Side-by-side comparison of the same task executor running with and without - * a harness. The harness version adds explicit rules, verification steps, - * and stop conditions. - * - * Run: npx tsx docs/lectures/lecture-02-what-a-harness-actually-is/code/harness-vs-no-harness.ts - */ - -// --------------------------------------------------------------------------- -// Types & helpers -// --------------------------------------------------------------------------- - -interface TaskResult { - name: string; - passed: boolean; - durationMs: number; - issues: string[]; -} - -function pad(s: string, len: number): string { - return s.length >= len ? s : s + " ".repeat(len - s.length); -} - -// --------------------------------------------------------------------------- -// Simulated task executor -// --------------------------------------------------------------------------- - -/** A simple task that can succeed or fail depending on rules. */ -type Task = { - name: string; - requiresAuth: boolean; - hasTests: boolean; - withinScope: boolean; -}; - -const tasks: Task[] = [ - { name: "Add search endpoint", requiresAuth: true, hasTests: false, withinScope: true }, - { name: "Add delete endpoint", requiresAuth: true, hasTests: true, withinScope: true }, - { name: "Refactor auth middleware", requiresAuth: true, hasTests: true, withinScope: false }, - { name: "Add health check", requiresAuth: false, hasTests: true, withinScope: true }, - { name: "Add rate limiter", requiresAuth: true, hasTests: false, withinScope: true }, -]; - -// --------------------------------------------------------------------------- -// Run WITHOUT harness -- just execute every task, no checks -// --------------------------------------------------------------------------- - -function runWithoutHarness(taskList: Task[]): TaskResult[] { - const results: TaskResult[] = []; - - for (const t of taskList) { - const start = performance.now(); - const issues: string[] = []; - - // The "agent" does the work and calls it done. - let passed = true; - - // Problems that go undetected without a harness - if (t.requiresAuth && !t.hasTests) { - issues.push("No tests for auth-protected endpoint"); - // Agent doesn't notice -- marks as pass anyway - } - if (!t.withinScope) { - issues.push("Task is outside current scope"); - // Agent doesn't notice - } - - const duration = performance.now() - start; - results.push({ name: t.name, passed, durationMs: duration, issues }); - } - - return results; -} - -// --------------------------------------------------------------------------- -// Run WITH harness -- rules, verification, stop conditions -// --------------------------------------------------------------------------- - -function runWithHarness(taskList: Task[]): TaskResult[] { - const rules = { - requireTestsForAuth: true, - enforceScope: true, - }; - - const results: TaskResult[] = []; - - for (const t of taskList) { - const start = performance.now(); - const issues: string[] = []; - let passed = true; - - // Rule: auth endpoints must have tests - if (rules.requireTestsForAuth && t.requiresAuth && !t.hasTests) { - issues.push("BLOCKED: Auth-protected endpoint missing tests"); - passed = false; - } - - // Rule: stay in scope - if (rules.enforceScope && !t.withinScope) { - issues.push("BLOCKED: Task outside active scope -- skipped"); - passed = false; - } - - // Verification: re-check after execution - if (passed && !t.hasTests) { - issues.push("WARNING: No tests exist for this change"); - } - - const duration = performance.now() - start; - results.push({ name: t.name, passed, durationMs: duration, issues }); - } - - return results; -} - -// --------------------------------------------------------------------------- -// Reporting -// --------------------------------------------------------------------------- - -function printComparison( - noHarness: TaskResult[], - withHarness: TaskResult[] -): void { - console.log("\n" + "=".repeat(80)); - console.log(" HARNESS vs NO-HARNESS COMPARISON"); - console.log("=".repeat(80)); - - const header = `| ${pad("Task", 28)}| ${pad("No-Harness", 12)}| ${pad("Issues(NH)", 35)}| ${pad("Harness", 12)}| ${pad("Issues(H)", 35)}|`; - const sep = `|${"-".repeat(30)}|${"-".repeat(14)}|${"-".repeat(37)}|${"-".repeat(14)}|${"-".repeat(37)}|`; - - console.log("\n" + header); - console.log(sep); - - for (let i = 0; i < noHarness.length; i++) { - const nh = noHarness[i]; - const wh = withHarness[i]; - const nhPass = nh.passed ? "PASS" : "FAIL"; - const whPass = wh.passed ? "PASS" : "FAIL"; - const nhIssue = nh.issues[0] ?? "(none)"; - const whIssue = wh.issues[0] ?? "(none)"; - - console.log( - `| ${pad(nh.name, 28)}| ${pad(nhPass, 12)}| ${pad(nhIssue, 35)}| ${pad(whPass, 12)}| ${pad(whIssue, 35)}|` - ); - } - - // Metrics - const nhPassed = noHarness.filter((r) => r.passed).length; - const whPassed = withHarness.filter((r) => r.passed).length; - const nhIssues = noHarness.reduce((sum, r) => sum + r.issues.length, 0); - const whIssues = withHarness.reduce((sum, r) => sum + r.issues.length, 0); - - console.log("\n" + "=".repeat(80)); - console.log(" SUMMARY METRICS"); - console.log("=".repeat(80)); - - const metricsHeader = `| ${pad("Metric", 30)}| ${pad("No Harness", 15)}| ${pad("With Harness", 15)}|`; - const metricsSep = `|${"-".repeat(32)}|${"-".repeat(17)}|${"-".repeat(17)}|`; - console.log("\n" + metricsHeader); - console.log(metricsSep); - console.log(`| ${pad("Tasks passed", 30)}| ${pad(String(nhPassed) + "/" + noHarness.length, 15)}| ${pad(String(whPassed) + "/" + withHarness.length, 15)}|`); - console.log(`| ${pad("Issues detected", 30)}| ${pad(String(nhIssues), 15)}| ${pad(String(whIssues), 15)}|`); - console.log(`| ${pad("False positives (passed but flawed)", 30)}| ${pad(String(nhPassed - truePassCount(noHarness)), 15)}| ${pad(String(whPassed - truePassCount(withHarness)), 15)}|`); - - console.log("\n The harness catches problems the no-harness run silently ignores."); - console.log(" Without a harness, every task 'passes' even when it shouldn't.\n"); -} - -/** Count tasks that actually pass (have tests and are in scope). */ -function truePassCount(results: TaskResult[]): number { - return results.filter((r) => r.passed && r.issues.length === 0).length; -} - -// --------------------------------------------------------------------------- -// Run -// --------------------------------------------------------------------------- - -printComparison(runWithoutHarness(tasks), runWithHarness(tasks)); diff --git a/courses/learn-harness-engineering/docs/ru/lectures/lecture-02-what-a-harness-actually-is/code/index.md b/courses/learn-harness-engineering/docs/ru/lectures/lecture-02-what-a-harness-actually-is/code/index.md deleted file mode 100644 index 34583249..00000000 --- a/courses/learn-harness-engineering/docs/ru/lectures/lecture-02-what-a-harness-actually-is/code/index.md +++ /dev/null @@ -1,8 +0,0 @@ -# Код для Лекции 02 - -Используйте эту папку для небольших примеров, разделяющих: - -- поведение модели -- поведение harness -- конфигурации только с промптом -- конфигурации агента с поддержкой среды diff --git a/courses/learn-harness-engineering/docs/ru/lectures/lecture-02-what-a-harness-actually-is/code/minimal-harness-loop.ts b/courses/learn-harness-engineering/docs/ru/lectures/lecture-02-what-a-harness-actually-is/code/minimal-harness-loop.ts deleted file mode 100644 index e56cddd8..00000000 --- a/courses/learn-harness-engineering/docs/ru/lectures/lecture-02-what-a-harness-actually-is/code/minimal-harness-loop.ts +++ /dev/null @@ -1,36 +0,0 @@ -type Message = { - role: "user" | "assistant"; - content: string; -}; - -type ToolResult = { - ok: boolean; - output: string; -}; - -function runTool(name: string, input: string): ToolResult { - if (name === "read_file") { - return { ok: true, output: `contents of ${input}` }; - } - - return { ok: false, output: `unknown tool: ${name}` }; -} - -export function minimalHarness(messages: Message[]) { - const nextAction = { - tool: "read_file", - input: "README.md" - }; - - const result = runTool(nextAction.tool, nextAction.input); - - return { - messages: [ - ...messages, - { - role: "assistant", - content: `Tool ${nextAction.tool} returned: ${result.output}` - } - ] - }; -} diff --git a/courses/learn-harness-engineering/docs/ru/lectures/lecture-02-what-a-harness-actually-is/index.md b/courses/learn-harness-engineering/docs/ru/lectures/lecture-02-what-a-harness-actually-is/index.md deleted file mode 100644 index e449a45f..00000000 --- a/courses/learn-harness-engineering/docs/ru/lectures/lecture-02-what-a-harness-actually-is/index.md +++ /dev/null @@ -1,109 +0,0 @@ -[中文版本 →](../../../zh/lectures/lecture-02-what-a-harness-actually-is/) - -> Примеры кода: [code/](https://github.com/walkinglabs/learn-harness-engineering/blob/main/docs/en/lectures/lecture-02-what-a-harness-actually-is/code/) -> Практический проект: [Project 01. Prompt-only vs. rules-first](./../../projects/project-01-baseline-vs-minimal-harness/index.md) - -# Лекция 02. Что на самом деле означает harness - -Слово «harness» в кругах AI-кодинг-агентов разбрасывают направо и налево, но честно говоря, большинство людей под ним подразумевает «файл с промптом». Это не harness. Это как открыть ресторан, имея только продукты — без плиты, без ножей, без рецептов, без процесса подачи. Это не ресторан. Это холодильник. - -В этой лекции вы получите точное и применимое определение harness. Не академическую абстракцию, а фреймворк, который можно использовать уже сегодня: harness состоит из пяти подсистем, у каждой — чёткие обязанности и критерии оценки. - -## Начнём с аналогии - -Представьте, что вы только что нанятый инженер, которого бросили в проект без какой-либо документации. Нет README, нет комментариев в коде, никто не сказал, как запускать тесты, конфиг CI закопан где-то глубоко. Сможете ли вы писать хороший код? Возможно — если вы достаточно умны и терпеливы. Но огромное количество времени уйдёт на «разобраться, что это вообще за проект», а не на «решить задачу». - -AI-агент сталкивается ровно с той же ситуацией. И ему хуже — вы хоть коллегу можете спросить. Агент видит только файлы, которые вы ему положите, и команды, которые он может выполнить. Он не может постучать кому-то по плечу и спросить «слушай, какой версии ORM здесь используется?». - -OpenAI формулируют главный принцип так: «the repo IS the spec» — весь необходимый контекст должен жить в репозитории, передаваясь через структурированные файлы инструкций, явные команды верификации и понятную организацию каталогов. В документации Anthropic про долгоживущих агентов делается упор на персистентность состояния, явные пути восстановления и структурированное отслеживание прогресса. Эти две компании смотрят на разные аспекты, но говорят об одном и том же: **всё в инженерной инфраструктуре за пределами модели определяет, какая часть способностей модели реально проявится на практике.** - -Посмотрите на знакомые вам инструменты: - -**Claude Code** воплощает harness-мышление. Он читает `CLAUDE.md` из вашего репо (полка с рецептами), может выполнять shell-команды (стойка с ножами), исполняется в вашей локальной среде (плита), хранит историю сессии (заготовочный стол) и может запускать тесты и видеть результаты (окно контроля качества). Но если вы не сказали ему, как запускать тесты, окно контроля качества разбито — никто не знает, готово ли блюдо. - -**Cursor** следует той же логике. Файл `.cursorrules` — это полка с рецептами, терминал — стойка с ножами, он читает структуру проекта и конфиг линтера для плиты. Но управление состоянием у Cursor сравнительно слабое — закрыли IDE, открыли заново, и предыдущий контекст пропал. - -**Codex** (кодинг-агент OpenAI) использует git worktrees, чтобы изолировать рантайм-среду каждой задачи, в паре с локальным observability-стеком (логи, метрики, трейсы), так что каждое изменение проверяется в независимом окружении. В репозиториях с `AGENTS.md` и понятными командами верификации он работает гораздо лучше, чем в «голых» репо. - -**AutoGPT** — поучительный антипример: отсутствие структурированного управления состоянием приводит к накоплению контекста на длинных задачах, а отсутствие точных механизмов обратной связи заставляет агента ходить кругами. Многие говорят, что AutoGPT «не работает», но на самом деле не работает harness AutoGPT — дайте повару сломанную плиту, и даже из лучших продуктов еды не выйдет. - -## Ключевые понятия - -- **Что такое harness**: всё в инженерной инфраструктуре за пределами весов модели. OpenAI сводят основную работу инженера к трём вещам: проектирование сред, выражение намерения и построение петель обратной связи. Anthropic называют свой Claude Agent SDK «универсальным harness для агентов». -- **Репозиторий — единственный источник истины**: то, чего агент не видит, для всех практических целей не существует. OpenAI рассматривают репо как «system of record» — весь необходимый контекст должен жить там, через структурированные файлы и понятную организацию каталогов. -- **Дайте карту, а не учебник**: опыт OpenAI — `AGENTS.md` должен быть оглавлением, а не энциклопедией. Около 100 строк — достаточно. Если не помещается, выносите в каталог `docs/` и пусть агент читает по необходимости. -- **Ограничивайте, а не микроменеджите**: хороший harness ограничивает агента исполняемыми правилами, а не перечисляет инструкции одну за другой. OpenAI говорят «enforce invariants, don't micromanage implementation»; Anthropic обнаружили, что агенты уверенно хвалят свою же работу, и решение — разделить «того, кто делает» и «того, кто проверяет». -- **Удаляйте компоненты по одному**: чтобы измерить предельный вклад каждого компонента harness, удаляйте их по одному и смотрите, какое удаление сильнее всего просаживает производительность. Anthropic именно так и делали — и обнаружили, что по мере усиления моделей некоторые компоненты перестают быть критичными, но всегда появляются новые. - -## Модель harness из пяти подсистем - -Возвращаемся к кухонной аналогии. У полноценной кухни пять функциональных зон, и у harness — пять подсистем: - -```mermaid -flowchart LR - Rules["Правила проекта
    AGENTS.md / CLAUDE.md"] --> Agent["AI-агент"] - State["Прогресс и git
    PROGRESS.md / коммиты"] --> Agent - Agent --> Tools["Инструменты
    shell / файлы / тесты"] - Tools --> Env["Рантайм
    зависимости / сервисы / версии"] - Env --> Checks["Результаты проверок
    test / lint / build"] - Checks --> Agent -``` - -**Подсистема инструкций (полка с рецептами)**: создайте `AGENTS.md` (или `CLAUDE.md`), содержащий обзор и цель проекта (одно предложение), стек и версии (Python 3.11, FastAPI 0.100+, PostgreSQL 15), команды первого запуска (`make setup`, `make test`), необсуждаемые жёсткие ограничения («все API должны использовать OAuth 2.0») и ссылки на более детальную документацию. - -**Подсистема инструментов (стойка с ножами)**: убедитесь, что у агента есть достаточный доступ к инструментам. Не отключайте shell ради «безопасности» — если агент не может даже запустить `pip install`, как ему работать? Но и не открывайте всё подряд — следуйте принципу наименьших привилегий. - -**Подсистема среды (плита)**: сделайте состояние среды самоописательным. Используйте `pyproject.toml` или `package.json`, чтобы залочить зависимости, `.nvmrc` или `.python-version` — для версий рантайма, Docker или devcontainers — для воспроизводимости. - -**Подсистема состояния (заготовочный стол)**: длинным задачам нужно отслеживание прогресса. Используйте простой файл `PROGRESS.md`, фиксирующий: что сделано, что в работе, что заблокировано. Обновляйте перед концом каждой сессии, читайте в начале следующей. - -**Подсистема обратной связи (окно контроля качества)**: подсистема с самым высоким ROI. Явно перечислите команды верификации в `AGENTS.md`: -``` -Команды верификации: -- Тесты: pytest tests/ -x -- Проверка типов: mypy src/ --strict -- Линт: ruff check src/ -- Полная верификация: make check (включает всё выше) -``` - -Отсутствие любой подсистемы — как отсутствие функциональной зоны на кухне: готовить можно, но всегда будет неудобно. - -**Оценка ценности компонентов harness**: используйте «изометрический контроль модели». Зафиксируйте модель, удаляйте подсистемы по одной и измеряйте, какое удаление сильнее всего просаживает производительность. Самая большая просадка показывает компонент с наибольшим предельным вкладом в этой задаче, а не автоматически бутылочное горлышко. Чтобы найти настоящее бутылочное горлышко, совместите эксперимент с журналами сбоев и атрибуцией: размытая задача, нехватка контекста, невоспроизводимая среда, отсутствие верификационной обратной связи или сломанное управление состоянием. - -## Реальная история одной команды - -Команда использовала GPT-4o на TypeScript + React фронтенде (~20 000 строк кода). Они прошли четыре стадии — по сути, добавляя кухонное оборудование по одной единице: - -**Стадия 1 — пустая кухня**: только базовое описание проекта в README. Из 5 прогонов успешен 1 (20%). Главные провалы: выбран не тот пакетный менеджер (npm vs yarn), не соблюдены конвенции именования компонентов, не получалось запустить тесты. - -**Стадия 2 — установлена полка с рецептами**: добавили `AGENTS.md` с версиями стека, конвенциями именования, ключевыми архитектурными решениями. Успешность выросла до 60%. Оставшиеся провалы — в основном проблемы среды и отсутствие верификации. - -**Стадия 3 — открыто окно контроля качества**: в `AGENTS.md` перечислены команды верификации: `yarn test && yarn lint && yarn build`. Успешность выросла до 80%. - -**Стадия 4 — заготовочный стол готов**: ввели шаблоны файлов прогресса, в которых агент в каждом прогоне фиксировал сделанное и оставшееся. Успешность стабилизировалась на 80–100%. - -Четыре итерации, модель не менялась вообще, успешность ушла с 20% к почти 100%. В этом сила harness engineering. Вы не покупали более дорогие продукты — вы просто навели порядок на кухне. - -## Главное - -- Harness = инструкции + инструменты + среда + состояние + обратная связь. Пять подсистем, как пять функциональных зон кухни — все обязательны. -- Если это не веса модели — это harness. Ваш harness определяет, какая часть способностей модели реализуется. -- Среди пяти подсистем у подсистемы обратной связи обычно самый низкий вход и самый высокий возврат. Сначала наладьте команды верификации — окно контроля качества стоит апгрейдить в первую очередь. -- Используйте «изометрический контроль модели», чтобы количественно оценить предельный вклад каждой подсистемы; используйте журналы сбоев и атрибуцию, чтобы найти настоящее бутылочное горлышко. -- Harness гниёт так же, как код. Ревизуйте регулярно, гасите harness-долг как технический долг. - -## Дальнейшее чтение - -- [OpenAI: Harness Engineering](https://openai.com/index/harness-engineering/) -- [Anthropic: Effective Harnesses for Long-Running Agents](https://www.anthropic.com/engineering/effective-harnesses-for-long-running-agents) -- [HumanLayer: Harness Engineering for Coding Agents](https://humanlayer.dev/articles/harness-engineering-for-coding-agents/) -- [SWE-agent: Agent-Computer Interfaces](https://github.com/princeton-nlp/SWE-agent) -- [Thoughtworks: Harness Engineering on Technology Radar](https://www.thoughtworks.com/radar) - -## Упражнения - -1. **Аудит harness по пятёрке**: возьмите проект, в котором используете AI-агента, и проведите полный аудит по пятикомпонентному фреймворку. Каждой подсистеме поставьте оценку 1–5. Найдите подсистему с самой низкой оценкой, потратьте 30 минут на её улучшение и понаблюдайте за изменениями в работе агента. - -2. **Эксперимент с изометрическим контролем модели**: возьмите одну модель и одну сложную задачу. Последовательно убирайте инструкции (удалите AGENTS.md), убирайте обратную связь (не давайте команды верификации), убирайте состояние (без файлов прогресса) — каждый раз убирайте только одно и измеряйте просадку. По результатам отранжируйте важность подсистем для вашего проекта. - -3. **Анализ affordance**: найдите в проекте сценарий, где агент «хочет что-то сделать, но не может» (например, знает, что нужны параметризованные запросы, но не знает паттернов ORM в вашем проекте). Разберите, это Gulf of Execution (не знает, как) или Gulf of Evaluation (не знает, правильно ли). Затем спроектируйте улучшение harness, чтобы перекрыть этот разрыв. diff --git a/courses/learn-harness-engineering/docs/ru/lectures/lecture-03-why-the-repository-must-become-the-system-of-record/code/index.md b/courses/learn-harness-engineering/docs/ru/lectures/lecture-03-why-the-repository-must-become-the-system-of-record/code/index.md deleted file mode 100644 index ba8e0b06..00000000 --- a/courses/learn-harness-engineering/docs/ru/lectures/lecture-03-why-the-repository-must-become-the-system-of-record/code/index.md +++ /dev/null @@ -1,7 +0,0 @@ -# Код для Лекции 03 - -Используйте эту папку для примеров: - -- читаемых агентом структур репо -- документации как system of record -- плохого vs хорошего размещения знаний diff --git a/courses/learn-harness-engineering/docs/ru/lectures/lecture-03-why-the-repository-must-become-the-system-of-record/code/repo-knowledge-layout.txt b/courses/learn-harness-engineering/docs/ru/lectures/lecture-03-why-the-repository-must-become-the-system-of-record/code/repo-knowledge-layout.txt deleted file mode 100644 index 45315dac..00000000 --- a/courses/learn-harness-engineering/docs/ru/lectures/lecture-03-why-the-repository-must-become-the-system-of-record/code/repo-knowledge-layout.txt +++ /dev/null @@ -1,13 +0,0 @@ -AGENTS.md -docs/ - ARCHITECTURE.md - PRODUCT.md - RELIABILITY.md - references/ - electron.md - sqlite.md - plans/ - active/ - completed/ -src/ -scripts/ diff --git a/courses/learn-harness-engineering/docs/ru/lectures/lecture-03-why-the-repository-must-become-the-system-of-record/code/repo-reader.ts b/courses/learn-harness-engineering/docs/ru/lectures/lecture-03-why-the-repository-must-become-the-system-of-record/code/repo-reader.ts deleted file mode 100644 index 46f6a62a..00000000 --- a/courses/learn-harness-engineering/docs/ru/lectures/lecture-03-why-the-repository-must-become-the-system-of-record/code/repo-reader.ts +++ /dev/null @@ -1,189 +0,0 @@ -/** - * repo-reader.ts - * - * Reads a directory structure and scores it on discoverability. - * Checks for: AGENTS.md, docs/, architecture docs, feature tracking, - * handoff files, and other signals of a repository that serves as the - * system of record. - * - * Usage: - * npx tsx docs/lectures/lecture-03.../code/repo-reader.ts [path] - * (defaults to current working directory if no path given) - * - * Run: npx tsx docs/lectures/lecture-03-why-the-repository-must-become-the-system-of-record/code/repo-reader.ts - */ - -import * as fs from "node:fs"; -import * as path from "node:path"; - -// --------------------------------------------------------------------------- -// Scoring criteria -// --------------------------------------------------------------------------- - -interface Check { - name: string; - description: string; - maxPoints: number; - check: (dir: string) => { points: number; found: string[]; missing: string[] }; -} - -const checks: Check[] = [ - { - name: "AGENTS.md / CLAUDE.md", - description: "Agent-readable instruction file at repo root", - maxPoints: 15, - check: (dir) => { - const candidates = ["AGENTS.md", "CLAUDE.md", ".claude/CLAUDE.md"]; - const found = candidates.filter((f) => fs.existsSync(path.join(dir, f))); - return { points: found.length > 0 ? 15 : 0, found, missing: found.length > 0 ? [] : candidates }; - }, - }, - { - name: "Documentation directory", - description: "Dedicated docs/ or documentation/ directory", - maxPoints: 10, - check: (dir) => { - const candidates = ["docs", "documentation", "doc"]; - const found = candidates.filter((f) => { - const p = path.join(dir, f); - return fs.existsSync(p) && fs.statSync(p).isDirectory(); - }); - return { points: found.length > 0 ? 10 : 0, found, missing: found.length > 0 ? [] : ["docs/"] }; - }, - }, - { - name: "Architecture documentation", - description: "Files describing system architecture", - maxPoints: 15, - check: (dir) => { - const patterns = [ - "architecture.md", "ARCHITECTURE.md", "docs/architecture.md", - "docs/architecture/", "design.md", "DESIGN.md", - ]; - const found = patterns.filter((f) => fs.existsSync(path.join(dir, f))); - return { points: found.length > 0 ? 15 : 0, found, missing: found.length > 0 ? [] : ["architecture.md or docs/architecture/"] }; - }, - }, - { - name: "Feature tracking", - description: "Feature list or task tracking file", - maxPoints: 15, - check: (dir) => { - const patterns = [ - "feature_list.json", "features.md", "FEATURES.md", - "docs/features.md", "tasks.json", "TODO.md", - ]; - const found = patterns.filter((f) => fs.existsSync(path.join(dir, f))); - return { points: found.length > 0 ? 15 : 0, found, missing: found.length > 0 ? [] : ["feature_list.json or features.md"] }; - }, - }, - { - name: "Handoff / session continuity", - description: "Files for multi-session continuity", - maxPoints: 15, - check: (dir) => { - const patterns = [ - "HANDOFF.md", "handoff.md", "SESSION_NOTES.md", - "docs/handoff.md", ".handoff", "PROGRESS.md", - ]; - const found = patterns.filter((f) => fs.existsSync(path.join(dir, f))); - return { points: found.length > 0 ? 15 : 0, found, missing: found.length > 0 ? [] : ["HANDOFF.md or PROGRESS.md"] }; - }, - }, - { - name: "Testing structure", - description: "Test directory or test configuration", - maxPoints: 10, - check: (dir) => { - const patterns = ["test", "tests", "__tests__", "spec"]; - const found = patterns.filter((f) => { - const p = path.join(dir, f); - return fs.existsSync(p) && fs.statSync(p).isDirectory(); - }); - return { points: found.length > 0 ? 10 : 0, found, missing: found.length > 0 ? [] : ["test/ or tests/"] }; - }, - }, - { - name: "Configuration files", - description: "Project config (package.json, tsconfig, etc.)", - maxPoints: 10, - check: (dir) => { - const patterns = ["package.json", "tsconfig.json", "pyproject.toml", "Cargo.toml", "go.mod"]; - const found = patterns.filter((f) => fs.existsSync(path.join(dir, f))); - return { points: found.length > 0 ? 10 : 0, found, missing: found.length > 0 ? [] : ["package.json or equivalent"] }; - }, - }, - { - name: "README", - description: "Root README file", - maxPoints: 10, - check: (dir) => { - const patterns = ["README.md", "README.rst", "README.txt", "README"]; - const found = patterns.filter((f) => fs.existsSync(path.join(dir, f))); - return { points: found.length > 0 ? 10 : 0, found, missing: found.length > 0 ? [] : ["README.md"] }; - }, - }, -]; - -// --------------------------------------------------------------------------- -// Report generation -// --------------------------------------------------------------------------- - -function pad(s: string, len: number): string { - return s.length >= len ? s : s + " ".repeat(len - s.length); -} - -function scoreRepo(targetDir: string): void { - const resolved = path.resolve(targetDir); - - if (!fs.existsSync(resolved)) { - console.error(` Error: Directory not found: ${resolved}`); - process.exit(1); - } - - console.log("\n" + "=".repeat(80)); - console.log(" REPOSITORY DISCOVERABILITY SCORE"); - console.log("=".repeat(80)); - console.log(` Target: ${resolved}\n`); - - const header = `| ${pad("Criterion", 30)}| ${pad("Points", 8)}| ${pad("Status", 10)}| Details`; - const sep = `|${"-".repeat(32)}|${"-".repeat(10)}|${"-".repeat(12)}|${"-".repeat(30)}`; - console.log(header); - console.log(sep); - - let totalScore = 0; - let maxScore = 0; - - for (const check of checks) { - const result = check.check(resolved); - totalScore += result.points; - maxScore += check.maxPoints; - const status = result.points > 0 ? "PASS" : "FAIL"; - const detail = result.found.length > 0 ? result.found.join(", ") : result.missing[0]; - - console.log(`| ${pad(check.name, 30)}| ${pad(result.points + "/" + check.maxPoints, 8)}| ${pad(status, 10)}| ${detail}`); - } - - // Final score - console.log("\n" + "-".repeat(80)); - console.log(` TOTAL SCORE: ${totalScore} / ${maxScore} (${Math.round((totalScore / maxScore) * 100)}%)`); - - // Grade - const pct = totalScore / maxScore; - let grade: string; - if (pct >= 0.9) grade = "A -- Repository is a strong system of record"; - else if (pct >= 0.7) grade = "B -- Good foundation, minor gaps"; - else if (pct >= 0.5) grade = "C -- Partial structure, significant gaps"; - else if (pct >= 0.3) grade = "D -- Minimal structure, hard for agents to navigate"; - else grade = "F -- Repository lacks discoverability signals"; - - console.log(` GRADE: ${grade}`); - console.log("=".repeat(80) + "\n"); -} - -// --------------------------------------------------------------------------- -// Main -// --------------------------------------------------------------------------- - -const target = process.argv[2] || process.cwd(); -scoreRepo(target); diff --git a/courses/learn-harness-engineering/docs/ru/lectures/lecture-03-why-the-repository-must-become-the-system-of-record/code/system-of-record-checklist.md b/courses/learn-harness-engineering/docs/ru/lectures/lecture-03-why-the-repository-must-become-the-system-of-record/code/system-of-record-checklist.md deleted file mode 100644 index da82f255..00000000 --- a/courses/learn-harness-engineering/docs/ru/lectures/lecture-03-why-the-repository-must-become-the-system-of-record/code/system-of-record-checklist.md +++ /dev/null @@ -1,11 +0,0 @@ -# Чеклист system of record - -Может ли свежий агент узнать из одного только репо: - -- Какой продукт строится? -- Что приложение должно делать для пользователей? -- Как устроена кодовая база? -- Как запускается приложение? -- Как проверяется здоровье? -- Какая работа сейчас в процессе? -- Какие стандарты качества важны? diff --git a/courses/learn-harness-engineering/docs/ru/lectures/lecture-03-why-the-repository-must-become-the-system-of-record/index.md b/courses/learn-harness-engineering/docs/ru/lectures/lecture-03-why-the-repository-must-become-the-system-of-record/index.md deleted file mode 100644 index 8479be5a..00000000 --- a/courses/learn-harness-engineering/docs/ru/lectures/lecture-03-why-the-repository-must-become-the-system-of-record/index.md +++ /dev/null @@ -1,137 +0,0 @@ -[中文版本 →](../../../zh/lectures/lecture-03-why-the-repository-must-become-the-system-of-record/) - -> Примеры кода: [code/](https://github.com/walkinglabs/learn-harness-engineering/blob/main/docs/en/lectures/lecture-03-why-the-repository-must-become-the-system-of-record/code/) -> Практический проект: [Project 02. Agent-readable workspace](./../../projects/project-02-agent-readable-workspace/index.md) - -# Лекция 03. Сделайте репозиторий своим единственным источником истины - -Архитектурные решения вашей команды разбросаны по Confluence, Slack, Jira и головам нескольких сеньоров. Для людей это кое-как работает — можно спросить коллегу, поискать в чате, покопаться в доках. В крайнем случае поймать кого-то в курилке. Но для AI-агента информация, которой нет в репозитории, попросту не существует. - -Это не преувеличение. Подумайте, что вообще является входом для агента: системные промпты и описания задач, содержимое файлов из репозитория, вывод инструментов. И всё. Ваша история в Slack, тикеты в Jira, страницы в Confluence и то архитектурное решение, которое вы обсудили с коллегой за кофе в пятницу днём — агент ничего из этого не видит. Он не может «пойти спросить» или «поискать в чате». Это инженер, запертый внутри репозитория — что снаружи, он не знает. - -Так что вопрос ставится так: дадите ли вы этому инженеру хорошую карту? - -## Что должно быть на карте - -OpenAI говорят это прямо: **информации, которой нет в репо, для агента не существует.** Они называют это принципом «repo as spec» — сам репозиторий и есть спецификационный документ высшей инстанции. - -Документация Anthropic про долгоживущих агентов вторит: персистентное состояние — необходимое условие непрерывности длинных задач. Восстановимость знаний между сессиями напрямую определяет успешность задач. И это состояние должно жить в репозитории — потому что это единственное стабильное, доступное хранилище у агента. - -Вы можете подумать: «У нас маленькая команда, знания в головах, и всё нормально работает». Хорошо, для людей. Но если вы используете агента, примите факт: агент не может никого спрашивать. Всё, что ему нужно знать, должно быть записано и положено там, где он сможет это найти. - -Это не про «писать больше документации». Это про «класть информацию о решениях в правильное место». 50 строк `ARCHITECTURE.md` в каталоге `src/api/` в десять тысяч раз полезнее, чем 500-страничный design-документ в Confluence, который никто не поддерживает. Это как нарисованная от руки карта офиса, приклеенная к столу, против красивого архитектурного чертежа, запертого в шкафу — первая под рукой, когда нужно; вторая технически круче, но в нужный момент бесполезна. - -## Видимость знаний - -```mermaid -flowchart LR - Slack["Правила в Slack"] --> Write["Запишите их в файлы репо
    AGENTS.md / ARCHITECTURE.md / PROGRESS.md"] - Confluence["Правила в Confluence"] --> Write - Heads["Правила в головах"] --> Write - Jira["Правила в тикетах Jira"] --> Write - Write --> Repo["Файлы репозитория"] - Repo --> Agent["Новая сессия агента
    читает репо напрямую"] - Warning["Если правила нет в репо,
    агент его не видит"] --> Agent -``` - -Как проверить, достаточно ли хороша ваша карта? Запустите «тест холодного старта»: откройте абсолютно новую сессию агента, дайте ему только содержимое репо и посмотрите, ответит ли он на пять базовых вопросов: - -```mermaid -flowchart TB - Q1["Что это за система?"] --> A1["AGENTS.md / README"] - Q2["Как она устроена?"] --> A2["ARCHITECTURE.md / документы модулей"] - Q3["Как её запустить?"] --> A3["Makefile / init.sh / скрипты пакетов"] - Q4["Как её проверить?"] --> A4["Команды тестов, линта и проверок"] - Q5["Где мы сейчас?"] --> A5["PROGRESS.md / список фич / история git"] - - A1 --> Ready["Новая сессия может начать работу
    не спрашивая человека"] - A2 --> Ready - A3 --> Ready - A4 --> Ready - A5 --> Ready -``` - -Если он не отвечает — на карте белые пятна. Где карта пустая, агент гадает: неверная догадка превращается в баг, излишнее гадание сжирает контекст. И каждая новая сессия гадает заново. Цена гадания всегда выше цены того, чтобы один раз нормально нарисовать карту. - -## Ключевые понятия - -- **Knowledge Visibility Gap**: доля общего знания о проекте, которой НЕТ в репозитории. Чем больше разрыв, тем выше процент провалов агента. Сколько неявного знания о проекте у вас в голове? Посчитайте всё, потом посмотрите, сколько попало в репо — разница и есть ваш visibility gap. -- **System of Record**: репозиторий кода как авторитетный источник по проектным решениям, архитектурным ограничениям, состоянию исполнения и стандартам верификации. У репо последнее слово, нигде больше не считается. Как карта с пометкой «дорога закрыта» — вы туда не пойдёте. А если эта информация только в голове у Старого Чжана, придётся каждый раз идти к нему. -- **Cold-Start Test**: те самые пять вопросов выше. Сколько он сможет ответить — настолько и полна ваша карта. -- **Discovery Cost**: сколько контекстного бюджета агент сжигает, чтобы найти ключевую информацию в репо. Чем глубже спрятана информация, тем выше стоимость поиска и тем меньше бюджета остаётся на саму задачу. Спрятать критичную информацию в README на десятом уровне вложенности — это как закрыть огнетушитель в подвальном сейфе: он есть, но в нужный момент не достанете. -- **Knowledge Decay Rate**: доля записей знаний, протухающих за единицу времени. Документация, расходящаяся с кодом — главный враг, хуже, чем отсутствие документации. -- **ACID Analogy**: применение принципов транзакций БД (атомарность, согласованность, изолированность, долговечность) к управлению состоянием агента. Развернём ниже. - -## Как нарисовать хорошую карту - -**Принцип 1: знания живут рядом с кодом.** Правило аутентификации API-эндпоинтов должно лежать рядом с кодом API, а не быть закопанным в гигантском глобальном документе. Положите в каждый каталог модуля короткий документ с описанием обязанностей модуля, интерфейсов и особых ограничений. Как полочные ярлыки в библиотеке: нужны книги по истории — идёте сразу к полке «История». Не нужно прочёсывать всю библиотеку. - -**Принцип 2: используйте стандартизированный входной файл.** `AGENTS.md` (или `CLAUDE.md`) — «лендинг» агента. Он не должен содержать всё, но должен дать агенту быстро ответить на три вопроса: «Что это за проект», «Как его запустить», «Как его проверить». 50–100 строк — достаточно. - -**Принцип 3: минимально, но полно.** У каждого знания должен быть понятный сценарий использования. Если удаление правила не влияет на качество решений агента — этого правила быть не должно. Но на каждый вопрос cold-start теста должен быть ответ. Это тонкий баланс — не слишком много, не слишком мало, ровно столько, сколько нужно. - -**Принцип 4: обновляйте вместе с кодом.** Привяжите обновления знаний к изменениям кода. Самый простой подход: положите архитектурные доки в соответствующий каталог модуля. Меняя код, вы естественно видите документ. После изменений CI может напомнить проверить, нужно ли обновить доку. - -**Конкретная структура репо**: - -``` -project/ -├── AGENTS.md # Вход: обзор проекта, команды запуска, жёсткие ограничения -├── src/ -│ ├── api/ -│ │ ├── ARCHITECTURE.md # Архитектурные решения слоя API -│ │ └── ... -│ ├── db/ -│ │ ├── CONSTRAINTS.md # Жёсткие ограничения работы с БД -│ │ └── ... -│ └── ... -├── PROGRESS.md # Текущий прогресс: сделано, в работе, заблокировано -└── Makefile # Стандартизированные команды: setup, test, lint, check -``` - -## Управление состоянием агента по принципам ACID - -Аналогия пришла из управления транзакциями в БД — может показаться оверкомплексом, но на деле даёт очень практичный фреймворк: - -- **Atomicity (атомарность)**: каждой «логической операции» (например, «добавить новый эндпоинт и обновить тесты») — один git-коммит. Если что-то пошло не так — `git stash` для отката. Всё или ничего, никакого «наполовину». -- **Consistency (согласованность)**: определите проверяющие предикаты «согласованного состояния» — все тесты проходят, линт без ошибок. Агент запускает верификацию после каждой операции; несогласованные промежуточные состояния не коммитятся. Как банковский перевод — нельзя списать без зачисления. -- **Isolation (изолированность)**: когда несколько агентов работают параллельно, спроектируйте файлы состояния так, чтобы избежать гонок. Простой подход: у каждого агента свой файл прогресса, или используйте git-ветки для изоляции. Два повара не могут одновременно солить одну кастрюлю — кому отвечать, если пересолили? -- **Durability (долговечность)**: критическое знание о проекте живёт в файлах под git. Временное состояние может оставаться в памяти сессии, но межсессионное знание должно быть персистировано в файлы. Что в голове — не считается, считается только то, что на бумаге. - -## Реальная история трансформации - -Команда поддерживала e-commerce платформу с ~30 микросервисами. Архитектурные решения (протоколы межсервисного общения, стратегии согласованности данных, правила версионирования API) были разбросаны по: Confluence (частично устарел), Slack (плохо ищется), голов нескольких сеньоров (не масштабируется) и эпизодических комментариев в коде (не системно). - -После внедрения AI-агентов 70% задач требовали человеческого вмешательства. Почти каждый провал был связан с тем, что агент нарушал какое-то «все знают, но никто не записал» неявное ограничение. Это как новенький, которому никто не сказал «о своём заказе на обед нужно писать в группу» — он гадает, его ругают, но после ругани правило ему так и не озвучивают. - -Команда провела трансформацию: -1. Создали `AGENTS.md` в корне репо с обзором проекта, версиями стека и глобальными жёсткими ограничениями -2. Добавили `ARCHITECTURE.md` в каталог каждого микросервиса с описанием обязанностей, интерфейсов и зависимостей -3. Создали централизованный `CONSTRAINTS.md` с жёсткими ограничениями в явных формулировках «MUST/MUST NOT» -4. Добавили `PROGRESS.md` в каталог каждого сервиса для отслеживания текущего статуса работы - -После трансформации: тот же агент мог ответить на все ключевые вопросы по проекту на холодном старте, а качество выполнения задач существенно выросло. - -## Главное - -- Знания, которых нет в репо, для агента не существуют. Положить критические решения в репо — самая базовая инвестиция в harness. Нарисуйте хорошую карту, чтобы не заблудиться. -- Используйте «cold-start тест» для оценки качества репо: может ли свежая сессия ответить на пять базовых вопросов, имея только содержимое репо? -- Знания должны быть рядом с кодом, минимальны, но полны, и обновляться вместе с кодом. Дело не в том, чтобы писать больше доков — дело в том, чтобы класть информацию в правильные места. -- Используйте принципы ACID для состояния агента: атомарные коммиты, верификация согласованности, изоляция параллелизма, долговечность критичных знаний. -- Устаревание знаний — главный враг. Документация, расходящаяся с кодом, опаснее отсутствия документации — она ведёт агента в неверном направлении, когда он думает, что прав. - -## Дальнейшее чтение - -- [OpenAI: Harness Engineering](https://openai.com/index/harness-engineering/) -- [Anthropic: Effective Harnesses for Long-Running Agents](https://www.anthropic.com/engineering/effective-harnesses-for-long-running-agents) -- [Infrastructure as Code — Martin Fowler](https://martinfowler.com/bliki/InfrastructureAsCode.html) -- [ADR: Architecture Decision Records](https://adr.github.io/) -- [The Twelve-Factor App](https://12factor.net/) - -## Упражнения - -1. **Cold-start тест**: откройте абсолютно свежую сессию агента в проекте (без устного контекста, только содержимое репо). Задайте ему пять вопросов: что это за система? как она устроена? как её запускать? как её проверять? каков текущий прогресс? Зафиксируйте, на что он не отвечает, и улучшайте репо, пока не сможет. - -2. **Количественная оценка экстернализации знаний**: перечислите все решения и ограничения, важные для разработки в проекте. Отметьте каждое: внутри репо или снаружи. Посчитайте knowledge visibility gap (долю снаружи). Составьте план, как опустить её ниже 10%. - -3. **ACID-оценка**: оцените управление состоянием в проекте по ACID-аналогии из лекции. Атомарность — можно ли чисто откатить операции агента? Согласованность — есть ли верификация «согласованного состояния»? Изолированность — мешают ли друг другу параллельные агенты? Долговечность — все ли межсессионные знания персистированы? diff --git a/courses/learn-harness-engineering/docs/ru/lectures/lecture-04-why-one-giant-instruction-file-fails/code/AGENTS-short.md b/courses/learn-harness-engineering/docs/ru/lectures/lecture-04-why-one-giant-instruction-file-fails/code/AGENTS-short.md deleted file mode 100644 index 93eb6026..00000000 --- a/courses/learn-harness-engineering/docs/ru/lectures/lecture-04-why-one-giant-instruction-file-fails/code/AGENTS-short.md +++ /dev/null @@ -1,15 +0,0 @@ -# AGENTS.md - -## Начните здесь (Start Here) - -- Прочитайте `docs/ARCHITECTURE.md` -- Прочитайте `docs/PRODUCT.md` -- Используйте `npm run dev` для запуска приложения -- Используйте `npm run check` перед тем как пометить работу как завершённую - -## Строгие правила (Hard Rules) - -- Не изменяйте границы main/preload/renderer процесса Electron без - прочтения `docs/ARCHITECTURE.md` -- Не помечайте функцию как завершённую без проверки (verification) -- Оставляйте чистое состояние (clean state) для следующей сессии diff --git a/courses/learn-harness-engineering/docs/ru/lectures/lecture-04-why-one-giant-instruction-file-fails/code/anti-patterns.md b/courses/learn-harness-engineering/docs/ru/lectures/lecture-04-why-one-giant-instruction-file-fails/code/anti-patterns.md deleted file mode 100644 index 6cdefa62..00000000 --- a/courses/learn-harness-engineering/docs/ru/lectures/lecture-04-why-one-giant-instruction-file-fails/code/anti-patterns.md +++ /dev/null @@ -1,7 +0,0 @@ -# Антипаттерны файлов инструкций - -- Складывать всё знание о репозитории в один файл -- Повторять одно и то же правило в нескольких местах -- Кодировать устаревшие правила, которые никто не ревизует -- Писать настолько специфичные условные инструкции, что они почти никогда не применяются -- Встраивать длинные мануалы по инструментам в стартовый контекст diff --git a/courses/learn-harness-engineering/docs/ru/lectures/lecture-04-why-one-giant-instruction-file-fails/code/index.md b/courses/learn-harness-engineering/docs/ru/lectures/lecture-04-why-one-giant-instruction-file-fails/code/index.md deleted file mode 100644 index 020de0ca..00000000 --- a/courses/learn-harness-engineering/docs/ru/lectures/lecture-04-why-one-giant-instruction-file-fails/code/index.md +++ /dev/null @@ -1,7 +0,0 @@ -# Код для Лекции 04 - -Используйте эту папку для примеров: - -- монолитных файлов инструкций -- коротких входных точек -- паттернов progressive disclosure diff --git a/courses/learn-harness-engineering/docs/ru/lectures/lecture-04-why-one-giant-instruction-file-fails/code/split-vs-monolithic.ts b/courses/learn-harness-engineering/docs/ru/lectures/lecture-04-why-one-giant-instruction-file-fails/code/split-vs-monolithic.ts deleted file mode 100644 index 59a96328..00000000 --- a/courses/learn-harness-engineering/docs/ru/lectures/lecture-04-why-one-giant-instruction-file-fails/code/split-vs-monolithic.ts +++ /dev/null @@ -1,209 +0,0 @@ -/** - * split-vs-monolithic.ts - * - * Creates a monolithic instruction file (~200 lines) and then shows how - * splitting into 4 focused files dramatically reduces the context needed - * for any single query. Simulates an "agent" searching for a specific rule - * and measures how many lines it must read in each approach. - * - * Run: npx tsx docs/lectures/lecture-04-why-one-giant-instruction-file-fails/code/split-vs-monolithic.ts - */ - -// --------------------------------------------------------------------------- -// Simulated monolithic instruction file (200 lines of rules) -// --------------------------------------------------------------------------- - -const monolithicInstructions: { lineNumber: number; section: string; content: string }[] = []; - -// Section 1: Project Overview (lines 1-50) -for (let i = 1; i <= 50; i++) { - monolithicInstructions.push({ - lineNumber: i, - section: "Project Overview", - content: i === 25 ? "This project uses React 18 with TypeScript strict mode." : `Overview detail line ${i}`, - }); -} - -// Section 2: Code Style Rules (lines 51-100) -for (let i = 51; i <= 100; i++) { - monolithicInstructions.push({ - lineNumber: i, - section: "Code Style", - content: - i === 72 - ? "RULE: Always use explicit return types on exported functions." - : i === 78 - ? "RULE: Use const assertions for immutable arrays." - : `Style rule detail line ${i}`, - }); -} - -// Section 3: Testing Standards (lines 101-150) -for (let i = 101; i <= 150; i++) { - monolithicInstructions.push({ - lineNumber: i, - section: "Testing", - content: - i === 120 - ? "RULE: Every new endpoint must have integration tests." - : i === 135 - ? "RULE: Test files must mirror the source file structure." - : `Testing detail line ${i}`, - }); -} - -// Section 4: Deployment Rules (lines 151-200) -for (let i = 151; i <= 200; i++) { - monolithicInstructions.push({ - lineNumber: i, - section: "Deployment", - content: - i === 175 - ? "RULE: Never deploy on Fridays. Deploy window is Tue-Thu 10am-3pm." - : `Deployment detail line ${i}`, - }); -} - -// --------------------------------------------------------------------------- -// Split instruction files (4 focused files) -// --------------------------------------------------------------------------- - -const splitInstructions: Record = { - "01-project-overview.md": monolithicInstructions.filter((l) => l.section === "Project Overview"), - "02-code-style.md": monolithicInstructions.filter((l) => l.section === "Code Style"), - "03-testing.md": monolithicInstructions.filter((l) => l.section === "Testing"), - "04-deployment.md": monolithicInstructions.filter((l) => l.section === "Deployment"), -}; - -// --------------------------------------------------------------------------- -// Simulated queries -- the agent needs to find specific rules -// --------------------------------------------------------------------------- - -interface Query { - description: string; - targetRule: string; - relevantSection: string; -} - -const queries: Query[] = [ - { - description: "Find the rule about return types", - targetRule: "explicit return types", - relevantSection: "Code Style", - }, - { - description: "Find the deployment window rule", - targetRule: "deploy on Fridays", - relevantSection: "Deployment", - }, - { - description: "Find the integration test rule", - targetRule: "integration tests", - relevantSection: "Testing", - }, - { - description: "Find the test file structure rule", - targetRule: "mirror the source file", - relevantSection: "Testing", - }, -]; - -// --------------------------------------------------------------------------- -// Search simulation -// --------------------------------------------------------------------------- - -function searchMonolithic(query: Query): { linesRead: number; found: boolean } { - // Agent must scan from the top, reading each line until it finds the rule. - // In the worst case it reads all lines. - let linesRead = 0; - let found = false; - - for (const line of monolithicInstructions) { - linesRead++; - if (line.content.toLowerCase().includes(query.targetRule.toLowerCase())) { - found = true; - break; - } - } - - return { linesRead, found }; -} - -function searchSplit(query: Query): { linesRead: number; found: boolean; fileAccessed: string } { - // Agent knows which file to look in based on the section. - // It only reads lines from that one file. - const fileMap: Record = { - "Project Overview": "01-project-overview.md", - "Code Style": "02-code-style.md", - Testing: "03-testing.md", - Deployment: "04-deployment.md", - }; - - const targetFile = fileMap[query.relevantSection]; - const lines = splitInstructions[targetFile]; - let linesRead = 0; - let found = false; - - for (const line of lines) { - linesRead++; - if (line.content.toLowerCase().includes(query.targetRule.toLowerCase())) { - found = true; - break; - } - } - - return { linesRead, found, fileAccessed: targetFile }; -} - -// --------------------------------------------------------------------------- -// Reporting -// --------------------------------------------------------------------------- - -function pad(s: string, len: number): string { - return s.length >= len ? s : s + " ".repeat(len - s.length); -} - -function run(): void { - console.log("\n" + "=".repeat(90)); - console.log(" MONOLITHIC vs SPLIT INSTRUCTION FILES"); - console.log("=".repeat(90)); - - console.log("\n Monolithic file: 1 file, " + monolithicInstructions.length + " lines total"); - console.log(" Split files: 4 files, ~" + Math.round(monolithicInstructions.length / 4) + " lines each\n"); - - const header = `| ${pad("Query", 42)}| ${pad("Monolithic (lines)", 20)}| ${pad("Split (lines)", 15)}| ${pad("File Accessed", 22)}| Savings`; - const sep = `|${"-".repeat(44)}|${"-".repeat(22)}|${"-".repeat(17)}|${"-".repeat(24)}|${"-".repeat(10)}`; - console.log(header); - console.log(sep); - - let totalMono = 0; - let totalSplit = 0; - - for (const q of queries) { - const mono = searchMonolithic(q); - const split = searchSplit(q); - totalMono += mono.linesRead; - totalSplit += split.linesRead; - - const savings = Math.round(((mono.linesRead - split.linesRead) / mono.linesRead) * 100); - console.log( - `| ${pad(q.description, 42)}| ${pad(String(mono.linesRead), 20)}| ${pad(String(split.linesRead), 15)}| ${pad(split.fileAccessed, 22)}| ${savings}%` - ); - } - - console.log(sep); - const avgMono = Math.round(totalMono / queries.length); - const avgSplit = Math.round(totalSplit / queries.length); - console.log( - `| ${pad("AVERAGE", 42)}| ${pad(String(avgMono), 20)}| ${pad(String(avgSplit), 15)}| ${pad("(targeted file)", 22)}| ${Math.round(((avgMono - avgSplit) / avgMono) * 100)}%` - ); - - console.log("\n" + "=".repeat(90)); - console.log(" KEY INSIGHT"); - console.log("=".repeat(90)); - console.log(" With a monolithic file, the agent must scan up to " + monolithicInstructions.length + " lines for every query."); - console.log(" With split files, it reads only the relevant " + Math.round(monolithicInstructions.length / 4) + "-line file."); - console.log(" This means less context window usage, fewer hallucinations, and faster execution.\n"); -} - -run(); diff --git a/courses/learn-harness-engineering/docs/ru/lectures/lecture-04-why-one-giant-instruction-file-fails/index.md b/courses/learn-harness-engineering/docs/ru/lectures/lecture-04-why-one-giant-instruction-file-fails/index.md deleted file mode 100644 index 709b8659..00000000 --- a/courses/learn-harness-engineering/docs/ru/lectures/lecture-04-why-one-giant-instruction-file-fails/index.md +++ /dev/null @@ -1,132 +0,0 @@ -[中文版本 →](../../../zh/lectures/lecture-04-why-one-giant-instruction-file-fails/) - -> Примеры кода: [code/](https://github.com/walkinglabs/learn-harness-engineering/blob/main/docs/en/lectures/lecture-04-why-one-giant-instruction-file-fails/code/) -> Практический проект: [Project 02. Agent-readable workspace](./../../projects/project-02-agent-readable-workspace/index.md) - -# Лекция 04. Разносите инструкции по файлам - -Вы серьёзно занялись harness engineering — здорово. Создали `AGENTS.md` и впихнули туда каждое правило, ограничение и вынесенный урок, какой только смогли вспомнить. Через месяц файл раздулся до 300 строк, через два — до 450, через три — до 600. И тут вы замечаете, что качество работы агента на самом деле падает: на простом фиксе бага агент сжигает кучу контекста на ненужные деплой-инструкции; критичное правило безопасности, закопанное на 300-й строке, просто игнорируется; три противоречащих друг другу правила стиля заставляют агента каждый раз случайно выбирать одно из них. - -Это ловушка «гигантского файла инструкций». Это как переупаковать чемодан — всё кажется полезным, поэтому вы запихиваете всё, пока молния не лопнет. Чтобы найти смену белья, приходится опустошить весь чемодан. Вы тащили полный чемодан, а реально использовали примерно треть содержимого. - -## Порочный круг в основе - -Самый частый порочный круг такой: агент ошибается, вы говорите «добавим правило, чтобы это не повторилось», добавляете в AGENTS.md, временно работает, агент делает другую ошибку, добавляете ещё правило, повторяется, файл бесконтрольно раздувается. - -Это не ваша вина. Это очень естественная реакция — «добавить правило» каждый раз, как что-то пошло не так, кажется разумным, как закидывать ещё одну вещь в сумку «на всякий случай» каждый раз перед выходом. Но накопительный эффект разрушителен. Посмотрим, что конкретно идёт не так. - -**Бюджет контекста съедается живьём.** Окно контекста у агента конечно. Допустим, у вашего агента окно 200K токенов (стандарт Claude). Раздутый файл инструкций может съедать 10–20K токенов. Кажется, ещё много места? Но в сложной задаче нужно прочитать десятки исходных файлов, вывод инструментов тоже занимает контекст, и история диалога накапливается. К тому моменту, когда агент должен понять код, бюджет уже поджимает — как чемодан, настолько забитый «на всякий случай», что для ноутбука не остаётся места. - -**Lost in the middle.** Статья «Lost in the Middle» (Liu et al., 2023) ясно показала, что LLM используют информацию в середине длинных текстов значительно хуже, чем в начале или конце. Ваш AGENTS.md на 600 строк, и на 300-й строке написано «все запросы к БД должны использовать параметризованные запросы» — это жёсткое требование безопасности. Но оно зарыто посередине, и агент почти наверняка его проигнорирует. Как тот флакон с солнцезащитным кремом на самом дне переполненного чемодана: знаете, что он там, копаетесь три раза, не находите, в итоге покупаете новый. - -**Конфликты приоритетов.** В файле смешиваются необсуждаемые жёсткие ограничения («никогда не используй eval()»), важные дизайн-рекомендации («предпочитайте функциональный стиль») и конкретный исторический урок («на той неделе пофиксили утечку памяти в WebSocket, следите за похожими паттернами»). У этих трёх правил совершенно разная важность, но в файле они выглядят одинаково. У агента нет надёжного сигнала, чтобы их различить — как загранпаспорт и зарядка, перемешанные в чемодане: непонятно, что важнее. - -**Гниение из-за поддержки.** Большие файлы по природе плохо поддерживаются. Устаревшие инструкции редко удаляют — потому что последствия удаления неясны («может, какое-то правило от этого зависит?»), а добавлять новые — кажется бесплатным. Итог: файл только растёт, никогда не уменьшается, и отношение сигнал/шум падает. Это ровно как накопление техдолга в софте. - -**Накопление противоречий.** Инструкции, добавленные в разное время, начинают противоречить друг другу — одна говорит «используйте strict mode TypeScript», другая «в некоторых легаси-файлах разрешён any». Агент каждый раз выбирает одно случайно. Как мама говорит «тепло одевайся», а папа — «не кутайся», и вы стоите в дверях, не зная, кого слушать. - -## Ключевые понятия - -- **Instruction Bloat**: когда файл инструкций занимает больше 10–15% окна контекста, он начинает вытеснять бюджет на чтение кода и осмысление задачи. `AGENTS.md` на 600 строк может потреблять 10 000–20 000 токенов — это 8–15% окна на 128K, съеденных ещё до того, как агент начал работать. -- **Lost in the Middle Effect**: исследование Liu et al. 2023 года доказало, что LLM используют информацию в середине длинных текстов значительно хуже, чем в начале или конце. У критического ограничения, зарытого на 300-й строке файла из 600, очень высокая вероятность быть фактически проигнорированным. -- **Instruction Signal-to-Noise Ratio (SNR)**: доля инструкций в файле, релевантных текущей задаче. Когда вас заставляют читать 50 строк деплой-инструкций при фиксе бага — это низкий SNR. -- **Routing File**: короткий входной файл, чья основная функция — указывать агенту на более детальные документы, а не содержать в себе всё. 50–200 строк за глаза. -- **Progressive Disclosure**: сначала обзорная информация, детальная — по требованию. Хороший дизайн harness как хороший дизайн UI: не вываливайте все опции на пользователя сразу. -- **Priority Ambiguity**: когда все инструкции выглядят одинаково по формату и месту, агент не может отличить необсуждаемые жёсткие ограничения от рекомендательных мягких советов. - -## Архитектура инструкций - -```mermaid -flowchart LR - Mono["Один AGENTS.md на 600 строк"] --> MonoLoad["Даже мелкий фикс бага
    читает деплой-правила и старые заметки"] - MonoLoad --> MonoRisk["Важные правила в середине
    легко пропустить"] - - Router["Короткий AGENTS.md"] --> Topics["Подгружать доки API / БД / тестов
    только когда нужно для задачи"] - Topics --> RoutedResult["Больше контекста остаётся на чтение кода
    и верификацию"] -``` - -```mermaid -flowchart TB - File["Файл инструкций на 600 строк"] --> Top["Верх
    quick start + жёсткие ограничения"] - File --> Mid["Середина
    критичное правило безопасности на 300-й строке"] - File --> Bot["Низ
    явный итоговый чеклист"] - Top --> Seen["Высокий шанс вспомнить"] - Bot --> Seen - Mid --> Missed["Высокий шанс размыться или быть пропущенным"] -``` - -## Как разбивать - -Главный принцип: часто нужное — под рукой, иногда нужное — убрать поглубже, никогда не нужное — выкинуть. - -Входной файл `AGENTS.md` остаётся на 50–200 строк и содержит только самое часто используемое — обзор проекта (одно-два предложения), команды первого запуска (`make setup && make test`), глобальные жёсткие ограничения (не больше 15 необсуждаемых правил) и ссылки на тематические документы (одна строка описания + условие применимости). - -```markdown -# AGENTS.md - -## Project Overview -Python 3.11 FastAPI backend, PostgreSQL 15 database. - -## Quick Start -- Install: `make setup` -- Test: `make test` -- Full verification: `make check` - -## Hard Constraints -- All APIs must use OAuth 2.0 authentication -- All database queries must use SQLAlchemy 2.0 syntax -- All PRs must pass pytest + mypy --strict + ruff check - -## Topic Docs -- [API Design Patterns](docs/api-patterns.md) — Required reading when adding endpoints -- [Database Rules](docs/database-rules.md) — Required when modifying database operations -- [Testing Standards](docs/testing-standards.md) — Reference when writing tests -``` - -Каждый тематический документ — 50–150 строк, организованы по темам в каталоге `docs/` или рядом с соответствующим модулем. Агент читает их только при необходимости. Как органайзеры в чемодане: бельё в одном, косметика в другом, зарядки в третьем. Не нужно вытряхивать весь чемодан, чтобы что-то найти. - -Часть информации лучше класть прямо в код — определения типов, комментарии к интерфейсам, пояснения в конфигах. Агент естественно увидит это при чтении кода, дублировать в инструкциях не надо. - -У каждой инструкции должен быть источник («почему это правило добавили?»), условие применимости («когда оно нужно?») и условие истечения срока («при каких обстоятельствах его можно удалить?»). Регулярно проводите ревизию, удаляйте устаревшее, дублирующее и противоречивое. Управляйте инструкциями как зависимостями кода — неиспользуемые удаляются, иначе только тормозят систему. - -Если инструкция обязана быть во входном файле, ставьте её в начало или в конец, никогда — в середину. Эффект «lost in the middle» говорит, что LLM значительно лучше используют информацию на краях, чем по центру. Но лучший подход — вынести инструкции в тематические документы и подгружать по требованию. - -И OpenAI, и Anthropic неявно поддерживают подход разбиения. OpenAI говорят, что входные файлы должны быть «короткими и роутинговыми», Anthropic — что управляющая информация для долгоживущих агентов должна быть «лаконичной и высокоприоритетной». Оба говорят об одном: не запихивайте всё в один файл. Чемодан надо организовывать, а не утрамбовывать в него всё подряд. - -## Пример из жизни - -`AGENTS.md` одной SaaS-команды разбух с 50 строк до 600. В содержимом смешались версии стека, стандарты кодирования, заметки о фиксах багов, гайды по API, процедуры деплоя и личные предпочтения членов команды — целый чемодан, лопающийся по швам. - -Качество работы агента заметно поползло вниз: при простых фиксах багов агент тратил много контекста на ненужные деплой-инструкции; ограничение безопасности «все запросы к БД должны быть параметризованы» было закопано на 300-й строке и часто игнорировалось; три противоречащих правила стиля приводили к случайному поведению. - -Команда провела «реорганизацию чемодана»: -1. `AGENTS.md` обрезали до 80 строк: только обзор проекта, команды запуска и 15 глобальных жёстких ограничений -2. Создали тематические документы: `docs/api-patterns.md` (120 строк), `docs/database-rules.md` (60 строк), `docs/testing-standards.md` (80 строк) -3. Добавили в роутинг-файл ссылки на тематические доки -4. Исторические заметки либо превратили в тест-кейсы, либо удалили - -После рефакторинга: успешность на том же наборе задач выросла с 45% до 72%. Соблюдение ограничения безопасности — с 60% до 95%, потому что оно переехало из середины файла на верх роутинг-файла и больше не «терялось в середине». - -## Главное - -- «Добавить правило» — обезболивающее на короткую дистанцию, яд на длинную. Прежде чем добавлять правило, спросите: не лучше ли это в тематической доке? Не нужно просто продолжать запихивать в чемодан. -- Входной файл — роутер, а не энциклопедия. 50–200 строк: только обзор, жёсткие ограничения и ссылки. -- Используйте эффект «lost in the middle»: важное — на верх или низ; неважное — в тематические документы. -- Управляйте раздуванием инструкций как техдолгом. Регулярные ревизии, у каждой инструкции — источник, условие применимости и условие истечения срока. -- После разбиения SNR растёт, и агент тратит больше контекстного бюджета на саму задачу, а не на разбор ненужных инструкций. - -## Дальнейшее чтение - -- [OpenAI: Harness Engineering](https://openai.com/index/harness-engineering/) -- [Anthropic: Effective Harnesses for Long-Running Agents](https://www.anthropic.com/engineering/effective-harnesses-for-long-running-agents) -- [Lost in the Middle: How Language Models Use Long Contexts](https://arxiv.org/abs/2307.03172) -- [HumanLayer: Harness Engineering for Coding Agents](https://humanlayer.dev/articles/harness-engineering-for-coding-agents/) -- [Nielsen Norman Group: Progressive Disclosure](https://www.nngroup.com/articles/progressive-disclosure/) - -## Упражнения - -1. **Аудит SNR**: возьмите свой текущий входной файл инструкций и перечислите все пункты. Возьмите 5 разных типов часто встречающихся задач и для каждой инструкции отметьте, релевантна ли она. Посчитайте SNR для каждого типа задач. Инструкции, которые для большинства задач — шум, переносите в тематические документы. - -2. **Рефакторинг под progressive disclosure**: если у вас есть файл инструкций больше 300 строк, разбейте его на: (a) роутинг-файл меньше 100 строк, (b) 3–5 тематических документов. Прогоните один и тот же набор задач (минимум 5) до и после, сравните успешность. - -3. **Проверка lost in the middle**: в длинном файле инструкций поставьте критическое ограничение последовательно в начало, в середину и в конец, каждый раз прогоняя один и тот же набор задач (минимум 5 прогонов на позицию). Посмотрите, есть ли разница в соблюдении. Можете удивиться, насколько сильным окажется эффект позиции. diff --git a/courses/learn-harness-engineering/docs/ru/lectures/lecture-05-why-long-running-tasks-lose-continuity/code/continuity-checklist.md b/courses/learn-harness-engineering/docs/ru/lectures/lecture-05-why-long-running-tasks-lose-continuity/code/continuity-checklist.md deleted file mode 100644 index d4cae612..00000000 --- a/courses/learn-harness-engineering/docs/ru/lectures/lecture-05-why-long-running-tasks-lose-continuity/code/continuity-checklist.md +++ /dev/null @@ -1,6 +0,0 @@ -# Чеклист непрерывности - -- Может ли свежий агент определить недавнюю работу менее чем за пять минут? -- Задокументирован ли текущий стабильный путь запуска? -- Чётко ли обозначена незавершённая работа? -- Видна ли следующая лучшая задача без чтения старых логов чата? diff --git a/courses/learn-harness-engineering/docs/ru/lectures/lecture-05-why-long-running-tasks-lose-continuity/code/index.md b/courses/learn-harness-engineering/docs/ru/lectures/lecture-05-why-long-running-tasks-lose-continuity/code/index.md deleted file mode 100644 index 81899625..00000000 --- a/courses/learn-harness-engineering/docs/ru/lectures/lecture-05-why-long-running-tasks-lose-continuity/code/index.md +++ /dev/null @@ -1,7 +0,0 @@ -# Код к лекции 05 - -Используйте эту папку для примеров: - -- сломанные многосессионные задачи -- отсутствующие артефакты непрерывности -- паттерны восстановления непрерывности diff --git a/courses/learn-harness-engineering/docs/ru/lectures/lecture-05-why-long-running-tasks-lose-continuity/code/session-handoff.md b/courses/learn-harness-engineering/docs/ru/lectures/lecture-05-why-long-running-tasks-lose-continuity/code/session-handoff.md deleted file mode 100644 index 3858d507..00000000 --- a/courses/learn-harness-engineering/docs/ru/lectures/lecture-05-why-long-running-tasks-lose-continuity/code/session-handoff.md +++ /dev/null @@ -1,17 +0,0 @@ -# Пример Session Handoff - -## Сделано - -- Добавлена поддержка импорта markdown -- Добавлен базовый список документов в renderer - -## Сломано или не проверено - -- Импорт работает для `.md`, но падает на больших `.txt` файлах -- Приложение запускается, но детальное представление не подключено - -## Следующий лучший шаг - -- Починить путь импорта `.txt` -- Проверить импорт сквозно -- Затем добавить панель деталей документа diff --git a/courses/learn-harness-engineering/docs/ru/lectures/lecture-05-why-long-running-tasks-lose-continuity/code/session-simulator.ts b/courses/learn-harness-engineering/docs/ru/lectures/lecture-05-why-long-running-tasks-lose-continuity/code/session-simulator.ts deleted file mode 100644 index 69f9c3e2..00000000 --- a/courses/learn-harness-engineering/docs/ru/lectures/lecture-05-why-long-running-tasks-lose-continuity/code/session-simulator.ts +++ /dev/null @@ -1,162 +0,0 @@ -/** - * session-simulator.ts - * - * Simulates two sessions working on a multi-step task. - * Run 1: No handoff file -- Session B starts from scratch and duplicates work. - * Run 2: With handoff file -- Session B picks up where Session A left off. - * - * Run: npx tsx docs/lectures/lecture-05-why-long-running-tasks-lose-continuity/code/session-simulator.ts - */ - -// --------------------------------------------------------------------------- -// Types -// --------------------------------------------------------------------------- - -interface TaskStep { - id: number; - name: string; - durationMs: number; -} - -interface SessionResult { - session: string; - stepsCompleted: number; - totalDurationMs: number; - duplicatedSteps: number; - output: string[]; -} - -// --------------------------------------------------------------------------- -// Task definition -// --------------------------------------------------------------------------- - -const taskSteps: TaskStep[] = [ - { id: 1, name: "Read project structure", durationMs: 50 }, - { id: 2, name: "Understand existing auth module", durationMs: 80 }, - { id: 3, name: "Design new search endpoint", durationMs: 60 }, - { id: 4, name: "Implement search endpoint", durationMs: 100 }, - { id: 5, name: "Write integration tests", durationMs: 70 }, - { id: 6, name: "Update documentation", durationMs: 40 }, -]; - -// --------------------------------------------------------------------------- -// Simulated session executor -// --------------------------------------------------------------------------- - -/** Simulate a session doing work. */ -function runSession( - sessionName: string, - startStep: number, - endStep: number -): SessionResult { - const output: string[] = []; - let totalDuration = 0; - - for (let i = startStep; i <= endStep; i++) { - const step = taskSteps.find((s) => s.id === i)!; - totalDuration += step.durationMs; - output.push(` [${sessionName}] Step ${step.id}: ${step.name} (${step.durationMs}ms)`); - } - - return { - session: sessionName, - stepsCompleted: endStep - startStep + 1, - totalDurationMs: totalDuration, - duplicatedSteps: 0, - output, - }; -} - -// --------------------------------------------------------------------------- -// Run 1: No handoff file -// --------------------------------------------------------------------------- - -function simulateNoHandoff(): { sessionA: SessionResult; sessionB: SessionResult } { - // Session A does steps 1-3 before timing out - const sessionA = runSession("Session A", 1, 3); - - // Session B has no context, starts from scratch - const sessionB = runSession("Session B", 1, 6); - sessionB.duplicatedSteps = 3; // Redoes steps 1-3 that A already did - - return { sessionA, sessionB }; -} - -// --------------------------------------------------------------------------- -// Run 2: With handoff file -// --------------------------------------------------------------------------- - -function simulateWithHandoff(): { sessionA: SessionResult; sessionB: SessionResult } { - // Session A does steps 1-3 and writes a handoff file - const sessionA = runSession("Session A", 1, 3); - - // Session B reads handoff, starts from step 4 - const sessionB = runSession("Session B", 4, 6); - sessionB.duplicatedSteps = 0; - - return { sessionA, sessionB }; -} - -// --------------------------------------------------------------------------- -// Reporting -// --------------------------------------------------------------------------- - -function pad(s: string, len: number): string { - return s.length >= len ? s : s + " ".repeat(len - s.length); -} - -function printRun(title: string, result: { sessionA: SessionResult; sessionB: SessionResult }): void { - console.log("\n" + "=".repeat(80)); - console.log(` ${title}`); - console.log("=".repeat(80)); - - console.log("\n Session A output:"); - for (const line of result.sessionA.output) { - console.log(line); - } - - console.log("\n Session B output:"); - for (const line of result.sessionB.output) { - console.log(line); - } - - console.log("\n Session B notes:"); - if (result.sessionB.duplicatedSteps > 0) { - console.log(` - Redid ${result.sessionB.duplicatedSteps} steps that Session A already completed`); - console.log(` - Wasted ${result.sessionB.totalDurationMs - (taskSteps.slice(3).reduce((s, t) => s + t.durationMs, 0))}ms on duplicate work`); - } else { - console.log(" - Read handoff file, continued from step 4"); - console.log(" - No duplicate work performed"); - } -} - -function printComparison(): void { - const noHandoff = simulateNoHandoff(); - const withHandoff = simulateWithHandoff(); - - printRun("RUN 1: NO HANDOFF FILE", noHandoff); - printRun("RUN 2: WITH HANDOFF FILE", withHandoff); - - // Comparison table - console.log("\n" + "=".repeat(80)); - console.log(" COMPARISON TABLE"); - console.log("=".repeat(80) + "\n"); - - const header = `| ${pad("Metric", 35)}| ${pad("No Handoff", 15)}| ${pad("With Handoff", 15)}|`; - const sep = `|${"-".repeat(37)}|${"-".repeat(17)}|${"-".repeat(17)}|`; - console.log(header); - console.log(sep); - - const noTotal = noHandoff.sessionA.totalDurationMs + noHandoff.sessionB.totalDurationMs; - const withTotal = withHandoff.sessionA.totalDurationMs + withHandoff.sessionB.totalDurationMs; - - console.log(`| ${pad("Session A steps completed", 35)}| ${pad(String(noHandoff.sessionA.stepsCompleted), 15)}| ${pad(String(withHandoff.sessionA.stepsCompleted), 15)}|`); - console.log(`| ${pad("Session B steps completed", 35)}| ${pad(String(noHandoff.sessionB.stepsCompleted), 15)}| ${pad(String(withHandoff.sessionB.stepsCompleted), 15)}|`); - console.log(`| ${pad("Duplicated steps", 35)}| ${pad(String(noHandoff.sessionB.duplicatedSteps), 15)}| ${pad(String(withHandoff.sessionB.duplicatedSteps), 15)}|`); - console.log(`| ${pad("Total work (ms)", 35)}| ${pad(String(noTotal), 15)}| ${pad(String(withTotal), 15)}|`); - console.log(`| ${pad("Time saved by handoff", 35)}| ${pad("-", 15)}| ${pad(String(noTotal - withTotal) + "ms", 15)}|`); - - console.log("\n A handoff file eliminates duplicate work and ensures continuity across sessions.\n"); -} - -printComparison(); diff --git a/courses/learn-harness-engineering/docs/ru/lectures/lecture-05-why-long-running-tasks-lose-continuity/index.md b/courses/learn-harness-engineering/docs/ru/lectures/lecture-05-why-long-running-tasks-lose-continuity/index.md deleted file mode 100644 index 57df6df9..00000000 --- a/courses/learn-harness-engineering/docs/ru/lectures/lecture-05-why-long-running-tasks-lose-continuity/index.md +++ /dev/null @@ -1,180 +0,0 @@ -[中文版本 →](../../../zh/lectures/lecture-05-why-long-running-tasks-lose-continuity/) - -> Примеры кода: [code/](https://github.com/walkinglabs/learn-harness-engineering/blob/main/docs/en/lectures/lecture-05-why-long-running-tasks-lose-continuity/code/) -> Практический проект: [Project 03. Multi-session continuity](./../../projects/project-03-multi-session-continuity/index.md) - -# Лекция 05. Сохраняйте контекст между сессиями - -Вы просите Claude Code реализовать целую фичу. Он работает 30 минут, делает большую часть работы, но контекст подходит к концу. Вы открываете новую сессию, чтобы продолжить, — и обнаруживаете, что он не помнит ни какие решения были приняты, ни почему вариант A был выбран вместо варианта B, ни какие файлы уже изменены, ни в каком состоянии находятся тесты. Он тратит 15 минут на повторное изучение проекта и может действовать вразрез с предыдущим подходом. - -Представьте, что вы мастер, который каждое утро после пробуждения всё забывает. Вам пришлось бы заново знакомиться со всей стройкой — какая стена недостроена, почему выбрали красный кирпич вместо синего, докуда дошли с водопроводом. Хуже того, вы могли бы выломать окно, которое уже было установлено вчера, просто потому что не помнили об этом. - -Именно в таком положении находятся AI-агенты при многосессионных задачах. Эта лекция объясняет, почему агенты «отключаются» во время длинных задач, и как структурированное сохранение состояния превращает их в мастера, который ведёт надёжный ежедневный журнал — всё ещё страдающего амнезией, но журнал помнит всё. - -## Контекстные окна: не бесконечны - -Контекстные окна конечны. Это не решается обновлением модели — даже если размер окна вырастет до 1M токенов, сложные задачи всё равно их исчерпают. Потому что агенты не просто генерируют код; они изучают кодовые базы, отслеживают историю собственных решений, обрабатывают вывод инструментов и поддерживают контекст разговора. Вся эта информация растёт быстрее, чем расширяется окно. - -Более глубокая проблема: информация, которую производит агент, неравноценна по важности. Промежуточные шаги рассуждений содержат «почему» решений — почему вариант B был выбран вместо A, почему именно эта библиотека, а не другая, почему конкретная оптимизация была пропущена. Финальный вывод содержит только «что» — сам код. Стратегии компакции обычно сохраняют второе, но теряют первое. Следующая сессия видит код, но не знает, почему он написан именно так, и может «оптимизировать» осознанное проектное решение. - -Anthropic в своём исследовании long-running агентов обнаружила любопытный эффект: когда агент чувствует, что контекст подходит к концу, он демонстрирует поведение «преждевременного схождения» — спешит закончить текущую работу, пропускает шаги верификации или выбирает простое решение вместо оптимального. Это похоже на то, как на экзамене, поняв, что время заканчивается, вы быстро гадаете в оставшихся вопросах с выбором ответа. Anthropic называет это «контекстной тревожностью». - -## Поток непрерывности сессий - -Без артефактов непрерывности каждая новая сессия — это катастрофа: - -```mermaid -flowchart LR - S1["Сессия 1
    фича сделана наполовину"] --> End1["Контекст почти полон
    сессия завершается"] - End1 --> S2["Сессия 2 стартует с нуля"] - S2 --> Guess["Пере-чтение папок, перезапуск тестов,
    догадки, почему код написан так"] - Guess --> Drift["Работа повторяется
    и восстановление идёт медленно"] -``` - -С артефактами непрерывности новые сессии могут быстро подхватить работу: - -```mermaid -flowchart LR - Work["Работа сессии 1"] --> Progress["PROGRESS.md
    сделано / в работе / следующий шаг"] - Work --> Decisions["DECISIONS.md
    почему выбран этот подход"] - Work --> Verify["Заметки по верификации
    какие тесты прошли и упали"] - Work --> Commit["Git checkpoint
    точное состояние репо"] - - Progress --> Rebuild["Восстановление в сессии 2"] - Decisions --> Rebuild - Verify --> Rebuild - Commit --> Rebuild - - Rebuild --> Resume["Новая сессия быстро подхватывает"] -``` - -## Ключевые понятия - -- **Контекстные окна конечны**: какой бы размер ни заявлялся (128K, 200K, 1M), длинные задачи рано или поздно их исчерпают. После исчерпания требуется либо компакция (с потерей информации), либо сброс (новая сессия). Оба варианта что-то теряют. -- **Артефакты непрерывности**: сохранённые файлы состояния, которые позволяют новой сессии однозначно продолжить с того места, где остановилась предыдущая. Базовая форма: лог прогресса + запись верификации + следующие действия. Тот самый журнал мастера. -- **Стоимость восстановления**: время, нужное новой сессии, чтобы выйти в рабочее состояние. Хорошие harness'ы сжимают её с 15 минут до 3 минут. -- **Дрейф (drift)**: разрыв между пониманием агента и реальным состоянием репозитория. Каждая граница сессии вносит дрейф; без контроля он накапливается. -- **Контекстная тревожность**: явление, наблюдаемое Anthropic, — агенты демонстрируют преждевременное схождение при приближении к воспринимаемому пределу контекста, преждевременно завершая задачи, чтобы избежать потери информации. Это иррациональная ресурсная тревожность. -- **Компакция против сброса**: компакция суммирует контекст внутри той же сессии (сохраняет «что», может потерять «почему»); сброс открывает новую сессию, восстанавливаясь из сохранённого состояния (чисто, но зависит от полноты артефактов). - -## Что происходит, когда непрерывность ломается - -Предыдущая сессия потратила значительный бюджет контекста на анализ трёх подходов и выбор варианта B. Текущая сессия не знает об этом анализе и может пере-решить вопрос на основе неполной информации — потенциально выбрав вариант A. Как мастер с амнезией, который не помнит, почему был выбран красный кирпич, смотрит сегодня на синий, думает, что он красивее, и сносит вчерашнюю стену, чтобы перестроить. - -Ещё хуже — дублирование работы. Агент не уверен, была ли уже выполнена определённая работа, и делает её снова. Или, что хуже, делает её наполовину, обнаруживает конфликт с существующей реализацией и вынужден переделывать. На стройке две бригады не могут одновременно строить одну стену, но без записей о прогрессе новая бригада не знает, что кто-то уже над ней работает. - -За несколько сессий направление реализации могло незаметно сместиться от исходных требований. Каждая новая сессия немного по-другому понимает цели проекта. Как в игре в «испорченный телефон»: после десяти передач «купи мне кофе» может превратиться в «купи мне кофемашину». - -Есть и пробел в верификации. Результаты верификации предыдущей сессии (какие тесты проходят, какие падают, почему падают) не были записаны. Новая сессия вынуждена перезапускать всю верификацию, чтобы понять текущее состояние. Каждая сессия диагностирует с нуля, каждый раз тратя драгоценный контекст. - -И OpenAI, и Anthropic подчёркивают в своей документации важность структурированного сохранения состояния. Статья OpenAI о harness engineering трактует репозиторий как «операционный журнал»: результаты каждой операции должны оставлять отслеживаемые следы в репо. Документация Anthropic по long-running агентам специально рекомендует «handoff-файлы» — структурированные документы, содержащие текущее состояние, известные проблемы и следующие действия. - -## Журнал для мастера с амнезией - -Основной подход: **относитесь к агенту как к гениальному инженеру с амнезией.** Прежде чем «уйти со смены», он должен записать критическую информацию, чтобы следующий «сменный» агент мог быстро подхватить работу. - -**Инструмент 1: файл прогресса (PROGRESS.md).** Самый базовый артефакт непрерывности — ядро журнала: - -```markdown -# Project Progress - -## Current State -- Latest commit: abc1234 (feat: add user preferences endpoint) -- Test status: 42/43 passing (test_pagination_edge_case failing) -- Lint: passing - -## Completed -- [x] User model and database migration -- [x] Basic CRUD endpoints -- [x] Auth middleware integration - -## In Progress -- [ ] Pagination feature (90% - edge case test failing) - -## Known Issues -- test_pagination_edge_case returns 500 on empty result sets -- Need to confirm whether deleted users should appear in listings - -## Next Steps -1. Fix pagination edge case bug -2. Add "include deleted users" query parameter -3. Update API documentation -``` - -**Инструмент 2: лог решений (DECISIONS.md).** Записывайте важные проектные решения и причины. Не нужны подробные дизайн-документы — достаточно «какое решение, почему, когда» — это памятки в журнале: - -```markdown -# Design Decisions - -## 2024-01-15: Use Redis for user preferences caching -- Reason: High read frequency (every API call), small data size -- Rejected alternative: PostgreSQL materialized view (high change frequency makes maintenance cost not worthwhile) -- Constraint: Cache TTL of 5 minutes, active invalidation on write -``` - -**Инструмент 3: Git-коммиты как контрольные точки.** Коммитьте после завершения каждой атомарной единицы работы. Сообщения коммитов должны объяснять, что сделано и почему. Это бесплатные, автоматически версионируемые снимки состояния. - -**Инструмент 4: init.sh или процедура инициализации harness'а.** В `AGENTS.md` опишите процедуры «прихода на смену» и «ухода со смены»: - -```markdown -## At session start (clock in) -1. Read PROGRESS.md for current state -2. Read DECISIONS.md for important decisions -3. Run make check to confirm repo is in consistent state -4. Continue from PROGRESS.md "Next Steps" section - -## Before session end (clock out) -1. Update PROGRESS.md -2. Run make check to confirm consistent state -3. Commit all completed work -``` - -**Смешанная стратегия**: не каждой задаче нужен сброс контекста. Короткие задачи (менее 30 минут) можно выполнить за одну сессию. Длинные задачи (на несколько сессий) обязаны использовать файлы прогресса и логи решений для непрерывности. Критерий: если задача требует более 60% окна, начинайте готовить handoff. - -### Подробнее о контекстной тревожности - -Исследование Anthropic в марте 2026 раскрыло конкретные проявления контекстной тревожности: на Sonnet 4.5, когда контекст приближается к пределу окна, агент демонстрирует выраженное поведение «преждевременного схождения». Это похоже на то, как на экзамене, увидев, что время почти вышло, вы быстро ставите случайные ответы. - -Две стратегии решают эту проблему: - -**Компакция**: суммирование раннего разговора внутри той же сессии. Преимущество: сохраняет непрерывность, агент видит «что». Недостаток: «почему» часто теряется в выжимках — почему вариант B был выбран вместо A, почему конкретная оптимизация была пропущена. Что критичнее, компакция не устраняет контекстную тревожность — агент знает, что контекст когда-то был большим, и психологически всё ещё стремится к скорейшему закрытию. - -**Сброс контекста**: полная очистка контекста, открытие новой сессии, восстановление из сохранённых артефактов. Преимущество: чистое ментальное состояние — у новой сессии нет тревожности «у меня заканчивается время». Недостаток: зависит от полноты артефактов handoff. Если в журнале нет критической информации, новая сессия может потратить время, идя в неверном направлении. - -Реальные данные Anthropic: для Sonnet 4.5 контекстная тревожность настолько серьёзна, что одной компакции недостаточно — сброс контекста становится критическим компонентом дизайна harness'а. Но для Opus 4.5 это поведение значительно ослаблено, и компакция может управлять контекстом без опоры на сбросы. Это значит: **дизайн harness'а должен учитывать конкретную целевую модель, а не использовать универсальный шаблон.** - -> Источник: [Anthropic: Harness design for long-running application development](https://www.anthropic.com/engineering/harness-design-long-running-apps) - -## Пример из реальной практики - -Агенту поручили реализовать блог-систему с аутентификацией пользователей — 12 фич, по оценке нужно 5 сессий. - -**Базовый случай без журнала**: сессия 1 реализовала модель пользователя и базовые маршруты. Сессия 2 началась без памяти о контракте интерфейса auth-middleware, потратив ~15 минут на восстановление прежнего проектного замысла. К сессии 3 накопленный дрейф привёл к тому, что агент начал переделывать уже готовые фичи. К сессии 5 в репо было много избыточного кода, но ключевая фича аутентификации всё ещё не проходила сквозные тесты. Завершено только 7 из 12 фич, у 3 — скрытые проблемы корректности. Как мастер, который никогда не пишет в журнале: к пятому дню стройка в хаосе, какие-то стены построены дважды, какие-то так и не начаты. - -**С журналом**: использовались файлы прогресса, логи решений, записи верификации и git-чекпоинты. Отчёт о состоянии автоматически обновлялся в конце каждой сессии. Стоимость восстановления в сессии 2 упала до ~3 минут. К сессии 5 все 12 фич завершены и проверены. - -Количественное сравнение: время восстановления сократилось на ~78%, доля завершённых фич — с 58% до 100%, доля скрытых дефектов — с 43% до 8%. Мастер по-прежнему страдает амнезией, но с журналом каждый день начинается с того места, где закончился вчерашний, а не с нуля. - -## Главные выводы - -- Контекстное окно — конечный ресурс. Длинные задачи будут охватывать несколько сессий, а сессии будут терять информацию — как мастер, забывающий каждый день, это объективная реальность. -- Решение не в больших окнах, а в лучшем сохранении состояния. Файлы прогресса + логи решений + git-чекпоинты — дайте мастеру с амнезией надёжный журнал. -- Относитесь к агенту как к инженеру с амнезией: перед «уходом со смены» запишите, что сделано, почему и что дальше. -- Стоимость восстановления — ключевая метрика. Хороший harness должен выводить новую сессию в рабочее состояние за 3 минуты. -- Смешанная стратегия: короткие задачи внутри сессии, длинные — через структурированные артефакты непрерывности. - -## Дополнительное чтение - -- [Anthropic: Effective Harnesses for Long-Running Agents](https://www.anthropic.com/engineering/effective-harnesses-for-long-running-agents) -- [OpenAI: Harness Engineering](https://openai.com/index/harness-engineering/) -- [Lost in the Middle: How Language Models Use Long Contexts](https://arxiv.org/abs/2307.03172) -- [Claude Code Documentation](https://docs.anthropic.com/en/docs/claude-code) -- [HumanLayer: Harness Engineering for Coding Agents](https://humanlayer.dev/articles/harness-engineering-for-coding-agents/) - -## Упражнения - -1. **Измерение потерь непрерывности**: выберите задачу разработки, требующую как минимум 3 сессий. Без артефактов непрерывности фиксируйте в начале каждой сессии, сколько контекста агент тратит на «понимание, что было в прошлый раз». В конце каждой сессии создавайте файл прогресса и пускайте следующую сессию с него. Сравните стоимость восстановления с файлами прогресса и без них. - -2. **Проектирование шаблона handoff**: разработайте минимальный шаблон handoff с четырьмя полями: состояние репо (хеш коммита), состояние времени выполнения (доля проходящих тестов), блокеры, следующие действия. Дайте полностью свежей сессии агента восстановить состояние проекта, используя только этот шаблон. Записывайте неоднозначности, возникшие при восстановлении, и итеративно улучшайте шаблон. - -3. **Эксперимент со смешанной стратегией**: в задаче на 5 сессий сравните три стратегии: (a) всегда новая сессия + файлы прогресса, (b) делать как можно больше за одну сессию (компакция контекста), (c) смешанная стратегия (короткие задачи внутри сессии, длинные — между сессиями + файлы прогресса). Сравните время восстановления, долю завершённых фич и согласованность решений. diff --git a/courses/learn-harness-engineering/docs/ru/lectures/lecture-06-why-initialization-needs-its-own-phase/code/index.md b/courses/learn-harness-engineering/docs/ru/lectures/lecture-06-why-initialization-needs-its-own-phase/code/index.md deleted file mode 100644 index d068f187..00000000 --- a/courses/learn-harness-engineering/docs/ru/lectures/lecture-06-why-initialization-needs-its-own-phase/code/index.md +++ /dev/null @@ -1,8 +0,0 @@ -# Код к лекции 06 - -Используйте эту папку для примеров: - -- результатов работы инициализатора -- скриптов инициализации -- файлов прогресса -- каркаса для первого запуска diff --git a/courses/learn-harness-engineering/docs/ru/lectures/lecture-06-why-initialization-needs-its-own-phase/code/init-check.ts b/courses/learn-harness-engineering/docs/ru/lectures/lecture-06-why-initialization-needs-its-own-phase/code/init-check.ts deleted file mode 100644 index 52dcd985..00000000 --- a/courses/learn-harness-engineering/docs/ru/lectures/lecture-06-why-initialization-needs-its-own-phase/code/init-check.ts +++ /dev/null @@ -1,296 +0,0 @@ -/** - * init-check.ts - * - * Programmatically checks initialization prerequisites: - * - Node.js version - * - Dependencies installed (node_modules exists) - * - Data directory exists - * - Config files present - * - * Simulates running with and without an explicit init phase, - * showing how missing prerequisites cause silent failures later. - * - * Run: npx tsx docs/lectures/lecture-06-why-initialization-needs-its-own-phase/code/init-check.ts - */ - -import * as fs from "node:fs"; -import * as path from "node:path"; -import * as childProcess from "node:child_process"; - -// --------------------------------------------------------------------------- -// Types -// --------------------------------------------------------------------------- - -interface CheckItem { - name: string; - category: string; - check: () => { pass: boolean; detail: string }; - impactIfMissing: string; -} - -interface CheckResult { - name: string; - category: string; - passed: boolean; - detail: string; - impactIfMissing: string; -} - -// --------------------------------------------------------------------------- -// Checks -// --------------------------------------------------------------------------- - -function createChecks(targetDir: string): CheckItem[] { - return [ - { - name: "Node.js version >= 18", - category: "Runtime", - check: () => { - try { - const version = process.version; // e.g. "v20.11.0" - const major = parseInt(version.replace("v", "").split(".")[0], 10); - return { - pass: major >= 18, - detail: `Detected: ${version} (need >= v18)`, - }; - } catch { - return { pass: false, detail: "Could not detect Node.js version" }; - } - }, - impactIfMissing: "TypeScript features and built-in APIs unavailable", - }, - { - name: "package.json exists", - category: "Config", - check: () => { - const p = path.join(targetDir, "package.json"); - const exists = fs.existsSync(p); - return { - pass: exists, - detail: exists ? `Found: ${p}` : "Not found in target directory", - }; - }, - impactIfMissing: "Cannot install dependencies or run scripts", - }, - { - name: "Dependencies installed (node_modules)", - category: "Dependencies", - check: () => { - const p = path.join(targetDir, "node_modules"); - const exists = fs.existsSync(p); - return { - pass: exists, - detail: exists ? `Found: ${p}` : "node_modules/ not found -- run npm install", - }; - }, - impactIfMissing: "All imports fail at runtime", - }, - { - name: "TypeScript available", - category: "Toolchain", - check: () => { - try { - const result = childProcess.execSync("npx tsc --version", { - encoding: "utf-8", - timeout: 10000, - stdio: ["pipe", "pipe", "pipe"], - }).trim(); - return { pass: true, detail: `Detected: ${result}` }; - } catch { - return { pass: false, detail: "TypeScript not available via npx" }; - } - }, - impactIfMissing: "Cannot compile TypeScript files", - }, - { - name: "tsconfig.json exists", - category: "Config", - check: () => { - const p = path.join(targetDir, "tsconfig.json"); - const exists = fs.existsSync(p); - return { - pass: exists, - detail: exists ? `Found: ${p}` : "Not found", - }; - }, - impactIfMissing: "TypeScript compiler uses defaults, may not match project needs", - }, - { - name: "Source directory exists", - category: "Structure", - check: () => { - const candidates = ["src", "lib", "app"]; - const found = candidates.filter((d) => { - const p = path.join(targetDir, d); - return fs.existsSync(p) && fs.statSync(p).isDirectory(); - }); - return { - pass: found.length > 0, - detail: found.length > 0 ? `Found: ${found.join(", ")}` : "No src/ lib/ or app/ directory found", - }; - }, - impactIfMissing: "Agent cannot locate source files to modify", - }, - { - name: "Test directory exists", - category: "Structure", - check: () => { - const candidates = ["test", "tests", "__tests__", "spec"]; - const found = candidates.filter((d) => { - const p = path.join(targetDir, d); - return fs.existsSync(p) && fs.statSync(p).isDirectory(); - }); - return { - pass: found.length > 0, - detail: found.length > 0 ? `Found: ${found.join(", ")}` : "No test directory found", - }; - }, - impactIfMissing: "Agent cannot find or run existing tests", - }, - { - name: "Git repository initialized", - category: "Version Control", - check: () => { - const gitDir = path.join(targetDir, ".git"); - const exists = fs.existsSync(gitDir); - return { - pass: exists, - detail: exists ? "Git repository detected" : "No .git directory found", - }; - }, - impactIfMissing: "No rollback capability, no change history", - }, - ]; -} - -// --------------------------------------------------------------------------- -// Simulation: with and without init phase -// --------------------------------------------------------------------------- - -interface SimResult { - scenario: string; - checksRun: boolean; - failuresBeforeWork: number; - workAttempted: boolean; - workSucceeded: boolean; - timeWastedMs: number; -} - -function simulateWithoutInit(): SimResult { - // Agent skips init, goes straight to work. - // Encounters failures one at a time as it hits missing prerequisites. - const checks = createChecks(process.cwd()); - let failures = 0; - let timeWasted = 0; - - for (const check of checks) { - const result = check.check(); - if (!result.pass) { - failures++; - timeWasted += 200; // Agent spends time discovering each missing piece - } - } - - return { - scenario: "NO INIT PHASE", - checksRun: false, - failuresBeforeWork: 0, // Didn't check upfront - workAttempted: true, - workSucceeded: failures === 0, - timeWastedMs: timeWasted, - }; -} - -function simulateWithInit(): SimResult { - // Agent runs init phase first, discovers all issues upfront. - const checks = createChecks(process.cwd()); - let failures = 0; - - for (const check of checks) { - if (!check.check().pass) { - failures++; - } - } - - return { - scenario: "WITH INIT PHASE", - checksRun: true, - failuresBeforeWork: failures, - workAttempted: failures === 0, - workSucceeded: failures === 0, - timeWastedMs: 0, - }; -} - -// --------------------------------------------------------------------------- -// Reporting -// --------------------------------------------------------------------------- - -function pad(s: string, len: number): string { - return s.length >= len ? s : s + " ".repeat(len - s.length); -} - -function run(): void { - const targetDir = process.cwd(); - console.log("\n" + "=".repeat(80)); - console.log(" INITIALIZATION PREREQUISITE CHECK"); - console.log("=".repeat(80)); - console.log(` Target: ${targetDir}\n`); - - const checks = createChecks(targetDir); - const results: CheckResult[] = checks.map((c) => { - const r = c.check(); - return { - name: c.name, - category: c.category, - passed: r.pass, - detail: r.detail, - impactIfMissing: c.impactIfMissing, - }; - }); - - // Detailed report - const header = `| ${pad("Check", 35)}| ${pad("Category", 12)}| ${pad("Status", 6)}| Detail`; - const sep = `|${"-".repeat(37)}|${"-".repeat(14)}|${"-".repeat(8)}|${"-".repeat(40)}`; - console.log(header); - console.log(sep); - - for (const r of results) { - const status = r.passed ? "PASS" : "FAIL"; - console.log(`| ${pad(r.name, 35)}| ${pad(r.category, 12)}| ${pad(status, 6)}| ${r.detail}`); - } - - const passCount = results.filter((r) => r.passed).length; - const failCount = results.filter((r) => !r.passed).length; - - console.log("\n" + "-".repeat(80)); - console.log(` Results: ${passCount} passed, ${failCount} failed out of ${results.length} checks`); - - if (failCount > 0) { - console.log("\n FAILED CHECKS -- Impact if not resolved:"); - for (const r of results.filter((r) => !r.passed)) { - console.log(` - ${r.name}: ${r.impactIfMissing}`); - } - } - - // Comparison: with vs without init - const noInit = simulateWithoutInit(); - const withInit = simulateWithInit(); - - console.log("\n" + "=".repeat(80)); - console.log(" INIT PHASE COMPARISON"); - console.log("=".repeat(80) + "\n"); - - const cHeader = `| ${pad("Metric", 35)}| ${pad("No Init Phase", 18)}| ${pad("With Init Phase", 18)}|`; - const cSep = `|${"-".repeat(37)}|${"-".repeat(20)}|${"-".repeat(20)}|`; - console.log(cHeader); - console.log(cSep); - console.log(`| ${pad("Prerequisites checked upfront", 35)}| ${pad("No", 18)}| ${pad("Yes", 18)}|`); - console.log(`| ${pad("Work attempted despite issues", 35)}| ${pad(String(noInit.workAttempted), 18)}| ${pad(String(withInit.workAttempted), 18)}|`); - console.log(`| ${pad("Time wasted discovering issues late", 35)}| ${pad(noInit.timeWastedMs + "ms", 18)}| ${pad(withInit.timeWastedMs + "ms", 18)}|`); - console.log(`| ${pad("Work succeeded", 35)}| ${pad(String(noInit.workSucceeded), 18)}| ${pad(String(withInit.workSucceeded), 18)}|`); - - console.log("\n An explicit init phase catches problems before they waste time.\n"); -} - -run(); diff --git a/courses/learn-harness-engineering/docs/ru/lectures/lecture-06-why-initialization-needs-its-own-phase/code/init.sh b/courses/learn-harness-engineering/docs/ru/lectures/lecture-06-why-initialization-needs-its-own-phase/code/init.sh deleted file mode 100644 index 2c6e1b0c..00000000 --- a/courses/learn-harness-engineering/docs/ru/lectures/lecture-06-why-initialization-needs-its-own-phase/code/init.sh +++ /dev/null @@ -1,10 +0,0 @@ -#!/usr/bin/env bash -set -euo pipefail - -echo "[init] installing dependencies" -npm install - -echo "[init] starting the docs site is optional" -echo "[init] use npm run docs:dev for course docs" - -echo "[init] project-specific startup would go here" diff --git a/courses/learn-harness-engineering/docs/ru/lectures/lecture-06-why-initialization-needs-its-own-phase/code/initializer-output-checklist.md b/courses/learn-harness-engineering/docs/ru/lectures/lecture-06-why-initialization-needs-its-own-phase/code/initializer-output-checklist.md deleted file mode 100644 index a9fe6e4f..00000000 --- a/courses/learn-harness-engineering/docs/ru/lectures/lecture-06-why-initialization-needs-its-own-phase/code/initializer-output-checklist.md +++ /dev/null @@ -1,7 +0,0 @@ -# Чеклист результатов инициализатора - -- Есть ли каноническая команда запуска? -- Есть ли каноническая команда верификации? -- Есть ли первый артефакт прогресса? -- Есть ли стабильный первый коммит? -- Есть ли видимый набор фич для последующих запусков? diff --git a/courses/learn-harness-engineering/docs/ru/lectures/lecture-06-why-initialization-needs-its-own-phase/index.md b/courses/learn-harness-engineering/docs/ru/lectures/lecture-06-why-initialization-needs-its-own-phase/index.md deleted file mode 100644 index 43148201..00000000 --- a/courses/learn-harness-engineering/docs/ru/lectures/lecture-06-why-initialization-needs-its-own-phase/index.md +++ /dev/null @@ -1,157 +0,0 @@ -[中文版本 →](../../../zh/lectures/lecture-06-why-initialization-needs-its-own-phase/) - -> Примеры кода: [code/](https://github.com/walkinglabs/learn-harness-engineering/blob/main/docs/en/lectures/lecture-06-why-initialization-needs-its-own-phase/code/) -> Практический проект: [Project 03. Multi-session continuity](./../../projects/project-03-multi-session-continuity/index.md) - -# Лекция 06. Инициализируйте проект перед каждой сессией агента - -Вы открываете новую сессию агента и говорите «добавь поиск». Он сразу бросается писать код — похвальный энтузиазм. Через 20 минут он обнаруживает, что тестовый фреймворк настроен неправильно, тратит ещё 10 минут на починку, потом оказывается, что формат скрипта миграции неверен, ещё возня. Поиск в итоге добавлен, но вся сессия прошла неэффективно — большая часть времени ушла на «разбирательство, как устроен этот проект», а не на саму фичу. - -Лучший подход: прежде чем дать агенту начать работу, в отдельной фазе подготовьте базовое окружение, рабочие команды верификации и понимание структуры проекта. Это как строить дом — нельзя одновременно заливать фундамент и поднимать стены. Если так сделать, стены пойдут вверх, пока фундамент не схватился, и всё здание придётся сносить и начинать заново. Залейте фундамент, дайте ему встать, потом стройте стены — чисто и эффективно. - -Эта лекция объясняет, почему инициализация должна быть отдельной фазой, а не смешанной с реализацией. - -## Фундамент и стены: две принципиально разные задачи - -У инициализации и реализации совершенно разные цели оптимизации. Фаза реализации оптимизирует: максимизацию количества и качества проверенных фич. Фаза инициализации оптимизирует: максимизацию надёжности и эффективности всех последующих фаз реализации. - -Когда вы смешиваете инициализацию и реализацию, агент сталкивается с многоцелевой оптимизацией — одновременно строит инфраструктуру и пишет код фич. Без явной расстановки приоритетов агент естественным образом тяготеет к написанию кода (потому что это напрямую видимый результат), жертвуя инфраструктурой (потому что её ценность проявится только в последующих сессиях). Это как сказать строительной бригаде одновременно заливать фундамент и поднимать стены — они, скорее всего, бросятся возводить стены, потому что стены видны и их можно показать. Но дом с плохим фундаментом получит системные проблемы потом. - -## Жизненный цикл инициализации - -```mermaid -flowchart TB - subgraph Wrong["Смешанная сессия (неправильно)"] - W1["Сразу начинаем работу над фичей"] --> W2["По ходу обнаруживаем пробелы в окружении и тестах"] - W2 --> W3["Накапливаем непроверенный код"] - W3 --> W4["Следующая сессия должна заново выяснять состояние проекта"] - end - - subgraph Right["Выделенная инициализация (правильно)"] - R1["Сессия 1: окружение запускается"] --> R2["Пример теста проходит"] - R2 --> R3["Bootstrap-контракт + список задач написаны"] - R3 --> R4["Чистый чекпоинт закоммичен"] - R4 --> R5["Последующие сессии сразу берут проверенные задачи"] - end -``` - -## Что происходит, когда вы их смешиваете - -Самая прямая проблема: фундамент не схватывается как следует. Агент тратит 80% усилий на код фич и 20% — на кое-как настроенную инфраструктуру. Тестовый фреймворк настроен, но не проверен; правила линтера выставлены, но слишком слабые; файл прогресса не создан. Эти дефекты не очевидны в первой сессии (агент ещё помнит, что делал), но проявляются во второй — новый агент не знает, как запускать, как тестировать и где сейчас находится проект. Хлипкий фундамент — шаткое здание. - -Более скрытая стоимость — «непроверенное накопление»: код фич, написанный до настройки тестового фреймворка, — это код без верификации. Когда вы наконец возвращаетесь добавить тесты к этому коду, вы можете обнаружить, что архитектура была неверной с самого начала — знай вы это, реализовали бы иначе. Это как класть плитку на сырой бетон: когда обнаруживаете, что пол неровный, всю плитку приходится отдирать и переделывать. - -Бюджет сессии тоже расходуется впустую. Работа по инициализации (настройка окружения, тестов, понимание структуры проекта) потребляет значительный бюджет, оставляя меньше на реальную реализацию фич. Результат: первая сессия выполняет только половину фич, а вторая вынуждена снова разбираться с проектом. Бюджет ушёл на фундамент, но и фундамент тоже не крепкий — ни одна цель не достигнута. - -Самая легко упускаемая проблема — «мины» неявных предположений. Решения, принимаемые агентом во время инициализации (какой тестовый фреймворк, как организовать каталоги, как управлять зависимостями), — если их явно не записать, последующие сессии не смогут их понять. Хуже того, последующие сессии могут принять противоречащие решения. Первая бригада сделала бетонный фундамент, вторая бригада об этом не знала и забила в него деревянные сваи — фундамент трескается. - -В исследовании long-running разработки приложений Anthropic явно рекомендует отделять инициализацию от реализации. Их экспериментальные данные: проекты с выделенной фазой инициализации показывали на 31% более высокую долю завершённых фич в многосессионных сценариях по сравнению со смешанным подходом. Ключевая мысль — время, вложенное в фазу инициализации, полностью окупается в следующих 3–4 сессиях. Чем крепче фундамент, тем быстрее идут стены. - -Руководство OpenAI Codex по harness engineering тоже подчёркивает принцип «репозиторий как операционный журнал»: с первого запуска нужно установить ясную операционную структуру, иначе каждая новая сессия будет вынуждена заново выводить соглашения проекта. - -## Ключевые понятия - -- **Фаза инициализации**: первая фаза в жизненном цикле агента — никакой реализации фич, только подготовка предпосылок для всех последующих фаз реализации. На выходе не код, а инфраструктура. -- **Bootstrap Contract**: условия, при которых проект может быть однозначно использован свежей сессией агента — может запуститься, может тестироваться, может видеть прогресс, может подхватывать следующие шаги. Четыре условия, все обязательны. -- **Холодный старт против тёплого старта**: холодный старт — из пустого каталога, где агент должен угадывать структуру проекта; тёплый старт — из шаблона или существующего проекта, где инфраструктура уже на месте. Тёплый старт значительно превосходит холодный — как начинать стройку с водой и электричеством против начала с голой пустоши. -- **Готовность к handoff**: проект в любой момент находится в состоянии, когда свежий агент может его подхватить. Никаких устных пояснений — только содержимое репо. -- **Время до первой верификации**: время от старта проекта до момента, когда первая фича проходит верификацию. Это ключевая метрика эффективности инициализации. -- **Полезность для последующих этапов**: лучшая мера качества инициализации — доля последующих сессий, которые успешно выполняют задачи, не опираясь на неявные знания. - -## Как делать инициализацию правильно - -**Считайте инициализацию выделенной фазой.** Первая сессия делает только инициализацию — никакого бизнес-кода. Инициализация производит: - -**1. Запускаемое окружение.** Проект стартует, зависимости установлены, проблем с окружением нет. Фундамент залит, без трещин. - -**2. Проверяемый тестовый фреймворк.** Хотя бы один пример теста проходит. Это доказывает, что сам тестовый фреймворк настроен правильно — как поставить столб на фундамент, чтобы убедиться, что он держит вес. - -**3. Документ bootstrap-контракта.** Чёткий документ, который сообщает последующим сессиям: -```markdown -# Initialization Contract - -## Start Commands -- Install dependencies: `make setup` -- Start dev server: `make dev` -- Run tests: `make test` -- Full verification: `make check` - -## Current State -- All dependencies installed and locked -- Test framework configured (Vitest + React Testing Library) -- Example test passing (1/1) -- Lint rules configured (ESLint + Prettier) - -## Project Structure -- src/ — Source code -- src/components/ — React components -- src/api/ — API client -- tests/ — Test files -``` - -**4. Декомпозиция задач.** Разбейте весь проект на упорядоченный список задач, у каждой — чёткие критерии приёмки: -```markdown -# Task Breakdown - -## Task 1: User Authentication Basics -- Implement JWT auth middleware -- Add login/register endpoints -- Acceptance: pytest tests/test_auth.py all passing - -## Task 2: User Profile Page -- Implement user profile CRUD -- Add profile edit form -- Acceptance: pytest tests/test_profile.py all passing - -## Task 3: Search Feature -- ... -``` - -**5. Git-коммит как чекпоинт.** После завершения инициализации закоммитьте чистый чекпоинт. Вся последующая работа стартует с него. - -**Стратегия тёплого старта**: не начинайте с пустого каталога. Используйте шаблон проекта (create-react-app, fastapi-template и т. д.), чтобы предустановить стандартную структуру каталогов, конфигурацию зависимостей и тестовый фреймворк. Запекайте общие шаги инициализации в шаблон, оставляя только специфичную для проекта работу. Это как начинать стройку с водой и электричеством — в десять тысяч раз лучше, чем с голой пустоши. - -**Критерии завершения инициализации**: не «сколько кода написано», а выполнены ли четыре условия bootstrap-контракта — может запуститься, может тестироваться, может видеть прогресс, может подхватывать следующие шаги. Используйте этот чеклист для валидации: - -```markdown -## Initialization Acceptance Checklist -- [ ] `make setup` succeeds from scratch -- [ ] `make test` has at least one passing test -- [ ] A new agent session can answer "how to run" and "how to test" from repo contents alone -- [ ] Task breakdown file exists with at least 3 tasks -- [ ] Everything committed to git -``` - -## Пример из реальной практики - -Два подхода к инициализации фронтенд-проекта на React: - -**Смешанный подход (одновременно льём фундамент и строим стены)**: агент в сессии 1 параллельно создал каркас проекта и реализовал первую фичу. К концу сессии репо содержал работоспособный код, но: не было явной документации команд запуска и тестов, не было файла отслеживания прогресса, не было декомпозиции задач. Сессия 2 потратила ~20 минут на восстановление структуры, тестового фреймворка и процесса сборки — как новая бригада на стройке, не знающая, до куда дошёл фундамент и где идут трубы, копающая ямы одну за другой, чтобы это выяснить. - -**Выделенная инициализация (сначала фундамент)**: сессия 1 занималась только инициализацией — создала структуру каталогов из шаблона, настроила тестовый фреймворк (Vitest + React Testing Library), написала и проверила один пример теста, создала документ bootstrap-контракта и файл декомпозиции задач, закоммитила начальный чекпоинт. Время восстановления в сессии 2 — менее 3 минут, и она сразу начала работать по списку задач — бригада приходит, бросает взгляд на чертёж и точно знает, где продолжать. - -Сравнение полного цикла проекта: общее время восстановления (по всем сессиям) при смешанном подходе было примерно на 60% больше, чем при выделенной инициализации. Дополнительные 20 минут на инициализацию многократно окупились в последующих сессиях. Как крепкий фундамент ускоряет возведение стен — медленнее значит быстрее. - -## Главные выводы - -- Цели оптимизации у инициализации и реализации разные — смешивание тянет вниз обе. Сначала фундамент, потом стены. -- Результат инициализации — не код, а инфраструктура: запускаемое окружение, проверяемые тесты, bootstrap-контракт, декомпозиция задач. -- Валидируйте инициализацию по четырём условиям bootstrap-контракта: может запуститься, может тестироваться, может видеть прогресс, может подхватывать следующие шаги. -- Тёплый старт превосходит холодный. Используйте шаблоны проектов для предустановки стандартной инфраструктуры. -- Время, вложенное в инициализацию, полностью окупается в следующих 3–4 сессиях. Это не дополнительная стоимость, а инвестиция вперёд. Чем крепче фундамент, тем быстрее растёт здание. - -## Дополнительное чтение - -- [Anthropic: Effective Harnesses for Long-Running Agents](https://www.anthropic.com/engineering/effective-harnesses-for-long-running-agents) -- [OpenAI: Harness Engineering](https://openai.com/index/harness-engineering/) -- [HumanLayer: Harness Engineering for Coding Agents](https://humanlayer.dev/articles/harness-engineering-for-coding-agents/) -- [Infrastructure as Code — Martin Fowler](https://martinfowler.com/bliki/InfrastructureAsCode.html) -- [SWE-agent: Agent-Computer Interfaces](https://github.com/princeton-nlp/SWE-agent) - -## Упражнения - -1. **Проектирование bootstrap-контракта**: напишите полный bootstrap-контракт для проекта, который вы разрабатываете. Затем откройте полностью свежую сессию агента, покажите ей только содержимое репо (без устного контекста) и попросите запустить проект, прогнать тесты и понять текущий прогресс. Записывайте каждую возникшую проблему — каждая соответствует пробелу в вашем bootstrap-контракте. - -2. **Сравнительный эксперимент**: возьмите умеренно сложный новый проект. Подход A: пусть агент инициализирует и сразу делает первую реализацию. Подход B: одна сессия на выделенную инициализацию, реализация — со второй сессии. После 4 сессий сравните: время до первой верификации, стоимость восстановления, долю завершённых фич. - -3. **Чеклист приёмки инициализации**: разработайте чеклист приёмки инициализации для своего проекта. Дайте свежей сессии агента выполнить каждый пункт и фиксируйте, какие проходят, а какие падают. Падающие пункты — те места, где ваш harness нуждается в усилении. diff --git a/courses/learn-harness-engineering/docs/ru/lectures/lecture-07-why-agents-overreach-and-under-finish/code/index.md b/courses/learn-harness-engineering/docs/ru/lectures/lecture-07-why-agents-overreach-and-under-finish/code/index.md deleted file mode 100644 index 7f258701..00000000 --- a/courses/learn-harness-engineering/docs/ru/lectures/lecture-07-why-agents-overreach-and-under-finish/code/index.md +++ /dev/null @@ -1,8 +0,0 @@ -# Код к лекции 07 - -Используйте эту папку для примеров: - -- провалы при «one-shot» -- слишком крупные промпты задач -- инкрементальное оформление задач -- структурированные поверхности фич diff --git a/courses/learn-harness-engineering/docs/ru/lectures/lecture-07-why-agents-overreach-and-under-finish/code/next-task-template.md b/courses/learn-harness-engineering/docs/ru/lectures/lecture-07-why-agents-overreach-and-under-finish/code/next-task-template.md deleted file mode 100644 index 85ee8e8a..00000000 --- a/courses/learn-harness-engineering/docs/ru/lectures/lecture-07-why-agents-overreach-and-under-finish/code/next-task-template.md +++ /dev/null @@ -1,6 +0,0 @@ -# Шаблон следующей задачи - -- Текущая фича с наивысшим приоритетом: -- Почему именно эта фича следующая: -- Что считается прохождением: -- Что нельзя менять на этом шаге: diff --git a/courses/learn-harness-engineering/docs/ru/lectures/lecture-07-why-agents-overreach-and-under-finish/code/scope-surface-example.md b/courses/learn-harness-engineering/docs/ru/lectures/lecture-07-why-agents-overreach-and-under-finish/code/scope-surface-example.md deleted file mode 100644 index 9ae66ccb..00000000 --- a/courses/learn-harness-engineering/docs/ru/lectures/lecture-07-why-agents-overreach-and-under-finish/code/scope-surface-example.md +++ /dev/null @@ -1,17 +0,0 @@ -# Пример поверхности скоупа - -Задача: - -- Добавить индексирование в Electron knowledge-приложение - -Плохая форма скоупа: - -- «Реализовать индексирование» - -Лучшая форма скоупа: - -- Парсить импортируемые документы -- Делить документы на чанки -- Сохранять метаданные чанков -- Показывать статус индексирования в UI -- Добавить действие переиндексации diff --git a/courses/learn-harness-engineering/docs/ru/lectures/lecture-07-why-agents-overreach-and-under-finish/code/scope-tracker.ts b/courses/learn-harness-engineering/docs/ru/lectures/lecture-07-why-agents-overreach-and-under-finish/code/scope-tracker.ts deleted file mode 100644 index c587a1dc..00000000 --- a/courses/learn-harness-engineering/docs/ru/lectures/lecture-07-why-agents-overreach-and-under-finish/code/scope-tracker.ts +++ /dev/null @@ -1,151 +0,0 @@ -/** - * scope-tracker.ts - * - * Reads a feature list and a change log. Enforces single-active-feature - * policy. Given a log of changes, flags any changes outside the active - * feature scope. Demonstrates how scope drift happens and how the tracker - * catches it. - * - * Run: npx tsx docs/lectures/lecture-07-why-agents-overreach-and-under-finish/code/scope-tracker.ts - */ - -// --------------------------------------------------------------------------- -// Types -// --------------------------------------------------------------------------- - -interface Feature { - id: string; - name: string; - status: "active" | "pending" | "done"; -} - -interface ChangeLogEntry { - step: number; - file: string; - description: string; - featureId: string; // The feature this change claims to belong to -} - -// --------------------------------------------------------------------------- -// Sample data -// --------------------------------------------------------------------------- - -const features: Feature[] = [ - { id: "F-001", name: "Search endpoint", status: "active" }, - { id: "F-002", name: "Delete endpoint", status: "pending" }, - { id: "F-003", name: "Rate limiting", status: "pending" }, - { id: "F-004", name: "User dashboard", status: "pending" }, -]; - -// A realistic change log where the agent gradually drifts from the active feature -const changeLog: ChangeLogEntry[] = [ - { step: 1, file: "src/routes/search.ts", description: "Add search route handler", featureId: "F-001" }, - { step: 2, file: "src/routes/search.ts", description: "Add query parameter validation", featureId: "F-001" }, - { step: 3, file: "src/routes/search.ts", description: "Add search results pagination", featureId: "F-001" }, - { step: 4, file: "src/routes/delete.ts", description: "Add delete route handler", featureId: "F-002" }, // DRIFT - { step: 5, file: "src/middleware/rate-limit.ts", description: "Add rate limiter middleware", featureId: "F-003" }, // DRIFT - { step: 6, file: "src/routes/search.ts", description: "Integrate rate limiter into search", featureId: "F-001" }, - { step: 7, file: "src/dashboard/ui.tsx", description: "Create dashboard layout component", featureId: "F-004" }, // DRIFT - { step: 8, file: "src/routes/search.ts", description: "Add search response formatting", featureId: "F-001" }, - { step: 9, file: "src/routes/delete.ts", description: "Add delete confirmation logic", featureId: "F-002" }, // DRIFT - { step: 10, file: "src/routes/search.ts", description: "Add search tests", featureId: "F-001" }, -]; - -// --------------------------------------------------------------------------- -// Scope tracker -// --------------------------------------------------------------------------- - -interface ScopeCheckResult { - step: number; - file: string; - description: string; - featureId: string; - inScope: boolean; - activeFeature: string; -} - -function trackScope( - featureList: Feature[], - changes: ChangeLogEntry[] -): ScopeCheckResult[] { - const activeFeatures = featureList.filter((f) => f.status === "active"); - const activeIds = new Set(activeFeatures.map((f) => f.id)); - const activeNames = activeFeatures.map((f) => f.name).join(", "); - - return changes.map((change) => ({ - step: change.step, - file: change.file, - description: change.description, - featureId: change.featureId, - inScope: activeIds.has(change.featureId), - activeFeature: activeNames, - })); -} - -// --------------------------------------------------------------------------- -// Reporting -// --------------------------------------------------------------------------- - -function pad(s: string, len: number): string { - return s.length >= len ? s : s + " ".repeat(len - s.length); -} - -function run(): void { - const results = trackScope(features, changeLog); - - console.log("\n" + "=".repeat(100)); - console.log(" SCOPE TRACKER -- Single Active Feature Enforcement"); - console.log("=".repeat(100)); - - console.log("\n Active feature: " + features.filter((f) => f.status === "active").map((f) => `${f.id} (${f.name})`).join(", ")); - console.log(" Pending features: " + features.filter((f) => f.status === "pending").map((f) => `${f.id} (${f.name})`).join(", ")); - - // Detailed change log - console.log("\n" + "-".repeat(100)); - const header = `| ${pad("Step", 5)}| ${pad("File", 35)}| ${pad("Description", 40)}| ${pad("Feature", 8)}| ${pad("In Scope", 10)}|`; - const sep = `|${"-".repeat(7)}|${"-".repeat(37)}|${"-".repeat(42)}|${"-".repeat(10)}|${"-".repeat(12)}|`; - console.log(header); - console.log(sep); - - let inScopeCount = 0; - let driftCount = 0; - - for (const r of results) { - const scopeLabel = r.inScope ? "OK" : "DRIFT"; - if (r.inScope) inScopeCount++; - else driftCount++; - - const marker = r.inScope ? " " : ">>"; - console.log(`${marker}| ${pad(String(r.step), 5)}| ${pad(r.file, 35)}| ${pad(r.description, 40)}| ${pad(r.featureId, 8)}| ${pad(scopeLabel, 10)}|`); - } - - // Summary - console.log("\n" + "=".repeat(100)); - console.log(" SCOPE DRIFT SUMMARY"); - console.log("=".repeat(100) + "\n"); - - const sHeader = `| ${pad("Metric", 40)}| ${pad("Value", 15)}|`; - const sSep = `|${"-".repeat(42)}|${"-".repeat(17)}|`; - console.log(sHeader); - console.log(sSep); - console.log(`| ${pad("Total changes", 40)}| ${pad(String(results.length), 15)}|`); - console.log(`| ${pad("Changes within active scope (F-001)", 40)}| ${pad(String(inScopeCount), 15)}|`); - console.log(`| ${pad("Changes outside active scope (DRIFT)", 40)}| ${pad(String(driftCount), 15)}|`); - console.log(`| ${pad("Features touched (total)", 40)}| ${pad(String(new Set(results.map((r) => r.featureId)).size), 15)}|`); - - // Drift detail - const driftFeatures = [...new Set(results.filter((r) => !r.inScope).map((r) => r.featureId))]; - if (driftFeatures.length > 0) { - console.log("\n DRIFTED FEATURES:"); - for (const fid of driftFeatures) { - const feat = features.find((f) => f.id === fid); - const driftChanges = results.filter((r) => r.featureId === fid); - console.log(` ${fid} (${feat?.name}): ${driftChanges.length} unauthorized changes`); - } - } - - console.log("\n Without a scope tracker, the agent silently worked on " + driftFeatures.length + " unrelated features."); - console.log(" The tracker catches this drift and enforces the single-active-feature policy.\n"); -} - -run(); diff --git a/courses/learn-harness-engineering/docs/ru/lectures/lecture-07-why-agents-overreach-and-under-finish/index.md b/courses/learn-harness-engineering/docs/ru/lectures/lecture-07-why-agents-overreach-and-under-finish/index.md deleted file mode 100644 index 5c74c660..00000000 --- a/courses/learn-harness-engineering/docs/ru/lectures/lecture-07-why-agents-overreach-and-under-finish/index.md +++ /dev/null @@ -1,134 +0,0 @@ -[中文版本 →](../../../zh/lectures/lecture-07-why-agents-overreach-and-under-finish/) - -> Примеры кода: [code/](https://github.com/walkinglabs/learn-harness-engineering/blob/main/docs/en/lectures/lecture-07-why-agents-overreach-and-under-finish/code/) -> Практический проект: [Project 04. Runtime feedback and scope control](./../../projects/project-04-incremental-indexing/index.md) - -# Лекция 07. Очерчивайте чёткие границы задач для агентов - -Вы говорите Claude Code: «добавь в проект аутентификацию пользователей», и он начинает менять схему БД, писать маршруты, переделывать фронтенд-компоненты и заодно — рефакторить middleware обработки ошибок. Через два часа вы проверяете: 12 файлов изменено, 800 строк нового кода, и ни одна фича не работает сквозно. - -Откусил больше, чем сможешь прожевать — эта поговорка отлично подходит к AI-агентам. У агентов врождённый импульс «сделать чуть больше» — они видят что-то связанное и заодно это правят, как тот, кто пошёл в магазин за бутылкой соевого соуса и вышел с полной тележкой. Проблема в том, что человек, который купил лишнего, всего лишь потратил деньги; агент, делающий слишком много дел одновременно, не доводит до конца ни одного. - -Инженерный блог Anthropic «Effective harnesses for long-running agents» прямо говорит: когда промпты слишком широкие, агенты склонны «начинать сразу несколько дел», вместо того чтобы «сначала закончить одно». Инженерные практики OpenAI Codex показывают то же самое — у задач без явного контроля скоупа доля завершения обваливается. Это проблема не модели — это проблема harness'а. Вы не очертили границу. - -## Внимание — конечный ресурс - -Это не метафора, а математика. Допустим, ёмкость контекста агента — C, и он одновременно активирует k задач. Каждая задача получает в среднем C/k ресурсов рассуждения. Когда C/k опускается ниже минимального порога, нужного для завершения одной задачи, ни одна не доходит до конца. Желудок имеет свой объём — впихни десять пельменей разом, и ни один не переваришь, получишь десять расстройств желудка. - -Реальное поведение Claude Code красноречиво. Попросите «добавить регистрацию пользователей», и он может: - -1. Создать модель User -2. Написать маршрут регистрации -3. Заметить, что нужна верификация по email, и добавить почтовый сервис -4. Увидеть, что пароли надо хешировать, и подключить bcrypt -5. Заметить, что обработка ошибок непоследовательна, и отрефакторить глобальный error-middleware -6. Увидеть, что структура тестовых файлов неаккуратная, и переорганизовать каталог - -Шесть шагов спустя — каждый сделан наполовину. Никакой сквозной верификации, сложная связанность между полу-готовыми кусками кода, и следующая сессия, которая попробует это разобрать, окончательно потеряется. Как тот, кто готовит шесть блюд одновременно: каждое в сковородке, но ни одно не выложено на тарелку. Все подгорают. - -Экспериментальные данные Anthropic прямо это подтверждают: агенты, использующие стратегию «маленький следующий шаг» (эквивалентно WIP=1), показывают на 37% более высокую долю завершённых задач, чем агенты с широкими промптами. Что интереснее, число строк кода, генерируемых агентами, слабо отрицательно коррелирует с реальным завершением фич — больше написанного кода, меньше доведённых до конца фич. Откусил больше, чем прожуёшь — подтверждено данными. - -## Рабочий процесс WIP=1 - -```mermaid -flowchart LR - Queue["Очередь фич"] --> Pick["Берём ровно одну задачу"] - Pick --> Active["Только один активный элемент"] - Active --> Verify["Запускаем сквозную верификацию"] - Verify -->|pass| Commit["Коммитим и разблокируем следующую"] - Verify -->|fail| Active - Commit --> Queue -``` - -```mermaid -flowchart TB - Budget["Доступный бюджет рассуждения = C"] --> One["WIP = 1
    C / 1 на задачу"] - Budget --> Many["WIP = 5
    C / 5 на задачу"] - - One --> Finish["Одна фича доходит до прохождения"] - Many --> Partial["Пять частичных реализаций"] - Partial --> VCR["Низкая доля проверенного завершения
    много переделок в следующей сессии"] -``` - -## Ключевые понятия - -- **Overreach (переусердствование)**: агент активирует за одну сессию больше задач, чем оптимально. Это поддаётся измерению — 5 фич с 0 проходящими сквозными — это overreach. -- **Under-finish (недодел)**: доля задач, прошедших сквозную верификацию, среди всех активированных задач, опускается ниже порога. Код написан, но тесты не проходят — это under-finish. -- **WIP Limit (лимит работы в процессе)**: из методологии Канбан. Главная идея: ограничить количество задач, находящихся в работе одновременно. Для агентов WIP=1 — самое безопасное значение по умолчанию: закончи одно, потом начинай следующее. Как на шведском столе — не накладывай горой, доешь одну тарелку, потом иди за следующей. -- **Доказательство завершения**: проверяемое условие, которое задача обязана удовлетворить, чтобы перейти из «в работе» в «готово». Без него агенты подменяют «код выглядит нормально» на «поведение проходит тесты». -- **Поверхность скоупа (Scope Surface)**: DAG-структура, где каждый узел — единица работы, а рёбра — зависимости. Состояний всего четыре: not_started, active, blocked, passing. -- **Давление на завершение**: ограничивающая сила, которую harness прикладывает через WIP-лимиты и требования к доказательствам завершения, заставляя агента закончить текущую задачу перед началом новой. - -## Overreach и under-finish — симбиоз - -Эти две проблемы не независимы — они усиливают друг друга. Overreach размывает внимание, размытое внимание ведёт к under-finish, а оставшийся полу-готовый код увеличивает сложность системы, что дальше подталкивает к overreach в следующей задаче. Порочный круг. - -В терминах Канбан: закон Литтла говорит L = lambda * W. Если объём работы в процессе L слишком велик (одновременно делается слишком много), время выполнения W каждой задачи неизбежно растёт. Для агентов это значит, что путь от старта до проверенного завершения каждой фичи становится длиннее, а вероятность отказа — выше. - -Это и в человеческом мире давняя проблема — Стив Макконнелл в *Rapid Development* документировал, что расширение скоупа — главная причина провала проектов. Но у людей хотя бы есть интуиция «я уже сделал достаточно». У агентов её нет. Сгенерировать следующую идею модели стоит почти ничего по дополнительным токенам — написать «давайте заодно починим и это» едва заметно, — но каждое дополнительное изменение размывает внимание агента. Как на шведском столе: каждая лишняя тарелка имеет почти нулевую маржинальную стоимость, но желудок ограничен. - -## Как делать правильно - -### 1. Принудительный WIP=1 - -Самый прямой и эффективный способ. В вашем harness'е явно скажите агенту: **в любой момент в статусе «active» может быть только одна задача.** В CLAUDE.md (Claude Code) или AGENTS.md (Codex) напишите: - -``` -## Work Rules -- Work on one feature at a time -- Only start the next feature after the current one passes end-to-end verification -- Don't "also refactor" feature B while implementing feature A -``` - -Как на шведском столе — одна тарелка за раз, доешь, потом иди за следующей. - -### 2. Определите явные доказательства завершения для каждой задачи - -«Готово» — это не «код написан», а «верификация поведения проходит». В списке фич у каждой записи нужна команда верификации: - -``` -F01: User Registration - Verification: curl -X POST /api/register -d '{"email":"test@example.com","password":"123456"}' | jq .status == 201 - State: passing -``` - -### 3. Вынесите поверхность скоупа наружу - -Используйте машиночитаемый файл (JSON или Markdown) для записи всех состояний задач. Любая новая сессия читает этот файл и сразу знает: какая задача активна? Что считается «готово»? Какие верификации пройдены? - -### 4. Отслеживайте долю проверенного завершения - -Harness должен непрерывно отслеживать VCR (Verified Completion Rate) = проверенные задачи / активированные задачи. Блокируйте активацию новых задач, когда VCR < 1.0. - -## Пример из реальной практики - -Проект REST API с 8 фичами, сравниваются две стратегии: - -**Режим шведского стола (без ограничений)**: агент в сессии 1 одновременно активирует 5 фич. Производит ~800 строк в 12 файлах. Доля прохождения сквозных тестов: 20% — работает только регистрация. Остальные 4 фичи: схема БД создана, но нет валидации; маршруты определены, но возвращают неверные форматы ответа. Как тот, кто готовит шесть блюд разом: только одно едва съедобно. К концу сессии 3 завершены только 3 из 8 фич. - -**Режим одной тарелки (WIP=1)**: в сессии 1 агент работает только над регистрацией. Производит ~200 строк в 4 файлах. Сквозные тесты: 100% проходят. Коммитит чистую проверенную реализацию. К концу сессии 4 — 7 из 8 фич завершены (8-я заблокирована внешней зависимостью). - -Итог: меньше кода в сумме (800 против 1200 строк), но больше эффективного кода. Доля завершения: 87,5% против 37,5%. Откусывай по чуть-чуть — и съешь больше. - -## Главные выводы - -- **WIP=1 — безопасное значение по умолчанию для harness'ов агентов** — закончи одно, потом начинай следующее; не пытайтесь распараллеливать. С одного укуса не растолстеешь. -- **Доказательство завершения должно быть исполняемым** — «код выглядит нормально» не считается; «curl возвращает 201» — считается. -- **Поверхность скоупа должна быть вынесена в файл** — не просто упомянута в разговоре, а записана в репо в машиночитаемом формате. -- **Overreach и under-finish — симбиоз** — решая одно, решаешь и второе. -- **«Меньше, но до конца» всегда побеждает «больше, но наполовину»** — строки кода агента и доля завершения фич отрицательно коррелируют. Качество всегда побеждает количество. - -## Дополнительное чтение - -- [Effective harnesses for long-running agents - Anthropic](https://www.anthropic.com/engineering/effective-harnesses-for-long-running-agents) — инженерный блог Anthropic, подробное обсуждение стратегии «маленький следующий шаг» -- [Harness Engineering - OpenAI](https://openai.com/index/harness-engineering/) — полное изложение OpenAI о harness engineering -- [Kanban: Successful Evolutionary Change - David Anderson](https://www.goodreads.com/book/show/1070822.Kanban) — классический источник о WIP-лимитах -- [Rapid Development - Steve McConnell](https://www.goodreads.com/book/show/125171.Rapid_Development) — эмпирические данные о расширении скоупа как главной причине провала проектов - -## Упражнения - -1. **Атомизация задач**: возьмите широкое требование (например, «реализовать систему управления пользователями») и разбейте на минимум 5 атомарных единиц работы. Для каждой укажите: (a) описание одного поведения, (b) исполняемую команду верификации, (c) зависимости. Проверьте, удовлетворяет ли декомпозиция ограничению WIP=1. - -2. **Сравнительный эксперимент**: запустите один и тот же проект дважды — раз без ограничений, раз с принудительным WIP=1. Сравните: долю проверенного завершения, общее число строк, долю эффективного кода. - -3. **Аудит доказательств завершения**: пересмотрите вывод недавнего прогона агента, классифицируя каждое изменение кода как «завершённое поведение», «незавершённое поведение» или «каркас». Для каждого незавершённого поведения добавьте недостающую команду верификации. diff --git a/courses/learn-harness-engineering/docs/ru/lectures/lecture-08-why-feature-lists-are-harness-primitives/code/feature-list-validator.ts b/courses/learn-harness-engineering/docs/ru/lectures/lecture-08-why-feature-lists-are-harness-primitives/code/feature-list-validator.ts deleted file mode 100644 index 52d6a2b9..00000000 --- a/courses/learn-harness-engineering/docs/ru/lectures/lecture-08-why-feature-lists-are-harness-primitives/code/feature-list-validator.ts +++ /dev/null @@ -1,218 +0,0 @@ -/** - * feature-list-validator.ts - * - * Reads a feature_list.json, validates its schema, and checks for features - * marked "pass" without verification evidence. Outputs a structured report. - * Can run against any project directory that has a feature_list.json. - * - * Usage: - * npx tsx docs/lectures/lecture-08.../code/feature-list-validator.ts [path-to-dir] - * (defaults to the directory containing this script) - * - * Run: npx tsx docs/lectures/lecture-08-why-feature-lists-are-harness-primitives/code/feature-list-validator.ts - */ - -import * as fs from "node:fs"; -import * as path from "node:path"; - -// --------------------------------------------------------------------------- -// Types -// --------------------------------------------------------------------------- - -interface FeatureEntry { - id?: string; - category?: string; - description?: string; - verification?: string[]; - passes?: boolean; - // Allow unknown fields for flexibility - [key: string]: unknown; -} - -interface ValidationResult { - featureId: string; - schemaValid: boolean; - schemaErrors: string[]; - hasVerification: boolean; - markedPassWithoutEvidence: boolean; - passes: boolean; - verificationCount: number; -} - -// --------------------------------------------------------------------------- -// Schema validation -// --------------------------------------------------------------------------- - -function validateSchema(entry: FeatureEntry, index: number): string[] { - const errors: string[] = []; - const label = entry.id ?? `entry ${index + 1}`; - - if (!entry.id || typeof entry.id !== "string") { - errors.push(`[${label}] Missing or invalid 'id' field`); - } - if (!entry.category || typeof entry.category !== "string") { - errors.push(`[${label}] Missing or invalid 'category' field`); - } - if (!entry.description || typeof entry.description !== "string") { - errors.push(`[${label}] Missing or invalid 'description' field`); - } - if (entry.verification !== undefined && !Array.isArray(entry.verification)) { - errors.push(`[${label}] 'verification' must be an array if present`); - } - if (entry.passes !== undefined && typeof entry.passes !== "boolean") { - errors.push(`[${label}] 'passes' must be a boolean if present`); - } - - return errors; -} - -// --------------------------------------------------------------------------- -// Evidence validation -// --------------------------------------------------------------------------- - -function checkEvidence(entry: FeatureEntry): { - hasVerification: boolean; - markedPassWithoutEvidence: boolean; -} { - const hasVerification = Array.isArray(entry.verification) && entry.verification.length > 0; - const markedPassWithoutEvidence = entry.passes === true && !hasVerification; - - return { hasVerification, markedPassWithoutEvidence }; -} - -// --------------------------------------------------------------------------- -// Process feature list -// --------------------------------------------------------------------------- - -function processFeatureList(entries: FeatureEntry[]): ValidationResult[] { - return entries.map((entry, index) => { - const schemaErrors = validateSchema(entry, index); - const evidence = checkEvidence(entry); - - return { - featureId: (entry.id as string) ?? `entry-${index + 1}`, - schemaValid: schemaErrors.length === 0, - schemaErrors, - hasVerification: evidence.hasVerification, - markedPassWithoutEvidence: evidence.markedPassWithoutEvidence, - passes: entry.passes === true, - verificationCount: Array.isArray(entry.verification) ? entry.verification.length : 0, - }; - }); -} - -// --------------------------------------------------------------------------- -// Reporting -// --------------------------------------------------------------------------- - -function pad(s: string, len: number): string { - return s.length >= len ? s : s + " ".repeat(len - s.length); -} - -function run(): void { - // Resolve target directory - const scriptDir = path.dirname(new URL(import.meta.url).pathname); - const targetDir = process.argv[2] - ? path.resolve(process.argv[2]) - : scriptDir; - - const filePath = path.join(targetDir, "feature_list.json"); - - console.log("\n" + "=".repeat(90)); - console.log(" FEATURE LIST VALIDATOR"); - console.log("=".repeat(90)); - console.log(` Reading: ${filePath}\n`); - - if (!fs.existsSync(filePath)) { - console.error(` ERROR: feature_list.json not found at ${filePath}`); - console.error(" Usage: npx tsx feature-list-validator.ts [path-to-directory-containing-feature_list.json]\n"); - process.exit(1); - } - - let entries: FeatureEntry[]; - try { - const raw = fs.readFileSync(filePath, "utf-8"); - entries = JSON.parse(raw); - } catch (err) { - console.error(` ERROR: Could not parse feature_list.json: ${err}`); - process.exit(1); - } - - if (!Array.isArray(entries)) { - console.error(" ERROR: feature_list.json must contain a JSON array at the top level."); - process.exit(1); - } - - // For demo purposes, also validate an extended test set - const demoEntries: FeatureEntry[] = [ - ...entries, - { - id: "qna-002", - category: "import", - description: "User can import a PDF document.", - verification: ["Upload a PDF file", "Verify it appears in the document list"], - passes: true, - }, - { - id: "qna-003", - category: "grounded_qa", - description: "System hallucination rate is below 5%.", - verification: [], // Empty -- no evidence - passes: true, // Marked as pass WITHOUT evidence - }, - { - id: "missing-fields", - // Missing 'category' and 'description' - passes: true, - } as FeatureEntry, - ]; - - const results = processFeatureList(demoEntries); - - // Print report - const header = `| ${pad("Feature ID", 14)}| ${pad("Schema", 8)}| ${pad("Passes", 7)}| ${pad("Verifications", 14)}| ${pad("Evidence?", 12)}| Notes`; - const sep = `|${"-".repeat(16)}|${"-".repeat(10)}|${"-".repeat(9)}|${"-".repeat(16)}|${"-".repeat(14)}|${"-".repeat(30)}`; - console.log(header); - console.log(sep); - - for (const r of results) { - const schemaLabel = r.schemaValid ? "OK" : "INVALID"; - const passesLabel = r.passes ? "PASS" : "FAIL"; - const evidenceLabel = r.hasVerification ? "Present" : "MISSING"; - const notes = r.markedPassWithoutEvidence - ? "FLAGGED: passes without evidence!" - : r.schemaErrors.length > 0 - ? r.schemaErrors[0] - : ""; - - const marker = r.markedPassWithoutEvidence ? ">>" : r.schemaValid ? " " : "!!"; - console.log( - `${marker}| ${pad(r.featureId, 14)}| ${pad(schemaLabel, 8)}| ${pad(passesLabel, 7)}| ${pad(String(r.verificationCount), 14)}| ${pad(evidenceLabel, 12)}| ${notes}` - ); - } - - // Summary - const total = results.length; - const schemaOk = results.filter((r) => r.schemaValid).length; - const passing = results.filter((r) => r.passes).length; - const flagged = results.filter((r) => r.markedPassWithoutEvidence).length; - const withEvidence = results.filter((r) => r.hasVerification).length; - - console.log("\n" + "-".repeat(90)); - console.log(" SUMMARY"); - console.log("-".repeat(90)); - console.log(` Total features: ${total}`); - console.log(` Schema valid: ${schemaOk}/${total}`); - console.log(` Marked as passing: ${passing}/${total}`); - console.log(` With verification evidence: ${withEvidence}/${total}`); - console.log(` Flagged (pass without evidence): ${flagged}`); - - if (flagged > 0) { - console.log(`\n WARNING: ${flagged} feature(s) marked as "pass" without any verification evidence.`); - console.log(" These features need verification before they can be trusted.\n"); - } else { - console.log("\n All passing features have verification evidence. Feature list is healthy.\n"); - } -} - -run(); diff --git a/courses/learn-harness-engineering/docs/ru/lectures/lecture-08-why-feature-lists-are-harness-primitives/code/feature_list.json b/courses/learn-harness-engineering/docs/ru/lectures/lecture-08-why-feature-lists-are-harness-primitives/code/feature_list.json deleted file mode 100644 index 8b973e31..00000000 --- a/courses/learn-harness-engineering/docs/ru/lectures/lecture-08-why-feature-lists-are-harness-primitives/code/feature_list.json +++ /dev/null @@ -1,15 +0,0 @@ -[ - { - "id": "qna-001", - "category": "grounded_qa", - "description": "User can ask a question about an imported document and receive an answer with visible citations.", - "verification": [ - "Import a markdown document", - "Open the Q&A panel", - "Ask a question about known content", - "Verify the answer is returned", - "Verify one or more citations are displayed" - ], - "passes": false - } -] diff --git a/courses/learn-harness-engineering/docs/ru/lectures/lecture-08-why-feature-lists-are-harness-primitives/code/index.md b/courses/learn-harness-engineering/docs/ru/lectures/lecture-08-why-feature-lists-are-harness-primitives/code/index.md deleted file mode 100644 index 89473c4d..00000000 --- a/courses/learn-harness-engineering/docs/ru/lectures/lecture-08-why-feature-lists-are-harness-primitives/code/index.md +++ /dev/null @@ -1,8 +0,0 @@ -# Код к лекции 08 - -Используйте эту папку для примеров: - -- гейтинг по состоянию passing -- сквозная верификация -- слабые против строгих критериев завершения -- примеры evaluator-loop diff --git a/courses/learn-harness-engineering/docs/ru/lectures/lecture-08-why-feature-lists-are-harness-primitives/code/pass-gate-policy.md b/courses/learn-harness-engineering/docs/ru/lectures/lecture-08-why-feature-lists-are-harness-primitives/code/pass-gate-policy.md deleted file mode 100644 index 8da05f0b..00000000 --- a/courses/learn-harness-engineering/docs/ru/lectures/lecture-08-why-feature-lists-are-harness-primitives/code/pass-gate-policy.md +++ /dev/null @@ -1,8 +0,0 @@ -# Политика гейта прохождения - -Фича может перейти из `passes: false` в `passes: true`, только когда: - -- ожидаемый сценарий был реально пройден -- доказательство успеха зафиксировано -- на проверенном пути нет блокирующей ошибки -- реализация не оставляет приложение в сломанном или неоднозначном состоянии diff --git a/courses/learn-harness-engineering/docs/ru/lectures/lecture-08-why-feature-lists-are-harness-primitives/index.md b/courses/learn-harness-engineering/docs/ru/lectures/lecture-08-why-feature-lists-are-harness-primitives/index.md deleted file mode 100644 index 9270d9ba..00000000 --- a/courses/learn-harness-engineering/docs/ru/lectures/lecture-08-why-feature-lists-are-harness-primitives/index.md +++ /dev/null @@ -1,137 +0,0 @@ -[中文版本 →](../../../zh/lectures/lecture-08-why-feature-lists-are-harness-primitives/) - -> Примеры кода: [code/](https://github.com/walkinglabs/learn-harness-engineering/blob/main/docs/en/lectures/lecture-08-why-feature-lists-are-harness-primitives/code/) -> Практический проект: [Project 04. Runtime feedback and scope control](./../../projects/project-04-incremental-indexing/index.md) - -# Лекция 08. Используйте списки фич, чтобы ограничивать поведение агента - -Вы просите агента построить интернет-магазин. Когда он закончил, он говорит «готово». Вы смотрите код — аутентификация работает, но кнопка оформления заказа в корзине ничего не делает, а оплата вообще не подключена. Проблема: вы никогда не сказали, что значит «готово», поэтому он использовал свой собственный стандарт — «я написал много кода, и он выглядит достаточно полным». - -Список фич, в глазах многих, — просто памятка: записал, чтобы не забыть, и отложил. Но в мире harness'а список фич — не памятка для людей, а позвоночник всего harness'а. Планировщик опирается на него, чтобы выбирать задачи; верификатор — чтобы судить о завершении; репортёр handoff — чтобы генерировать саммари. Сломайте позвоночник — и тело парализовано. - -И Anthropic, и OpenAI подчёркивают: **артефакты должны быть вынесены наружу.** Состояние фич должно жить в машиночитаемом файле в репо, а не в неструктурированном тексте разговора. - -## Агенты не знают, что значит «готово» - -Ни Claude Code, ни Codex автоматически не знают, что вы понимаете под «готово». Вы говорите «добавь корзину», и интерпретация модели может быть «написать компонент Cart и метод addToCart». А вы имели в виду «пользователь может сквозно просмотреть товары, добавить в корзину и оформить покупку». Этот разрыв в понимании сохраняется без списка фич. Агент использует свой неявный стандарт — обычно «в коде нет очевидных синтаксических ошибок». А вам нужна сквозная верификация поведения. Это как попросить друга купить фрукты — вы говорите «купи фруктов», а он приносит лимоны. Ваши фрукты и его фрукты — не одни и те же фрукты. - -Посмотрите на типичную заметку о прогрессе: - -``` -Did user auth, shopping cart mostly done, still need payments -``` -Может ли новая сессия агента ответить по этой записи? Что значит «mostly done»? Какие тесты прошла корзина? Что блокирует оплату? Ответ на всё — «никто не знает». Это как сказать врачу «болит живот, в последнее время нормально» — какое лекарство он выпишет? - -Результат: новая сессия тратит 20 минут на восстановление состояния и может пере-реализовать уже готовые фичи. Инженерные данные Anthropic показывают, что хорошие записи прогресса сокращают время диагностики при старте сессии на 60–80%. - -## Конечный автомат фичи - -```mermaid -flowchart LR - Feature["Одна строка фичи"] --> Behavior["Поведение
    например: POST /cart/items возвращает 201"] - Feature --> Check["Команда верификации
    точная проверка для запуска"] - Feature --> State["Состояние
    not_started / active / blocked / passing"] - - Behavior --> Complete["Только при всех трёх полях
    строка фичи пригодна"] - Check --> Complete - State --> Complete -``` - -```mermaid -flowchart LR - List["feature_list.json / features.md"] --> Scheduler["Берём следующий not_started"] - Scheduler --> Agent["Агент работает только над ним"] - Agent --> Verifier["Запускаем команду верификации этой записи"] - Verifier -->|pass| Passing["Помечаем passing
    и записываем доказательство"] - Verifier -->|fail| Active["Оставляем active"] - Verifier -->|проблема зависимости| Blocked["Помечаем blocked"] - Passing --> Handoff["Обновляем handoff-заметку
    и текущий прогресс"] - Active --> Agent -``` - -## Ключевые понятия - -- **Списки фич — примитивы harness'а**: не «опциональный инструмент планирования», а фундаментальная структура данных, от которой зависят все остальные компоненты harness'а. Как структура таблицы в БД — нельзя сказать «давайте обойдёмся без первичных ключей». -- **Тройственная структура**: каждая запись фичи — это тройка `(описание поведения, команда верификации, текущее состояние)`. Без любого элемента запись неполная. -- **Модель конечного автомата**: у каждой записи фичи четыре состояния — `not_started`, `active`, `blocked`, `passing`. Переходами управляет harness, а не агент по своему усмотрению. -- **Гейт по состоянию passing**: единственный способ перевести фичу из `active` в `passing` — успешный запуск команды верификации. Это необратимо: став `passing`, нельзя вернуться. Сдал экзамен — значит сдал, оценку задним числом не поменяешь. -- **Единственный источник истины**: вся информация «что нужно сделать» должна происходить из одного списка фич. Никаких противоречий между списком фич и историей разговора. -- **Обратное давление**: число фич, ещё не получивших passing, — это давление, которое harness прикладывает к агенту. Нулевое давление = проект завершён. - -## Почему списки фич должны быть «примитивами» - -Документы — для чтения людьми; примитивы — для исполнения системой. Документы можно проигнорировать, примитивы — нельзя обойти. - -Это как ограничения через триггеры в БД против проверок на уровне приложения: первые обеспечиваются движком БД, никакой SQL их не пропустит; вторые зависят от корректности кода и могут быть случайно обойдены. Списки фич как примитивы harness'а конкретно обслуживают четыре компонента harness'а: - -1. **Планировщик**: читает состояния, выбирает следующую `not_started` фичу. Как система производственного планирования на заводе. -2. **Верификатор**: исполняет команды верификации, решает, разрешить ли смену состояния. Как ОТК. -3. **Репортёр handoff**: автоматически генерирует саммари сессии из списка фич. Как автоматический отчёт о смене. -4. **Трекер прогресса**: суммирует распределение состояний, даёт метрики здоровья проекта. Как дашборд. - -## Как делать правильно - -### 1. Определите минимальный формат списка фич - -Сложная система не нужна — подойдёт структурированный Markdown или JSON. Ключ в том, чтобы у каждой записи была тройка: - -```json -{ - "id": "F03", - "behavior": "POST /cart/items with {product_id, quantity} returns 201", - "verification": "curl -X POST http://localhost:3000/api/cart/items -H 'Content-Type: application/json' -d '{\"product_id\":1,\"quantity\":2}' | jq .status == 201", - "state": "passing", - "evidence": "commit abc123, test output log" -} -``` - -### 2. Передайте управление состояниями harness'у - -Агент не может напрямую перевести фичу в `passing`. Он может только подать запрос на верификацию; harness исполняет команду верификации и решает, разрешить ли переход. Это и есть «гейт по состоянию passing». - -### 3. Запишите правила в CLAUDE.md - -``` -## Feature List Rules -- Feature list file: /docs/features.md -- Only one feature active at a time -- Verification command must pass before marking as passing -- Don't modify feature list states yourself — the verification script updates them automatically -``` - -### 4. Калибруйте гранулярность - -Каждая запись фичи должна быть масштаба «можно завершить за одну сессию». Слишком широко — не закончится; слишком узко — растёт накладной расход на управление. «Пользователь может добавить товар в корзину» — хорошая гранулярность. «Реализовать корзину» — слишком широко. «Создать поле name в модели Cart» — слишком узко. Как нарезать стейк — не цельным куском и не фаршем. - -## Пример из реальной практики - -Платформа e-commerce с 10 фичами. Сравниваются два подхода к учёту: - -**Режим памятки**: агент использует неструктурированные заметки. После 3 сессий заметки превращаются в «сделал auth и список товаров, корзина почти готова но баги, оплата не начата». Новая сессия тратит 20 минут на восстановление состояния и в итоге пере-реализует готовые фичи. Это как ваш список покупок гласит «молоко, хлеб и эта штука» — в магазине вы всё равно не знаете, что брать. - -**Режим позвоночника**: у каждой фичи чёткое состояние и команда верификации. Новая сессия читает список фич и за 3 минуты знает: F01–F05 — `passing`, F06 — `active`, F07–F10 — `not_started`. Сразу подхватывает с F06, никаких переделок. - -Количественно: проекты со структурированными списками фич показывают на 45% более высокую долю завершения по сравнению со свободной формой учёта, при нулевом дублировании реализаций. - -## Главные выводы - -- **Списки фич — позвоночник harness'а**, а не памятки для людей. Планировщик, верификатор и репортёр handoff все зависят от них. -- **У каждой записи фичи должна быть тройка**: описание поведения + команда верификации + текущее состояние. Без одного элемента она неполна — как трёхногий табурет с потерянной ногой. -- **Переходами состояний управляет harness** — агент не может менять состояния сам по себе. Прохождение верификации = единственный путь повышения статуса. -- **Список фич — единственный источник истины проекта** — вся информация «что делать» происходит из одного списка. -- **Калибруйте гранулярность под «выполнимо за одну сессию».** - -## Дополнительное чтение - -- [Building Effective Agents - Anthropic](https://www.anthropic.com/research/building-effective-agents) — явно называет список фич «ключевой структурой данных» для контроля скоупа агента -- [Harness Engineering - OpenAI](https://openai.com/index/harness-engineering/) — подчёркивает принцип «выноса артефактов наружу» -- [Design by Contract - Bertrand Meyer](https://www.goodreads.com/book/show/130439.Object_Oriented_Software_Construction) — принципы проектирования по контракту, теоретическая основа списков фич -- [How Google Tests Software](https://www.goodreads.com/book/show/13563030-how-google-tests-software) — пирамида тестирования и инженерные практики поведенческой спецификации - -## Упражнения - -1. **Проектирование списка фич**: определите минимальную JSON-схему списка фич. Включите: id, описание поведения, команду верификации, текущее состояние, ссылку на доказательство. Опишите ею реальный проект из 5 фич. - -2. **Сравнение строгости верификации**: возьмите 3 фичи и спроектируйте «слабую» верификацию (например, «в коде нет синтаксических ошибок») и «строгую» (например, «сквозной тест проходит»). Сравните долю ложных срабатываний при каждом подходе. - -3. **Аудит принципа единственного источника**: пересмотрите существующий проект с агентом и поищите информацию о скоупе, противоречащую списку фич (неявные требования в разговорах, TODO-комментарии в коде и т. д.). Спроектируйте план объединения всей информации в список фич. diff --git a/courses/learn-harness-engineering/docs/ru/lectures/lecture-09-why-agents-declare-victory-too-early/code/clean-state-checklist.md b/courses/learn-harness-engineering/docs/ru/lectures/lecture-09-why-agents-declare-victory-too-early/code/clean-state-checklist.md deleted file mode 100644 index 8c15a0ca..00000000 --- a/courses/learn-harness-engineering/docs/ru/lectures/lecture-09-why-agents-declare-victory-too-early/code/clean-state-checklist.md +++ /dev/null @@ -1,6 +0,0 @@ -# Чек-лист чистого состояния - -- Приложение запускается без ручного ремонта -- Текущий прогресс зафиксирован -- Не остаётся незадокументированных полудоделанных шагов импорта или индексации -- Следующий агент может сразу выполнить стандартный путь запуска и верификации diff --git a/courses/learn-harness-engineering/docs/ru/lectures/lecture-09-why-agents-declare-victory-too-early/code/index.md b/courses/learn-harness-engineering/docs/ru/lectures/lecture-09-why-agents-declare-victory-too-early/code/index.md deleted file mode 100644 index 016aae64..00000000 --- a/courses/learn-harness-engineering/docs/ru/lectures/lecture-09-why-agents-declare-victory-too-early/code/index.md +++ /dev/null @@ -1,8 +0,0 @@ -# Код к лекции 09 - -Используйте эту папку для примеров: - -- логи как обратная связь -- видимость состояния во время выполнения -- проверки чистого состояния -- примеры восстановления diff --git a/courses/learn-harness-engineering/docs/ru/lectures/lecture-09-why-agents-declare-victory-too-early/code/victory-detector.ts b/courses/learn-harness-engineering/docs/ru/lectures/lecture-09-why-agents-declare-victory-too-early/code/victory-detector.ts deleted file mode 100644 index fad1a3ba..00000000 --- a/courses/learn-harness-engineering/docs/ru/lectures/lecture-09-why-agents-declare-victory-too-early/code/victory-detector.ts +++ /dev/null @@ -1,241 +0,0 @@ -/** - * victory-detector.ts - * - * Simulates an agent that claims task completion, then checks the claimed - * state against actual verification. Outputs: claimed vs actual, highlighting - * gaps between what the agent said and what's really true. - * - * Run: npx tsx docs/lectures/lecture-09-why-agents-declare-victory-too-early/code/victory-detector.ts - */ - -// --------------------------------------------------------------------------- -// Types -// --------------------------------------------------------------------------- - -interface Task { - name: string; - claimedComplete: boolean; - checks: VerificationCheck[]; -} - -interface VerificationCheck { - description: string; - claimed: boolean; // What the agent says - actual: boolean; // What is actually true - severity: "critical" | "warning"; -} - -// --------------------------------------------------------------------------- -// Simulated tasks with claimed vs actual states -// --------------------------------------------------------------------------- - -const tasks: Task[] = [ - { - name: "Add search endpoint", - claimedComplete: true, - checks: [ - { - description: "Route handler exists", - claimed: true, - actual: true, - severity: "critical", - }, - { - description: "Authentication middleware applied", - claimed: true, - actual: false, // Agent forgot auth - severity: "critical", - }, - { - description: "Input validation added", - claimed: true, - actual: true, - severity: "critical", - }, - { - description: "Unit tests written", - claimed: true, - actual: false, // Agent skipped tests - severity: "critical", - }, - { - description: "Integration tests pass", - claimed: true, - actual: false, // No tests to run - severity: "critical", - }, - { - description: "Documentation updated", - claimed: true, - actual: false, // Agent skipped docs - severity: "warning", - }, - ], - }, - { - name: "Fix pagination bug", - claimedComplete: true, - checks: [ - { - description: "Bug reproduction case confirmed", - claimed: true, - actual: true, - severity: "critical", - }, - { - description: "Root cause identified", - claimed: true, - actual: true, - severity: "critical", - }, - { - description: "Fix applied", - claimed: true, - actual: true, - severity: "critical", - }, - { - description: "Edge cases tested", - claimed: true, - actual: false, // Only happy path tested - severity: "warning", - }, - { - description: "Regression tests added", - claimed: true, - actual: false, - severity: "critical", - }, - ], - }, - { - name: "Refactor auth module", - claimedComplete: true, - checks: [ - { - description: "Old code removed", - claimed: true, - actual: false, // Old code still present - severity: "warning", - }, - { - description: "All existing tests still pass", - claimed: true, - actual: false, // Two tests broke - severity: "critical", - }, - { - description: "New auth flow works", - claimed: true, - actual: true, - severity: "critical", - }, - { - description: "Migration guide written", - claimed: true, - actual: false, - severity: "warning", - }, - ], - }, -]; - -// --------------------------------------------------------------------------- -// Verification -// --------------------------------------------------------------------------- - -interface TaskVerification { - taskName: string; - claimedComplete: boolean; - actuallyComplete: boolean; - totalChecks: number; - claimedPassing: number; - actualPassing: number; - gaps: { description: string; severity: string }[]; -} - -function verifyTask(task: Task): TaskVerification { - const claimedPassing = task.checks.filter((c) => c.claimed).length; - const actualPassing = task.checks.filter((c) => c.actual).length; - const gaps = task.checks.filter((c) => c.claimed && !c.actual); - - return { - taskName: task.name, - claimedComplete: task.claimedComplete, - actuallyComplete: gaps.length === 0, - totalChecks: task.checks.length, - claimedPassing, - actualPassing, - gaps: gaps.map((g) => ({ description: g.description, severity: g.severity })), - }; -} - -// --------------------------------------------------------------------------- -// Reporting -// --------------------------------------------------------------------------- - -function pad(s: string, len: number): string { - return s.length >= len ? s : s + " ".repeat(len - s.length); -} - -function run(): void { - console.log("\n" + "=".repeat(90)); - console.log(" VICTORY DETECTOR -- Claimed vs Actual Verification"); - console.log("=".repeat(90)); - - const verifications = tasks.map(verifyTask); - - for (const v of verifications) { - console.log("\n Task: " + v.taskName); - console.log(" " + "-".repeat(70)); - console.log(` Agent claimed: ${v.claimedComplete ? "COMPLETE" : "NOT COMPLETE"}`); - console.log(` Actually is: ${v.actuallyComplete ? "COMPLETE" : "INCOMPLETE"}`); - - if (!v.actuallyComplete) { - console.log(` MISMATCH: Agent declared victory but ${v.gaps.length} check(s) failed!`); - } - - // Detailed checks for this task - const task = tasks.find((t) => t.name === v.taskName)!; - console.log("\n Check detail:"); - const header = ` | ${pad("Check", 40)}| ${pad("Claimed", 9)}| ${pad("Actual", 9)}| ${pad("Gap?", 6)}|`; - const sep = ` |${"-".repeat(42)}|${"-".repeat(11)}|${"-".repeat(11)}|${"-".repeat(8)}|`; - console.log(header); - console.log(sep); - - for (const check of task.checks) { - const claimedLabel = check.claimed ? "PASS" : "FAIL"; - const actualLabel = check.actual ? "PASS" : "FAIL"; - const gapLabel = check.claimed && !check.actual ? "GAP" : ""; - const marker = gapLabel ? ">>" : " "; - console.log(`${marker}| ${pad(check.description, 40)}| ${pad(claimedLabel, 9)}| ${pad(actualLabel, 9)}| ${pad(gapLabel, 6)}|`); - } - } - - // Overall summary - console.log("\n" + "=".repeat(90)); - console.log(" OVERALL SUMMARY"); - console.log("=".repeat(90) + "\n"); - - const sHeader = `| ${pad("Task", 28)}| ${pad("Claimed", 10)}| ${pad("Actual", 10)}| ${pad("Gaps", 6)}| ${pad("Accuracy", 10)}|`; - const sSep = `|${"-".repeat(30)}|${"-".repeat(12)}|${"-".repeat(12)}|${"-".repeat(8)}|${"-".repeat(12)}|`; - console.log(sHeader); - console.log(sSep); - - for (const v of verifications) { - const accuracy = Math.round((v.actualPassing / v.totalChecks) * 100); - const cLabel = v.claimedComplete ? "Done" : "Pending"; - const aLabel = v.actuallyComplete ? "Done" : "Incomplete"; - console.log(`| ${pad(v.taskName, 28)}| ${pad(cLabel, 10)}| ${pad(aLabel, 10)}| ${pad(String(v.gaps.length), 6)}| ${pad(accuracy + "%", 10)}|`); - } - - const totalGaps = verifications.reduce((s, v) => s + v.gaps.length, 0); - const falseVictories = verifications.filter((v) => v.claimedComplete && !v.actuallyComplete).length; - - console.log("\n False victories: " + falseVictories + " of " + verifications.length + " tasks"); - console.log(" Total undetected gaps: " + totalGaps); - console.log("\n The agent declared 'done' on every task, but verification reveals " + totalGaps + " unmet criteria."); - console.log(" Without explicit verification, premature victory declarations go unchecked.\n"); -} - -run(); diff --git a/courses/learn-harness-engineering/docs/ru/lectures/lecture-09-why-agents-declare-victory-too-early/index.md b/courses/learn-harness-engineering/docs/ru/lectures/lecture-09-why-agents-declare-victory-too-early/index.md deleted file mode 100644 index e0dc7a2d..00000000 --- a/courses/learn-harness-engineering/docs/ru/lectures/lecture-09-why-agents-declare-victory-too-early/index.md +++ /dev/null @@ -1,145 +0,0 @@ -[中文版本 →](../../../zh/lectures/lecture-09-why-agents-declare-victory-too-early/) - -> Примеры кода: [code/](https://github.com/walkinglabs/learn-harness-engineering/blob/main/docs/en/lectures/lecture-09-why-agents-declare-victory-too-early/code/) -> Практический проект: [Project 05. Let the agent verify its own work](./../../projects/project-05-grounded-qa-verification/index.md) - -# Лекция 09. Предотвращение преждевременных заявлений о завершении - -Вы просите агента реализовать функцию «сброс пароля». Он изменяет схему базы данных, пишет API-эндпоинт, добавляет email-шаблон, запускает юнит-тесты (все проходят) и уверенно сообщает: «готово». Когда вы реально пытаетесь это запустить — ссылка сброса пароля не отправляется (отсутствует конфигурация email-сервиса), миграция базы данных падает на середине (несогласованность схемы), а сквозной поток ни разу не выполнялся. - -Это чувство не должно быть незнакомым — это как заполнить всю экзаменационную работу, уверенно сдать её первым и затем провалиться при выставлении оценок. То, что работа заполнена, не значит, что ответы правильные. - -Это не единичный случай. Классическая статья Guo et al. на ICML 2017 доказала: **современные нейронные сети систематически сверх-уверены** — уверенность, сообщаемая моделями, значительно выше их фактической точности. То же самое относится и к AI-агентам для кодинга: они «чувствуют», что закончили, но на самом деле до завершения далеко. Ваш harness должен заменить «чувства» агента на экстернализированную, основанную на выполнении верификацию. - -## Скользкий склон - -Преждевременные заявления о завершении почти всегда следуют одному паттерну: код выглядит нормально — синтаксис правильный, логика кажется разумной, статический анализ не показывает очевидных ошибок. Но harness не обеспечивает комплексной верификации выполнения, поэтому агент пропускает реальный запуск или запускает только часть тестов. Он запускает юнит-тесты, но пропускает интеграционные; запускает тесты, но не проверяет покрытие. В итоге «код выглядит нормально» принимается как доказательство того, что «фича завершена». И экзаменационная работа сдаётся. - -На каждом шаге теряется информация. От спецификации задачи к реализации кода к runtime-поведению — каждое преобразование может внести искажение, и каждая пропущенная верификация усугубляет информационную асимметрию. - -## Трёхслойная проверка завершения - -```mermaid -flowchart LR - Claim["Агент говорит: готово"] --> L1["Сначала
    lint / typecheck"] - L1 --> L2["Затем
    тесты и стартовые проверки"] - L2 --> L3["Наконец
    полный пользовательский поток"] - L3 --> Done["Пройти все три уровня"] -``` - -```mermaid -flowchart LR - A["Код написан
    юнит-тесты зелёные"] --> B["Но приложение не запустилось по-настоящему
    полный поток ни разу не выполнялся"] - B --> C["Проблемы конфига, БД, внешних сервисов
    всё остаётся скрытым"] - C --> D["Поэтому агент заявляет о завершении слишком рано"] -``` - -## Ключевые концепции - -- **Преждевременное заявление о завершении**: Агент утверждает, что задача выполнена, но невыполненные спецификации корректности всё ещё существуют. Главная проблема: агент судит на основе локальной уверенности на уровне кода, тогда как системная корректность требует глобальной верификации. -- **Искажение калибровки уверенности**: Систематический разрыв между самооценкой уверенности агента в завершении и фактическим качеством завершения. Для сложных мультифайловых задач это смещение значительно положительно — агент всегда увереннее, чем его реальный результат. Как студент, который всегда переоценивает свой балл после экзамена. -- **Критерии завершения**: Чёткий, выполнимый набор условий, определённый в harness'е. Агент должен удовлетворить все условия перед заявлением о завершении. «Готово» переходит от субъективного суждения к объективному определению. -- **Двойной шлюз верификации-валидации**: Первый слой верификации проверяет «правильно ли код реализует указанное поведение»; второй слой валидации проверяет «соответствует ли системное поведение сквозным требованиям». Оба должны пройти, чтобы считаться завершёнными. -- **Runtime-сигналы обратной связи**: Логи, состояния процессов и проверки здоровья из выполнения программы. Это объективная основа для harness'а, чтобы судить о качестве завершения. -- **Ограничение приоритета завершения**: Сначала верифицировать функциональную корректность, затем производительность, и наконец стиль. Рефакторинг запрещён до верификации основной функциональности. - -## Юнит-тесты пройдены ≠ Задача завершена - -Это самая распространённая ловушка и самая опасная. Агент написал код, запустил юнит-тесты, получил все зелёные и сказал «готово». Но философия дизайна юнит-тестов — изоляция тестируемого модуля и мокирование зависимостей — именно то, что делает их неспособными обнаружить проблемы межкомпонентного взаимодействия: - -**Несоответствие интерфейсов**: Файловый путь, переданный процессом рендеринга в preload-скрипт, является относительным путём, но preload-скрипт ожидает абсолютный путь. Их соответствующие юнит-тесты оба использовали моки и прошли. Проблема обнаруживается только при сквозном тестировании. Как каждый музыкант в группе идеально отыгрывает свою партию, но при совместной игре выясняется, что все в разных тональностях. - -**Ошибки распространения состояния**: Миграция базы данных изменяет схему таблицы, но слой кэширования ORM всё ещё содержит записи кэша для старой схемы. Юнит-тесты предоставляют свежую мок-среду каждый раз, что не выявляет эту кросс-слоевую несогласованность состояния. - -**Зависимость от окружения**: Код ведёт себя корректно в тестовой среде (где всё замокировано), но падает в реальной среде из-за различий в конфигурации, сетевых задержек или недоступности сервисов. Как идеальное пение в репетиционной комнате, но проблемы со звукооборудованием на сцене. - -### «Заодно отрефакторим» — яд для оценки завершения - -У Claude Code есть распространённый паттерн поведения: он начинает рефакторить код, оптимизировать производительность и улучшать стиль до того, как основная функциональность прошла верификацию. Цитата Кнута «Преждевременная оптимизация — корень всех зол» обретает новый смысл в сценарии агента — рефакторинг изменяет границу между верифицированным и неверифицированным кодом, потенциально ломая ранее неявно корректные пути выполнения. Это как переписывать ответы с черновика набело, пока вы ещё не решили задачи — не только трата времени, но можно ещё и переписать с ошибкой. - -### Систематическое искажение самооценки - -Anthropic обнаружила более глубокий паттерн провала в исследовании 2026 года: **когда агента просят оценить свою собственную работу, он систематически даёт слишком позитивные оценки — даже когда сторонний наблюдатель счёл бы качество явно неудовлетворительным.** Это как попросить студента оценить свой собственный экзамен — он всегда будет особенно снисходителен к своим ответам. - -Эта проблема особенно остра в субъективных задачах (таких как эстетика дизайна) — является ли «вёрстка изысканной» — это субъективное суждение, и агент надёжно смещается в положительную сторону. Даже в задачах с проверяемыми результатами производительность агента может быть ограничена плохой оценкой. - -Решение не в том, чтобы сделать агента «более объективным» — та же модель, генерирующая и оценивающая, внутренне склонна быть щедрой к себе. **Решение — разделить «исполнителя» и «проверяющего».** Как студент не должен проверять свой собственный экзамен — нужен независимый проверяющий. - -Независимый оценивающий агент, специально настроенный на «придирчивость», значительно эффективнее, чем генерирующий агент, оценивающий сам себя. Экспериментальные данные Anthropic: - -| Архитектура | Время | Стоимость | Основные фичи работают? | -|--------------|---------|------|------------------------| -| Одиночный агент (bare run) | 20 мин | $9 | Нет (игровые сущности не реагируют на ввод) | -| Три агента (planner + generator + evaluator) | 6 часов | $200 | Да (игра полностью играбельна) | - -Это та же самая модель (Opus 4.5) с тем же самым промптом («создать 2D ретро-игровой редактор»). Единственная разница — harness: от «bare run» к «планировщик разворачивает требования → генератор реализует фича за фичей → оценщик выполняет реальное клик-тестирование через Playwright». - -> Источник: [Anthropic: Harness design for long-running application development](https://www.anthropic.com/engineering/harness-design-long-running-apps) - -## Как предотвратить преждевременные сдачи - -### 1. Экстернализировать суждение о завершении - -Суждение о завершении не должно выноситься самим агентом. Harness должен независимо выполнять валидацию завершения, используя runtime-сигналы как входные данные, а не уверенность агента. Запишите это чётко в `CLAUDE.md`: - -``` -## Определение завершения -- Фича завершена = сквозная верификация пройдена, а не «код написан» -- Требуемые уровни верификации: - 1. Юнит-тесты пройдены - 2. Интеграционные тесты пройдены - 3. Сквозная верификация потока пройдена -- Не переходить к уровню 2, если уровень 1 не пройден -- Не переходить к уровню 3, если уровень 2 не пройден -``` - -### 2. Построить трёхслойную валидацию завершения - -- **Слой 1: Синтаксис и статический анализ**. Наименьшая стоимость, наименьшая информация, но должен пройти. Это минимальная проверка — сначала надо написать слова правильно, прежде чем рассматривать что-либо ещё. -- **Слой 2: Верификация runtime-поведения**. Выполнение тестов, проверки запуска приложения, валидация критических путей. Это основное доказательство завершения. Недостаточно просто написать — это должно работать. -- **Слой 3: Системное подтверждение**. Сквозное тестирование, интеграционная валидация, симуляция пользовательских сценариев. Последняя линия обороны от преждевременных заявлений. Недостаточно работать — должно работать правильно. - -### 3. Разработайте хорошие «красные пометки» для агентов - -OpenAI внедрила особенно эффективный паттерн в своей практике Codex: **сообщения об ошибках для агентов должны включать инструкции по исправлению.** Не просто ставьте большой красный крест как ленивый проверяющий; будьте как хороший учитель и напишите на полях «вот как это нужно изменить». Не используйте `"Test failed"`, а используйте `"Test failed: POST /api/reset-password вернул 500. Проверьте, что конфигурация email-сервиса существует в переменных окружения. Файл шаблона должен быть в templates/reset-email.html."` Эта конкретная, действенная обратная связь позволяет агенту самокорректироваться без вмешательства человека. - -### 4. Собирайте runtime-сигналы - -Эффективные runtime-сигналы включают: -- Успешно ли запустилось приложение и достигло состояния готовности? -- Успешно ли выполнились критические функциональные пути во время выполнения? -- Были ли корректными записи в БД, файловые операции и другие побочные эффекты? -- Были ли очищены временные ресурсы? - -## Реальный кейс - -**Задача**: Реализовать функциональность сброса пароля пользователя. Включает операции с базой данных, отправку email и модификацию API-эндпоинтов. - -**Путь преждевременной сдачи**: Агент изменяет схему БД, пишет API-эндпоинт, добавляет email-шаблон, запускает юнит-тесты (проходят) и заявляет о завершении. Экзаменационная работа полностью заполнена. - -**Фактические вычеты баллов**: (1) Сквозной поток не протестирован — фактическая отправка и верификация ссылки сброса никогда не подтверждались. (2) Миграция БД упала после частичного выполнения, вызвав несогласованность схемы. (3) Конфигурация email-сервиса отсутствовала в целевом окружении. - -**Вмешательство harness'а**: Принудительная валидация завершения — (1) Запустить полное приложение для проверки доступности эндпоинта сброса; (2) Выполнить полный поток сброса; (3) Верифицировать согласованность состояния БД. Все дефекты были обнаружены в рамках сессии, сэкономив в 5–10 раз стоимость последующих исправлений. Независимый проверяющий нашёл реальные проблемы. - -## Ключевые выводы - -- **Агенты систематически сверх-уверены** — искажение калибровки уверенности — это объективная реальность. Заполнение экзаменационной работы не означает, что ответы правильные. -- **Суждение о завершении должно быть экстернализировано** — harness верифицирует независимо; не доверяйте «чувствам» агента. Студенты не могут проверять свои собственные экзамены. -- **Все три слоя валидации необходимы** — синтаксис пройден, поведение пройдено, система пройдена, прогрессия слой за слоем. -- **Сообщения об ошибках должны быть как красные пометки хорошего учителя** — включать конкретные шаги исправления, чтобы агент мог самокорректироваться. -- **Никакого рефакторинга до верификации основной функциональности** — ограничение приоритета завершения — ключ к предотвращению преждевременной оптимизации. - -## Дополнительное чтение - -- [On Calibration of Modern Neural Networks - Guo et al.](https://arxiv.org/abs/1706.04599) — Доказывает, что современные глубокие сети систематически сверх-уверены -- [Building Effective Agents - Anthropic](https://www.anthropic.com/research/building-effective-agents) — Критическая роль runtime-доказательств в суждении о завершении -- [Harness Engineering - OpenAI](https://openai.com/index/harness-engineering/) — Преждевременное заявление о завершении — один из основных режимов провала агентов -- [The Art of Software Testing - Myers](https://www.goodreads.com/book/show/137543.The_Art_of_Software_Testing) — Классический справочник по иерархиям методов тестирования и их эффективности - -## Упражнения - -1. **Дизайн функции валидации завершения**: Спроектируйте полную валидацию завершения для задачи, включающей миграцию БД и модификацию API. Перечислите требуемые runtime-сигналы и критерии пройдено/провалено для каждого сигнала. Запустите на реальной задаче и зафиксируйте, какие скрытые проблемы она обнаруживает. - -2. **Измерение искажения калибровки**: Выберите 10 различных типов задач кодинга и зафиксируйте самооценку уверенности агента в завершении vs. фактическое качество завершения. Вычислите значение смещения и проанализируйте его связь со сложностью задачи. - -3. **Эксперимент с многослойной обороной**: Запустите три конфигурации на одном наборе задач — (a) только статический анализ, (b) добавить юнит-тестирование, (c) полная трёхслойная валидация. Сравните долю преждевременных заявлений о завершении и количество необнаруженных дефектов. diff --git a/courses/learn-harness-engineering/docs/ru/lectures/lecture-10-why-end-to-end-testing-changes-results/code/architecture-rules.md b/courses/learn-harness-engineering/docs/ru/lectures/lecture-10-why-end-to-end-testing-changes-results/code/architecture-rules.md deleted file mode 100644 index b52a04b6..00000000 --- a/courses/learn-harness-engineering/docs/ru/lectures/lecture-10-why-end-to-end-testing-changes-results/code/architecture-rules.md +++ /dev/null @@ -1,6 +0,0 @@ -# Архитектурные правила Electron - -- Код рендерера не должен напрямую обращаться к файловой системе. -- Preload — единственный мост между рендерером и main-процессом Electron. -- Логика поиска и индексации живёт в сервисных модулях, а не в UI-компонентах. -- Логирование должно быть структурированным и исходить из границ сервисов. diff --git a/courses/learn-harness-engineering/docs/ru/lectures/lecture-10-why-end-to-end-testing-changes-results/code/e2e-runner.ts b/courses/learn-harness-engineering/docs/ru/lectures/lecture-10-why-end-to-end-testing-changes-results/code/e2e-runner.ts deleted file mode 100644 index 24284165..00000000 --- a/courses/learn-harness-engineering/docs/ru/lectures/lecture-10-why-end-to-end-testing-changes-results/code/e2e-runner.ts +++ /dev/null @@ -1,253 +0,0 @@ -/** - * e2e-runner.ts - * - * A minimal E2E test harness. Defines test cases as user action sequences - * (import doc -> index -> ask question -> verify citation). Simulates - * running them and shows the difference between "unit tests pass" and - * "full pipeline works". - * - * Run: npx tsx docs/lectures/lecture-10-why-end-to-end-testing-changes-results/code/e2e-runner.ts - */ - -// --------------------------------------------------------------------------- -// Types -// --------------------------------------------------------------------------- - -interface PipelineStep { - name: string; - unitTestPasses: boolean; - // Simulated actual behavior in the pipeline - actualBehavior: "works" | "fails" | "partial"; - failureReason?: string; -} - -interface TestCase { - name: string; - steps: PipelineStep[]; -} - -interface TestResult { - testCase: string; - unitTestsPassed: number; - unitTestsTotal: number; - unitTestResult: "PASS" | "FAIL"; - e2eResult: "PASS" | "FAIL"; - e2eFailureStep?: string; - e2eFailureReason?: string; -} - -// --------------------------------------------------------------------------- -// Test cases -- realistic scenarios where unit tests pass but E2E fails -// --------------------------------------------------------------------------- - -const testCases: TestCase[] = [ - { - name: "Import document and ask question", - steps: [ - { - name: "Parse uploaded document", - unitTestPasses: true, - actualBehavior: "works", - }, - { - name: "Store document chunks", - unitTestPasses: true, - actualBehavior: "works", - }, - { - name: "Index chunks for retrieval", - unitTestPasses: true, - actualBehavior: "partial", // Indexes but with wrong embedding dimensions - failureReason: "Embedding dimension mismatch between indexer and retriever", - }, - { - name: "Retrieve relevant chunks", - unitTestPasses: true, // Unit test uses mock data with correct dimensions - actualBehavior: "fails", - failureReason: "Empty results due to dimension mismatch from previous step", - }, - { - name: "Generate answer with citations", - unitTestPasses: true, // Unit test provides pre-retrieved chunks - actualBehavior: "fails", - failureReason: "No chunks retrieved, so answer has no citations", - }, - ], - }, - { - name: "Delete document and verify removal", - steps: [ - { - name: "Find document by ID", - unitTestPasses: true, - actualBehavior: "works", - }, - { - name: "Delete document record", - unitTestPasses: true, - actualBehavior: "works", - }, - { - name: "Remove indexed chunks", - unitTestPasses: true, - actualBehavior: "fails", // Orphaned chunks remain in the index - failureReason: "Index cleanup query timed out, chunks remain orphaned", - }, - { - name: "Verify document not in search results", - unitTestPasses: true, // Unit test mocks the search - actualBehavior: "fails", - failureReason: "Orphaned chunks from previous step still appear in results", - }, - ], - }, - { - name: "Multi-user concurrent access", - steps: [ - { - name: "User A imports document", - unitTestPasses: true, - actualBehavior: "works", - }, - { - name: "User B imports document", - unitTestPasses: true, - actualBehavior: "works", - }, - { - name: "User A queries their document", - unitTestPasses: true, - actualBehavior: "partial", // Cross-contamination of results - failureReason: "No user-scoping on retrieval, returns chunks from User B's doc", - }, - { - name: "Verify only User A's results returned", - unitTestPasses: true, - actualBehavior: "fails", - failureReason: "Results include documents from other users", - }, - ], - }, -]; - -// --------------------------------------------------------------------------- -// Run tests -// --------------------------------------------------------------------------- - -function runUnitTests(tc: TestCase): { passed: number; total: number } { - const passed = tc.steps.filter((s) => s.unitTestPasses).length; - return { passed, total: tc.steps.length }; -} - -function runE2ETest(tc: TestCase): { pass: boolean; failureStep?: string; failureReason?: string } { - // Pipeline: if any step actually fails, the whole E2E fails - for (const step of tc.steps) { - if (step.actualBehavior === "fails") { - return { - pass: false, - failureStep: step.name, - failureReason: step.failureReason ?? "Unknown failure", - }; - } - // "partial" means the step technically completes but creates problems downstream - // We let it continue but track it - } - // Check if any step was "partial" (which may cause downstream issues) - const partialSteps = tc.steps.filter((s) => s.actualBehavior === "partial"); - if (partialSteps.length > 0) { - // The partial steps may or may not cause overall failure - // In our simulation, partial steps always lead to failure downstream - // unless there's an explicit "fails" step that already caught it - // This case means all steps were either "works" or "partial" - return { - pass: false, - failureStep: partialSteps[partialSteps.length - 1].name, - failureReason: partialSteps[partialSteps.length - 1].failureReason ?? "Partial completion", - }; - } - - return { pass: true }; -} - -// --------------------------------------------------------------------------- -// Reporting -// --------------------------------------------------------------------------- - -function pad(s: string, len: number): string { - return s.length >= len ? s : s + " ".repeat(len - s.length); -} - -function run(): void { - console.log("\n" + "=".repeat(95)); - console.log(" E2E TEST RUNNER -- Unit Tests vs Full Pipeline"); - console.log("=".repeat(95)); - - const results: TestResult[] = testCases.map((tc) => { - const unit = runUnitTests(tc); - const e2e = runE2ETest(tc); - - return { - testCase: tc.name, - unitTestsPassed: unit.passed, - unitTestsTotal: unit.total, - unitTestResult: unit.passed === unit.total ? "PASS" : "FAIL", - e2eResult: e2e.pass ? "PASS" : "FAIL", - e2eFailureStep: e2e.failureStep, - e2eFailureReason: e2e.failureReason, - }; - }); - - // Per-test-case detail - for (let i = 0; i < testCases.length; i++) { - const tc = testCases[i]; - const r = results[i]; - - console.log("\n Test Case: " + tc.name); - console.log(" " + "-".repeat(70)); - - const stepHeader = ` | ${pad("Step", 40)}| ${pad("Unit Test", 11)}| ${pad("E2E Actual", 12)}|`; - const stepSep = ` |${"-".repeat(42)}|${"-".repeat(13)}|${"-".repeat(14)}|`; - console.log(stepHeader); - console.log(stepSep); - - for (const step of tc.steps) { - const utLabel = step.unitTestPasses ? "PASS" : "FAIL"; - let e2eLabel: string; - if (step.actualBehavior === "works") e2eLabel = "PASS"; - else if (step.actualBehavior === "partial") e2eLabel = "PARTIAL*"; - else e2eLabel = "FAIL"; - - const marker = step.actualBehavior !== "works" ? ">>" : " "; - console.log(`${marker}| ${pad(step.name, 40)}| ${pad(utLabel, 11)}| ${pad(e2eLabel, 12)}|`); - } - - if (r.e2eFailureStep) { - console.log(`\n E2E Failure at: ${r.e2eFailureStep}`); - console.log(` Reason: ${r.e2eFailureReason}`); - } - } - - // Summary comparison - console.log("\n" + "=".repeat(95)); - console.log(" COMPARISON: Unit Tests vs E2E Tests"); - console.log("=".repeat(95) + "\n"); - - const header = `| ${pad("Test Case", 35)}| ${pad("Unit Tests", 15)}| ${pad("E2E Result", 12)}| Discrepancy`; - const sep = `|${"-".repeat(37)}|${"-".repeat(17)}|${"-".repeat(14)}|${"-".repeat(30)}`; - console.log(header); - console.log(sep); - - for (const r of results) { - const utLabel = `${r.unitTestsPassed}/${r.unitTestsTotal} ${r.unitTestResult}`; - const discrepancy = r.unitTestResult === "PASS" && r.e2eResult === "FAIL"; - const discLabel = discrepancy ? "UNIT PASS BUT E2E FAIL" : "Consistent"; - - console.log(`| ${pad(r.testCase, 35)}| ${pad(utLabel, 15)}| ${pad(r.e2eResult, 12)}| ${discLabel}`); - } - - const falseConfidence = results.filter((r) => r.unitTestResult === "PASS" && r.e2eResult === "FAIL").length; - console.log("\n False confidence count: " + falseConfidence + " of " + results.length); - console.log(" Unit tests pass but E2E reveals integration failures that units cannot catch.\n"); -} - -run(); diff --git a/courses/learn-harness-engineering/docs/ru/lectures/lecture-10-why-end-to-end-testing-changes-results/code/index.md b/courses/learn-harness-engineering/docs/ru/lectures/lecture-10-why-end-to-end-testing-changes-results/code/index.md deleted file mode 100644 index f706111c..00000000 --- a/courses/learn-harness-engineering/docs/ru/lectures/lecture-10-why-end-to-end-testing-changes-results/code/index.md +++ /dev/null @@ -1,8 +0,0 @@ -# Код к лекции 10 - -Используйте эту папку для примеров: - -- архитектурные ограничения -- структурные тесты -- инварианты вкуса -- сообщения линтера, ориентированные на исправление diff --git a/courses/learn-harness-engineering/docs/ru/lectures/lecture-10-why-end-to-end-testing-changes-results/code/review-feedback-to-rule.md b/courses/learn-harness-engineering/docs/ru/lectures/lecture-10-why-end-to-end-testing-changes-results/code/review-feedback-to-rule.md deleted file mode 100644 index 7fc4a7c5..00000000 --- a/courses/learn-harness-engineering/docs/ru/lectures/lecture-10-why-end-to-end-testing-changes-results/code/review-feedback-to-rule.md +++ /dev/null @@ -1,10 +0,0 @@ -# Пример: превращение фидбэка с ревью в правило - -Повторяющийся комментарий с ревью: - -> Не вызывайте утилиты файловой системы из рендерера. Используйте preload-мост. - -Поднято до правила harness: - -- добавить правило линтера или импорта, запрещающее использование `fs` в коде рендерера -- добавить текст с инструкцией, объясняющей границу preload diff --git a/courses/learn-harness-engineering/docs/ru/lectures/lecture-10-why-end-to-end-testing-changes-results/index.md b/courses/learn-harness-engineering/docs/ru/lectures/lecture-10-why-end-to-end-testing-changes-results/index.md deleted file mode 100644 index a4e95138..00000000 --- a/courses/learn-harness-engineering/docs/ru/lectures/lecture-10-why-end-to-end-testing-changes-results/index.md +++ /dev/null @@ -1,158 +0,0 @@ -[中文版本 →](../../../zh/lectures/lecture-10-why-end-to-end-testing-changes-results/) - -> Примеры кода: [code/](https://github.com/walkinglabs/learn-harness-engineering/blob/main/docs/en/lectures/lecture-10-why-end-to-end-testing-changes-results/code/) -> Практический проект: [Project 05. Let the agent verify its own work](./../../projects/project-05-grounded-qa-verification/index.md) - -# Лекция 10. Только сквозное тестирование — настоящая верификация - -Вы просите агента добавить функцию экспорта файлов в Electron-приложение. Он пишет компонент процесса рендеринга, preload-скрипт и логику сервисного слоя. Юнит-тесты каждого компонента проходят идеально. Агент говорит: «Готово». Когда вы реально нажимаете кнопку экспорта — формат файлового пути неправильный, прогресс-бар не обновляется, а экспорт больших файлов вызывает утечку памяти. Пять дефектов на границах компонентов, и юнит-тесты не поймали ни одного. - -Это как репетиция хора — каждая партия звучит идеально по отдельности, но когда поют вместе, сопрано на полбита быстрее басов, а аккомпанемент на полтона расходится с основной мелодией. Каждая часть «правильна» сама по себе, но整体 — фальшиво. - -Тест-пирамида Google говорит нам: большое количество юнит-тестов — это фундамент, но если остановиться на этом, вы систематически упустите проблемы взаимодействия компонентов. Для AI-агентов кодинга эта проблема ещё серьёзнее — агенты склонны запускать только самые быстрые тесты и затем заявлять о завершении. **Только сквозное тестирование может доказать, что системные дефекты отсутствуют.** - -## Слепые зоны юнит-тестирования - -Философия дизайна юнит-тестирования — изоляция: мокирование зависимостей и фокус исключительно на тестируемом модуле. Эта философия делает юнит-тесты быстрыми и точными, но также создаёт систематические слепые зоны. Это как репетировать хор в наушниках — каждому звучит нормально, но проблемы выявляются только при совместном пении: - -**Несоответствие интерфейсов**: Файловый путь, переданный процессом рендеринга в preload-скрипт, является относительным путём, но preload-скрипт ожидает абсолютный путь. Их соответствующие юнит-тесты оба использовали моки и прошли. Проблема обнаруживается только при выполнении сквозного потока — как две вокальные партии, репетирующие отдельно и чувствующие себя прекрасно, но на совместной репетиции выясняется, что одна поёт в размере 4/4, а другая в 3/4. - -**Ошибки распространения состояния**: Миграция базы данных изменяет схему таблицы, но слой кэширования ORM всё ещё содержит записи кэша для старой схемы. Юнит-тесты предоставляют полностью новую мок-среду каждый раз, что не выявляет эту кросс-слоевую несогласованность состояния. Как изменение текста песни, но кто-то всё ещё поёт старую версию. - -**Проблемы жизненного цикла ресурсов**: Захват и освобождение файловых дескрипторов, подключений к БД и сетевых сокетов охватывают несколько компонентов. Юнит-тесты создают и уничтожают независимые ресурсы для каждого теста, не выявляя конкуренции за ресурсы или утечек. Как партия хора, по очереди пользующаяся микрофонами на репетиции, но когда все выходят на сцену вместе — микрофонов не хватает. - -**Зависимость от окружения**: Код ведёт себя корректно в тестовой среде (где всё замокировано), но падает в реальной среде из-за различий в конфигурации, сетевых задержек или недоступности сервисов. Как идеальное пение в репетиционной комнате, но акустическая обратная связь и ветер на открытом фестивале. - -## Сквозное тестирование не только меняет результаты, оно меняет поведение - -Это то, чего многие не осознают: когда агент знает, что его работа будет подвергнута сквозному тестированию, его поведение при кодинге меняется. - -1. **Учёт взаимодействия компонентов**: При написании кода он будет думать о «как этот интерфейс связывается с восходящим потоком», а не просто фокусироваться на одной функции. Как знать, что в итоге будете петь вместе — вы будете обращать внимание на другие партии во время репетиции. -2. **Уважение архитектурных границ**: В системах с архитектурными ограничениями сквозное тестирование заставляет агента соблюдать пограничные правила. Как ноты с пометкой «здесь крещендо» — приходится следовать. -3. **Обработка ошибочных путей**: Сквозные тесты обычно включают сценарии отказов, заставляя агента учитывать обработку исключений. Как симуляция «что если микрофон внезапно умрёт» на репетиции, чтобы знать, что делать. - -## Тест-пирамида и продвижение review-обратной связи - -```mermaid -flowchart TB - subgraph Unit["Юнит-тесты проверяют только изолированные части"] - U1["Тесты рендерера"] - U2["Тесты preload"] - U3["Тесты сервисов"] - end - - subgraph E2E["E2E прогоняет реальную систему"] - R["Клик по кнопке рендерера"] --> P["Preload bridge"] - P --> S["Сервисный слой"] - S --> F["Файловая система / OS"] - F --> Result["Реальный экспортированный файл"] - end -``` - -```mermaid -flowchart LR - Review["Review-обратная связь:
    рендерер не может импортировать fs напрямую"] --> Rule["Добавить проверку прямого импорта fs"] - Rule --> Message["Сказать агенту в сообщении об ошибке
    перенести файловый доступ в preload"] - Message --> Harness["Добавить эту проверку в harness"] - Harness --> Stronger["В следующий раз сразу упадёт"] -``` - -В инженерных практиках Codex OpenAI подчёркивает: **сообщения об ошибках для агентов должны включать инструкции по исправлению.** Не просто пишите `"Direct filesystem access in renderer"`; напишите `"Прямой доступ к файловой системе в рендерере. Все файловые операции должны проходить через preload bridge. Перенесите этот вызов в preload/file-ops.ts и вызовите через window.api."` Это превращает архитектурные правила в цикл автокоррекции. Как дирижёр хора, который не просто говорит «ты спел неправильно», а говорит «здесь ты был на полбита быстрее, послушай ритм альтов и входи в такте 32». - -## Ключевые концепции - -- **Дефекты на границах компонентов**: Компонент A и B оба проходят свои юнит-тесты, но их взаимодействие производит некорректное поведение. Это именно тот тип проблем, который сквозное тестирование лучше всего ловит — как партии хора, individually правильные, но вместе фальшивые. -- **Градиент адекватности тестирования**: Дефекты, пойманные юнит-тестами <= дефекты, пойманные интеграционными тестами <= дефекты, пойманные сквозными тестами. Каждый слой вверх увеличивает способность обнаружения. -- **Правила обеспечения архитектурных границ**: Превращение правил из архитектурных документов (например, «процесс рендеринга не может напрямую обращаться к файловой системе») в выполнимые, автоматизированные проверки. От «написано на бумаге» к «работает в CI». -- **Продвижение review-обратной связи**: Конвертация повторяющихся комментариев code review в автоматизированные тесты. Каждый раз, когда обнаруживается повторяющаяся проблема, добавляется правило, и harness автоматически становится сильнее. Как дирижёр, превращающий типичные репетиционные ошибки в разминку — в следующий раз та же ошибка выявляется сама, без слов дирижёра. -- **Ориентированные на агентов сообщения об ошибках**: Сообщения о провале не должны просто констатировать «что пошло не так», но и точно говорить агенту, как это исправить. Это превращает тестовые провалы в самокорректирующиеся циклы обратной связи. - -## Как это сделать - -### 0. Сначала определите архитектурные границы, затем пишите E2E-тесты - -Предпосылка сквозного тестирования — чёткие системные границы. Если архитектура — тарелка спагетти, сквозное тестирование только докажет «эта тарелка спагетти работает», но не скажет, где были нарушены проектные намерения. Как хор, который даже не разделился на партии — сколько ни репетируй, хорошо не зазвучит. - -Опыт OpenAI: **для кодовых баз, генерируемых агентами, архитектурные ограничения должны быть ранними предпосылками, установленными с первого дня, а не чем-то, что учитывается, когда команда вырастет.** Причина проста — агенты копируют существующие паттерны в репозитории, даже если эти паттерны неровные или неоптимальные. Без архитектурных ограничений агент будет вносить больше отклонений в каждой сессии. - -OpenAI приняла «Многослойную доменную архитектуру» — каждый бизнес-домен разделён на фиксированные слои: Types → Config → Repo → Service → Runtime → UI. Зависимости текут строго вперёд, а кросс-доменные вопросы входят через явные интерфейсы Providers. Любые другие зависимости запрещены и механически обеспечиваются через кастомный линтинг. - -Ключевой принцип: **Обеспечивать инварианты, не микроменеджить реализацию.** Например, требовать «данные парсятся на границе», но не диктовать, какую библиотеку использовать. Сообщения об ошибках должны включать инструкции по исправлению — не просто говорить «нарушение», но точно говорить агенту, как это изменить. - -> Источник: [OpenAI: Harness engineering: leveraging Codex in an agent-first world](https://openai.com/index/harness-engineering/) - -### 1. Harness должен включать сквозной слой - -Сделайте это явным в вашем процессе валидации: для задач, включающих кросс-компонентные изменения, прохождение сквозных тестов является предпосылкой для завершения: - -``` -## Иерархия валидации -- Уровень 1: Юнит-тесты (Должны пройти) -- Уровень 2: Интеграционные тесты (Должны пройти) -- Уровень 3: Сквозные тесты (Должны пройти при кросс-компонентных изменениях) -- Пропуск любого требуемого уровня = Не завершено -``` - -### 2. Превратите архитектурные правила в выполнимые проверки - -Каждое архитектурное ограничение должно иметь соответствующий тест или правило линтинга: - -```bash -# Проверить, не вызывает ли процесс рендеринга напрямую Node.js API -grep -r "require('fs')" src/renderer/ && exit 1 || echo "OK: no direct fs access in renderer" -``` - -### 3. Разработайте ориентированные на агентов сообщения об ошибках - -Сообщения о провале должны содержать три элемента: что пошло не так, почему и как исправить: - -``` -ERROR: Найден прямой импорт 'fs' в src/renderer/App.tsx:12 -WHY: Процесс рендеринга не имеет доступа к Node.js API по соображениям безопасности -FIX: Перенесите файловые операции в src/preload/file-ops.ts и вызовите через window.api.readFile() -``` - -### 4. Установите процесс продвижения review-обратной связи - -Каждый раз, когда обнаруживается новый тип ошибки агента при code review, превратите его в автоматизированную проверку. Через месяц ваш harness будет значительно сильнее, чем в начале месяца. Как заметки к репетиции хора — запись проблем, найденных на каждой репетиции, для проверки перед следующей. Со временем типичные ошибки уменьшаются, и музыка становится гармоничнее. - -## Реальный кейс - -**Задача**: Реализовать функцию экспорта файлов в Electron-приложении. Включает UI процесса рендеринга, прокси файловой системы preload-скрипта и трансформацию данных сервисного слоя. - -**Пение по частям (Юнит-тесты пройдены)**: Тесты компонента рендеринга (пройдены, файловые операции замокированы), тесты preload-скрипта (пройдены, файловая система замокирована), тесты сервисного слоя (пройдены, источник данных замокирован). Агент заявляет о завершении. - -**Совместное пение (Дефекты, выявленные сквозными тестами)**: - -| Дефект | Описание | Юнит-тест | E2E | -|--------|-------------|-----------|-----| -| Несоответствие интерфейсов | Несогласованный формат файлового пути | Пропущен | Пойман | -| Распространение состояния | Прогресс экспорта не отправляется обратно в UI через IPC | Пропущен | Пойман | -| Утечка ресурсов | Дескрипторы экспорта больших файлов не освобождаются | Пропущен | Пойман | -| Проблема разрешений | Различные разрешения в упакованном окружении | Пропущен | Пойман | -| Распространение ошибок | Исключения сервисного слоя не достигли слоя UI | Пропущен | Пойман | - -Все 5 дефектов были пойманы сквозными тестами, в то время как юнит-тесты не поймали ни одного. Стоимость — увеличение времени тестирования с 2 секунд до 15 секунд — полностью приемлемо в workflow агента. Как бы хорошо каждая часть ни пела индивидуально, это не заменит полную совместную репетицию. - -## Ключевые выводы - -- **Юнит-тесты систематически слепы к дефектам на границах компонентов** — их дизайн изоляции именно то, что мешает им обнаруживать проблемы взаимодействия. Все поют правильно — не значит, что хор не фальшивит. -- **Сквозное тестирование не только обнаруживает дефекты, оно меняет поведение агента при кодинге** — заставляя его больше фокусироваться на интеграции и границах. -- **Архитектурные правила должны быть выполнимыми** — не написаны в документе в ожидании прочтения, а автоматически проверяться при каждом коммите. -- **Сообщения об ошибках должны быть разработаны для агентов** — включая конкретные шаги «как исправить» для формирования самокорректирующегося цикла. -- **Продвижение review-обратной связи делает harness автоматически сильнее** — каждая категория пойманных дефектов становится постоянной линией обороны. - -## Дополнительное чтение - -- [How Google Tests Software - Whittaker et al.](https://www.goodreads.com/book/show/13563030-how-google-tests-software) — Классический источник модели тест-пирамиды -- [Harness Engineering - OpenAI](https://openai.com/index/harness-engineering/) — Инженерные практики для автоматизированного выполнения архитектурных ограничений -- [Chaos Engineering - Netflix (Basiri et al.)](https://ieeexplore.ieee.org/document/7466237) — Проактивное внедрение отказов для проверки устойчивости системы -- [QuickCheck - Claessen & Hughes](https://www.cs.tufts.edu/~nr/cs257/archive/john-hughes/quick.pdf) — Методология property-тестирования, расположенная между примерным тестированием и формальной верификацией - -## Упражнения - -1. **Обнаружение кросс-компонентных дефектов**: Выберите задачу модификации, включающую как минимум три компонента. Сначала запустите только юнит-тесты и зафиксируйте результаты, затем запустите сквозные тесты. Проанализируйте, к какому типу кросс-слоевого взаимодействия относится каждый дополнительно обнаруженный дефект. - -2. **Автоматизация архитектурных правил**: Выберите архитектурное ограничение из вашего проекта и превратите его в выполнимую проверку (с ориентированным на агента сообщением об ошибке). Интегрируйте её в harness и верифицируйте эффективность на базовой задаче. - -3. **Продвижение review-обратной связи**: Найдите повторяющийся тип комментария из вашей истории code review и конвертируйте его в автоматизированную проверку, используя пятиступенчатый процесс. Сравните частоту появления проблемы до и после продвижения. diff --git a/courses/learn-harness-engineering/docs/ru/lectures/lecture-11-why-observability-belongs-inside-the-harness/code/evaluator-rubric.md b/courses/learn-harness-engineering/docs/ru/lectures/lecture-11-why-observability-belongs-inside-the-harness/code/evaluator-rubric.md deleted file mode 100644 index 85749893..00000000 --- a/courses/learn-harness-engineering/docs/ru/lectures/lecture-11-why-observability-belongs-inside-the-harness/code/evaluator-rubric.md +++ /dev/null @@ -1,8 +0,0 @@ -# Пример рубрики оценщика - -Используйте оценку 1–5 по каждому измерению: - -- Заземлённость: явно ли ответы привязаны к импортированным источникам? -- Качество цитирования: видны ли ссылки на источники и насколько они конкретны? -- Функциональность: может ли пользователь пройти флоу «вопрос-ответ»? -- Целостность продукта: ощущается ли воркфлоу как единое целое? diff --git a/courses/learn-harness-engineering/docs/ru/lectures/lecture-11-why-observability-belongs-inside-the-harness/code/index.md b/courses/learn-harness-engineering/docs/ru/lectures/lecture-11-why-observability-belongs-inside-the-harness/code/index.md deleted file mode 100644 index 6aa8a407..00000000 --- a/courses/learn-harness-engineering/docs/ru/lectures/lecture-11-why-observability-belongs-inside-the-harness/code/index.md +++ /dev/null @@ -1,8 +0,0 @@ -# Код к лекции 11 - -Используйте эту папку для примеров: - -- выходы планировщика -- рубрики оценщика -- циклы generator/evaluator -- сравнения одиночного агента и многоролевой схемы diff --git a/courses/learn-harness-engineering/docs/ru/lectures/lecture-11-why-observability-belongs-inside-the-harness/code/runtime-logger.ts b/courses/learn-harness-engineering/docs/ru/lectures/lecture-11-why-observability-belongs-inside-the-harness/code/runtime-logger.ts deleted file mode 100644 index 213dc198..00000000 --- a/courses/learn-harness-engineering/docs/ru/lectures/lecture-11-why-observability-belongs-inside-the-harness/code/runtime-logger.ts +++ /dev/null @@ -1,260 +0,0 @@ -/** - * runtime-logger.ts - * - * A structured logging module demo. Shows ad-hoc console.log output vs - * structured JSON log output when diagnosing a failure. Includes a seeded - * failure scenario and demonstrates how structured logs pinpoint the issue - * faster. - * - * Run: npx tsx docs/lectures/lecture-11-why-observability-belongs-inside-the-harness/code/runtime-logger.ts - */ - -// --------------------------------------------------------------------------- -// Types -// --------------------------------------------------------------------------- - -interface StructuredLogEntry { - timestamp: string; - level: "info" | "warn" | "error" | "debug"; - component: string; - action: string; - durationMs?: number; - input?: unknown; - output?: unknown; - error?: string; - correlationId: string; -} - -// --------------------------------------------------------------------------- -// Simulated pipeline with a seeded failure -// --------------------------------------------------------------------------- - -const CORRELATION_ID = "req-" + Math.random().toString(36).slice(2, 8); - -// Simulated stages of a document Q&A pipeline -interface PipelineStage { - component: string; - action: string; - durationMs: number; - success: boolean; - errorMessage?: string; - input?: unknown; - output?: unknown; -} - -function runPipeline(): PipelineStage[] { - return [ - { - component: "DocumentLoader", - action: "parse_upload", - durationMs: 45, - success: true, - input: { filename: "report.pdf", size: "2.3MB" }, - output: { chunks: 47 }, - }, - { - component: "ChunkIndexer", - action: "embed_and_store", - durationMs: 230, - success: true, - input: { chunks: 47 }, - output: { indexed: 47, embeddingDim: 1536 }, - }, - { - component: "QueryRouter", - action: "route_query", - durationMs: 12, - success: true, - input: { query: "What is the revenue target?" }, - output: { routedTo: "RetrievalEngine" }, - }, - { - // SEEDED FAILURE: Retrieval returns 0 results due to dimension mismatch - component: "RetrievalEngine", - action: "semantic_search", - durationMs: 180, - success: false, - errorMessage: "Vector dimension mismatch: query embedding dim=768, index embedding dim=1536", - input: { query: "What is the revenue target?", topK: 5 }, - output: { results: 0 }, - }, - { - component: "AnswerGenerator", - action: "generate_with_citations", - durationMs: 1500, - success: true, // Doesn't crash, but produces a bad answer - input: { context: [], query: "What is the revenue target?" }, - output: { answer: "I could not find relevant information.", citations: 0 }, - }, - ]; -} - -// --------------------------------------------------------------------------- -// Ad-hoc logging (console.log style) -// --------------------------------------------------------------------------- - -function printAdHocLog(stages: PipelineStage[]): void { - console.log("Starting document Q&A pipeline..."); - console.log("User uploaded report.pdf"); - - for (const stage of stages) { - if (stage.success) { - console.log(`${stage.component}: ${stage.action} done (${stage.durationMs}ms)`); - } else { - console.log(`${stage.component}: something went wrong`); - } - } - - console.log("Pipeline finished. Answer: I could not find relevant information."); -} - -// --------------------------------------------------------------------------- -// Structured logging (JSON) -// --------------------------------------------------------------------------- - -function printStructuredLog(stages: PipelineStage[]): StructuredLogEntry[] { - const entries: StructuredLogEntry[] = []; - - for (const stage of stages) { - entries.push({ - timestamp: new Date().toISOString(), - level: stage.success ? "info" : "error", - component: stage.component, - action: stage.action, - durationMs: stage.durationMs, - input: stage.input, - output: stage.output, - error: stage.errorMessage, - correlationId: CORRELATION_ID, - }); - } - - return entries; -} - -// --------------------------------------------------------------------------- -// Diagnose failure from structured logs -// --------------------------------------------------------------------------- - -function diagnoseFromStructured(entries: StructuredLogEntry[]): string[] { - const diagnosis: string[] = []; - - // Find errors - const errors = entries.filter((e) => e.level === "error"); - if (errors.length > 0) { - diagnosis.push("ERRORS FOUND:"); - for (const err of errors) { - diagnosis.push(` - ${err.component}.${err.action}: ${err.error}`); - } - } - - // Check for latency spikes - const slowStages = entries.filter((e) => (e.durationMs ?? 0) > 1000); - if (slowStages.length > 0) { - diagnosis.push("LATENCY SPIKES:"); - for (const s of slowStages) { - diagnosis.push(` - ${s.component}.${s.action}: ${s.durationMs}ms`); - } - } - - // Check for cascading failures - const emptyOutputs = entries.filter((e) => { - const out = e.output as Record | undefined; - return out && typeof out === "object" && "results" in out && (out.results as number) === 0; - }); - if (emptyOutputs.length > 0) { - diagnosis.push("EMPTY OUTPUTS (possible cascade):"); - for (const e of emptyOutputs) { - diagnosis.push(` - ${e.component}.${e.action}: returned 0 results`); - } - } - - return diagnosis; -} - -// --------------------------------------------------------------------------- -// Reporting -// --------------------------------------------------------------------------- - -function pad(s: string, len: number): string { - return s.length >= len ? s : s + " ".repeat(len - s.length); -} - -function run(): void { - const pipeline = runPipeline(); - - console.log("\n" + "=".repeat(90)); - console.log(" OBSERVABILITY DEMO -- Ad-hoc vs Structured Logging"); - console.log("=".repeat(90)); - - // --- Ad-hoc output --- - console.log("\n" + "-".repeat(90)); - console.log(" SCENARIO A: Ad-hoc console.log output"); - console.log("-".repeat(90) + "\n"); - printAdHocLog(pipeline); - console.log("\n Diagnosis from ad-hoc logs: ??? Hard to tell what went wrong."); - console.log(" The failure message is vague: 'something went wrong'."); - console.log(" No dimensions, no input/output data, no correlation ID."); - - // --- Structured output --- - console.log("\n" + "-".repeat(90)); - console.log(" SCENARIO B: Structured JSON log output"); - console.log("-".repeat(90) + "\n"); - - const structuredEntries = printStructuredLog(pipeline); - - const header = `| ${pad("Timestamp", 26)}| ${pad("Level", 6)}| ${pad("Component", 20)}| ${pad("Action", 25)}| ${pad("Duration", 9)}| Error?`; - const sep = `|${"-".repeat(28)}|${"-".repeat(8)}|${"-".repeat(22)}|${"-".repeat(27)}|${"-".repeat(11)}|${"-".repeat(30)}`; - console.log(header); - console.log(sep); - - for (const entry of structuredEntries) { - const hasError = entry.error ? entry.error.slice(0, 30) : ""; - const marker = entry.level === "error" ? ">>" : " "; - console.log( - `${marker}| ${pad(entry.timestamp, 26)}| ${pad(entry.level, 6)}| ${pad(entry.component, 20)}| ${pad(entry.action, 25)}| ${pad(String(entry.durationMs) + "ms", 9)}| ${hasError}` - ); - } - - // --- Diagnosis --- - console.log("\n" + "-".repeat(90)); - console.log(" AUTOMATED DIAGNOSIS FROM STRUCTURED LOGS"); - console.log("-".repeat(90) + "\n"); - - const errors = structuredEntries.filter((e) => e.level === "error"); - for (const err of errors) { - console.log(` ROOT CAUSE: ${err.component}.${err.action}`); - console.log(` Error: ${err.error}`); - console.log(` Input: ${JSON.stringify(err.input)}`); - console.log(` Output: ${JSON.stringify(err.output)}`); - console.log(` Correlation ID: ${err.correlationId}`); - } - - // Downstream impact - console.log("\n DOWNSTREAM IMPACT:"); - const answerGen = structuredEntries.find((e) => e.component === "AnswerGenerator"); - if (answerGen) { - const out = answerGen.output as Record; - console.log(` AnswerGenerator received empty context (${JSON.stringify(answerGen.input)})`); - console.log(` Produced answer: "${out.answer}" with ${out.citations} citations`); - } - - // Comparison summary - console.log("\n" + "=".repeat(90)); - console.log(" COMPARISON"); - console.log("=".repeat(90) + "\n"); - - const cHeader = `| ${pad("Metric", 35)}| ${pad("Ad-hoc Logs", 18)}| ${pad("Structured Logs", 18)}|`; - const cSep = `|${"-".repeat(37)}|${"-".repeat(20)}|${"-".repeat(20)}|`; - console.log(cHeader); - console.log(cSep); - console.log(`| ${pad("Root cause identifiable", 35)}| ${pad("No", 18)}| ${pad("Yes", 18)}|`); - console.log(`| ${pad("Input/output traceable", 35)}| ${pad("No", 18)}| ${pad("Yes", 18)}|`); - console.log(`| ${pad("Correlation across steps", 35)}| ${pad("No", 18)}| ${pad("Yes", 18)}|`); - console.log(`| ${pad("Machine-parseable", 35)}| ${pad("No", 18)}| ${pad("Yes", 18)}|`); - console.log(`| ${pad("Time to diagnose", 35)}| ${pad("Minutes (manual)", 18)}| ${pad("Seconds (auto)", 18)}|`); - - console.log("\n Structured logging transforms debugging from guesswork into a deterministic lookup.\n"); -} - -run(); diff --git a/courses/learn-harness-engineering/docs/ru/lectures/lecture-11-why-observability-belongs-inside-the-harness/code/sprint-contract.md b/courses/learn-harness-engineering/docs/ru/lectures/lecture-11-why-observability-belongs-inside-the-harness/code/sprint-contract.md deleted file mode 100644 index faa0042d..00000000 --- a/courses/learn-harness-engineering/docs/ru/lectures/lecture-11-why-observability-belongs-inside-the-harness/code/sprint-contract.md +++ /dev/null @@ -1,12 +0,0 @@ -# Пример Sprint Contract - -Цель спринта: - -- Добавить видимые цитаты в результаты заземлённого Q&A - -Done означает: - -- Пользователь задаёт вопрос -- Приложение возвращает ответ -- Показана хотя бы одна цитата -- Клик по цитате открывает место источника во вьюхе документа diff --git a/courses/learn-harness-engineering/docs/ru/lectures/lecture-11-why-observability-belongs-inside-the-harness/index.md b/courses/learn-harness-engineering/docs/ru/lectures/lecture-11-why-observability-belongs-inside-the-harness/index.md deleted file mode 100644 index da9668f5..00000000 --- a/courses/learn-harness-engineering/docs/ru/lectures/lecture-11-why-observability-belongs-inside-the-harness/index.md +++ /dev/null @@ -1,154 +0,0 @@ -[中文版本 →](../../../zh/lectures/lecture-11-why-observability-belongs-inside-the-harness/) - -> Примеры кода: [code/](https://github.com/walkinglabs/learn-harness-engineering/blob/main/docs/en/lectures/lecture-11-why-observability-belongs-inside-the-harness/code/) -> Практический проект: [Проект 06. Полноценный harness (Capstone)](./../../projects/project-06-runtime-observability-and-debugging/index.md) - -# Лекция 11. Сделайте рантайм агента наблюдаемым - -## Какую проблему решает эта лекция? - -Вы просите агента реализовать фичу. Он работает 20 минут, меняет кучу файлов и сообщает: «готово, но два теста падают». Спрашиваете почему — «не уверен, может быть, проблема с таймингом». Спрашиваете, какие критические пути он изменил — «дайте посмотреть в код…» - -Дело не в том, что агенту не хватает способностей. Дело в том, что ваш harness не обеспечивает достаточной наблюдаемости. **Без наблюдаемости агенты принимают решения в условиях неопределённости, оценки превращаются в субъективные суждения, а ретраи — в слепое блуждание.** И OpenAI, и Anthropic определяют надёжность как проблему доказательств: harness должен раскрывать поведение во время выполнения и сигналы оценки в форме, которая может направлять следующее решение. - -## Ключевые понятия - -- **Рантайм-наблюдаемость**: сигналы системного уровня — логи, трейсы, события процессов, проверки здоровья. Отвечает на вопрос «что сделала система». -- **Процессная наблюдаемость**: видимость артефактов решений harness — планов, рубрик оценки, критериев приёмки. Отвечает на вопрос «почему это изменение должно быть принято». -- **Трейс задачи**: полная запись пути решения от начала задачи до завершения, аналогично трассировке запросов в распределённых системах. Каждый шаг агента, с контекстом, фиксируется. -- **Sprint contract**: краткосрочное соглашение, согласованное до начала кодинга — определяет скоуп задачи, стандарты верификации и исключения. Основной инструмент процессной наблюдаемости. -- **Рубрика оценщика**: превращает оценку качества из субъективного суждения в основанную на доказательствах структурированную систему баллов. Заставляет разных оценщиков выдавать схожие результаты для одного и того же выхода. -- **Многослойная наблюдаемость**: наблюдаемость на системном и процессном уровнях, спроектированные одновременно и взаимно усиливающие друг друга. Рантайм-сигналы объясняют поведение; процессные артефакты объясняют намерение. - -## Многослойная наблюдаемость - -```mermaid -flowchart LR - Contract["Сначала фиксируем задачу
    что менять / что не менять / критерии прохождения"] --> Generator["Generator"] - Generator --> Signals["Собираем логи приложения, трейсы
    и health-чеки во время работы"] - Contract --> Review["Проверяем результат пункт за пунктом
    поведение / тесты / границы"] - Signals --> Review - Review --> Verdict["Указываем на провалившуюся проверку
    и где её чинить"] - Verdict --> Generator -``` - -## Почему так происходит - -### Реальная цена отсутствия наблюдаемости - -Когда у harness нет наблюдаемости, систематически появляются четыре типа проблем: - -**Невозможно отличить «корректно» от «выглядит корректно»**: функция выглядит идеально на код-ревью — синтаксис правильный, логика здравая. Но во время выполнения ошибка обработки граничного случая выдаёт неверные результаты на конкретных входах. Только рантайм-трейсы могут показать, что фактический путь выполнения отклонился от ожиданий. - -**Оценка превращается в мистицизм**: без рубрик оценки и критериев приёмки оценщики (человек или агент) опираются на неявные предположения. Один и тот же выход может получить дико разные оценки от разных проверяющих. Оценка качества становится невоспроизводимой. - -**Ретраи превращаются в слепые догадки**: когда агент не знает, почему что-то не сработало, направление ретрая случайно. Он может многократно пытаться в неверном направлении — чинить несвязанные пути кода, игнорируя реальную корневую причину сбоя. Каждый слепой ретрай стоит токенов и времени. - -**Информационный обрыв при передаче сессии**: когда незавершённая работа передаётся следующей сессии, отсутствие наблюдаемости означает, что новая сессия должна диагностировать состояние системы с нуля. Наблюдения Anthropic за долгоработающими агентами показывают, что эта избыточная диагностика может съедать 30–50% общего времени сессии. - -### Реальный сценарий с Claude Code - -Представьте harness с трёхролевым воркфлоу «планировщик-генератор-оценщик», выполняющий задачу «добавить тёмную тему в приложение». - -**Без наблюдаемости**: планировщик выдаёт расплывчатое описание. Генератор реализует тёмную тему на основе этой расплывчатости, но это не совпадает с неявными ожиданиями планировщика. Оценщик отвергает результат на основе своих собственных неявных стандартов, но не может точно сформулировать, что именно не так. Генератор слепо ретраит на основе размытых причин отказа. Цикл повторяется 3–4 раза, занимает около 45 минут и даёт едва приемлемый выход. - -**С полной наблюдаемостью**: планировщик выдаёт sprint contract — список компонентов для модификации, стандарты верификации для каждого и исключения (никаких стилей для печати). Генератор реализует согласно контракту. Рантайм-наблюдаемость фиксирует процесс загрузки и применения стилей для каждого компонента. Оценщик использует балльную рубрику для оценки по измерениям с конкретными ссылками на доказательства. Одна итерация даёт высококачественный результат, около 15 минут. - -Разница в эффективности в 3 раза. Единственное изменение — наблюдаемость. - -### Почему агенты не могут решить это сами - -Вы можете подумать: «Почему агент сам не печатает свои логи?» Проблемы такие: - -1. Агент не знает того, чего не знает — он не станет проактивно записывать сигналы, о необходимости которых он не подозревает. -2. Форматы логов несогласованы — разные сессии используют разные форматы логов, что делает систематический анализ невозможным. -3. Процессную наблюдаемость нельзя решить логами — sprint contracts и рубрики оценки являются структурированными артефактами, которым нужна поддержка на уровне harness. - -## Как делать правильно - -### 1. Встройте сбор рантайм-сигналов в harness - -Не полагайтесь на то, что агент сам напечатает логи. Harness должен автоматически собирать эти сигналы: - -- **Жизненный цикл приложения**: состояния фаз startup, ready, running, shutdown -- **Выполнение пути фичи**: записи выполнения критических путей, включая точки входа, контрольные точки и выходы -- **Поток данных**: записи о потоке данных между компонентами -- **Использование ресурсов**: аномальные паттерны использования ресурсов (например, постоянно растущая память) -- **Ошибки и исключения**: полный контекст ошибки, а не просто сообщение об ошибке - -### 2. Внедрите Sprint Contracts - -Перед стартом каждой задачи генератор и оценщик (которые могут быть разными вызовами одного и того же агента) согласуют контракт: - -```markdown -# Sprint Contract: Dark Mode Support - -## Scope -- Modify the theme toggle component -- Update global CSS variables -- Add dark mode tests - -## Verification Standards -- Visual regression tests pass for each component -- Main flow end-to-end tests pass -- No flash of unstyled content (FOUC) - -## Exclusions -- Not handling print styles -- Not handling third-party component dark mode -``` - -### 3. Установите рубрику оценщика - -Превратите «хорошо или нет» в количественную систему баллов: - -```markdown -# Scoring Rubric - -| Dimension | A | B | C | D | -|-----------|---|---|---|---| -| Code correctness | All tests pass | Main flow passes | Partial pass | Build fails | -| Architecture compliance | Fully compliant | Minor deviations | Obvious deviations | Serious violations | -| Test coverage | Main + edge cases | Main flow only | Only skeleton | No tests | -``` - -### 4. Стандартизируйте через OpenTelemetry - -Создавайте трейс для каждой сессии harness, span для каждой задачи и саб-span для каждого шага верификации. Используйте стандартные атрибуты для аннотации ключевой информации. Так данные наблюдаемости интегрируются со стандартными цепочками инструментов (Jaeger, Zipkin). - -## Реальный кейс - -Harness, использующий воркфлоу «планировщик-генератор-оценщик», выполняющий задачу «добавить поддержку тёмной темы»: - -**Версия без наблюдаемости**: 3–4 раунда слепых ретраев, 45 минут, едва приемлемый результат. Оценщик говорит «что-то не то», но не может сказать что именно. Генератор тратит много времени в неверных направлениях. - -**Полностью наблюдаемая версия**: -- Sprint contract уточняет скоуп, стандарты и исключения -- Рантайм-трейсы фиксируют процесс загрузки стилей каждого компонента -- Балльная рубрика обеспечивает структурированную оценку по измерениям -- Одна итерация даёт высококачественные результаты, 15 минут - -Прирост эффективности в 3 раза, более стабильное качество, воспроизводимые оценки. - -## Ключевые выводы - -- **Наблюдаемость — это архитектурное свойство harness** — не фича, добавляемая постфактум, а ключевая способность, которую нужно учитывать при проектировании. -- **Оба слоя наблюдаемости обязательны** — рантайм-сигналы объясняют «что произошло», процессные артефакты объясняют «почему это сделано именно так». -- **Sprint contracts заранее выравнивают понимание** — предотвращают ситуации, когда «генератор построил то, что оценщик сразу отвергает по предсказуемым причинам». -- **Балльные рубрики делают оценку воспроизводимой** — разные оценщики выдают схожие баллы для одного и того же выхода. -- **Отсутствие наблюдаемости тратит 30–50% времени сессии на избыточную диагностику.** - -## Дополнительное чтение - -- [Observability Engineering — Charity Majors](https://www.honeycomb.io/blog/observability-engineering-book) — теория и практика современного инжиниринга наблюдаемости -- [Dapper — Google (Sigelman et al.)](https://research.google/pubs/pub36356/) — прорывная практика крупномасштабной распределённой трассировки -- [Harness Design — Anthropic](https://www.anthropic.com/engineering/harness-design-long-running-apps) — введение sprint contracts и рубрик оценщика -- [Site Reliability Engineering — Google](https://sre.google/sre-book/table-of-contents/) — систематическое применение наблюдаемости в продакшен-системах - -## Упражнения - -1. **Анализ пробелов в наблюдаемости**: проведите аудит вашего текущего harness на предмет наблюдаемости системного и процессного уровней. Найдите состояния системы, которые невозможно различить по существующим сигналам, и предложите дополнения. - -2. **Практика Sprint Contract**: напишите sprint contract для реальной задачи. Заставьте агента выполнить её согласно контракту и сравните эффективность и качество с контрактом и без него. - -3. **Построение трейса задачи**: запишите каждый шаг операций агента в течение полной задачи кодинга. Аннотируйте семантическими конвенциями OpenTelemetry. Проанализируйте узкие места информации в трейсе — на каких шагах не хватает достаточных сигналов для решений. \ No newline at end of file diff --git a/courses/learn-harness-engineering/docs/ru/lectures/lecture-12-why-every-session-must-leave-a-clean-state/code/benchmark-comparison-template.md b/courses/learn-harness-engineering/docs/ru/lectures/lecture-12-why-every-session-must-leave-a-clean-state/code/benchmark-comparison-template.md deleted file mode 100644 index 01027c11..00000000 --- a/courses/learn-harness-engineering/docs/ru/lectures/lecture-12-why-every-session-must-leave-a-clean-state/code/benchmark-comparison-template.md +++ /dev/null @@ -1,18 +0,0 @@ -# Шаблон сравнения бенчмарков - -Harness A: - -- доля завершённых -- среднее число ретраев -- баги, пойманные до ревью человеком - -Harness B: - -- доля завершённых -- среднее число ретраев -- баги, пойманные до ревью человеком - -Интерпретация: - -- Какой harness изменил результат? -- Какой harness изменил стоимость получения результата? diff --git a/courses/learn-harness-engineering/docs/ru/lectures/lecture-12-why-every-session-must-leave-a-clean-state/code/benchmark-runner.ts b/courses/learn-harness-engineering/docs/ru/lectures/lecture-12-why-every-session-must-leave-a-clean-state/code/benchmark-runner.ts deleted file mode 100644 index 0388cad7..00000000 --- a/courses/learn-harness-engineering/docs/ru/lectures/lecture-12-why-every-session-must-leave-a-clean-state/code/benchmark-runner.ts +++ /dev/null @@ -1,216 +0,0 @@ -/** - * benchmark-runner.ts - * - * Reads a benchmark task definition (JSON array of tasks with pass criteria), - * "executes" each task, records timing and pass/fail, outputs a comparison - * report showing which tasks pass and which fail. - * - * Run: npx tsx docs/lectures/lecture-12-why-every-session-must-leave-a-clean-state/code/benchmark-runner.ts - */ - -// --------------------------------------------------------------------------- -// Types -// --------------------------------------------------------------------------- - -interface BenchmarkTask { - id: string; - name: string; - category: string; - passCriteria: string[]; - expectedDurationMs: number; - // Simulated actual results - actualDurationMs: number; - actualPass: boolean; - failureReason?: string; -} - -interface BenchmarkResult { - id: string; - name: string; - category: string; - criteriaTotal: number; - criteriaPassed: number; - pass: boolean; - expectedMs: number; - actualMs: number; - durationDelta: number; - failureReason?: string; -} - -// --------------------------------------------------------------------------- -// Benchmark task definitions -// --------------------------------------------------------------------------- - -const benchmarkTasks: BenchmarkTask[] = [ - { - id: "bench-001", - name: "Import markdown document", - category: "Document Pipeline", - passCriteria: ["File accepted", "Chunks created", "Metadata stored"], - expectedDurationMs: 100, - actualDurationMs: 95, - actualPass: true, - }, - { - id: "bench-002", - name: "Import PDF document", - category: "Document Pipeline", - passCriteria: ["File accepted", "Text extracted", "Chunks created", "Metadata stored"], - expectedDurationMs: 200, - actualDurationMs: 310, - actualPass: false, - failureReason: "PDF text extraction failed on page 3 -- encoding issue", - }, - { - id: "bench-003", - name: "Ask grounded question", - category: "Q&A Pipeline", - passCriteria: ["Query received", "Relevant chunks retrieved", "Answer generated", "Citations present"], - expectedDurationMs: 500, - actualDurationMs: 480, - actualPass: true, - }, - { - id: "bench-004", - name: "Ask question with no relevant docs", - category: "Q&A Pipeline", - passCriteria: ["Query received", "Graceful 'no results' message", "No hallucinated citations"], - expectedDurationMs: 400, - actualDurationMs: 420, - actualPass: false, - failureReason: "Model hallucinated a citation instead of saying 'no results'", - }, - { - id: "bench-005", - name: "Delete imported document", - category: "Document Pipeline", - passCriteria: ["Document removed from list", "Chunks removed from index", "No orphan data"], - expectedDurationMs: 150, - actualDurationMs: 145, - actualPass: true, - }, - { - id: "bench-006", - name: "Concurrent user access", - category: "Security", - passCriteria: ["Users isolated", "No cross-user data leak", "Performance within SLA"], - expectedDurationMs: 300, - actualDurationMs: 850, - actualPass: false, - failureReason: "Cross-user data leak detected + response time exceeded SLA (850ms > 500ms)", - }, - { - id: "bench-007", - name: "Session continuity after restart", - category: "Reliability", - passCriteria: ["State persisted", "Session recovers", "No data loss"], - expectedDurationMs: 200, - actualDurationMs: 180, - actualPass: true, - }, - { - id: "bench-008", - name: "API rate limiting", - category: "Security", - passCriteria: ["Rate limit enforced", "429 response after limit", "Legitimate traffic unaffected"], - expectedDurationMs: 100, - actualDurationMs: 100, - actualPass: true, - }, -]; - -// --------------------------------------------------------------------------- -// Execution simulation -// --------------------------------------------------------------------------- - -function executeBenchmark(tasks: BenchmarkTask[]): BenchmarkResult[] { - return tasks.map((task) => ({ - id: task.id, - name: task.name, - category: task.category, - criteriaTotal: task.passCriteria.length, - criteriaPassed: task.actualPass ? task.passCriteria.length : Math.floor(task.passCriteria.length * 0.5), - pass: task.actualPass, - expectedMs: task.expectedDurationMs, - actualMs: task.actualDurationMs, - durationDelta: task.actualDurationMs - task.expectedDurationMs, - failureReason: task.failureReason, - })); -} - -// --------------------------------------------------------------------------- -// Reporting -// --------------------------------------------------------------------------- - -function pad(s: string, len: number): string { - return s.length >= len ? s : s + " ".repeat(len - s.length); -} - -function run(): void { - console.log("\n" + "=".repeat(100)); - console.log(" BENCHMARK RUNNER -- Task Execution Report"); - console.log("=".repeat(100)); - - const results = executeBenchmark(benchmarkTasks); - - // Detailed results - const header = `| ${pad("ID", 10)}| ${pad("Task", 35)}| ${pad("Category", 18)}| ${pad("Pass?", 6)}| ${pad("Criteria", 10)}| ${pad("Expected", 10)}| ${pad("Actual", 10)}| ${pad("Delta", 8)}|`; - const sep = `|${"-".repeat(12)}|${"-".repeat(37)}|${"-".repeat(20)}|${"-".repeat(8)}|${"-".repeat(12)}|${"-".repeat(12)}|${"-".repeat(12)}|${"-".repeat(10)}|`; - console.log("\n" + header); - console.log(sep); - - for (const r of results) { - const passLabel = r.pass ? "PASS" : "FAIL"; - const criteriaLabel = `${r.criteriaPassed}/${r.criteriaTotal}`; - const deltaLabel = r.durationDelta >= 0 ? `+${r.durationDelta}ms` : `${r.durationDelta}ms`; - const marker = r.pass ? " " : ">>"; - - console.log( - `${marker}| ${pad(r.id, 10)}| ${pad(r.name, 35)}| ${pad(r.category, 18)}| ${pad(passLabel, 6)}| ${pad(criteriaLabel, 10)}| ${pad(r.expectedMs + "ms", 10)}| ${pad(r.actualMs + "ms", 10)}| ${pad(deltaLabel, 8)}|` - ); - } - - // Failure details - const failures = results.filter((r) => !r.pass); - if (failures.length > 0) { - console.log("\n" + "-".repeat(100)); - console.log(" FAILURE DETAILS"); - console.log("-".repeat(100)); - for (const f of failures) { - console.log(`\n [${f.id}] ${f.name}`); - console.log(` Category: ${f.category}`); - console.log(` Criteria: ${f.criteriaPassed}/${f.criteriaTotal} passed`); - console.log(` Reason: ${f.failureReason ?? "Unknown"}`); - console.log(` Timing: Expected ${f.expectedMs}ms, actual ${f.actualMs}ms (delta: ${f.durationDelta >= 0 ? "+" : ""}${f.durationDelta}ms)`); - } - } - - // Summary - console.log("\n" + "=".repeat(100)); - console.log(" SUMMARY BY CATEGORY"); - console.log("=".repeat(100) + "\n"); - - const categories = [...new Set(results.map((r) => r.category))]; - const catHeader = `| ${pad("Category", 20)}| ${pad("Total", 8)}| ${pad("Passed", 8)}| ${pad("Failed", 8)}| ${pad("Pass Rate", 12)}|`; - const catSep = `|${"-".repeat(22)}|${"-".repeat(10)}|${"-".repeat(10)}|${"-".repeat(10)}|${"-".repeat(14)}|`; - console.log(catHeader); - console.log(catSep); - - for (const cat of categories) { - const catResults = results.filter((r) => r.category === cat); - const catPassed = catResults.filter((r) => r.pass).length; - const catFailed = catResults.filter((r) => !r.pass).length; - const rate = Math.round((catPassed / catResults.length) * 100); - - console.log(`| ${pad(cat, 20)}| ${pad(String(catResults.length), 8)}| ${pad(String(catPassed), 8)}| ${pad(String(catFailed), 8)}| ${pad(rate + "%", 12)}|`); - } - - // Overall - const totalPassed = results.filter((r) => r.pass).length; - const totalFailed = results.filter((r) => !r.pass).length; - console.log("\n" + "-".repeat(100)); - console.log(` OVERALL: ${totalPassed}/${results.length} passed (${Math.round((totalPassed / results.length) * 100)}%), ${totalFailed} failures`); - console.log("=".repeat(100) + "\n"); -} - -run(); diff --git a/courses/learn-harness-engineering/docs/ru/lectures/lecture-12-why-every-session-must-leave-a-clean-state/code/cleanup-loop.md b/courses/learn-harness-engineering/docs/ru/lectures/lecture-12-why-every-session-must-leave-a-clean-state/code/cleanup-loop.md deleted file mode 100644 index 7917810c..00000000 --- a/courses/learn-harness-engineering/docs/ru/lectures/lecture-12-why-every-session-must-leave-a-clean-state/code/cleanup-loop.md +++ /dev/null @@ -1,9 +0,0 @@ -# Пример Cleanup Loop - -Регулярные задачи очистки: - -- сканирование устаревшей документации -- сканирование структурных нарушений -- обновление оценок качества -- открытие точечных PR с очисткой -- повторный прогон фиксированного среза бенчмарка после очистки diff --git a/courses/learn-harness-engineering/docs/ru/lectures/lecture-12-why-every-session-must-leave-a-clean-state/code/cleanup-scanner.ts b/courses/learn-harness-engineering/docs/ru/lectures/lecture-12-why-every-session-must-leave-a-clean-state/code/cleanup-scanner.ts deleted file mode 100644 index 6b35905c..00000000 --- a/courses/learn-harness-engineering/docs/ru/lectures/lecture-12-why-every-session-must-leave-a-clean-state/code/cleanup-scanner.ts +++ /dev/null @@ -1,336 +0,0 @@ -/** - * cleanup-scanner.ts - * - * Scans a project directory for stale artifacts, dead code, structural - * violations, and outputs a cleanup report. Helps enforce the "clean state - * at end of every session" principle. - * - * Usage: - * npx tsx docs/lectures/lecture-12.../code/cleanup-scanner.ts [path] - * (defaults to current working directory) - * - * Run: npx tsx docs/lectures/lecture-12-why-every-session-must-leave-a-clean-state/code/cleanup-scanner.ts - */ - -import * as fs from "node:fs"; -import * as path from "node:path"; - -// --------------------------------------------------------------------------- -// Types -// --------------------------------------------------------------------------- - -interface ScanResult { - category: string; - check: string; - severity: "critical" | "warning" | "info"; - found: string[]; - description: string; -} - -// --------------------------------------------------------------------------- -// Scanner checks -// --------------------------------------------------------------------------- - -interface ScannerCheck { - category: string; - name: string; - severity: "critical" | "warning" | "info"; - description: string; - scan: (dir: string) => string[]; -} - -function createChecks(): ScannerCheck[] { - return [ - { - category: "Stale Artifacts", - name: "Temporary files (*.tmp, *.bak, *.swp)", - severity: "warning", - description: "Leftover temp files from editing or processing", - scan: (dir) => findFiles(dir, [".tmp", ".bak", ".swp", "~"]), - }, - { - category: "Stale Artifacts", - name: "Debug/log files in source", - severity: "warning", - description: "Log files that should not be in the source tree", - scan: (dir) => { - const found: string[] = []; - const logPatterns = [".log"]; - for (const p of findFiles(dir, logPatterns)) { - // Only flag log files in source directories - if (p.includes("/src/") || p.includes("/lib/") || p.includes("/app/")) { - found.push(p); - } - } - return found; - }, - }, - { - category: "Dead Code", - name: "Unused imports (placeholder detection)", - severity: "info", - description: "Files that may contain unused code (heuristic scan)", - scan: (dir) => { - const found: string[] = []; - const srcDirs = ["src", "lib", "app"]; - for (const srcDir of srcDirs) { - const srcPath = path.join(dir, srcDir); - if (fs.existsSync(srcPath)) { - scanForPatterns(srcPath, ["TODO:", "FIXME:", "HACK:", "XXX:"], found, dir); - } - } - return found; - }, - }, - { - category: "Structural Violations", - name: "Missing .gitignore", - severity: "warning", - description: "No .gitignore file found -- risk of committing build artifacts", - scan: (dir) => fs.existsSync(path.join(dir, ".gitignore")) ? [] : [".gitignore (MISSING)"], - }, - { - category: "Structural Violations", - name: "Build artifacts in source", - severity: "critical", - description: "Compiled output (dist/, build/) mixed with source", - scan: (dir) => { - const found: string[] = []; - // Check if dist/ or build/ exists inside src/ - const dirs = ["src/dist", "src/build", "lib/dist", "lib/build"]; - for (const d of dirs) { - if (fs.existsSync(path.join(dir, d))) { - found.push(d + "/"); - } - } - return found; - }, - }, - { - category: "Structural Violations", - name: "node_modules in source tree", - severity: "critical", - description: "node_modules directory found outside root (nested dependency)", - scan: (dir) => { - const found: string[] = []; - const srcDirs = ["src", "lib", "app", "test", "tests"]; - for (const srcDir of srcDirs) { - const nmPath = path.join(dir, srcDir, "node_modules"); - if (fs.existsSync(nmPath)) { - found.push(srcDir + "/node_modules/"); - } - } - return found; - }, - }, - { - category: "Session Cleanliness", - name: "Uncommitted changes indicator", - severity: "info", - description: "Checks for common artifacts of incomplete sessions", - scan: (dir) => { - const found: string[] = []; - const indicators = [ - "WIP.md", "IN_PROGRESS.md", "scratch.ts", "scratch.js", - "debug.ts", "test-manual.ts", "temp.ts", - ]; - for (const f of indicators) { - if (fs.existsSync(path.join(dir, f))) { - found.push(f); - } - } - return found; - }, - }, - { - category: "Session Cleanliness", - name: "Empty directories", - severity: "info", - description: "Empty directories that may be leftover from incomplete work", - scan: (dir) => { - const found: string[] = []; - const commonDirs = ["src", "lib", "app", "test", "tests", "docs"]; - for (const d of commonDirs) { - const dp = path.join(dir, d); - if (fs.existsSync(dp) && fs.statSync(dp).isDirectory()) { - try { - const contents = fs.readdirSync(dp); - if (contents.length === 0) { - found.push(d + "/"); - } - } catch { - // Skip directories we can't read - } - } - } - return found; - }, - }, - { - category: "Configuration", - name: "Environment files in source", - severity: "critical", - description: ".env files that may contain secrets", - scan: (dir) => { - const found: string[] = []; - const envFiles = [".env", ".env.local", ".env.production", ".env.staging"]; - for (const f of envFiles) { - if (fs.existsSync(path.join(dir, f))) { - found.push(f); - } - } - return found; - }, - }, - ]; -} - -// --------------------------------------------------------------------------- -// Helper functions -// --------------------------------------------------------------------------- - -function findFiles(dir: string, extensions: string[]): string[] { - const found: string[] = []; - - function walk(current: string, depth: number): void { - if (depth > 4) return; // Limit recursion depth - try { - const entries = fs.readdirSync(current, { withFileTypes: true }); - for (const entry of entries) { - // Skip common non-source directories - if (entry.isDirectory() && ["node_modules", ".git", "dist", "build", ".next", "coverage"].includes(entry.name)) { - continue; - } - - const fullPath = path.join(current, entry.name); - const relativePath = path.relative(dir, fullPath); - - if (entry.isDirectory()) { - walk(fullPath, depth + 1); - } else if (entry.isFile()) { - if (extensions.some((ext) => entry.name.endsWith(ext))) { - found.push(relativePath); - } - } - } - } catch { - // Skip directories we can't read - } - } - - walk(dir, 0); - return found; -} - -function scanForPatterns(dir: string, patterns: string[], results: string[], baseDir: string): void { - try { - const entries = fs.readdirSync(dir, { withFileTypes: true }); - for (const entry of entries) { - if (entry.isDirectory()) { - if (!["node_modules", ".git", "dist", "build"].includes(entry.name)) { - scanForPatterns(path.join(dir, entry.name), patterns, results, baseDir); - } - continue; - } - - const fullPath = path.join(dir, entry.name); - if (!entry.name.endsWith(".ts") && !entry.name.endsWith(".tsx") && !entry.name.endsWith(".js")) { - continue; - } - - try { - const content = fs.readFileSync(fullPath, "utf-8"); - const lines = content.split("\n"); - for (let i = 0; i < Math.min(lines.length, 50); i++) { - for (const pattern of patterns) { - if (lines[i].includes(pattern)) { - const relative = path.relative(baseDir, fullPath); - results.push(`${relative}:${i + 1} contains ${pattern}`); - } - } - } - } catch { - // Skip files we can't read - } - } - } catch { - // Skip directories we can't read - } -} - -// --------------------------------------------------------------------------- -// Reporting -// --------------------------------------------------------------------------- - -function pad(s: string, len: number): string { - return s.length >= len ? s : s + " ".repeat(len - s.length); -} - -function run(): void { - const targetDir = process.argv[2] ? path.resolve(process.argv[2]) : process.cwd(); - - console.log("\n" + "=".repeat(90)); - console.log(" CLEANUP SCANNER -- Stale Artifact & Violation Report"); - console.log("=".repeat(90)); - console.log(` Scanning: ${targetDir}\n`); - - const checks = createChecks(); - const results: ScanResult[] = []; - - for (const check of checks) { - const found = check.scan(targetDir); - results.push({ - category: check.category, - check: check.name, - severity: check.severity, - found, - description: check.description, - }); - } - - // Report - const header = `| ${pad("Category", 20)}| ${pad("Check", 40)}| ${pad("Severity", 10)}| ${pad("Count", 6)}| Details`; - const sep = `|${"-".repeat(22)}|${"-".repeat(42)}|${"-".repeat(12)}|${"-".repeat(8)}|${"-".repeat(30)}`; - console.log(header); - console.log(sep); - - for (const r of results) { - const count = r.found.length; - const detail = count > 0 ? r.found.slice(0, 2).join(", ") : "(clean)"; - const marker = count > 0 && r.severity === "critical" ? ">>" : count > 0 ? " * " : " "; - - console.log(`${marker}| ${pad(r.category, 20)}| ${pad(r.check, 40)}| ${pad(r.severity, 10)}| ${pad(String(count), 6)}| ${detail}`); - } - - // Summary - const criticals = results.filter((r) => r.severity === "critical" && r.found.length > 0); - const warnings = results.filter((r) => r.severity === "warning" && r.found.length > 0); - const infos = results.filter((r) => r.severity === "info" && r.found.length > 0); - const clean = results.filter((r) => r.found.length === 0); - - console.log("\n" + "=".repeat(90)); - console.log(" CLEANUP SUMMARY"); - console.log("=".repeat(90) + "\n"); - - console.log(` Critical issues: ${criticals.length}`); - console.log(` Warnings: ${warnings.length}`); - console.log(` Info items: ${infos.length}`); - console.log(` Clean checks: ${clean.length}/${results.length}`); - - if (criticals.length > 0) { - console.log("\n ACTION REQUIRED (Critical):"); - for (const c of criticals) { - console.log(` - ${c.check}: ${c.found.length} item(s) found`); - console.log(` ${c.description}`); - } - } - - const totalIssues = results.reduce((s, r) => s + r.found.length, 0); - if (totalIssues === 0) { - console.log("\n Project is in a clean state. No issues found.\n"); - } else { - console.log(`\n Total issues found: ${totalIssues}. Run this scanner at the end of every session to maintain clean state.\n`); - } -} - -run(); diff --git a/courses/learn-harness-engineering/docs/ru/lectures/lecture-12-why-every-session-must-leave-a-clean-state/code/index.md b/courses/learn-harness-engineering/docs/ru/lectures/lecture-12-why-every-session-must-leave-a-clean-state/code/index.md deleted file mode 100644 index cf7c3dd3..00000000 --- a/courses/learn-harness-engineering/docs/ru/lectures/lecture-12-why-every-session-must-leave-a-clean-state/code/index.md +++ /dev/null @@ -1,8 +0,0 @@ -# Код к лекции 12 - -Используйте эту папку для примеров: - -- срезы бенчмарков -- задачи очистки -- примеры снижения энтропии -- повторяемые запуски harness diff --git a/courses/learn-harness-engineering/docs/ru/lectures/lecture-12-why-every-session-must-leave-a-clean-state/index.md b/courses/learn-harness-engineering/docs/ru/lectures/lecture-12-why-every-session-must-leave-a-clean-state/index.md deleted file mode 100644 index 58390cbb..00000000 --- a/courses/learn-harness-engineering/docs/ru/lectures/lecture-12-why-every-session-must-leave-a-clean-state/index.md +++ /dev/null @@ -1,186 +0,0 @@ -[中文版本 →](../../../zh/lectures/lecture-12-why-every-session-must-leave-a-clean-state/) - -> Примеры кода: [code/](https://github.com/walkinglabs/learn-harness-engineering/blob/main/docs/en/lectures/lecture-12-why-every-session-must-leave-a-clean-state/code/) -> Практический проект: [Проект 06. Полноценный harness (Capstone)](./../../projects/project-06-runtime-observability-and-debugging/index.md) - -# Лекция 12. Чистая передача в конце каждой сессии - -## Какую проблему решает эта лекция? - -Ваш агент работает весь день, меняет 20 файлов, коммитит код, сессия заканчивается. Следующая сессия агента стартует и сразу обнаруживает: сборка сломана, тесты красные, повсюду временные дебаг-файлы, список фич не обновлён, прогресс совершенно неясен. Новая сессия тратит первые 30 минут просто на выяснение «что вообще делала прошлая сессия». - -И OpenAI, и Anthropic заявляют чётко: **долгосрочная надёжность зависит от операционной дисциплины, а не только от успеха одного запуска.** Качество состояния на выходе из сессии напрямую определяет эффективность следующей сессии. Думайте об этом как о лучших практиках Git — каждый коммит должен быть атомарным, компилируемым изменением, а не кучей полудоделанного кода. - -## Ключевые понятия - -- **Чистое состояние**: система удовлетворяет пяти условиям в конце сессии — сборка проходит, тесты проходят, прогресс зафиксирован, нет устаревших артефактов, доступен путь запуска. Если что-то одно отсутствует — сессия не «завершена». -- **Целостность сессии**: аналогично транзакциям БД — либо полностью коммитим и оставляем чистое состояние, либо откатываемся к последнему согласованному состоянию. Никакой золотой середины. -- **Документ качества**: активный артефакт, который непрерывно фиксирует рейтинги качества по каждому модулю. Не разовая оценка, а трекер, показывающий, становится ли кодовая база сильнее или слабее со временем. -- **Cleanup loop**: регулярная сессия обслуживания, направленная на систематическое снижение энтропии в кодовой базе. Не аварийный фикс, а рутинные операции. -- **Упрощение harness**: по мере роста возможностей моделей периодически удаляйте компоненты harness, которые больше не нужны. Ограничение, существенное сегодня, может оказаться ненужным оверхедом через три месяца. -- **Идемпотентная очистка**: операции очистки дают тот же результат независимо от того, сколько раз они выполняются. Гарантирует безопасность очистки даже в сценариях «сбой-ретрай». - -## Пять измерений чистого состояния - -```mermaid -flowchart LR - Work["Работа над фичей завершена"] --> Build{"Сборка проходит?"} - Build -->|да| Test{"Тесты проходят?"} - Build -->|нет| Fix["Починить перед выходом"] - Test -->|да| Record["Обновить список фич + прогресс"] - Test -->|нет| Fix - Record --> Cleanup["Удалить временные артефакты / дебаг-код"] - Cleanup --> Startup{"Стандартный путь запуска работает?"} - Startup -->|да| Clean["Чистая передача"] - Startup -->|нет| Fix - Fix --> Build -``` - -```mermaid -flowchart LR - Dirty["Сессия заканчивается с
    красными тестами / временными файлами / без обновления прогресса"] --> Diagnose["Следующей сессии сначала придётся
    разбираться, что произошло"] - Diagnose --> Fragile["Новая работа стартует на грязном репозитории"] - Fragile --> More["Больше дебаг-файлов, больше сломанных проверок,
    больше неясного прогресса"] - More --> Dirty - - Clean["Сессия заканчивается с
    зелёными тестами / обновлённым прогрессом / удалёнными временными файлами"] --> Fast["Следующая сессия может сразу начинать кодить"] - Fast --> Stable["Не нужно сначала спасать репозиторий"] - Stable --> Clean -``` - -## Почему так происходит - -### Рост энтропии — это состояние по умолчанию - -Законы эволюции ПО Лемана говорят нам: системы, подвергающиеся непрерывным изменениям, неизбежно усложняются, если ими активно не управлять. Это особенно верно для AI-агентов кодинга — каждая сессия вносит изменения, и без очистки на выходе технический долг накапливается экспоненциально. - -Реальные данные красноречивы. Проект, разрабатывавшийся с агентами 12 недель без стратегии очистки: - -- Неделя 1: успех сборки 100%, успех тестов 100%, запуск новой сессии 5 мин -- Неделя 4: сборка 95%, тесты 92%, запуск 15 мин -- Неделя 8: сборка 82%, тесты 78%, запуск 35 мин -- Неделя 12: сборка 68%, тесты 61%, запуск 60+ мин - -Тот же проект со стратегией очистки: - -- Неделя 1: 100%, 100%, 5 мин -- Неделя 12: 97%, 95%, 9 мин - -После 12 недель: успех сборки отличается на 29 процентных пунктов, время запуска новой сессии — на 85%. Это не теория — это наблюдаемая разница. - -### Пять измерений чистого состояния - -Чистое состояние — это не просто «код собирается». Это пять измерений, оцениваемых вместе: - -**Измерение сборки**: собирается ли код без ошибок? Это самое базовое — следующей сессии не должно приходиться сначала чинить ошибки сборки. - -**Измерение тестов**: проходят ли все тесты? Включая тесты, существовавшие до сессии — сессия отвечает за то, чтобы не сломать существующую функциональность. И это должно быть проверено в CI, а не только «работает у меня на машине». - -**Измерение прогресса**: зафиксирован ли текущий прогресс в машиночитаемом артефакте? Завершённые подзадачи с критериями их прохождения, начатые, но незавершённые подзадачи с текущим состоянием, ещё не начатые подзадачи. Хорошие записи прогресса сокращают 60–80% времени диагностики при старте сессии. - -**Измерение артефактов**: есть ли устаревшие или неоднозначные временные артефакты? Дебаг-логи, временные файлы, закомментированный код, TODO-маркеры — всё это увеличивает когнитивную нагрузку для следующей сессии. - -**Измерение запуска**: доступен ли стандартный путь запуска? Может ли следующая сессия начать работу без ручного вмешательства? Инициализация окружения, загрузка кодовой базы, получение контекста, выбор задачи — эти пути не должны быть сломаны. - -### «Уберём потом» означает «никогда не убираем» - -Самая распространённая ментальная ловушка — «нет времени убирать в этой сессии, сделаю в следующий раз». Но следующая сессия агента не знает, что вы оставили — она видит беспорядок кода и неопределённое состояние. Она потратит много времени, выясняя «какие части этого кода намеренные, а какие временные». - -Хуже того, у каждой сессии свои цели. Новая сессия здесь, чтобы делать новую работу, а не убирать беспорядок предыдущей сессии. Она проигнорирует хаос и начнёт новую работу поверх него, добавляя ещё больше хаоса поверх хаоса. Это положительная обратная связь энтропии. - -## Как делать правильно - -### 1. Чистое состояние как требование к завершению - -Явно определите в harness: **завершение сессии = задача проходит верификацию И проверка чистого состояния пройдена.** Если что-то одно не выполнено — сессия не завершена. Запишите в CLAUDE.md: - -``` -## Session Exit Checklist -- [ ] Build passes (npm run build) -- [ ] All tests pass (npm test) -- [ ] Feature list updated -- [ ] No debug code remaining (console.log, debugger, TODO) -- [ ] Standard startup path available (npm run dev) -``` - -### 2. Двухрежимная стратегия очистки - -Совместите два режима очистки: - -**Немедленная очистка (в конце каждой сессии)**: убрать временные артефакты, созданные за сессию, обновить состояние списка фич, убедиться, что сборка и тесты проходят. Это очистка по «подсчёту ссылок». - -**Периодическая очистка (еженедельно)**: полное сканирование системы — обработать накопившиеся структурные проблемы, обновить документы качества, прогнать бенчмарк-тесты для выявления дрейфа. Это «трассирующая» очистка. - -### 3. Поддерживайте документ качества - -Документ качества — это активный артефакт, непрерывно оценивающий каждый модуль: - -```markdown -# Quality Document - -## User Authentication Module (Quality: A) -- Verification passing: Yes -- Agent understandable: Yes -- Test stability: Stable -- Architecture boundaries: Compliant -- Code conventions: Followed - -## Payment Module (Quality: C) -- Verification passing: Partial (payment callback untested) -- Agent understandable: Difficult (logic spread across 3 files) -- Test stability: Unstable (2 flaky tests) -- Architecture boundaries: Violations present -- Code conventions: Partially followed -``` - -Новые сессии читают этот документ и сразу понимают, где приоритеты. Сначала чините модуль с самым низким баллом. - -### 4. Периодически упрощайте harness - -Важный инсайт от Anthropic: **каждый компонент harness существует, потому что модель не может надёжно делать что-то самостоятельно. Но по мере улучшения моделей эти предположения устаревают.** Ограничение, существенное три месяца назад, может сегодня оказаться ненужным оверхедом. - -Рекомендуемая практика: каждый месяц выбирайте один компонент harness, временно отключайте его и прогоняйте бенчмарк-задачи. Если результаты не деградируют — удалите его навсегда. Если деградируют — восстановите или замените более лёгкой альтернативой. - -### 5. Операции очистки должны быть идемпотентными - -Скрипты очистки должны быть безопасны для повторного выполнения: - -```bash -# Idempotent cleanup operations -rm -f /tmp/debug-*.log # -f ensures no error when files don't exist -git checkout -- .env.local # Restore to known state -npm run test # Verify cleanup didn't break anything -``` - -## Реальный кейс - -Electron-приложение, разрабатываемое с агентами 12 недель, сравнение двух подходов: - -**Без стратегии очистки** (контрольная группа): неделя 12, успех сборки 68%, успех тестов 61%, запуск новой сессии 60+ мин, устаревших артефактов 103. - -**Со стратегией очистки** (экспериментальная группа): полная проверка чистого состояния в конце каждой сессии + еженедельный cleanup loop. Неделя 12, успех сборки 97%, успех тестов 95%, запуск новой сессии 9 мин, устаревших артефактов 11. - -К 12-й неделе у экспериментальной группы успех сборки выше на 29 процентных пунктов, успех тестов — на 34 пункта, время запуска новой сессии ниже на 85%. - -## Ключевые выводы - -- **Чистое состояние — необходимое условие завершения сессии** — не опциональная уборка, а часть «definition of done». -- **Все пять измерений обязательны** — сборка, тесты, прогресс, артефакты, запуск — каждое должно быть явно проверено. -- **Документы качества делают здоровье кодовой базы отслеживаемым** — починить можно только то, о чьей деградации вы знаете. -- **Периодически упрощайте harness** — по мере роста возможностей моделей удаляйте ограничения, которые больше не нужны. -- **«Уберём потом» равно «никогда не уберём»** — рост энтропии — это состояние по умолчанию; противодействует ей только активная очистка. - -## Дополнительное чтение - -- [Clean Code — Robert C. Martin](https://www.goodreads.com/book/show/3735293-clean-code) — систематические принципы чистоты кода -- [Harness Engineering — OpenAI](https://openai.com/index/harness-engineering/) — воспроизводимость как ключевое требование к проектированию harness -- [Effective Harnesses — Anthropic](https://www.anthropic.com/engineering/effective-harnesses-for-long-running-agents) — критическая роль чистых выходов из сессии для долгосрочной надёжности -- [Programs, Life Cycles, and Laws of Software Evolution — Lehman](https://ieeexplore.ieee.org/document/1702314) — законы эволюции ПО, доказывающие, что сложность системы неизбежно растёт без активного обслуживания - -## Упражнения - -1. **Чек-лист чистого состояния**: спроектируйте чек-лист выхода из сессии для вашей кодовой базы, охватывающий все пять измерений. Применяйте его на 5 последовательных сессиях и фиксируйте нарушения по каждому измерению. - -2. **Сравнение бенчмарков**: используйте фиксированный набор задач с двумя вариантами harness (с требованиями чистого состояния и без). Сравните долю завершённых, число ретраев и долю просочившихся дефектов. - -3. **Практика упрощения harness**: выберите один компонент harness, временно отключите его и прогоните бенчмарк-задачи. Сравните результаты с ним и без него. Решите, оставить, удалить или заменить. \ No newline at end of file diff --git a/courses/learn-harness-engineering/docs/ru/projects/index.md b/courses/learn-harness-engineering/docs/ru/projects/index.md deleted file mode 100644 index 15c48fab..00000000 --- a/courses/learn-harness-engineering/docs/ru/projects/index.md +++ /dev/null @@ -1,23 +0,0 @@ -# Добро пожаловать в Проекты - -Это практическая часть курса Learn Harness Engineering. Чтения лекций недостаточно — вам нужно собрать окружения самостоятельно и понаблюдать, как Codex, Claude Code или другие AI-агенты ведут себя при разных правилах. - -## Обзор проектов - -Курс включает 6 прогрессивных практических проектов, которые научат вас строить надёжное агентское рабочее окружение с нуля: - -1. **Только промпт vs. правила в первую очередь**: сравните, как агент справляется с одним лишь промптом против базового harness. -2. **Воркспейс, читаемый агентом**: научитесь структурировать репозиторий так, чтобы он был дружелюбен к ИИ, и наладьте механизмы передачи работы. -3. **Непрерывность между сессиями**: спроектируйте файлы состояния и инициализационные скрипты, чтобы агент мог бесшовно продолжать работу между сессиями. -4. **Runtime-обратная связь и контроль скоупа**: внедрите инструменты, позволяющие агенту тестировать свой код и исправлять ошибки во время выполнения. -5. **Самопроверка и разделение ролей**: постройте независимый механизм ревью, чтобы предотвратить галлюцинации и преждевременные объявления о победе. -6. **Полный harness (capstone)**: соберите финальное, наблюдаемое, end-to-end агентское рабочее окружение. - -## Как двигаться - -В каждой папке проекта обычно есть: -- `starter/`: ваш стартовый воркспейс. -- `solution/`: эталонная реализация (если застряли). -- Инструкции к задаче с описанием контекста и конкретных целей. - -Используйте предпочитаемый AI Coding Agent (например, Claude Code, Cursor, Trae) для выполнения задач внутри директории `starter/`. diff --git a/courses/learn-harness-engineering/docs/ru/projects/project-01-baseline-vs-minimal-harness/index.md b/courses/learn-harness-engineering/docs/ru/projects/project-01-baseline-vs-minimal-harness/index.md deleted file mode 100644 index 69c3a456..00000000 --- a/courses/learn-harness-engineering/docs/ru/projects/project-01-baseline-vs-minimal-harness/index.md +++ /dev/null @@ -1,25 +0,0 @@ -[中文版本 →](../../../zh/projects/project-01-baseline-vs-minimal-harness/) - -> Связанные лекции: [Лекция 01. Сильные модели не означают надёжного выполнения](./../../lectures/lecture-01-why-capable-agents-still-fail/index.md) · [Лекция 02. Что на самом деле такое harness](./../../lectures/lecture-02-what-a-harness-actually-is/index.md) -> Файлы шаблонов: [templates/](https://github.com/walkinglabs/learn-harness-engineering/blob/main/docs/en/resources/templates/) - -# Проект 01. Только промпт vs. правила в первую очередь: насколько велика разница - -## Что вы делаете - -Соберите минимальный каркас Electron-приложения базы знаний — окно со списком документов слева, панелью Q&A справа и локальной директорией данных. Сама задача несложная. Сложно — заставить агента её выполнить. - -Вы запускаете её дважды. Первый раз: только промпт, без подготовки. Второй раз: `AGENTS.md`, `init.sh`, `feature_list.json` уже размещены в репозитории. Затем сравниваете. - -Суть этого проекта не в написании кода — а в том, чтобы понять, насколько велика разница между «потратить 15 минут на правила» и «просто отпустить агента». - -## Инструменты - -- Claude Code или Codex (выберите один, используйте для обоих запусков) -- Git (управление ветками и сравнение) -- Node.js + Electron (стек проекта) -- Таймер (фиксируйте длительность каждого запуска) - -## Механизм harness - -Минимальный harness: `AGENTS.md` + `init.sh` + `feature_list.json` diff --git a/courses/learn-harness-engineering/docs/ru/projects/project-02-agent-readable-workspace/index.md b/courses/learn-harness-engineering/docs/ru/projects/project-02-agent-readable-workspace/index.md deleted file mode 100644 index d86d5e14..00000000 --- a/courses/learn-harness-engineering/docs/ru/projects/project-02-agent-readable-workspace/index.md +++ /dev/null @@ -1,22 +0,0 @@ -[中文版本 →](../../../zh/projects/project-02-agent-readable-workspace/) - -> Связанные лекции: [Лекция 03. Сделайте репозиторий единственным источником истины](./../../lectures/lecture-03-why-the-repository-must-become-the-system-of-record/index.md) · [Лекция 04. Разделите инструкции по файлам](./../../lectures/lecture-04-why-one-giant-instruction-file-fails/index.md) -> Файлы шаблонов: [templates/](https://github.com/walkinglabs/learn-harness-engineering/blob/main/docs/en/resources/templates/) - -# Проект 02. Сделайте проект читаемым и продолжите с того места, где остановились - -## Что вы делаете - -Добавьте «читаемость» в репозиторий, чтобы новый агент мог быстро понять структуру проекта, узнать текущий прогресс и продолжить работу. Конкретно: реализуйте импорт документов, просмотр деталей документа и локальное хранение, выполнив всё за две сессии. - -Вы запускаете задачу дважды: первый раз — без какой-либо помощи, второй — с заранее размещёнными в репозитории `ARCHITECTURE.md`, `PRODUCT.md` и `session-handoff.md`. - -## Инструменты - -- Claude Code или Codex -- Git -- Node.js + Electron - -## Механизм harness - -Воркспейс, читаемый агентом + персистентные файлы состояния diff --git a/courses/learn-harness-engineering/docs/ru/projects/project-03-multi-session-continuity/index.md b/courses/learn-harness-engineering/docs/ru/projects/project-03-multi-session-continuity/index.md deleted file mode 100644 index 7ba3404f..00000000 --- a/courses/learn-harness-engineering/docs/ru/projects/project-03-multi-session-continuity/index.md +++ /dev/null @@ -1,22 +0,0 @@ -[中文版本 →](../../../zh/projects/project-03-multi-session-continuity/) - -> Связанные лекции: [Лекция 05. Сохраняйте контекст живым между сессиями](./../../lectures/lecture-05-why-long-running-tasks-lose-continuity/index.md) · [Лекция 06. Инициализируйте перед каждой сессией агента](./../../lectures/lecture-06-why-initialization-needs-its-own-phase/index.md) -> Файлы шаблонов: [templates/](https://github.com/walkinglabs/learn-harness-engineering/blob/main/docs/en/resources/templates/) - -# Проект 03. Заставьте агента продолжать работу через перезапуски сессий - -## Что вы делаете - -Добавьте агенту контроль скоупа и проверочные шлюзы. Реализуйте чанкование документов, извлечение метаданных, отображение прогресса индексации и Q&A-флоу с цитатами. Используйте `feature_list.json` для отслеживания статуса фич — по одной фиче за раз, никакого «pass» без доказательств верификации. - -Вы запускаете задачу дважды: первый раз — без ограничений, второй — со строгим контролем. - -## Инструменты - -- Claude Code или Codex -- Git -- Node.js + Electron - -## Механизм harness - -Лог прогресса + handoff между сессиями + непрерывность между сессиями diff --git a/courses/learn-harness-engineering/docs/ru/projects/project-04-incremental-indexing/index.md b/courses/learn-harness-engineering/docs/ru/projects/project-04-incremental-indexing/index.md deleted file mode 100644 index 5a32ea9b..00000000 --- a/courses/learn-harness-engineering/docs/ru/projects/project-04-incremental-indexing/index.md +++ /dev/null @@ -1,22 +0,0 @@ -[中文版本 →](../../../zh/projects/project-04-incremental-indexing/) - -> Связанные лекции: [Лекция 07. Чётко обозначьте границы задач для агентов](./../../lectures/lecture-07-why-agents-overreach-and-under-finish/index.md) · [Лекция 08. Используйте списки фич, чтобы ограничивать действия агента](./../../lectures/lecture-08-why-feature-lists-are-harness-primitives/index.md) -> Файлы шаблонов: [templates/](https://github.com/walkinglabs/learn-harness-engineering/blob/main/docs/en/resources/templates/) - -# Проект 04. Используйте runtime-обратную связь, чтобы корректировать поведение агента - -## Что вы делаете - -Добавьте runtime-наблюдаемость (логи запуска, логи импорта/индексации, состояния ошибок) и архитектурные ограничения, чтобы предотвратить нарушения границ слоёв. Подкиньте агенту runtime-баг для исправления. - -Вы запускаете задачу дважды: первый раз — без логов и ограничений, второй — с правильными инструментами и правилами. - -## Инструменты - -- Claude Code или Codex -- Git -- Node.js + Electron - -## Механизм harness - -Runtime-обратная связь + контроль скоупа + инкрементальная индексация diff --git a/courses/learn-harness-engineering/docs/ru/projects/project-05-grounded-qa-verification/index.md b/courses/learn-harness-engineering/docs/ru/projects/project-05-grounded-qa-verification/index.md deleted file mode 100644 index 41ce9564..00000000 --- a/courses/learn-harness-engineering/docs/ru/projects/project-05-grounded-qa-verification/index.md +++ /dev/null @@ -1,22 +0,0 @@ -[中文版本 →](../../../zh/projects/project-05-grounded-qa-verification/) - -> Связанные лекции: [Лекция 09. Не давайте агентам объявлять победу слишком рано](./../../lectures/lecture-09-why-agents-declare-victory-too-early/index.md) · [Лекция 10. Только полный прогон пайплайна считается настоящей верификацией](./../../lectures/lecture-10-why-end-to-end-testing-changes-results/index.md) -> Файлы шаблонов: [templates/](https://github.com/walkinglabs/learn-harness-engineering/blob/main/docs/en/resources/templates/) - -# Проект 05. Заставьте агента самостоятельно проверять свою работу - -## Что вы делаете - -Реализуйте разделение ролей — генератор, который пишет код, evaluator, который ревьюит, и опционально планировщик. Запустите трижды, чтобы измерить эффект каждой добавленной роли. - -Выберите содержательную фичу-апгрейд (многоходовой диалог, редизайн панели цитирования или фильтрация документов) и удерживайте её одинаковой во всех запусках. - -## Инструменты - -- Claude Code или Codex -- Git -- Node.js + Electron - -## Механизм harness - -Самопроверка + Q&A с обоснованием + завершение по доказательствам diff --git a/courses/learn-harness-engineering/docs/ru/projects/project-06-runtime-observability-and-debugging/index.md b/courses/learn-harness-engineering/docs/ru/projects/project-06-runtime-observability-and-debugging/index.md deleted file mode 100644 index ce411598..00000000 --- a/courses/learn-harness-engineering/docs/ru/projects/project-06-runtime-observability-and-debugging/index.md +++ /dev/null @@ -1,25 +0,0 @@ -[中文版本 →](../../../zh/projects/project-06-runtime-observability-and-debugging/) - -> Связанные лекции: [Лекция 11. Сделайте runtime агента наблюдаемым](./../../lectures/lecture-11-why-observability-belongs-inside-the-harness/index.md) · [Лекция 12. Чистый handoff в конце каждой сессии](./../../lectures/lecture-12-why-every-session-must-leave-a-clean-state/index.md) -> Файлы шаблонов: [templates/](https://github.com/walkinglabs/learn-harness-engineering/blob/main/docs/en/resources/templates/) - -# Проект 06. Соберите полноценный агентский harness (Capstone) - -## Что вы делаете - -Это capstone-проект. Соберите всё, чему научились в первых пяти проектах, проведите полный бенчмарк, затем сделайте проход уборки, чтобы убедиться, что качество поддерживаемое. - -Используйте фиксированный набор multi-feature задач, охватывающий полный продуктовый срез: импорт документов, индексация, Q&A с цитатами, runtime-наблюдаемость и читаемое перезапускаемое состояние репозитория. Сначала запустите со слабым harness-baseline, затем с самым сильным harness, потом — уборку и повторный запуск. Наконец, проведите эксперимент с абляцией harness — убирайте по одному компоненту за раз и смотрите, какие из них реально важны. - -## Инструменты - -- Claude Code или Codex -- Git -- Node.js + Electron -- Шаблон quality-документа -- Рубрика evaluator -- Все компоненты harness, накопленные за первые пять проектов - -## Механизм harness - -Полный harness: все механизмы + наблюдаемость + ablation-исследование diff --git a/courses/learn-harness-engineering/docs/ru/resources/index.md b/courses/learn-harness-engineering/docs/ru/resources/index.md deleted file mode 100644 index 29197618..00000000 --- a/courses/learn-harness-engineering/docs/ru/resources/index.md +++ /dev/null @@ -1,50 +0,0 @@ -# Английская библиотека ресурсов - -Эта папка превращает методы курса в готовые к копированию шаблоны и компактные референсы, которые можно использовать в реальном репозитории. - -## Когда её использовать - -Начните отсюда, когда хотите, чтобы Codex, Claude Code или другой агент-кодер работал на протяжении нескольких сессий, не выводя каждый раз заново сетап, статус и скоуп. - -Особенно полезно, когда: - -- работа растягивается на несколько сессий -- фич много, и их легко оставить недоделанными -- агенты склонны слишком рано объявлять победу -- шаги старта каждый раз выясняются заново - -## Начните здесь - -Для минимального сетапа начните с: - -- корневые инструкции: [`templates/AGENTS.md`](./templates/AGENTS.md) или [`templates/CLAUDE.md`](./templates/CLAUDE.md) -- состояние фич: [`templates/feature_list.json`](./templates/feature_list.json) -- лог прогресса: [`templates/claude-progress.md`](./templates/claude-progress.md) -- референс bootstrap-скрипта: `docs/en/resources/templates/init.sh` - -Затем добавьте: - -- session handoff: [`templates/session-handoff.md`](./templates/session-handoff.md) -- чек-лист чистого выхода: [`templates/clean-state-checklist.md`](./templates/clean-state-checklist.md) -- рубрика evaluator: [`templates/evaluator-rubric.md`](./templates/evaluator-rubric.md) - -Если вам нужна более полная структура репозитория в стиле OpenAI из поста «Harness engineering», используйте advanced-пак: - -- [`openai-advanced/index.md`](./openai-advanced/index.md) - -## Структура библиотеки - -- [`templates/`](./templates/index.md): шаблоны для копирования в реальный репозиторий -- [`reference/`](./reference/index.md): заметки по методу, флоу старта и карты режимов отказа -- [`openai-advanced/`](./openai-advanced/index.md): продвинутый каркас репозитория, документы system-of-record и шаблоны agent-first управления - -## Рекомендуемый минимальный пак - -- `AGENTS.md` или `CLAUDE.md` -- `feature_list.json` -- `claude-progress.md` -- `init.sh` - -Этих четырёх файлов достаточно, чтобы заметно стабилизировать большинство агентских воркфлоу. - -Когда репозиторий вырастает в долгоживущую систему с несколькими доменами, активными планами, оценкой качества и политиками надёжности, переходите на пак [`openai-advanced/`](./openai-advanced/index.md), а не растягивайте минимальный пак сверх меры. diff --git a/courses/learn-harness-engineering/docs/ru/resources/openai-advanced/index.md b/courses/learn-harness-engineering/docs/ru/resources/openai-advanced/index.md deleted file mode 100644 index dbb5dfe7..00000000 --- a/courses/learn-harness-engineering/docs/ru/resources/openai-advanced/index.md +++ /dev/null @@ -1,83 +0,0 @@ -# OpenAI Advanced Pack - -Эта папка упаковывает более выверенную форму репозитория, описанную в -статье OpenAI «Harness engineering: leveraging Codex in an agent-first world», -в готовые к копированию стартовые файлы. - -Используйте этот пак, когда минимального harness уже недостаточно и вашему -репозиторию теперь требуется: - -- короткий `AGENTS.md` в стиле маршрутизатора -- долговечные документы — система записи внутри репозитория -- активные и завершённые exec plans -- явные файлы политик по продукту, надёжности, безопасности и фронтенду -- скоринг качества по продуктовым доменам и архитектурным слоям -- папки со справочными материалами, удобными для модели -- стандартные операционные процедуры по архитектуре, фиксации знаний и валидации в рантайме - -## Что входит в стартовую раскладку - -Стартовый пак в [`repo-template/`](./repo-template/index.md) повторяет -структуру ниже: - -```text -AGENTS.md -ARCHITECTURE.md -docs/ -├── design-docs/ -│ ├── index.md -│ └── core-beliefs.md -├── exec-plans/ -│ ├── active/ -│ ├── completed/ -│ └── tech-debt-tracker.md -├── generated/ -│ └── db-schema.md -├── product-specs/ -│ ├── index.md -│ └── new-user-onboarding.md -├── references/ -│ ├── design-system-reference-llms.txt -│ ├── nixpacks-llms.txt -│ └── uv-llms.txt -├── DESIGN.md -├── FRONTEND.md -├── PLANS.md -├── PRODUCT_SENSE.md -├── QUALITY_SCORE.md -├── RELIABILITY.md -└── SECURITY.md -``` - -## Как это внедрить - -1. Начните с минимального пака, если ваш репозиторий ещё небольшой. -2. Скопируйте файлы из [`repo-template/`](./repo-template/index.md) в ваш - репозиторий, как только понадобится более жёсткая структура. -3. Держите `AGENTS.md` коротким. Воспринимайте его как маршрутизатор в более - глубокие документы, а не как энциклопедию. -4. Обновляйте документы по качеству, надёжности и планам в рамках обычной - работы, а не отдельным днём уборки. -5. Делайте сгенерированные артефакты и внешние ссылки явными, чтобы агенты - находили их без опоры на историю чата. - -## Библиотека SOP - -Папка [`sops/`](./sops/index.md) превращает диаграммы из статьи в пошаговые -операционные процедуры: - -- настройка слоёной доменной архитектуры -- кодирование скрытых знаний в репозиторий -- локальный стек observability и feedback-loop рабочий процесс -- цикл валидации UI через Chrome DevTools - -## Принципы дизайна - -- Короткая входная точка, более глубокие связанные документы -- Репозиторий как система записи -- Механические проверки лучше запомненных правил -- Планы и история качества живут рядом с кодом -- Уборка и упрощение — первоклассная ответственность - -Этот пак намеренно выверенный, но его всё равно следует адаптировать под ваш -проект, а не копировать вслепую. diff --git a/courses/learn-harness-engineering/docs/ru/resources/openai-advanced/repo-template/AGENTS.md b/courses/learn-harness-engineering/docs/ru/resources/openai-advanced/repo-template/AGENTS.md deleted file mode 100644 index e83759e5..00000000 --- a/courses/learn-harness-engineering/docs/ru/resources/openai-advanced/repo-template/AGENTS.md +++ /dev/null @@ -1,61 +0,0 @@ -# AGENTS.md - -Этот репозиторий заточен под долгие сессии работы coding-агентов. Держите этот -файл коротким. Используйте его как маршрутизирующий слой к документам — системе -записи, а не как огромный сброс инструкций. - -## Стартовый рабочий процесс - -Перед изменением кода: - -1. Подтвердите корень репозитория через `pwd`. -2. Прочитайте `ARCHITECTURE.md` для текущей карты системы и жёстких правил зависимостей. -3. Прочитайте `docs/QUALITY_SCORE.md`, чтобы увидеть, какие домены или слои самые слабые. -4. Прочитайте `docs/PLANS.md`, затем откройте активный план, по которому работаете. -5. Прочитайте релевантную продуктовую спецификацию в `docs/product-specs/`. -6. Запустите стандартный путь bootstrap и верификации для этого репозитория. -7. Если базовая верификация падает, почините её до расширения скоупа. - -## Карта маршрутизации - -- `ARCHITECTURE.md`: карта доменов, модель слоёв, правила зависимостей -- `docs/design-docs/index.md`: дизайн-решения и базовые убеждения -- `docs/product-specs/index.md`: текущее продуктовое поведение и критерии приёмки -- `docs/PLANS.md`: жизненный цикл планов и политика exec plans -- `docs/QUALITY_SCORE.md`: здоровье продуктовых доменов и слоёв -- `docs/RELIABILITY.md`: рантайм-сигналы, бенчмарки и ожидания по перезапуску -- `docs/SECURITY.md`: правила по секретам, песочнице, данным и внешним действиям -- `docs/FRONTEND.md`: ограничения UI, правила дизайн-системы, проверки доступности - -## Рабочий контракт - -- Работайте по одному ограниченному плану или фиче-срезу за раз. -- Не помечайте работу как готовую только по результатам инспекции кода; нужны - запускаемые доказательства. -- Если вы меняете поведение, обновите соответствующие продуктовые, плановые - или reliability-документы в той же сессии. -- Если вы видите повторяющийся фидбек на ревью, превратите его в механическое - правило, проверку или линтер вместо повторного объяснения в чате. -- Храните сгенерированные материалы в `docs/generated/`, а исходные ссылки — в - `docs/references/`. -- Предпочитайте добавлять небольшие актуальные документы вместо разрастания этого файла. - -## Definition Of Done - -Изменение считается готовым только когда выполнено всё: - -- целевое поведение реализовано -- требуемая верификация действительно прошла -- доказательство приложено в соответствующем плане или документе по качеству -- затронутые документы остаются актуальными -- репозиторий чисто перезапускается со стандартного стартового пути - -## Конец сессии - -Перед завершением сессии: - -1. Обновите активный exec plan. -2. Обновите `docs/QUALITY_SCORE.md`, если какой-либо домен или слой существенно изменился. -3. Запишите новый техдолг в `docs/exec-plans/tech-debt-tracker.md`, если вы его отложили. -4. Перенесите завершённые планы в `docs/exec-plans/completed/`, когда уместно. -5. Оставьте репозиторий в перезапускаемом состоянии с чётким следующим шагом. diff --git a/courses/learn-harness-engineering/docs/ru/resources/openai-advanced/repo-template/ARCHITECTURE.md b/courses/learn-harness-engineering/docs/ru/resources/openai-advanced/repo-template/ARCHITECTURE.md deleted file mode 100644 index 4d083564..00000000 --- a/courses/learn-harness-engineering/docs/ru/resources/openai-advanced/repo-template/ARCHITECTURE.md +++ /dev/null @@ -1,57 +0,0 @@ -# ARCHITECTURE.md - -Этот файл — верхнеуровневая карта системы. Он должен оставаться лаконичным и -указывать на более глубокие документы, когда нужно. - -## Форма системы - -- Продукт: `[замените названием продукта]` -- Основной пользовательский сценарий: `[замените основным сценарием]` -- Поверхности рантайма: `[desktop / web / cli / services / workers]` -- Источник истины для продуктового поведения: `docs/product-specs/` - -## Карта доменов - -| Домен | Назначение | Основные точки входа | Связанная спецификация | -|--------|---------|----------------------|--------------| -| `[domain-a]` | `[чем владеет]` | `[модули / роуты / команды]` | `[путь к спецификации]` | -| `[domain-b]` | `[чем владеет]` | `[модули / роуты / команды]` | `[путь к спецификации]` | - -## Модель слоёв - -Используйте фиксированную направленную модель, чтобы агенты не выдумывали ad hoc архитектуру: - -`Types -> Config -> Repo -> Service -> Runtime -> UI` - -Сквозные сущности должны попадать через явные провайдеры или границы адаптеров, -а не тянуться напрямую через слои. - -## Жёсткие правила зависимостей - -- Нижние слои не должны зависеть от верхних. -- UI не должен обходить контракты рантайма или сервисов. -- Доступ к данным должен идти через репозитории или эквивалентные адаптеры. -- Общие утилиты должны оставаться универсальными и не накапливать доменную логику. -- Новые зависимости должны быть обоснованы в соответствующем плане или дизайн-документе. - -## Сквозные интерфейсы - -| Сущность | Утверждённая граница | Заметки | -|--------|-------------------|-------| -| Логирование и трейсинг | `[провайдер / путь утилиты]` | `[только структурированно, никакого ad hoc console]` | -| Аутентификация | `[путь провайдера]` | `[правила токенов/сессий]` | -| Внешние API | `[путь клиента или провайдера]` | `[ограничение скорости / правила ретраев]` | -| Feature flags | `[граница флагов]` | `[владение]` | - -## Текущие горячие точки - -- `[область, которую агентам сложнее всего безопасно менять]` -- `[область со слабыми границами или хрупкими тестами]` - -## Чеклист изменений - -Когда вы трогаете архитектурно значимый код: - -1. Обновите этот файл, если изменилась карта доменов или допустимые границы. -2. Обновите соответствующий дизайн-документ в `docs/design-docs/`, если изменилась логика рассуждений. -3. Добавьте или обновите исполняемую проверку, если правило должно быть проверено механически. diff --git a/courses/learn-harness-engineering/docs/ru/resources/openai-advanced/repo-template/docs/DESIGN.md b/courses/learn-harness-engineering/docs/ru/resources/openai-advanced/repo-template/docs/DESIGN.md deleted file mode 100644 index 159e9e7c..00000000 --- a/courses/learn-harness-engineering/docs/ru/resources/openai-advanced/repo-template/docs/DESIGN.md +++ /dev/null @@ -1,28 +0,0 @@ -# DESIGN.md - -Этот файл — точка входа в дизайн. Держите его кратким и используйте, чтобы -маршрутизировать в более детальные файлы в `docs/design-docs/`. - -## Назначение - -Фиксировать долговечные продуктовые и системные дизайн-решения, которые должны -пережить отдельный чат, спринт или память ревьюера. - -## Когда читать - -- вам нужна текущая дизайн-философия -- вы собираетесь ввести новый паттерн -- вам нужно понять, какие дизайн-решения утверждены, а какие ещё открыты - -## Канонические дизайн-документы - -- `docs/design-docs/index.md`: индекс принятых, предложенных и устаревших документов -- `docs/design-docs/core-beliefs.md`: общепроектные agent-first убеждения - -## Правила дизайна - -- Держите дизайн-документы небольшими и актуальными. -- Предпочитайте один документ на одну зону решения. -- Связывайте дизайн-документы из планов и спецификаций, когда изменение от них зависит. -- Если дизайн-правило становится операционно критичным, превратите его в - автоматизированную проверку или обновите `ARCHITECTURE.md`. diff --git a/courses/learn-harness-engineering/docs/ru/resources/openai-advanced/repo-template/docs/FRONTEND.md b/courses/learn-harness-engineering/docs/ru/resources/openai-advanced/repo-template/docs/FRONTEND.md deleted file mode 100644 index df535e62..00000000 --- a/courses/learn-harness-engineering/docs/ru/resources/openai-advanced/repo-template/docs/FRONTEND.md +++ /dev/null @@ -1,24 +0,0 @@ -# FRONTEND.md - -Этот файл задаёт стабильные ожидания по фронтенду, чтобы агенты не изобретали -UI-паттерны непредсказуемо. - -## Принципы UI - -- Оптимизируйте под ясность раньше, чем под новизну. -- Делайте потоки взаимодействия обнаруживаемыми и перезапускаемыми. -- Предпочитайте небольшое число переиспользуемых компонентов одноразовым вариантам. -- Проверки доступности — часть обычной верификации, а не финального полирования. - -## Ограничители - -- Документируйте дизайн-систему или библиотеку компонентов в `docs/references/`. -- Фиксируйте ключевые состояния, видимые пользователю: пустое, загрузка, успех, ошибка, повтор. -- Сохраняйте согласованность копирайтинга, поведения клавиатуры и визуальной иерархии между потоками. -- Когда UI-баг исправлен, добавьте или обновите соответствующий шаг валидации. - -## Ожидания по верификации - -- Захватывайте доказательства для критичных пользовательских сценариев. -- Записывайте шаги валидации в браузере или рантайме в соответствующий план. -- Если визуальные регрессии часты, стандартизируйте проверки скриншотов или DOM. diff --git a/courses/learn-harness-engineering/docs/ru/resources/openai-advanced/repo-template/docs/PLANS.md b/courses/learn-harness-engineering/docs/ru/resources/openai-advanced/repo-template/docs/PLANS.md deleted file mode 100644 index 531a6739..00000000 --- a/courses/learn-harness-engineering/docs/ru/resources/openai-advanced/repo-template/docs/PLANS.md +++ /dev/null @@ -1,35 +0,0 @@ -# PLANS.md - -Этот файл задаёт, как exec plans создаются, обновляются, завершаются и -архивируются. - -## Когда план обязателен - -Создавайте exec plan, когда работа: - -- занимает более одной сессии -- меняет более одной подсистемы -- имеет нетривиальные риски верификации или раскатки -- зависит от открытых решений, которые должны быть зафиксированы - -## Где живут планы - -- `docs/exec-plans/active/`: планы, которые сейчас ведут работу -- `docs/exec-plans/completed/`: завершённые планы, сохранённые для будущего контекста агентов -- `docs/exec-plans/tech-debt-tracker.md`: отложенная работа и follow-ups - -## Минимальные секции плана - -- цель -- скоуп и out-of-scope -- путь верификации -- риски и блокеры -- лог прогресса -- открытые решения - -## Правила работы - -- У одного активного плана должен быть один чётко принадлежащий текущий шаг. -- Обновляйте план по мере работы; не относитесь к нему как к статической прозе. -- Если решение меняет направление реализации, зафиксируйте это в плане. -- Перемещайте завершённые планы в `completed/`, чтобы агенты могли всё ещё находить прежний контекст. diff --git a/courses/learn-harness-engineering/docs/ru/resources/openai-advanced/repo-template/docs/PRODUCT_SENSE.md b/courses/learn-harness-engineering/docs/ru/resources/openai-advanced/repo-template/docs/PRODUCT_SENSE.md deleted file mode 100644 index a88b2bc0..00000000 --- a/courses/learn-harness-engineering/docs/ru/resources/openai-advanced/repo-template/docs/PRODUCT_SENSE.md +++ /dev/null @@ -1,26 +0,0 @@ -# PRODUCT_SENSE.md - -Этот файл фиксирует долговечное продуктовое суждение, которое агенты не могут -надёжно вывести из одного только кода. - -## Ядро продукта - -- Основной пользователь: `[заменить]` -- Job to be done: `[заменить]` -- Главное раздражение, которое нужно убрать: `[заменить]` -- Планка качества для приёмки: `[заменить]` - -## Продуктовые правила - -- Предпочитайте видимую пользователю надёжность количеству фич. -- Считайте неоднозначное поведение пробелом в спецификации, а не разрешением гадать. -- Если реализация меняет то, что пользователи видят или чему доверяют, обновите соответствующую спецификацию. -- Используйте продуктовые спецификации для конкретных потоков, а этот файл — для сквозных - продуктовых приоритетов. - -## Запрещённые паттерны - -- Скрытые разрушительные действия -- Тихий сбой без обратной связи пользователю -- Неясный источник истины для видимого состояния -- Фичи, которые нельзя объяснить одним предложением diff --git a/courses/learn-harness-engineering/docs/ru/resources/openai-advanced/repo-template/docs/QUALITY_SCORE.md b/courses/learn-harness-engineering/docs/ru/resources/openai-advanced/repo-template/docs/QUALITY_SCORE.md deleted file mode 100644 index f932475f..00000000 --- a/courses/learn-harness-engineering/docs/ru/resources/openai-advanced/repo-template/docs/QUALITY_SCORE.md +++ /dev/null @@ -1,39 +0,0 @@ -# QUALITY_SCORE.md - -Этот документ отслеживает, становится ли репозиторий со временем сильнее или слабее. - -## Шкала оценок - -- `A`: проверено, читаемо, стабильно, границы соблюдаются -- `B`: работает с небольшими пробелами -- `C`: частично работает, заметная путаница или нестабильность -- `D`: сломано, небезопасно или структурно неясно - -## Продуктовые домены - -| Домен | Оценка | Верификация | Читаемость для агентов | Стабильность тестов | Ключевые пробелы | Последнее обновление | -|--------|-------|-------------|-----------------|---------------|----------|-------------| -| `[domain-a]` | - | - | - | - | - | - | -| `[domain-b]` | - | - | - | - | - | - | -| `[domain-c]` | - | - | - | - | - | - | - -## Архитектурные слои - -| Слой | Оценка | Соблюдение границ | Читаемость для агентов | Ключевые пробелы | Последнее обновление | -|-------|-------|---------------------|-----------------|----------|-------------| -| Types | - | - | - | - | - | -| Services | - | - | - | - | - | -| Runtime | - | - | - | - | - | -| UI | - | - | - | - | - | - -## Снимки бенчмарков - -| Дата | Вариант harness | Доля завершённых | Повторы | Дефекты до ревью | Заметки | -|------|-----------------|----------------|--------|-----------------------|------| -| YYYY-MM-DD | `[baseline / improved / simplified]` | - | - | - | - | - -## Лог упрощений - -| Дата | Удалённый компонент | Результат | Решение | -|------|-------------------|---------|----------| -| YYYY-MM-DD | `[компонент]` | `[ухудшилось / без изменений]` | `[вернуть / оставить удалённым]` | diff --git a/courses/learn-harness-engineering/docs/ru/resources/openai-advanced/repo-template/docs/RELIABILITY.md b/courses/learn-harness-engineering/docs/ru/resources/openai-advanced/repo-template/docs/RELIABILITY.md deleted file mode 100644 index f33fefde..00000000 --- a/courses/learn-harness-engineering/docs/ru/resources/openai-advanced/repo-template/docs/RELIABILITY.md +++ /dev/null @@ -1,32 +0,0 @@ -# RELIABILITY.md - -Этот файл задаёт, как система доказывает, что она здорова и перезапускаема. - -## Стандартные пути - -- Bootstrap: `[команда]` -- Верификация: `[команда]` -- Запуск приложения или сервиса: `[команда]` -- Отладка или инспекция рантайма: `[команда]` - -## Обязательные рантайм-сигналы - -- структурированные логи для запуска и критичных потоков -- health-проверки для ключевых сервисов -- данные трейсинга или таймингов для медленных путей, когда доступно -- видимые пользователю состояния ошибок для восстановимых сбоев - -## Golden journeys - -- `[сценарий 1]` -- `[сценарий 2]` -- `[сценарий 3]` - -У каждого golden journey должен быть повторяемый путь верификации и понятные сигналы сбоя. - -## Правила надёжности - -- Ни одна фича не считается завершённой, если после неё система не может чисто перезапуститься. -- Сбои в рантайме должны быть диагностируемы по локальным сигналам репозитория. -- Если появляется повторяющийся режим сбоя, добавьте бенчмарк или ограничитель для него. -- Уборка — часть надёжности, а не отдельная забота. diff --git a/courses/learn-harness-engineering/docs/ru/resources/openai-advanced/repo-template/docs/SECURITY.md b/courses/learn-harness-engineering/docs/ru/resources/openai-advanced/repo-template/docs/SECURITY.md deleted file mode 100644 index bdc3b2ba..00000000 --- a/courses/learn-harness-engineering/docs/ru/resources/openai-advanced/repo-template/docs/SECURITY.md +++ /dev/null @@ -1,27 +0,0 @@ -# SECURITY.md - -Этот файл задаёт правила безопасности, о которых агенты не должны догадываться. - -## Секреты и учётные данные - -- Никогда не зашивайте секреты в исходники или документы. -- Документируйте здесь утверждённые пути загрузки секретов. -- Скрывайте токены, API-ключи и персональные данные в логах и скриншотах. - -## Недоверенный ввод - -- Считайте внешний контент недоверенным, пока он не валидирован. -- Записывайте здесь разрешённые границы fetch или выполнения. -- Если есть риск prompt injection или command injection, документируйте ограничитель. - -## Внешние действия - -- Перечислите, какие действия требуют явного одобрения. -- Запишите любые продакшн или разрушительные команды, которые агенты не должны запускать по умолчанию. -- Предпочитайте sandbox-безопасные рабочие процессы для отладки и верификации. - -## Правила по зависимостям и ревью - -- Новые зависимости требуют обоснования в активном плане. -- Изменения, чувствительные к безопасности, требуют явных шагов верификации. -- Повторяющиеся комментарии security-ревью должны становиться проверками, а не племенным знанием. diff --git a/courses/learn-harness-engineering/docs/ru/resources/openai-advanced/repo-template/docs/design-docs/core-beliefs.md b/courses/learn-harness-engineering/docs/ru/resources/openai-advanced/repo-template/docs/design-docs/core-beliefs.md deleted file mode 100644 index f6fb9829..00000000 --- a/courses/learn-harness-engineering/docs/ru/resources/openai-advanced/repo-template/docs/design-docs/core-beliefs.md +++ /dev/null @@ -1,9 +0,0 @@ -# Базовые убеждения - -- Репозиторий — это система записи для агентов. -- `AGENTS.md` — это маршрутизатор, а не энциклопедия. -- Доказательства верификации важнее уверенности. -- Одна ограниченная задача лучше многих наполовину сделанных. -- Повторяющийся фидбек от людей должен превращаться в переиспользуемые правила harness. -- Уборка и упрощение — часть выпуска, а не довесок. -- Если агент не может обнаружить факт внутри репозитория, считайте этот факт операционно недоступным. diff --git a/courses/learn-harness-engineering/docs/ru/resources/openai-advanced/repo-template/docs/design-docs/index.md b/courses/learn-harness-engineering/docs/ru/resources/openai-advanced/repo-template/docs/design-docs/index.md deleted file mode 100644 index 8c77a10f..00000000 --- a/courses/learn-harness-engineering/docs/ru/resources/openai-advanced/repo-template/docs/design-docs/index.md +++ /dev/null @@ -1,21 +0,0 @@ -# Индекс дизайн-документов - -Используйте этот индекс как обнаруживаемую карту истории дизайна. - -## Принятые - -- `core-beliefs.md`: agent-first операционные убеждения и долговечные нормы проекта - -## Предложенные - -- `[добавьте сюда пути новых дизайн-документов]` - -## Устаревшие - -- `[перенесите сюда старые или замещённые дизайн-документы со ссылками на замену]` - -## Правила сопровождения - -- У каждого дизайн-документа должен быть владелец или триггер обновления. -- Удаляйте устаревшие документы или помечайте их как deprecated, не давая им дрейфовать. -- Связывайте активные exec plans с дизайн-документами, от которых они зависят. diff --git a/courses/learn-harness-engineering/docs/ru/resources/openai-advanced/repo-template/docs/exec-plans/active/index.md b/courses/learn-harness-engineering/docs/ru/resources/openai-advanced/repo-template/docs/exec-plans/active/index.md deleted file mode 100644 index 4528587b..00000000 --- a/courses/learn-harness-engineering/docs/ru/resources/openai-advanced/repo-template/docs/exec-plans/active/index.md +++ /dev/null @@ -1,10 +0,0 @@ -# Активные планы - -Держите по одному markdown-файлу на каждый активный exec plan в этой папке. - -Рекомендуемый шаблон имени файла: - -- `YYYY-MM-DD-short-topic.md` - -Каждый активный план должен быть достаточно актуален, чтобы свежая сессия агента -могла продолжить работу, опираясь только на репозиторий. diff --git a/courses/learn-harness-engineering/docs/ru/resources/openai-advanced/repo-template/docs/exec-plans/completed/index.md b/courses/learn-harness-engineering/docs/ru/resources/openai-advanced/repo-template/docs/exec-plans/completed/index.md deleted file mode 100644 index 395de51b..00000000 --- a/courses/learn-harness-engineering/docs/ru/resources/openai-advanced/repo-template/docs/exec-plans/completed/index.md +++ /dev/null @@ -1,5 +0,0 @@ -# Завершённые планы - -Перемещайте завершённые планы сюда вместо удаления. Завершённые планы — часть -поверхности памяти репозитория, они помогают будущим запускам агентов понять, -почему код выглядит так, как выглядит. diff --git a/courses/learn-harness-engineering/docs/ru/resources/openai-advanced/repo-template/docs/exec-plans/tech-debt-tracker.md b/courses/learn-harness-engineering/docs/ru/resources/openai-advanced/repo-template/docs/exec-plans/tech-debt-tracker.md deleted file mode 100644 index 92ec6f1f..00000000 --- a/courses/learn-harness-engineering/docs/ru/resources/openai-advanced/repo-template/docs/exec-plans/tech-debt-tracker.md +++ /dev/null @@ -1,7 +0,0 @@ -# Трекер техдолга - -Используйте этот файл для долга, который реален, осознан и сознательно отложен. - -| Дата | Область | Долг | Почему отложено | Риск | Следующий триггер | -|------|------|------|--------------|------|--------------| -| YYYY-MM-DD | `[область]` | `[долг]` | `[причина]` | `[риск]` | `[когда вернуться]` | diff --git a/courses/learn-harness-engineering/docs/ru/resources/openai-advanced/repo-template/docs/generated/db-schema.md b/courses/learn-harness-engineering/docs/ru/resources/openai-advanced/repo-template/docs/generated/db-schema.md deleted file mode 100644 index 62b60d16..00000000 --- a/courses/learn-harness-engineering/docs/ru/resources/openai-advanced/repo-template/docs/generated/db-schema.md +++ /dev/null @@ -1,14 +0,0 @@ -# Схема базы данных - -Используйте эту папку для сгенерированных или производных артефактов, которые -агенты должны иметь возможность инспектировать без реверс-инжиниринга из кода. - -## Источник - -- Сгенерировано из: `[команда или путь к источнику]` -- Последнее обновление: `YYYY-MM-DD` - -## Заметки - -- Не редактируйте сгенерированные секции вручную. -- Перегенерируйте этот файл, когда меняется исходная схема. diff --git a/courses/learn-harness-engineering/docs/ru/resources/openai-advanced/repo-template/docs/product-specs/index.md b/courses/learn-harness-engineering/docs/ru/resources/openai-advanced/repo-template/docs/product-specs/index.md deleted file mode 100644 index da94c4c9..00000000 --- a/courses/learn-harness-engineering/docs/ru/resources/openai-advanced/repo-template/docs/product-specs/index.md +++ /dev/null @@ -1,14 +0,0 @@ -# Индекс продуктовых спецификаций - -Используйте эту папку для текущих спецификаций поведения, видимого пользователю. - -## Активные спецификации - -- `new-user-onboarding.md` - -## Правила - -- Спецификации должны описывать видимое пользователю поведение и критерии приёмки. -- Если реализация расходится со спецификацией, обновите одно из них в той же - сессии. -- Поддерживайте этот индекс актуальным, чтобы свежий агент мог быстро обнаружить продуктовый скоуп. diff --git a/courses/learn-harness-engineering/docs/ru/resources/openai-advanced/repo-template/docs/product-specs/new-user-onboarding.md b/courses/learn-harness-engineering/docs/ru/resources/openai-advanced/repo-template/docs/product-specs/new-user-onboarding.md deleted file mode 100644 index 7398a8c1..00000000 --- a/courses/learn-harness-engineering/docs/ru/resources/openai-advanced/repo-template/docs/product-specs/new-user-onboarding.md +++ /dev/null @@ -1,26 +0,0 @@ -# Онбординг нового пользователя - -## Цель - -Описать опыт первого запуска, который должен получить новый пользователь. - -## Условия входа - -- `[состояние перед началом потока]` - -## Пользовательский поток - -1. `[шаг один]` -2. `[шаг два]` -3. `[шаг три]` - -## Критерии приёмки - -- `[наблюдаемый результат]` -- `[наблюдаемый результат]` -- `[наблюдаемый результат]` - -## Состояния сбоев - -- `[восстановимая ошибка и обратная связь пользователю]` -- `[заблокированное состояние и фолбэк]` diff --git a/courses/learn-harness-engineering/docs/ru/resources/openai-advanced/repo-template/docs/references/design-system-reference-llms.txt b/courses/learn-harness-engineering/docs/ru/resources/openai-advanced/repo-template/docs/references/design-system-reference-llms.txt deleted file mode 100644 index 6f1c612c..00000000 --- a/courses/learn-harness-engineering/docs/ru/resources/openai-advanced/repo-template/docs/references/design-system-reference-llms.txt +++ /dev/null @@ -1,9 +0,0 @@ -Purpose: store model-friendly reference material for the design system. - -Suggested contents: -- component naming rules -- spacing and typography tokens -- state variants -- accessibility expectations - -Keep this file concise and refresh it when the upstream design system changes. diff --git a/courses/learn-harness-engineering/docs/ru/resources/openai-advanced/repo-template/docs/references/nixpacks-llms.txt b/courses/learn-harness-engineering/docs/ru/resources/openai-advanced/repo-template/docs/references/nixpacks-llms.txt deleted file mode 100644 index c30cc9c2..00000000 --- a/courses/learn-harness-engineering/docs/ru/resources/openai-advanced/repo-template/docs/references/nixpacks-llms.txt +++ /dev/null @@ -1,8 +0,0 @@ -Purpose: store a clean agent-readable extract of the deployment or packaging -rules your repository depends on. - -Suggested contents: -- build entrypoints -- runtime assumptions -- environment variable expectations -- common failure signatures diff --git a/courses/learn-harness-engineering/docs/ru/resources/openai-advanced/repo-template/docs/references/uv-llms.txt b/courses/learn-harness-engineering/docs/ru/resources/openai-advanced/repo-template/docs/references/uv-llms.txt deleted file mode 100644 index f2ceba9a..00000000 --- a/courses/learn-harness-engineering/docs/ru/resources/openai-advanced/repo-template/docs/references/uv-llms.txt +++ /dev/null @@ -1,8 +0,0 @@ -Purpose: store a compact reference for your Python package and environment -workflow when `uv` or similar tooling matters to the repo. - -Suggested contents: -- install and sync commands -- lockfile policy -- virtualenv expectations -- verification commands diff --git a/courses/learn-harness-engineering/docs/ru/resources/openai-advanced/repo-template/index.md b/courses/learn-harness-engineering/docs/ru/resources/openai-advanced/repo-template/index.md deleted file mode 100644 index 7a94b192..00000000 --- a/courses/learn-harness-engineering/docs/ru/resources/openai-advanced/repo-template/index.md +++ /dev/null @@ -1,26 +0,0 @@ -# Advanced Repo Template - -Скопируйте этот стартер в реальный репозиторий, когда вам нужна -agent-first документационная поверхность в стиле OpenAI, а не просто -минимальный harness. - -## Порядок копирования - -1. Скопируйте `AGENTS.md` и `ARCHITECTURE.md` в корень репозитория. -2. Скопируйте всё дерево `docs/`. -3. Сначала заполните `docs/PRODUCT_SENSE.md`, `docs/QUALITY_SCORE.md` и - `docs/RELIABILITY.md`. -4. Добавьте ваш первый активный план в `docs/exec-plans/active/`. -5. Держите входные файлы короткими и уводите детали в связанные документы. - -## На что заточен этот шаблон - -- долговечный репо-локальный контекст -- постепенное раскрытие вместо одного гигантского файла инструкций -- явный жизненный цикл планов -- отслеживание качества во времени -- читаемые границы для агентов и людей - -Считайте каждый файл здесь стартером. Замените заглушки, примеры и -демонстрационные команды реальной спецификой вашего проекта, прежде чем -полагаться на него. diff --git a/courses/learn-harness-engineering/docs/ru/resources/openai-advanced/sops/chrome-devtools-validation-loop.md b/courses/learn-harness-engineering/docs/ru/resources/openai-advanced/sops/chrome-devtools-validation-loop.md deleted file mode 100644 index 995cdbef..00000000 --- a/courses/learn-harness-engineering/docs/ru/resources/openai-advanced/sops/chrome-devtools-validation-loop.md +++ /dev/null @@ -1,51 +0,0 @@ -# SOP: Цикл валидации Chrome DevTools - -Используйте этот SOP, когда работа с UI зависит от реального рантайм-взаимодействия и -скриншоты, состояние DOM и вывод консоли важнее одной только инспекции кода. - -## Цель - -Превратить валидацию UI в повторяемый цикл взаимодействия, который агент может -запускать, пока сценарий не станет чистым. - -## Основной цикл - -1. Выберите целевую страницу или экземпляр приложения. -2. Очистите устаревший шум в консоли. -3. Зафиксируйте состояние ДО. -4. Триггерните UI-путь. -5. Наблюдайте за рантайм-событиями во время взаимодействия. -6. Зафиксируйте состояние ПОСЛЕ. -7. Примените исправление и при необходимости перезапустите приложение. -8. Повторяйте валидацию, пока сценарий не станет чистым. - -## Обязательные входы - -- стабильная команда запуска -- воспроизводимый UI-сценарий -- способ сделать снимок DOM, консоли или скриншоты -- правило, что считается «чистым» - -## SOP исполнения - -1. Запишите целевой сценарий в активный план. -2. Определите успех в наблюдаемых терминах: текст присутствует, кнопка активна, ошибка ушла, - консоль чистая, запрос успешен. -3. Сделайте снимок начального состояния перед взаимодействием. -4. Триггерьте ровно один путь за раз. -5. Записывайте рантайм-события, изменения DOM и видимый вывод. -6. Если сценарий падает, чините наименьший ответственный слой и перезапускайте. -7. Перезапустите тот же путь и сравните доказательства ДО/ПОСЛЕ. - -## Критерии чистоты - -- задуманное видимое состояние присутствует -- неожиданных ошибок нет -- шум в консоли понят или очищен -- повторный прогон того же пути даёт тот же результат - -## Артефакты репозитория для обновления - -- активный exec plan -- `docs/RELIABILITY.md`, если сценарий стал golden path -- продуктовая спецификация, если изменилось видимое поведение diff --git a/courses/learn-harness-engineering/docs/ru/resources/openai-advanced/sops/encode-knowledge-into-repo.md b/courses/learn-harness-engineering/docs/ru/resources/openai-advanced/sops/encode-knowledge-into-repo.md deleted file mode 100644 index e2da3dbe..00000000 --- a/courses/learn-harness-engineering/docs/ru/resources/openai-advanced/sops/encode-knowledge-into-repo.md +++ /dev/null @@ -1,45 +0,0 @@ -# SOP: Закодировать невидимое знание в репозиторий - -Используйте этот SOP, когда важный контекст всё ещё живёт в Google Docs, -тредах чатов, тикетах или головах людей. - -## Цель - -Сделать невидимое для агента знание обнаруживаемым в кодовой базе, чтобы свежая -сессия могла на нём действовать, не полагаясь на предыдущий разговор. - -## Сигналы триггера - -- Агент постоянно спрашивает, как работает система. -- Люди говорят «мы это решили в Slack» или «следуй тому, что X сказал на прошлой неделе». -- Ревью ссылается на продуктовые правила или правила безопасности, которых нет в репозитории. -- Новые сессии повторяют работу по обнаружению, которая уже должна быть закрыта. - -## SOP исполнения - -1. Перечислите источники невидимого знания: документы, чаты, негласные командные правила, устные решения. -2. Для каждого источника спросите: это архитектура, продуктовое поведение, политика безопасности, - ожидание по надёжности, контекст плана или справочный материал? -3. Закодируйте это в соответствующий артефакт репозитория: - - архитектура -> `ARCHITECTURE.md` - - продуктовое поведение -> `docs/product-specs/` - - дизайн-обоснование -> `docs/design-docs/` - - состояние исполнения -> `docs/exec-plans/` - - повторяющиеся внешние ссылки -> `docs/references/` - - ожидания по качеству или надёжности -> `docs/QUALITY_SCORE.md` или `docs/RELIABILITY.md` -4. Заменяйте размытые формулировки операционно полезными. -5. Удаляйте или помечайте как deprecated устаревшие копии, чтобы в репозитории сохранялась одна обнаруживаемая истина. - -## Правила хорошего кодирования - -- Пишите ради обнаруживаемости, а не литературной полноты. -- Предпочитайте короткие документы с понятными именами файлов. -- Связывайте родственные артефакты между собой. -- Храните долговечные правила, а не транскрипты встреч. -- Обновляйте репозиторий в той же сессии, в которой принято решение. - -## Definition Of Done - -- Свежий агент может обнаружить нужное правило, не спрашивая человека. -- Один и тот же факт не разбросан по нескольким противоречивым файлам. -- Новый артефакт живёт рядом с кодом или процессом, которым он управляет. diff --git a/courses/learn-harness-engineering/docs/ru/resources/openai-advanced/sops/index.md b/courses/learn-harness-engineering/docs/ru/resources/openai-advanced/sops/index.md deleted file mode 100644 index 06300a84..00000000 --- a/courses/learn-harness-engineering/docs/ru/resources/openai-advanced/sops/index.md +++ /dev/null @@ -1,25 +0,0 @@ -# OpenAI Advanced SOPs - -Эти SOP переводят операционные паттерны из статьи в конкретные исполняемые -плейбуки, которым можно следовать или адаптировать. - -## Включённые SOP - -- [`layered-domain-architecture.md`](./layered-domain-architecture.md): - ввести явные слои и сквозные границы -- [`encode-knowledge-into-repo.md`](./encode-knowledge-into-repo.md): - перенести невидимое знание из чатов, документов и памяти в репо-локальные файлы -- [`observability-feedback-loop.md`](./observability-feedback-loop.md): - дать агентам логи, метрики, трейсы и повторяемый цикл отладки -- [`chrome-devtools-validation-loop.md`](./chrome-devtools-validation-loop.md): - использовать автоматизацию браузера и снимки для валидации UI-поведения до чистого результата - -## Как ими пользоваться - -1. Выберите SOP, соответствующий вашему текущему узкому месту. -2. Используйте чек-лист, чтобы настроить недостающие артефакты или инструментарий. -3. Закодируйте получившиеся правила в скопированные документы `repo-template/`. -4. Превращайте повторяющиеся комментарии ревью в проверки, скрипты или ограничители. - -Это не для слепого следования. Это для того, чтобы сделать harness -читаемее, проверяемее и повторяемее. diff --git a/courses/learn-harness-engineering/docs/ru/resources/openai-advanced/sops/layered-domain-architecture.md b/courses/learn-harness-engineering/docs/ru/resources/openai-advanced/sops/layered-domain-architecture.md deleted file mode 100644 index 239bf9e0..00000000 --- a/courses/learn-harness-engineering/docs/ru/resources/openai-advanced/sops/layered-domain-architecture.md +++ /dev/null @@ -1,50 +0,0 @@ -# SOP: Слоёная доменная архитектура - -Используйте этот SOP, когда агент постоянно нарушает границы, дублирует логику между -слоями или производит код, который через несколько сессий становится сложно ревьюить. - -## Цель - -Сделать доменные границы достаточно явными, чтобы агенты могли двигаться быстро, -не разрушая структуру тихим образом. - -## Целевая модель - -Внутри бизнес-домена предпочитайте такой направленный поток: - -`Types -> Config -> Repo -> Service -> Runtime -> UI` - -Сквозные сущности должны попадать через явные провайдеры или адаптеры. -Общие утилиты остаются вне домена и не должны накапливать доменную логику. - -## Чеклист настройки - -- Определите текущие домены в `ARCHITECTURE.md`. -- Запишите допустимые направления зависимостей в `ARCHITECTURE.md`. -- Зафиксируйте сквозные интерфейсы: аутентификация, телеметрия, внешние API. -- Добавьте короткую заметку о самом тяжёлом текущем нарушении границ. -- Решите, что должно проверяться механически линтером, тестами или скриптами. - -## SOP исполнения - -1. Разметьте кодовую базу по доменам, прежде чем трогать стиль реализации. -2. Для каждого домена определите допустимую последовательность слоёв. -3. Идентифицируйте все сквозные сущности и проводите их через провайдеры или адаптеры. -4. Перенесите неоднозначную общую логику либо в её домен-владелец, либо в действительно универсальные утилиты. -5. Документируйте правила в `ARCHITECTURE.md`. -6. Добавьте один исполняемый ограничитель для самого дорогого нарушения. -7. Обновите скоринг качества после изменения. - -## Definition Of Done - -- Свежий агент может сказать, какому слою принадлежит изменение. -- UI-код больше не лезет напрямую в репозиторий или внешние сайд-эффекты. -- У сквозных сущностей есть именованные точки входа. -- Хотя бы одна важная граница соблюдается механически. - -## Артефакты репозитория для обновления - -- `ARCHITECTURE.md` -- `docs/QUALITY_SCORE.md` -- `docs/design-docs/`, когда изменилась обосновывающая логика -- `docs/PLANS.md` или активный exec plan diff --git a/courses/learn-harness-engineering/docs/ru/resources/openai-advanced/sops/observability-feedback-loop.md b/courses/learn-harness-engineering/docs/ru/resources/openai-advanced/sops/observability-feedback-loop.md deleted file mode 100644 index 09c501b3..00000000 --- a/courses/learn-harness-engineering/docs/ru/resources/openai-advanced/sops/observability-feedback-loop.md +++ /dev/null @@ -1,44 +0,0 @@ -# SOP: Observability Feedback Loop - -Используйте этот SOP, когда отладка идёт медленно, агенты постоянно заявляют об успехе без -доказательств, или поведение в рантайме сложнее инспектировать, чем сам код. - -## Цель - -Дать агенту локальный feedback-loop по логам, метрикам, трейсам и запускаемой -нагрузке, чтобы он мог рассуждать от исполнения, а не только от инспекции кода. - -## Минимальный стек - -- приложение эмитит структурированные логи -- приложение эмитит метрики и трейсы, когда возможно -- локальный fan-out или collection-слой -- интерфейсы запросов для логов, метрик и трейсов -- повторяемая нагрузка или пользовательский сценарий, который можно перезапустить после каждого изменения - -## SOP исполнения - -1. Определите golden рантайм-сценарии, которые важнее всего. -2. Добавьте структурированные логи на запуск и критический путь. -3. Добавьте метрики для латентности, числа сбоев или глубины очереди, где это полезно. -4. Добавьте трейсы или временны́е маркеры для медленных или многошаговых потоков. -5. Сделайте сигналы запрашиваемыми из локального dev-окружения. -6. Дайте агенту одну повторяемую нагрузку или сценарий для перезапуска. -7. Требуйте цикл: запрос -> корреляция -> рассуждение -> реализация -> перезапуск -> - повтор -> верификация. - -## Чеклист сессии отладки - -- Что упало? -- Какой сигнал доказывает сбой? -- Какому слою принадлежит сбой? -- Что изменилось после исправления? -- Чисто ли перезапустилось приложение? -- Прошла ли та же нагрузка после повторного запуска? - -## Definition Of Done - -- Агент может объяснить режим сбоя по рантайм-доказательствам. -- Одну и ту же нагрузку можно перезапустить после каждого изменения. -- Перезапуск и повторный прогон — часть обычного цикла задачи. -- Сигналы надёжности задокументированы в `docs/RELIABILITY.md`. diff --git a/courses/learn-harness-engineering/docs/ru/resources/reference/coding-agent-startup-flow.md b/courses/learn-harness-engineering/docs/ru/resources/reference/coding-agent-startup-flow.md deleted file mode 100644 index 8997b245..00000000 --- a/courses/learn-harness-engineering/docs/ru/resources/reference/coding-agent-startup-flow.md +++ /dev/null @@ -1,33 +0,0 @@ -# Флоу старта агента-кодера - -Используйте это в начале каждой сессии после того, как инициализация завершена. - -## Фиксированный шаблон старта - -1. Запустите `pwd` и подтвердите корень репозитория. -2. Прочитайте `claude-progress.md`. -3. Прочитайте `feature_list.json`. -4. Просмотрите последние коммиты: `git log --oneline -5`. -5. Запустите `./init.sh`. -6. Прогоните базовый smoke- или end-to-end-путь. -7. Если базовая верификация сломана, в первую очередь чините её. -8. Выберите незавершённую фичу с самым высоким приоритетом. -9. Работайте только над этой фичей, пока она не будет верифицирована или явно заблокирована. - -## Почему этот порядок важен - -- `pwd` предотвращает случайную работу в неправильной директории. -- Файлы прогресса и фич восстанавливают долговечное состояние до начала новых правок. -- Последние коммиты объясняют, что изменилось последним. -- `init.sh` стандартизирует старт вместо того, чтобы полагаться на память. -- Базовая верификация ловит сломанные стартовые состояния, прежде чем новая работа их замаскирует. - -## Зеркало конца сессии - -Та же сессия должна заканчиваться: - -1. фиксацией прогресса -2. обновлением состояния фич -3. написанием handoff, если нужно -4. коммитом безопасной работы -5. оставлением чистого пути рестарта diff --git a/courses/learn-harness-engineering/docs/ru/resources/reference/index.md b/courses/learn-harness-engineering/docs/ru/resources/reference/index.md deleted file mode 100644 index cad319bc..00000000 --- a/courses/learn-harness-engineering/docs/ru/resources/reference/index.md +++ /dev/null @@ -1,17 +0,0 @@ -# Английский референс - -Эти заметки объясняют, как использовать шаблоны как рабочий harness, а не как разрозненную кучу файлов. - -## Референс-заметки - -- [`method-map.md`](./method-map.md): сопоставляет распространённые режимы отказа в долгих задачах с артефактом или политикой, которая первой их решает -- [`initializer-agent-playbook.md`](./initializer-agent-playbook.md): что инициализатор должен оставить, прежде чем начнётся работа над фичами -- [`coding-agent-startup-flow.md`](./coding-agent-startup-flow.md): фиксированный флоу старта сессии для последующих кодовых прогонов -- [`prompt-calibration.md`](./prompt-calibration.md): как держать корневые инструкции острыми, не делая их раздутыми и хрупкими - -## Рекомендуемый порядок чтения - -1. `method-map.md` -2. `initializer-agent-playbook.md` -3. `coding-agent-startup-flow.md` -4. `prompt-calibration.md` diff --git a/courses/learn-harness-engineering/docs/ru/resources/reference/initializer-agent-playbook.md b/courses/learn-harness-engineering/docs/ru/resources/reference/initializer-agent-playbook.md deleted file mode 100644 index b1359dfe..00000000 --- a/courses/learn-harness-engineering/docs/ru/resources/reference/initializer-agent-playbook.md +++ /dev/null @@ -1,35 +0,0 @@ -# Playbook агента-инициализатора - -Используйте этот playbook для первой серьёзной сессии в репозитории, до того как начнётся инкрементальная работа над фичами. - -## Цель - -Создать стабильную операционную поверхность, чтобы последующие сессии могли реализовывать поведение, не выводя заново стартовые команды, текущий статус или границы задач. - -## Обязательные результаты - -Инициализатор должен оставить как минимум эти артефакты: - -- корневой файл инструкций — например, `AGENTS.md` или `CLAUDE.md` -- машиночитаемая поверхность фич — например, `feature_list.json` -- долговечный артефакт прогресса — например, `claude-progress.md` -- стандартный хелпер старта — например, `init.sh` -- начальный безопасный коммит, фиксирующий базовый каркас - -## Чек-лист - -1. Определите стандартный путь старта. -2. Определите стандартный путь верификации. -3. Создайте лог прогресса и зафиксируйте начальное состояние. -4. Разложите работу на явные фичи со статусами. -5. Сделайте первый чистый baseline-коммит. - -## Тест успеха - -Свежая сессия без предыдущего контекста чата должна уметь ответить: - -- что делает этот репозиторий -- как его запустить -- как его верифицировать -- что не закончено -- какой следующий лучший шаг diff --git a/courses/learn-harness-engineering/docs/ru/resources/reference/method-map.md b/courses/learn-harness-engineering/docs/ru/resources/reference/method-map.md deleted file mode 100644 index 8e76ce6f..00000000 --- a/courses/learn-harness-engineering/docs/ru/resources/reference/method-map.md +++ /dev/null @@ -1,16 +0,0 @@ -# Карта методов - -Эта таблица сопоставляет самые распространённые режимы отказа в долгих задачах агента-кодера с артефактом или операционным правилом, которое обычно решает их первым. - -| Режим отказа | Как это выглядит на практике | Первичный фикс | Поддерживающий артефакт | -| --- | --- | --- | --- | -| Cold-start confusion | Новая сессия тратит большую часть времени на повторное выяснение сетапа и статуса | Сделайте репозиторий системой учёта | `claude-progress.md` | -| Scope sprawl | Агент начинает несколько фич и ни одну из них не доводит до чистого конца | Ограничьте активный скоуп | `feature_list.json` | -| Premature completion | Агент заявляет о done после правок кода, но до runnable-доказательства | Привяжите завершение к доказательствам | `clean-state-checklist.md` | -| Fragile startup | Каждая сессия заново учится, как поднимать проект | Стандартизируйте сетап и верификацию | `init.sh` | -| Weak handoff | Следующая сессия не может понять, что верифицировано, что сломано и что дальше | Заканчивайте явным handoff | `session-handoff.md` | -| Subjective review | Качество ревью зависит от вкуса или памяти | Оценивайте вывод по фиксированным категориям | `evaluator-rubric.md` | - -## Операционный принцип - -Добавляйте наименьший артефакт, который напрямую адресует наблюдаемый режим отказа. Не пытайтесь решить любую проблему надёжности, сваливая всё больше текста в один глобальный файл инструкций. diff --git a/courses/learn-harness-engineering/docs/ru/resources/reference/prompt-calibration.md b/courses/learn-harness-engineering/docs/ru/resources/reference/prompt-calibration.md deleted file mode 100644 index d9e131bf..00000000 --- a/courses/learn-harness-engineering/docs/ru/resources/reference/prompt-calibration.md +++ /dev/null @@ -1,23 +0,0 @@ -# Калибровка промпта - -Корневые инструкции должны задавать операционный фрейм, а не каждое возможное действие. - -## Держите в корневом файле - -- цель и скоуп репозитория -- путь старта -- путь верификации -- неподлежащие обсуждению ограничения -- обязательные артефакты состояния -- правила окончания сессии - -## Уберите из корневого файла - -- длинные исторические edge-кейсы -- детали реализации, специфичные для тем -- локальные архитектурные заметки, которым место рядом с кодом -- примеры, применимые только к одной подсистеме - -## Рабочее правило - -Корневой файл должен помогать свежей сессии быстро сориентироваться. Если файл превращается в свалку каждого прошлого провала, вынесите детали в более мелкие документы и оставьте на них ссылки. diff --git a/courses/learn-harness-engineering/docs/ru/resources/templates/AGENTS.md b/courses/learn-harness-engineering/docs/ru/resources/templates/AGENTS.md deleted file mode 100644 index 9fcb4d69..00000000 --- a/courses/learn-harness-engineering/docs/ru/resources/templates/AGENTS.md +++ /dev/null @@ -1,50 +0,0 @@ -# AGENTS.md - -Этот репозиторий рассчитан на длительную работу с агентом-кодером. Цель — не максимизировать сырой объём кода. Цель — оставить репозиторий в состоянии, в котором следующая сессия сможет продолжить, не гадая. - -## Стартовый воркфлоу - -Перед написанием кода: - -1. Подтвердите рабочую директорию командой `pwd`. -2. Прочитайте `claude-progress.md`, чтобы узнать последнее проверенное состояние и следующий шаг. -3. Прочитайте `feature_list.json` и выберите незавершённую фичу с самым высоким приоритетом. -4. Просмотрите последние коммиты: `git log --oneline -5`. -5. Запустите `./init.sh`. -6. Перед началом новой работы выполните требуемую smoke- или end-to-end-верификацию. - -Если базовая верификация уже падает, в первую очередь чините именно её. Не наслаивайте новую работу над фичей поверх сломанного стартового состояния. - -## Рабочие правила - -- Работайте над одной фичей за раз. -- Не помечайте фичу как завершённую только потому, что был добавлен код. -- Удерживайте изменения внутри скоупа выбранной фичи, если только блокер не вынуждает сделать узкий вспомогательный фикс. -- Не меняйте втихую правила верификации в процессе реализации. -- Предпочитайте долговечные артефакты репозитория сводкам в чате. - -## Обязательные артефакты - -- `feature_list.json`: источник истины о состоянии фич -- `claude-progress.md`: лог сессии и текущий проверенный статус -- `init.sh`: стандартный путь старта и верификации -- `session-handoff.md`: опциональный компактный handoff для крупных сессий - -## Definition Of Done - -Фича считается завершённой только когда выполнено всё следующее: - -- целевое поведение реализовано -- требуемая верификация реально была запущена -- доказательство зафиксировано в `feature_list.json` или `claude-progress.md` -- репозиторий по-прежнему перезапускаем по стандартному стартовому пути - -## Окончание сессии - -Перед завершением сессии: - -1. Обновите `claude-progress.md`. -2. Обновите `feature_list.json`. -3. Запишите все нерешённые риски или блокеры. -4. Закоммитьте с описательным сообщением, как только работа в безопасном состоянии. -5. Оставьте репозиторий достаточно чистым, чтобы следующая сессия могла сразу запустить `./init.sh`. diff --git a/courses/learn-harness-engineering/docs/ru/resources/templates/CLAUDE.md b/courses/learn-harness-engineering/docs/ru/resources/templates/CLAUDE.md deleted file mode 100644 index e7169bab..00000000 --- a/courses/learn-harness-engineering/docs/ru/resources/templates/CLAUDE.md +++ /dev/null @@ -1,43 +0,0 @@ -# CLAUDE.md - -Вы работаете в репозитории, спроектированном под длительную работу по реализации. Ставьте надёжное завершение, непрерывность между сессиями и явную верификацию выше скорости. - -## Операционный цикл - -В начале каждой сессии: - -1. Запустите `pwd` и убедитесь, что вы в ожидаемом корне репозитория. -2. Прочитайте `claude-progress.md`. -3. Прочитайте `feature_list.json`. -4. Просмотрите последние коммиты: `git log --oneline -5`. -5. Запустите `./init.sh`. -6. Проверьте, не сломан ли уже базовый smoke- или end-to-end-путь. - -Затем выберите ровно одну незавершённую фичу и работайте только над ней, пока не верифицируете её или не задокументируете причину блокировки. - -## Правила - -- Одна активная фича за раз. -- Не заявляйте о завершении без runnable-доказательств. -- Не переписывайте список фич, чтобы скрыть незавершённую работу. -- Не удаляйте и не ослабляйте тесты только ради того, чтобы задача выглядела завершённой. -- Используйте артефакты репозитория как систему учёта. - -## Обязательные файлы - -- `feature_list.json` -- `claude-progress.md` -- `init.sh` -- `session-handoff.md`, когда полезен компактный handoff - -## Шлюз завершения - -Фича может перейти в `passing` только после того, как требуемая верификация прошла успешно и результат зафиксирован. - -## Перед остановкой - -1. Обновите лог прогресса. -2. Обновите состояние фич. -3. Запишите, что ещё сломано или не верифицировано. -4. Закоммитьте, как только репозиторий безопасно возобновляем. -5. Оставьте чистый путь рестарта для следующей сессии. diff --git a/courses/learn-harness-engineering/docs/ru/resources/templates/claude-progress.md b/courses/learn-harness-engineering/docs/ru/resources/templates/claude-progress.md deleted file mode 100644 index 93bc7238..00000000 --- a/courses/learn-harness-engineering/docs/ru/resources/templates/claude-progress.md +++ /dev/null @@ -1,35 +0,0 @@ -# Лог прогресса - -## Текущее проверенное состояние - -- Корень репозитория: -- Стандартный путь старта: -- Стандартный путь верификации: -- Текущая незавершённая фича с наивысшим приоритетом: -- Текущий блокер: - -## Лог сессий - -### Сессия 001 - -- Дата: -- Цель: -- Сделано: -- Запущенная верификация: -- Зафиксированные доказательства: -- Коммиты: -- Обновлённые файлы или артефакты: -- Известный риск или нерешённая проблема: -- Следующий лучший шаг: - -### Сессия 002 - -- Дата: -- Цель: -- Сделано: -- Запущенная верификация: -- Зафиксированные доказательства: -- Коммиты: -- Обновлённые файлы или артефакты: -- Известный риск или нерешённая проблема: -- Следующий лучший шаг: diff --git a/courses/learn-harness-engineering/docs/ru/resources/templates/clean-state-checklist.md b/courses/learn-harness-engineering/docs/ru/resources/templates/clean-state-checklist.md deleted file mode 100644 index 4ddfd844..00000000 --- a/courses/learn-harness-engineering/docs/ru/resources/templates/clean-state-checklist.md +++ /dev/null @@ -1,8 +0,0 @@ -# Чек-лист чистого состояния - -- [ ] Стандартный путь старта по-прежнему работает. -- [ ] Стандартный путь верификации по-прежнему запускается. -- [ ] Текущий прогресс зафиксирован в логе прогресса. -- [ ] Состояние фич отражает то, что реально проходит, против того, что не верифицировано. -- [ ] Не осталось незадокументированных недоделанных шагов. -- [ ] Следующая сессия может продолжить без ручного ремонта. diff --git a/courses/learn-harness-engineering/docs/ru/resources/templates/evaluator-rubric.md b/courses/learn-harness-engineering/docs/ru/resources/templates/evaluator-rubric.md deleted file mode 100644 index 76e3231d..00000000 --- a/courses/learn-harness-engineering/docs/ru/resources/templates/evaluator-rubric.md +++ /dev/null @@ -1,24 +0,0 @@ -# Рубрика evaluator - -Используйте эту рубрику после реализации и перед окончательным принятием. - -| Категория | Вопрос | Балл (0-2) | Заметки | -| --- | --- | --- | --- | -| Correctness | Соответствует ли реализованное поведение запрошенной фиче? | | | -| Verification | Реально ли были запущены требуемые проверки, с доказательствами? | | | -| Scope discipline | Оставалась ли сессия в рамках выбранного скоупа фичи? | | | -| Reliability | Переживает ли результат перезапуск или повторный прогон без ремонта? | | | -| Maintainability | Достаточно ли понятны код и документация для следующей сессии? | | | -| Handoff readiness | Может ли свежая сессия продолжить работу только по артефактам репозитория? | | | - -## Вердикт - -- Accept -- Revise -- Block - -## Требуемые действия - -- Отсутствующие доказательства: -- Требуемые фиксы: -- Триггер следующего ревью: diff --git a/courses/learn-harness-engineering/docs/ru/resources/templates/feature_list.json b/courses/learn-harness-engineering/docs/ru/resources/templates/feature_list.json deleted file mode 100644 index 138adc6a..00000000 --- a/courses/learn-harness-engineering/docs/ru/resources/templates/feature_list.json +++ /dev/null @@ -1,64 +0,0 @@ -{ - "project": "replace-with-project-name", - "last_updated": "YYYY-MM-DD", - "rules": { - "single_active_feature": true, - "passing_requires_evidence": true, - "do_not_skip_verification": true - }, - "status_legend": { - "not_started": "Work has not begun.", - "in_progress": "The feature is the current active task.", - "blocked": "Work cannot continue until a documented blocker is resolved.", - "passing": "Required verification has passed and evidence is recorded." - }, - "features": [ - { - "id": "chat-001", - "priority": 1, - "area": "chat", - "title": "Create a new conversation", - "user_visible_behavior": "A user can click New Chat and see a fresh empty conversation.", - "status": "not_started", - "verification": [ - "Open the app.", - "Click New Chat.", - "Verify a new conversation appears in the sidebar.", - "Verify the main panel shows an empty conversation state." - ], - "evidence": [], - "notes": "" - }, - { - "id": "chat-002", - "priority": 2, - "area": "chat", - "title": "Send a message in the current conversation", - "user_visible_behavior": "A user can submit a message and see it appear in the active thread.", - "status": "not_started", - "verification": [ - "Open an existing conversation.", - "Type a message into the input.", - "Submit the message.", - "Verify the new message appears in the thread." - ], - "evidence": [], - "notes": "" - }, - { - "id": "chat-003", - "priority": 3, - "area": "chat", - "title": "Persist the active conversation list", - "user_visible_behavior": "A user sees previously created conversations after restarting the app.", - "status": "not_started", - "verification": [ - "Create two conversations.", - "Restart the app.", - "Verify both conversations still appear in the sidebar." - ], - "evidence": [], - "notes": "" - } - ] -} diff --git a/courses/learn-harness-engineering/docs/ru/resources/templates/index.md b/courses/learn-harness-engineering/docs/ru/resources/templates/index.md deleted file mode 100644 index 7314b278..00000000 --- a/courses/learn-harness-engineering/docs/ru/resources/templates/index.md +++ /dev/null @@ -1,226 +0,0 @@ -# Гайд по шаблонам - -Эти шаблоны готовы к копированию в ваш проект. Каждый из них служит конкретной цели в воркфлоу агента. Редактируйте содержимое, чтобы оно соответствовало командам, путям, именам фич и шагам верификации вашего проекта. - -## Как начать - -Сначала скопируйте эти четыре файла в корень вашего проекта: - -1. `AGENTS.md` или `CLAUDE.md` -2. `init.sh` -3. `claude-progress.md` -4. `feature_list.json` - -Добавляйте остальные файлы по мере роста проекта. - ---- - -## AGENTS.md - -Корневой файл инструкций. Это первое, что читает агент в начале сессии. Он задаёт операционные правила: что делать перед написанием кода, как работать и как заканчивать. - -**Как использовать:** - -- Скопируйте в корневую директорию проекта -- Замените шаги стартового воркфлоу на реальные пути и команды вашего проекта -- Настройте рабочие правила под конвенции вашей команды -- Сохраните секцию definition of done — это самая важная часть - -**Что он делает для агента:** - -- Заставляет читать прогресс и состояние фич перед началом работы -- Требует работать над одной фичей за раз -- Требует доказательства, прежде чем что-либо помечать как done -- Определяет, как выглядит чистое окончание сессии - -Используйте `AGENTS.md` для Codex или других агентов. Используйте `CLAUDE.md`, если работаете с Claude Code — структура та же, просто отформатировано под стиль инструкций Claude. - -## init.sh - -Скрипт старта. Запускает установку зависимостей, верификацию и печатает команду запуска — всё за один проход. - -**Как использовать:** - -- Скопируйте в корень проекта -- Отредактируйте эти три переменные сверху: - - `INSTALL_CMD` — ваша команда установки зависимостей (например, `npm install`, `pip install -r requirements.txt`) - - `VERIFY_CMD` — базовая команда верификации (например, `npm test`, `pytest`) - - `START_CMD` — команда запуска dev-сервера (например, `npm run dev`) -- Сделайте его исполняемым: `chmod +x init.sh` - -**Что он делает:** - -1. Печатает текущую директорию (чтобы можно было убедиться, что он запускается в правильном месте) -2. Устанавливает зависимости -3. Выполняет команду верификации -4. Печатает команду запуска (или выполняет её, если установлен `RUN_START_COMMAND=1`) - -Если верификация падает, агент должен остановиться и починить базу, прежде чем делать что-либо ещё. - -## claude-progress.md - -Лог прогресса. Каждая сессия пишет в этот файл, и каждая новая сессия читает его первым делом. - -**Как использовать:** - -- Скопируйте в корень проекта -- Заполните секцию «Current Verified State» информацией о вашем проекте -- После каждой сессии обновляйте запись о сессии - -**Что значит каждое поле:** - -- **Current Verified State** — единственный источник истины о том, в каком состоянии проект - - `Repository root directory` — где живёт проект - - `Standard startup path` — команда для запуска проекта - - `Standard verification path` — команда для запуска тестов - - `Highest priority unfinished feature` — над чем должна работать следующая сессия - - `Current blocker` — всё, что застопорилось -- **Session Record** — одна запись на сессию - - `Goal` — что вы планировали сделать - - `Completed` — что реально было сделано - - `Verification run` — какие тесты были запущены - - `Evidence recorded` — какие доказательства были зафиксированы - - `Commits` — что было закоммичено - - `Known risks` — что может быть сломано - - `Next best action` — с чего должна начать следующая сессия - -## feature_list.json - -Трекер фич. Машиночитаемый список всех фич, которые агенту нужно реализовать, вместе со статусом, шагами верификации и доказательствами. - -**Как использовать:** - -- Скопируйте в корень проекта -- Замените примеры фич на свои -- Каждой фиче нужно: - - `id` — короткий уникальный идентификатор - - `priority` — целое число, меньше = выше приоритет - - `area` — какая часть приложения (например, "chat", "import", "search") - - `title` — короткое описание - - `user_visible_behavior` — что пользователь должен увидеть, когда оно работает - - `status` — одно из `not_started`, `in_progress`, `blocked`, `passing` - - `verification` — пошаговые инструкции для подтверждения, что оно работает - - `evidence` — зафиксированное доказательство того, что верификация прошла (заполняется агентом) - - `notes` — любой дополнительный контекст - -**Правила статусов:** - -- `not_started` — не трогали -- `in_progress` — единственная фича, над которой сейчас идёт работа (только одна за раз) -- `blocked` — не может продолжаться из-за задокументированной проблемы -- `passing` — верификация прошла, и доказательство зафиксировано - -У агента в любой момент должна быть только одна фича в `in_progress`. - -## session-handoff.md - -Компактная заметка-передача между сессиями. Используйте её, когда сессия заканчивается, и вы хотите, чтобы следующая быстро подхватила работу. - -**Как использовать:** - -- Скопируйте в корень проекта -- Заполняйте в конце каждой сессии (или попросите агента заполнить) - -**Что покрывает каждая секция:** - -- **Currently verified** — что подтверждённо работает и какая верификация была запущена -- **Changes this session** — что изменилось в коде или инфраструктуре -- **Still broken or unverified** — известные проблемы и рискованные участки -- **Next best action** — что должна сделать следующая сессия и чего не трогать -- **Commands** — команды старта, верификации и отладки для быстрого доступа - -Этот файл опционален для коротких сессий. Он становится важным, когда сессии длинные или когда у проекта несколько активных областей. - -## clean-state-checklist.md - -Чек-лист, который нужно пройти перед окончанием каждой сессии. Гарантирует, что репозиторий в хорошем состоянии для чистого старта следующей сессии. - -**Как использовать:** - -- Скопируйте в корень проекта -- Пройдитесь по нему перед тем, как закрыть сессию -- Агент тоже должен проверять эти пункты как часть своей end-of-session рутины - -**Что он проверяет:** - -- Стандартный старт всё ещё работает -- Стандартная верификация всё ещё запускается -- Лог прогресса обновлён -- Список фич отражает реальное состояние (нет ложных записей `passing`) -- Никаких незаписанных недоделанных шагов -- Следующая сессия может продолжить без ручного ремонта - -## evaluator-rubric.md - -Оценочный лист для ревью качества вывода агента. Используйте после сессии или на майлстоунах проекта, чтобы оценить, дотягивает ли работа до планки. - -**Как использовать:** - -- Скопируйте в корень проекта -- После сессии (или набора сессий) оценивайте работу агента по шести измерениям -- Каждое измерение оценивается 0–2 - -**Шесть измерений:** - -1. **Correctness** — соответствует ли реализация целевому поведению? -2. **Verification** — реально ли были запущены требуемые проверки, с доказательствами? -3. **Scope discipline** — оставался ли агент в рамках выбранной фичи? -4. **Reliability** — переживает ли результат перезапуск или повторный прогон? -5. **Maintainability** — достаточно ли понятны код и документация для следующей сессии? -6. **Handoff readiness** — может ли новая сессия продолжить, опираясь только на артефакты репозитория? - -**Варианты заключения:** - -- Accept — соответствует планке -- Revise — нужны фиксы перед принятием -- Block — фундаментальные проблемы, которые надо решать в первую очередь - -**Важно: evaluator нужно калибровать.** «Из коробки» агенты — плохие судьи самих себя: они выявляют проблемы, а потом уговаривают себя одобрить. Вам придётся итерировать: - -1. Запустите evaluator на завершённом спринте. -2. Сравните его оценки с собственным человеческим суждением. -3. Где они расходятся — сделайте рубрику конкретнее по критериям pass/fail. -4. Перезапустите и проверьте сходимость. -5. Повторяйте, пока evaluator не начнёт стабильно совпадать с человеческим ревью. - -Закладывайте 3–5 раундов калибровки. Записывайте каждое изменение, чтобы отслеживать, что улучшило сходимость. - -## quality-document.md - -Снимок качества, оценивающий каждый продуктовый домен и архитектурный слой вашего проекта. Отслеживает здоровье кодовой базы во времени, а не только вывод отдельной сессии. - -**Как использовать:** - -- Скопируйте в корень проекта -- Перед началом сессии: прочитайте, чтобы понять, где кодовая база слабее всего -- После сессии: обновите оценки на основе того, что изменилось -- Со временем: сравнивайте снимки, чтобы увидеть, какие изменения harness реально улучшили здоровье кодовой базы - -**Что он оценивает:** - -- **Продуктовые домены** (например, импорт документов, Q&A-флоу, индексация): каждому домену ставится оценка (A–D) по статусу верификации, читаемости для агента, стабильности тестов и ключевым пробелам -- **Архитектурные слои** (например, main process, preload, renderer, services): каждому слою ставится оценка за соблюдение границ и читаемость для агента - -**Почему это важно:** - -Рубрика evaluator оценивает отдельные выводы агента. Quality-документ оценивает саму кодовую базу. Они отвечают на разные вопросы: - -- Рубрика evaluator: «Хорошо ли агент сработал в этой сессии?» -- Quality-документ: «Проект становится сильнее или слабее со временем?» - -**Когда обновлять:** - -- После каждой значимой сессии -- Перед сравнениями бенчмарков -- После проходов по уборке или упрощению -- При онбординге нового агента или модели в проект - -**Связь с упрощением harness:** - -Quality-документ также поддерживает упрощение harness. Каждый компонент harness кодирует предположение о том, чего модель не умеет. По мере улучшения моделей эти предположения устаревают. Чтобы проверить, нужен ли компонент: - -1. Сделайте снимок quality-документа. -2. Удалите один компонент harness. -3. Прогоните набор бенчмарк-задач. -4. Сделайте ещё один снимок. -5. Сравните — если оценки не упали, компонент был излишним. Если упали — верните его обратно. diff --git a/courses/learn-harness-engineering/docs/ru/resources/templates/init.sh b/courses/learn-harness-engineering/docs/ru/resources/templates/init.sh deleted file mode 100755 index d7522d66..00000000 --- a/courses/learn-harness-engineering/docs/ru/resources/templates/init.sh +++ /dev/null @@ -1,29 +0,0 @@ -#!/usr/bin/env bash - -set -euo pipefail - -ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" -cd "$ROOT_DIR" - -# Replace these commands with the correct commands for your repository. -INSTALL_CMD=(npm install) -VERIFY_CMD=(npm test) -START_CMD=(npm run dev) - -echo "==> Working directory: $PWD" -echo "==> Syncing dependencies" -"${INSTALL_CMD[@]}" - -echo "==> Running baseline verification" -"${VERIFY_CMD[@]}" - -echo "==> Startup command" -printf ' %q' "${START_CMD[@]}" -printf '\n' - -if [ "${RUN_START_COMMAND:-0}" = "1" ]; then - echo "==> Starting the app" - exec "${START_CMD[@]}" -fi - -echo "Set RUN_START_COMMAND=1 if you want init.sh to launch the app directly." diff --git a/courses/learn-harness-engineering/docs/ru/resources/templates/quality-document.md b/courses/learn-harness-engineering/docs/ru/resources/templates/quality-document.md deleted file mode 100644 index 502f6156..00000000 --- a/courses/learn-harness-engineering/docs/ru/resources/templates/quality-document.md +++ /dev/null @@ -1,43 +0,0 @@ -# Quality-документ - -Снимок качества для каждого продуктового домена и архитектурного слоя. Как агенты, так и люди могут использовать этот документ, чтобы быстро понять, где кодовая база сильна, а где требует работы. - -**Частота обновления:** после каждой значимой сессии или перед началом новой фазы работы. - -**Шкала оценок:** - -- **A**: вся верификация проходит, чистая архитектура, читаемо для агента, стабильные тесты -- **B**: верификация проходит, в основном чисто, незначительные пробелы в читаемости или покрытии тестами -- **C**: частично работает, известные пробелы, некоторые участки кода трудны для понимания агентом -- **D**: не работает или серьёзные структурные проблемы - ---- - -## Продуктовые домены - -| Домен | Оценка | Верификация | Читаемость для агента | Стабильность тестов | Ключевые пробелы | Последнее обновление | -|--------|-------|-------------|-----------------|---------------|----------|-------------| -| Импорт документов | - | - | - | - | - | - | -| Управление документами | - | - | - | - | - | - | -| Индексация документов | - | - | - | - | - | - | -| Q&A-флоу | - | - | - | - | - | - | -| Обоснованные ответы | - | - | - | - | - | - | - -## Архитектурные слои - -| Слой | Оценка | Соблюдение границ | Читаемость для агента | Ключевые пробелы | Последнее обновление | -|-------|-------|---------------------|-----------------|----------|-------------| -| Main Process | - | - | - | - | - | -| Preload | - | - | - | - | - | -| Renderer | - | - | - | - | - | -| Services | - | - | - | - | - | - -## История изменений - -### YYYY-MM-DD - -- Изменения: -- Повышенные домены: -- Понижены: -- Новые выявленные пробелы: -- Закрытые пробелы: diff --git a/courses/learn-harness-engineering/docs/ru/resources/templates/session-handoff.md b/courses/learn-harness-engineering/docs/ru/resources/templates/session-handoff.md deleted file mode 100644 index 57535893..00000000 --- a/courses/learn-harness-engineering/docs/ru/resources/templates/session-handoff.md +++ /dev/null @@ -1,30 +0,0 @@ -# Session Handoff - -## Проверено сейчас - -- Что сейчас работает: -- Какая верификация реально была запущена: - -## Изменено в этой сессии - -- Добавленный код или поведение: -- Изменения в инфраструктуре или harness: - -## Сломано или не верифицировано - -- Известный дефект: -- Не верифицированный путь: -- Риск для следующей сессии: - -## Следующий лучший шаг - -- Незавершённая фича с наивысшим приоритетом: -- Почему именно она следующая: -- Что считается прохождением: -- Что нельзя менять во время этого шага: - -## Команды - -- Старт: -- Верификация: -- Точечная команда отладки: diff --git a/courses/learn-harness-engineering/docs/ru/skills/index.md b/courses/learn-harness-engineering/docs/ru/skills/index.md deleted file mode 100644 index a374c441..00000000 --- a/courses/learn-harness-engineering/docs/ru/skills/index.md +++ /dev/null @@ -1,49 +0,0 @@ -# Skills - -Эта директория содержит встроенные скилы AI-агентов, которые поставляются с курсом. Скилы — это самодостаточные шаблоны промптов, которые могут быть загружены AI-агентами для написания кода (Claude Code, Codex, Cursor, Windsurf и т. д.) для выполнения специализированных задач. - -## harness-creator - -Production-grade скил по harness-инжинирингу для AI-агентов. Помогает создавать, оценивать и улучшать пять ключевых подсистем harness: инструкции, состояние, верификация, скоуп и жизненный цикл сессии. - -### Что он делает - -- **Создаёт harness с нуля** — AGENTS.md, списки фич, флоу верификации -- **Улучшает существующие harness** — оценка по пяти подсистемам с приоритизированными улучшениями -- **Проектирует непрерывность сессий** — персистентность памяти, отслеживание прогресса, процедуры handoff -- **Применяет production-паттерны** — память, контекст-инжиниринг, безопасность инструментов, координация мульти-агентов - -### Быстрый старт - -Файлы скила лежат в репозитории по пути [`skills/harness-creator/`](https://github.com/walkinglabs/learn-harness-engineering/tree/main/skills/harness-creator). - -Чтобы использовать его с Claude Code, скопируйте директорию `harness-creator/` в путь скилов вашего проекта или направьте агент на файл SKILL.md. - -### Референсные паттерны - -Скил включает 6 углублённых референсных документов: - -| Паттерн | Когда применять | -|---------|-------------| -| Memory Persistence | Агент забывает между сессиями | -| Context Engineering | Управление бюджетом контекста, JIT-загрузка | -| Tool Registry | Безопасность инструментов, контроль конкурентности | -| Multi-Agent Coordination | Параллелизм, флоу со специализацией | -| Lifecycle & Bootstrap | Хуки, фоновые задачи, инициализация | -| Gotchas | 15 неочевидных режимов отказа с фиксами | - -### Шаблоны - -Скил содержит готовые к использованию шаблоны: - -- `agents.md` — каркас AGENTS.md с рабочими правилами -- `feature-list.json` — JSON Schema + пример списка фич -- `init.sh` — стандартный инициализационный скрипт -- `progress.md` — шаблон лога прогресса сессии - -### Как был построен этот скил - -`harness-creator` был разработан с использованием методологии **skill-creator** — официальной мета-скила Anthropic для создания, тестирования и итерации над скилами агентов. skill-creator предоставляет структурированный воркфлоу (draft → test → evaluate → iterate) со встроенными eval-runner'ами, грейдерами и средством просмотра бенчмарков. - -- **Исходник skill-creator**: [anthropics/skills — skill-creator](https://github.com/anthropics/skills/tree/main/skills/skill-creator) -- **Документация по скилам Claude Code**: [anthropics/claude-code — plugin-dev/skills](https://github.com/anthropics/claude-code/tree/main/plugins/plugin-dev/skills) diff --git a/courses/learn-harness-engineering/docs/uz/GLOSSARY.md b/courses/learn-harness-engineering/docs/uz/GLOSSARY.md deleted file mode 100644 index d3a7fdd1..00000000 --- a/courses/learn-harness-engineering/docs/uz/GLOSSARY.md +++ /dev/null @@ -1,377 +0,0 @@ -# Terminologik glossary — Harness Engineering - -Ushbu hujjat barcha tarjimalarda atamalar bir xil qoʻllanishini taʼminlaydi. Yangi maʼruza yoki loyiha tarjima qilinganda — avval shu hujjat bilan tekshirib chiqing, ixtiro qilmang. - -**Tartib:** har bir atama uchun: -- **EN** — inglizcha asl -- **UZ** — standart oʻzbekcha tarjima (yoki `[saqlanadi]` — inglizcha qoldiriladi) -- **Tafsif** — qisqa izoh -- **Qoʻllash** — qachon, qayerda, qanday yozish - ---- - -## 1. Markaziy tushunchalar (Core) - -### Harness -- **EN:** Harness -- **UZ:** Harness *[saqlanadi — kalit atama]* -- **Tafsif:** Modeldan tashqaridagi barcha muhandislik infratuzilmasi — yoʻriqnomalar, vositalar, muhit, holat boshqaruvi, tekshiruv tizimi. -- **Qoʻllash:** Birinchi marta uchraganda — `harness (egar)` deb metafora bilan beriladi. Keyin oddiy `harness`. Suffikslar: `harnessʼga`, `harnessʼni`, `harnessʼlangan` (qoʻshma keng ishlatilmasin — "harnessga ega" afzal). - -### Agent -- **EN:** AI agent / coding agent / agent -- **UZ:** Agent *[saqlanadi]* -- **Tafsif:** Avtonom kod yozish/oʻqish/tahrirlash imkoniyatiga ega LLM-asoslangan dastur (Codex, Claude Code va h.k.). -- **Qoʻllash:** Doim `agent` (kichik harf, tarjima qilinmaydi). "Sun'iy intellekt agenti" — birinchi marta keng kontekstda. Keyin shunchaki `agent`. - -### Repository -- **EN:** Repository / repo -- **UZ:** Repozitoriy / repo *[saqlanadi]* -- **Tafsif:** Git boshqaruvidagi kod va hujjatlar majmuasi. -- **Qoʻllash:** Birinchi marta — `repozitoriy (repo)`. Keyin `repo`. Yopiq atamalar: `repo strukturasi`, `repo ildizi`, `repo tarixi`. - -### Model -- **EN:** Model / LLM -- **UZ:** Model *[saqlanadi]* -- **Tafsif:** Til modeli (GPT-4o, Claude Opus, va h.k.) — agentning "miya"si. -- **Qoʻllash:** `model og'irliklari` (model weights) — texnik termin sifatida saqlanadi. - -### Prompt -- **EN:** Prompt -- **UZ:** Prompt *[saqlanadi]* -- **Tafsif:** Foydalanuvchi tomonidan modelga beriladigan koʻrsatma matni. -- **Qoʻllash:** "system prompt", "user prompt" — inglizcha qoldiriladi. Suffikslar: `promptʼda`, `promptʼning`. - -### Context window -- **EN:** Context window -- **UZ:** Kontekst oynasi *yoki* `context window` *[saqlanadi]* -- **Tafsif:** Modelning bir vaqt ichida koʻra oladigan matn hajmi (token bilan oʻlchanadi). -- **Qoʻllash:** Texnik kontekstda inglizcha. Tushuntirish kontekstida `kontekst oynasi` ishlatilishi mumkin. - ---- - -## 2. Asosiy muvaffaqiyatsizlik turlari (Failure layers) - -### Capability Gap -- **EN:** Capability Gap -- **UZ:** Capability Gap (imkoniyatlar tafovuti) -- **Tafsif:** Modelning benchmark natijalari va haqiqiy vazifalardagi natijalari oʻrtasidagi farq. -- **Qoʻllash:** Birinchi marta — toʻliq atama (qavs ichida tarjima). Keyin — `Capability Gap` yoki `imkoniyatlar tafovuti`. - -### Verification Gap -- **EN:** Verification Gap -- **UZ:** Verification Gap (tekshiruv tafovuti) -- **Tafsif:** Agentning oʻz natijasiga ishonchi va haqiqiy toʻgʻrilik oʻrtasidagi farq. -- **Qoʻllash:** Atamani saqlang; tarjimasi qavsda. Suffikslar: `verification gapʼingiz`. - -### Harness-Induced Failure -- **EN:** Harness-Induced Failure -- **UZ:** Harness-Induced Failure (harness keltirib chiqargan muvaffaqiyatsizlik) -- **Tafsif:** Modelda imkoniyat bor, lekin muhitda nuqson tufayli yiqilish. -- **Qoʻllash:** Toʻliq atama saqlanadi. Tarjimaga "harness sabab" deb yozmang — "harness keltirib chiqargan" toʻgʻri. - -### Context Anxiety -- **EN:** Context anxiety -- **UZ:** Kontekst xavotiri (context anxiety) -- **Tafsif:** Agent kontekst tugayotganini sezsa shoshilib oddiy yechimni tanlaydigan hodisa. -- **Qoʻllash:** Birinchi marta toʻliq, keyin `kontekst xavotiri`. - -### Five Failure Layers (Beshta muvaffaqiyatsizlik qatlami) - -Bularning tarjimasi har joyda bir xil boʻlsin: - -1. **Task specification** → `vazifa spetsifikatsiyasi` -2. **Context provision** → `kontekst taʼminoti` -3. **Execution environment** → `bajarilish muhiti` -4. **Verification feedback** → `tekshiruv qayta aloqasi` -5. **State management** → `holat boshqaruvi` - -> **Diqqat:** "feedback" har joyda bir xil tarjima qilinmaydi: -> - "verification feedback" → `tekshiruv qayta aloqasi` -> - "feedback loop" → `qayta aloqa sikli` -> - "runtime feedback" → `runtime fikr-mulohaza` *yoki* `runtime qayta aloqa` - ---- - -## 3. Jarayon va metodologiya (Process) - -### Diagnostic Loop -- **EN:** Diagnostic Loop -- **UZ:** Diagnostik sikl (diagnostic loop) -- **Tafsif:** Bajarish → muvaffaqiyatsizlikni kuzatish → qatlamga bogʻlash → tuzatish → qaytadan bajarish. -- **Qoʻllash:** `Diagnostik sikl` — asosiy. Inglizcha qavsda birinchi marta. - -### Definition of Done (DoD) -- **EN:** Definition of Done / DoD -- **UZ:** Definition of Done (bajarilganlik mezonlari) / DoD -- **Tafsif:** Mashina tekshirishi mumkin boʻlgan tugatish shartlari toʻplami. -- **Qoʻllash:** - - Birinchi marta toʻliq. - - Keyin **DoD** abbreviature OK. - - Tarjimada **bajarilganlik mezonlari** ishlatiladi (NOT "tugatildi taʼrifi", NOT "tugatish mezonlari"). - -### Initialization phase -- **EN:** Initialization / Init phase -- **UZ:** Inisializatsiya / inisializatsiya bosqichi -- **Tafsif:** Agent ish boshlashidan oldin loyiha bilan tanishish va muhitni sozlash bosqichi. -- **Qoʻllash:** `init.sh` — fayl nomi, tarjima qilinmaydi. - -### Handoff -- **EN:** Handoff -- **UZ:** Topshirish (handoff) -- **Tafsif:** Bir sessiyadan keyingisiga, yoki agentdan odamga ish holatini topshirish. -- **Qoʻllash:** `claude-progress.md` — handoff fayli, nomi saqlanadi. - -### Session -- **EN:** Session -- **UZ:** Sessiya *[saqlanadi]* -- **Tafsif:** Bir agent ishlashining boshidan oxirigacha boʻlgan davri. -- **Qoʻllash:** `koʻp sessiyali`, `sessiyalar oraligʻi`, `sessiyalararo`. - -### Diagnostic loop attribution -- **EN:** Attribute failure to layer -- **UZ:** Muvaffaqiyatsizlikni qatlamga bogʻlash -- **Qoʻllash:** "atribut qilish" kalka — ishlatmang. "Bogʻlash" yoki "tegishli qatlamga ajratish". - ---- - -## 4. Infratuzilma fayllari (Files) - -Quyidagi fayl nomlari **doim saqlanadi** — tarjima qilinmaydi: - -| Fayl | Vazifasi | -|------|----------| -| `AGENTS.md` | Agent uchun loyiha boʻyicha asosiy yoʻriqnoma | -| `CLAUDE.md` | Claude Code uchun maxsus yoʻriqnoma | -| `feature_list.json` | Loyiha funksiyalari roʻyxati | -| `init.sh` | Inisializatsiya skripti | -| `claude-progress.md` | Sessiyalar oraligʻi handoff fayli | - ---- - -## 5. Sifat va tekshiruv (Quality) - -### Test -- **EN:** Test / unit test / integration test / E2E test -- **UZ:** Test *[saqlanadi]* -- **Qoʻllash:** `unit test`, `integration test`, `end-to-end test` — inglizcha. `pytest`, `vitest` — fayl nomi sifatida. - -### Lint -- **EN:** Lint / linter -- **UZ:** Lint *[saqlanadi]* -- **Qoʻllash:** "lint toza" (clean lint), "lint xatolari" (lint errors). - -### Type check -- **EN:** Type check / type checking -- **UZ:** Type check *[saqlanadi]* -- **Qoʻllash:** **NOT** "tip tekshiruvi" — yarim-tarjima. Toʻliq inglizcha qoldiriladi (mypy, tsc, pyright kontekstlarida). - -### End-to-end testing -- **EN:** End-to-end testing / E2E -- **UZ:** End-to-end testlash / E2E test -- **Tafsif:** Toʻliq pipeline (boshidan oxirigacha) test. -- **Qoʻllash:** `E2E` qisqartmasi keyingi marotabalarda OK. - -### Observability -- **EN:** Observability -- **UZ:** Kuzatuvchanlik (observability) -- **Tafsif:** Tizim ichida nima sodir boʻlayotganini kuzatish va tushunish imkoniyati. -- **Qoʻllash:** `kuzatuvchanlik` — texnik atama. Keng oʻzbek tilida kam ishlatiladi, shuning uchun birinchi marta toʻliq inglizcha qavsda. - -### Pipeline -- **EN:** Pipeline / CI pipeline -- **UZ:** Pipeline *[saqlanadi]* - -### Benchmark -- **EN:** Benchmark / SWE-bench -- **UZ:** Benchmark *[saqlanadi]* -- **Qoʻllash:** `SWE-bench Verified`, `benchmark natijalari`. Tarjima qilinmaydi. - -### Runtime -- **EN:** Runtime -- **UZ:** Runtime *[saqlanadi]* -- **Qoʻllash:** "runtime xatoliklari" (runtime errors), "runtime fikr-mulohaza" (runtime feedback). - ---- - -## 6. Loyihalar va arxitektura (Architecture) - -### System of Record (SoR) -- **EN:** System of Record / SoR -- **UZ:** Yagona haqiqat manbai (system of record) -- **Tafsif:** Tizim holatining yagona, ishonchli manbai (3-maʼruza markaziy tushunchasi). -- **Qoʻllash:** Birinchi marta toʻliq. Keyin `SoR` yoki `yagona haqiqat manbai`. NOT "yozuv tizimi" (kalka). - -### Feature list -- **EN:** Feature list -- **UZ:** Funksiyalar roʻyxati / feature list -- **Qoʻllash:** Loyihaning rasmiy `feature_list.json` fayliga ishora qilganda — atama saqlanadi. Umumiy maʼnoda — `funksiyalar roʻyxati`. - -### Workspace -- **EN:** Workspace / agent-readable workspace -- **UZ:** Ish maydoni / agent oʻqiy oladigan ish maydoni -- **Qoʻllash:** Loyiha-2 nomi. - -### Tech stack -- **EN:** Tech stack -- **UZ:** Tech stack *[saqlanadi]* -- **Qoʻllash:** **NOT** "texnologik stak" (yarim-tarjima). Inglizcha qoldiriladi: `loyihaning tech stack'i`. - -### Tool -- **EN:** Tool / tools / tooling -- **UZ:** Vosita / vositalar -- **Tafsif:** Agent foydalanadigan dasturiy vositalar (shell, file editor, test runner, va h.k.). -- **Qoʻllash:** **Doim** `vosita` deb tarjima qilinadi (NOT preserved English). "Vosita" oʻzbekchada "tool" ning tabiiy ekvivalenti. Mermaid block'larda ham `Vositalar`. Texnik kompozit atamalar (`tool calling`, `tool use`) — toʻliq inglizcha qoldirilishi mumkin. - -### Architecture decision record (ADR) -- **EN:** ADR / Architecture Decision Record -- **UZ:** Arxitektura qarorlari yozuvlari (ADR) -- **Qoʻllash:** Birinchi marta toʻliq, keyin ADR. - ---- - -## 7. Agent xatti-harakati (Agent behavior) - -### Overreach / Under-finish -- **EN:** Agents overreach and under-finish -- **UZ:** Agentlar haddan oshib, oxirigacha yetmaydi -- **Tafsif:** Agent skopdan tashqariga chiqib ketadi va asosiy vazifani tugatmaydi (7-maʼruza). - -### Declare victory too early -- **EN:** Declare victory too early -- **UZ:** Vaqtidan oldin gʻalabani eʼlon qilish *yoki* `vaqtidan oldin "tugadim" deyish` -- **Qoʻllash:** Idiomatik. "Gʻalaba eʼlon qilish" — kalka boʻlsa-da, oʻzbekchada tushunarli. - -### Scope control -- **EN:** Scope control -- **UZ:** Skoup nazorati (scope control) -- **Tafsif:** Agent vazifa chegarasidan chiqmasligini taʼminlash. - -### Self-verification -- **EN:** Self-verification -- **UZ:** Oʻz-oʻzini tekshirish -- **Qoʻllash:** "self-verification" inglizcha qavsda birinchi marta. - -### Grounded Q&A -- **EN:** Grounded Q&A / Grounded answers -- **UZ:** Asoslangan savol-javob (grounded Q&A) -- **Tafsif:** Javob har bir tasdiq uchun manba (kod yoki hujjatda) bilan asoslangan. - ---- - -## 8. Uslubiy atamalar (Style) - -### Bare environment -- **EN:** Bare environment / no support -- **UZ:** Qoʻshimcha tuzilmasiz muhit *yoki* `harness'siz muhit` -- **Qoʻllash:** **NOT** "yalangʻoch muhit" (kalka). Toʻgʻri rendering — `qoʻshimcha tuzilmasiz`. - -### Out of the box -- **EN:** Out of the box -- **UZ:** Boshlangʻich holatda *yoki* `qutidan chiqqan zahoti` -- **Qoʻllash:** "qutidan tashqarida" — kalka, ishlatmang. - -### Reach for your wallet -- **EN:** Reach for your wallet -- **UZ:** Pul sarflashga shoshilmoq -- **Qoʻllash:** **NOT** "hamyonga qoʻl urmoq" (kalka — oʻzbekchada bunday ibora yoʻq). - -### Time to upgrade -- **EN:** Time to upgrade / Time to X -- **UZ:** X vaqti keldi *(NOT "X-ga vaqt keldi")* -- **Misol:** "Time to upgrade" → "Yangiroq modelga oʻtish vaqti keldi" (NOT "Yaxshilashga vaqt keldi"). - -### Looks reasonable -- **EN:** Code that looks reasonable -- **UZ:** Yuzaki qaraganda mantiqli koʻringan kod -- **Qoʻllash:** **NOT** "koʻrinishidan oqilona" (kalka). - -### Run (experiment) -- **EN:** First run / second run / independent run -- **UZ:** Birinchi sinov / ikkinchi sinov / mustaqil sinov -- **Qoʻllash:** **NOT** "yugurish" (sport maʼnosida noʻgʻri). `Sinov` — texnik tajriba. - -### Gimmick -- **EN:** Gimmick -- **UZ:** Hiyla *yoki* `effekt uchun qilingan ish` -- **Qoʻllash:** **NOT** "oʻyin" (oʻyin = game, boshqa maʼno). - -### Down-to-earth -- **EN:** Down-to-earth example -- **UZ:** Hayotiy misol -- **Qoʻllash:** **NOT** "amaliy misol" (juda umumiy). - -### Wallet/money idioms -- "burn money/budget" → `pul sarflash` -- "expensive option" → `qimmat variant` -- "ROI-yuqori" — saqlanadi (ROI'ga ega) - ---- - -## 9. Punktuatsiya va imlo (Orthography) - -| Notoʻgʻri | Toʻgʻri | Eslatma | -|-----------|---------|---------| -| `o'`, `O'`, `g'`, `G'` | `oʻ`, `Oʻ`, `gʻ`, `Gʻ` | U+02BB | -| `ma'ruza`, `sun'iy`, `e'lon`, `ta'lim` | `maʼruza`, `sunʼiy`, `eʼlon`, `taʼlim` | U+02BC | -| `"matn"` (matn ichida) | `"matn"` | Egma tirnoq | -| HTML `class="..."` | `class="..."` | Toʻgʻri tirnoq | -| `qaer`, `qaerda` | `qayer`, `qayerda` | Imloviy xato | -| `senariy` | `ssenariy` | Russian-borrowed, double s | -| `etarli` (so'z boshida) | `yetarli` | y kerak | -| `xayron`, `xolat`, `xokim` | `hayron`, `holat`, `hokim` | h/x adashishi | -| `xech` | `hech` | h, NOT x | -| `shablon` | `andoza` | Lugʻat tanlovi | -| `tip tekshiruvi` | `type check` | Ingliz texnik atamani saqlang | - ---- - -## 10. Saqlanadigan inglizcha atamalar — toʻliq roʻyxat - -Quyidagilar **hech qachon tarjima qilinmaydi** (oʻzbekcha matn ichida ham inglizcha yoziladi): - -`Harness`, `agent`, `prompt`, `repository`, `repo`, `pull request`, `PR`, `commit`, `endpoint`, `pipeline`, `runtime`, `context window`, `lint`, `test`, `type check`, `benchmark`, `feature list`, `init`, `tech stack`, `framework`, `Markdown`, `JSON`, `YAML`, `JSONL`, `API`, `IPC`, `Electron`, `React`, `TypeScript`, `Codex`, `Claude Code`, `OpenAI`, `Anthropic`, `LLM`, `SDK`, `CLI`, `IDE`, `IDE-extension`, `MCP`, `webhook`, `mock`, `stub`, `branch`, `merge`, `rebase`, `diff`, `staging`, `production`, `deploy`, `build`, `dev`, `prod`. - -**Diqqat — saqlanmaydigan, tarjima qilinadigan atamalar:** -- `tool` → **vosita** (NOT preserved English) -- `template` → **andoza** (NOT shablon, NOT preserved English) -- `subsystem` → **quyi tizim** (defissiz) -- `framework` → **freymvork** (oʻzbek transliteratsiyasi, NOT preserved English) -- `isometric model control` → **izometrik model boshqaruvi** (birinchi marta qavsda tushuntirish: "modelni oʻzgarmas saqlab, harness komponentlarini birma-bir olib tashlab tekshirish usuli") -- `package manager` izoh: "X vs Y" → "X yoki Y" (NOT "X oʻrniga Y", NOT "X va Y orasida") - -Suffikslar bilan: `harnessʼga`, `Codexʼni`, `OpenAIʼning`, `repoʼda`, `endpointʼlar` (lotin atamadan keyin `ʼ` belgisi qoʻyiladi). - ---- - -## 11. Suffikslar va grammatik birikma - -Inglizcha ot ortidan oʻzbekcha qoʻshimcha: -- Egalik kelishigi: `OpenAIʼning`, `Anthropicʼning` (NOT `OpenAIning`) -- Tushum: `Codexʼni`, `agentʼni` -- Joʻnalish: `harnessʼga`, `repoʼga` -- Oʻrin: `repoʼda`, `endpointʼda` -- Chiqish: `repoʼdan`, `kontekstʼdan` -- Koʻplik: `agentlar`, `endpointʼlar`, `harnessʼlar` - -**Qoida:** Lotin asosli (oʻzbek bo'lmagan) ot va oʻzbek qoʻshimchasi orasida `ʼ` (U+02BC) qoʻyiladi. - -Istisno: oddiy ko'plik `lar/lar` — `ʼ`siz ham koʻp uchraydi (`agentlar` toʻgʻri, `agentʼlar` ham toʻgʻri). Bir xillik uchun: koʻplikda `ʼ`siz, qolgan kelishiklarda `ʼ` bilan. - ---- - -## 12. Yangi atama uchragan paytda - -Tarjimon yangi atamaga duch kelganda: - -1. Avval ushbu glossary'ni qidirib chiqing. -2. Mavjud bo'lmasa — quyidagi tartibda yondashing: - - **Texnik atama** (dasturlash, AI, devops) → inglizcha saqlang. - - **Umumiy soʻz** → tabiiy oʻzbek tarjimasini bering. - - **Idioma/metafora** → mazmunan, kalkasiz. -3. Yangi qaror — bu hujjatga qoʻshing (Pull Request orqali). -4. Toʻgʻri ma'noni LQA agent yordamida tasdiqlang. - ---- - -*Versiya: 0.1 — bunday tarjima boshlanishida tayyorlangan. Har bir maʼruza/loyiha tarjimasidan keyin kengaytiriladi.* diff --git a/courses/learn-harness-engineering/docs/uz/STYLE-GUIDE.md b/courses/learn-harness-engineering/docs/uz/STYLE-GUIDE.md deleted file mode 100644 index c0c669e3..00000000 --- a/courses/learn-harness-engineering/docs/uz/STYLE-GUIDE.md +++ /dev/null @@ -1,127 +0,0 @@ -# Oʻzbekcha tarjima uslub qoʻllanmasi - -Ushbu hujjat `docs/uz/` ostidagi barcha tarjimalar uchun majburiy. LQA tekshiruvi shu qoidalarga muvofiq oʻtkaziladi. - -## 1. Orfografiya - -### Maxsus belgilar - -| Notoʻgʻri | Toʻgʻri | Belgi (Unicode) | -|-----------|---------|-----------------| -| `o'` | `oʻ` | U+02BB MODIFIER LETTER TURNED COMMA | -| `O'` | `Oʻ` | U+02BB | -| `g'` | `gʻ` | U+02BB | -| `G'` | `Gʻ` | U+02BB | -| `ma'ruza`, `sun'iy`, `e'lon` | `maʼruza`, `sunʼiy`, `eʼlon` | U+02BC MODIFIER LETTER APOSTROPHE | -| `"matn"` (toʻgʻri tirnoq) | `“matn”` | U+201C / U+201D | - -### Qoida - -- Unli + apostrof (`oʻ`, `gʻ`) uchun **ʻ** (U+02BB). -- Undosh + apostrof (so'z ichida `maʼruza`, `sanʼat`, `taʼlim`, `eʼlon`, `sunʼiy`) uchun **ʼ** (U+02BC). -- Lotin tirnoqlari `"…"` matn ichida `“…”` (egma) bilan almashtiriladi. -- HTML atributlaridagi tirnoqlar (`class="card"`) **toʻgʻri** qoldiriladi. -- Kod bloklari ichida sintaksis tirnoqlari (mermaid `["matn"]`, JSON, va h.k.) **toʻgʻri** qoldiriladi. - -### Tipik so'z imlosi xatolari (avto-tekshiriladi) - -| Notoʻgʻri | Toʻgʻri | -|-----------|---------| -| `qaer`, `qaerda`, `qaerga`, `qaerdan` | `qayer`, `qayerda`, `qayerga`, `qayerdan` | -| `etarli`, `etadi`, `etib` (so'z boshida) | `yetarli`, `yetadi`, `yetib` | -| `xayron`, `xafa`, `xolat`, `xokim` | `hayron`, `hafa`, `holat`, `hokim` | -| `havf`, `havola` (xato sifatida) | `xavf` (danger), `havola` (link — mana shu toʻgʻri) | -| `hoxlamoq`, `xoxlamoq` | `xohlamoq` | -| `nahot`, `naxot` | `naxot` (so'roq), aksincha `nahotki` ham toʻgʻri | -| `xech`, `xeshta` | `hech`, `hechta` | -| `muzlatgich` *(LQA xato tavsiya etishi mumkin)* | `muzlatkich` — toʻgʻri imlo | - -## 2. Lugʻat (Glossary) - -### Texnik atamalar — saqlanadi (asl holida) - -`Harness`, `agent`, `prompt`, `repository`, `pull request`, `commit`, `endpoint`, `pipeline`, `runtime`, `context window`, `lint`, `test`, `type check`, `benchmark`, `feature list`, `init`, `Markdown`, `JSON`, `API`, `IPC`, `Electron`, `React`, `TypeScript`, `Codex`, `Claude Code`, `OpenAI`, `Anthropic`. - -Bu soʻzlar tarjima qilinmaydi va **kursivga olinmaydi** (matn ichida tabiiy yozilishi). - -### Standart tarjimalar - -| Inglizcha | Oʻzbekcha | -|-----------|-----------| -| reliable | ishonchli | -| failure | muvaffaqiyatsizlik / yiqilish | -| verification | tekshiruv / verifikatsiya | -| environment | muhit | -| state management | holat boshqaruvi | -| feedback | qaytma aloqa / fikr-mulohaza | -| observability | kuzatuvchanlik | -| capability | imkoniyat / qobiliyat | -| reliability | ishonchlilik | -| handoff | topshirish | -| boundary | chegara | -| layer | qatlam | -| primitive | primitiv (asosiy birlik) | -| skill issue | mahorat masalasi | -| workspace | ish maydoni | -| session | sessiya | -| ground truth | haqiqiy holat / yagona haqiqat | -| system of record | yagona haqiqat manbai | -| template | **andoza** (NOT shablon) | -| guide / guideline | yoʻriqnoma | -| convention | konvensiya | -| dependency | bogʻliqlik / kutubxona | - -### Atamalar terminologiyasi - -Maxsus terminlar (Capability Gap, Verification Gap, Diagnostic Loop va h.k.) birinchi marta uchraganida: - -``` -**Capability Gap (Imkoniyatlar tafovuti)** -``` - -Keyingi joylarda toʻliq inglizcha atama yoki oʻzbekcha tarjima — qaysi biri kontekstga mos kelsa. - -## 3. Stilistika - -### Uslub - -- **Suhbatdosh, professional ohang.** Akademik quruq emas, lekin xolislik saqlanadi. -- **Ikkinchi shaxs koʻplik**: "siz qiling", "tekshiring", "yarating" (hurmat shakli). -- **Bir xil terminologiya** matn boʻyicha. Bir atama uchun bir tarjima. -- **Faol nisbat** afzal: "agent kod yozadi" — "kod agent tomonidan yoziladi" emas. -- **Uzun jumlalarni boʻling.** Inglizcha 30+ soʻzli jumlalarni 2-3 ga ajrating. -- **Kalkani tashlang.** Soʻzma-soʻz emas, maʼno boʻyicha. - -### Punktuatsiya - -- Tire (`—`) U+2014 — fikr ajratish, izoh kiritish. -- Egma tirnoq `“ ”` faqat keltirma va qisqa iboralar uchun. -- Inline kod `` ` `` saqlanadi (`AGENTS.md`, `pip install`). -- Roʻyxatlarda nuqta-vergul yoki nuqta — bir uslubda. - -### Sarlavhalar - -- `# H1` — sahifaning yagona asosiy sarlavhasi. -- `##` va `###` — kichik harf bilan boshlanadi (faqat birinchi soʻz va atoqli otlar). -- Sarlavha yakunida nuqta yoʻq. - -### Havolalar - -- Inglizcha manba sarlavhalari **tarjima qilinadi** (havola matni). -- URL oʻzgarmaydi. -- Repo ichidagi havolalar `/uz/` ga yoʻnaltiriladi. - -## 4. Saqlanadigan elementlar - -Tarjimada **oʻzgarmaydigan** narsalar: - -- Kod bloklari — har qanday til (`ts`, `python`, `bash`, `mermaid`). -- HTML teglari va atributlari. -- Inline kod: `` `AGENTS.md` ``, `` `pytest` ``. -- URL'lar. -- Frontmatter (`---` blok). -- Markdown sintaksisi. - -## 5. Maqsadli auditoriya - -Oʻquvchi — oʻrtacha-yuqori darajadagi dasturchi, ingliz tilida texnik atamalarni biladi. Shu sababli inglizcha terminologiyani saqlash maqbul, lekin matn oqim oʻzbekcha boʻlishi shart. diff --git a/courses/learn-harness-engineering/docs/uz/index.md b/courses/learn-harness-engineering/docs/uz/index.md deleted file mode 100644 index 4c8e8252..00000000 --- a/courses/learn-harness-engineering/docs/uz/index.md +++ /dev/null @@ -1,74 +0,0 @@ -# Harness Engineering kursiga xush kelibsiz - -Learn Harness Engineering — sunʼiy intellektga asoslangan kod yozuvchi agentlar muhandisligiga bagʻishlangan kurs. Sanoatdagi eng ilgʻor Harness Engineering nazariyasi va amaliyotini chuqur oʻrganib sintez qildik. Asosiy manbalarimiz: - -- [OpenAI: Harness engineering — agent ustuvor dunyoda Codexdan foydalanish](https://openai.com/index/harness-engineering/) -- [Anthropic: Uzoq ishlovchi agentlar uchun samarali harnessʼlar](https://www.anthropic.com/engineering/effective-harnesses-for-long-running-agents) -- [Anthropic: Uzoq muddatli ilova ishlab chiqish uchun harness dizayni](https://www.anthropic.com/engineering/harness-design-long-running-apps) -- [Awesome Harness Engineering](https://github.com/walkinglabs/awesome-harness-engineering) - -Tizimli muhit dizayni, holat boshqaruvi, tekshiruv va nazorat tizimlari orqali ushbu kurs sizga Codex va Claude Code kabi agentik kod yozish vositalarini haqiqatan ham ishonchli qilishni oʻrgatadi. AI yordamchingizni aniq qoidalar va chegaralar bilan boshqarib, yangi funksiyalar qoʻshish, xatolarni tuzatish va dasturlash vazifalarini avtomatlashtirishga yordam beradi. - -## Boshlash - -Oʻqish yoʻlingizni tanlang. Kurs uch qismdan iborat: nazariy maʼruzalar, amaliy loyihalar va tayyor koʻchirib olinadigan resurslar kutubxonasi. - - - -## Harnessʼning asosiy mexanizmi - -Harness modelni “aqlliroq qilmaydi”; aksincha, model uchun yopiq sikldagi **ishchi tizim** oʻrnatadi. Uning ishlash jarayonini quyidagi sodda diagramma orqali tushunish mumkin: - -```mermaid -graph TD - A["Aniq maqsad
    AGENTS.md"] --> B("Inisializatsiya
    init.sh") - B --> C{"Vazifani bajarish
    AI Agent"} - C -->|Muammoga duch keldi| D["Runtime fikr-mulohaza
    CLI / Loglar"] - D -->|Avto-tuzatish| C - C -->|Kod tayyor| E{"Tekshirish va QA
    Test toʻplami"} - E -->|Yiqildi| D - E -->|Oʻtdi| F["Tozalash va topshirish
    claude-progress.md"] - - classDef primary fill:#D95C41,stroke:#C14E36,color:#fff,font-weight:bold; - classDef process fill:#F4F3EE,stroke:#D1D1D1,color:#1A1A1A; - classDef check fill:#EAE8E1,stroke:#B3B3B3,color:#1A1A1A; - - class A,F primary; - class B,D process; - class C,E check; -``` - -## Nimalarni oʻrganasiz - -Mana oʻzlashtiradigan asosiy tushunchalardan baʼzilari: - -
      -
    • Agent xatti-harakatini cheklash — aniq qoidalar va chegaralar yordamida.
    • -
    • Kontekstni saqlash — uzoq davom etadigan, koʻp sessiyali vazifalar boʻyicha.
    • -
    • Agentlarni toʻxtatish — vaqtidan oldin “tugadim” deb eʼlon qilishlarining oldini olish.
    • -
    • Ishni tekshirish — toʻliq pipeline testlari va oʻz-oʻzini baholash orqali.
    • -
    • Runtimeʼni kuzatib borish va xatolarni topish oson boʻlishi uchun.
    • -
    - -## Keyingi qadamlar - -Asosiy tushunchalarni oʻzlashtirgandan soʻng, quyidagi yoʻriqnomalar yanada chuqurroq kirib borishga yordam beradi: - - diff --git a/courses/learn-harness-engineering/docs/uz/lectures/lecture-01-why-capable-agents-still-fail/code/failure-pattern-demo.ts b/courses/learn-harness-engineering/docs/uz/lectures/lecture-01-why-capable-agents-still-fail/code/failure-pattern-demo.ts deleted file mode 100644 index 9ec7d3e1..00000000 --- a/courses/learn-harness-engineering/docs/uz/lectures/lecture-01-why-capable-agents-still-fail/code/failure-pattern-demo.ts +++ /dev/null @@ -1,170 +0,0 @@ -/** - * failure-pattern-demo.ts - * - * Simulates the 4-step failure pattern that capable agents fall into: - * 1. Incomplete context - * 2. Locally reasonable changes - * 3. No global verification - * 4. Premature completion - * - * Run: npx tsx docs/lectures/lecture-01-why-capable-agents-still-fail/code/failure-pattern-demo.ts - */ - -// --------------------------------------------------------------------------- -// Types -// --------------------------------------------------------------------------- - -interface StepState { - step: number; - name: string; - contextAvailable: string[]; - contextMissing: string[]; - actionTaken: string; - localOutcome: string; - globalImpact: string; - completed: boolean; -} - -// --------------------------------------------------------------------------- -// Simulated “model” -- a simple decision function that bases its output -// solely on whatever context it has been given. -// --------------------------------------------------------------------------- - -function modelDecide(context: string[], task: string): string { - const has = (s: string) => context.some((c) => c.includes(s)); - - // The task is to “add a search endpoint to the API”. - // Correct answer requires knowing about auth middleware and rate limiting. - - if (!has(“auth”)) { - return “Created new route handler /search without authentication checks”; - } - if (!has(“rate-limit”)) { - return “Added search route with auth but forgot rate limiting”; - } - if (!has(“test-standards”)) { - return “Implemented search with auth and rate-limit, but no tests”; - } - return “Fully implemented search endpoint with auth, rate-limit, and tests”; -} - -// --------------------------------------------------------------------------- -// Failure simulation -// --------------------------------------------------------------------------- - -function simulateFailurePattern(): StepState[] { - const steps: StepState[] = []; - - // ---- Step 1: Incomplete Context ---- - const step1Context = [“project structure”, “route definitions”]; - const step1Missing = [“auth middleware”, “rate-limiting policy”, “test standards”]; - const step1Decision = modelDecide(step1Context, “add search endpoint”); - - steps.push({ - step: 1, - name: “Incomplete Context”, - contextAvailable: step1Context, - contextMissing: step1Missing, - actionTaken: step1Decision, - localOutcome: “Looks good -- route compiles, returns data”, - globalImpact: “Missing auth means unauthenticated access to search”, - completed: false, - }); - - // ---- Step 2: Locally Reasonable Changes ---- - // The agent adds auth after a hint, but still lacks other context. - const step2Context = [...step1Context, “auth middleware”]; - const step2Missing = [“rate-limiting policy”, “test standards”]; - const step2Decision = modelDecide(step2Context, “add search endpoint”); - - steps.push({ - step: 2, - name: “Locally Reasonable Changes”, - contextAvailable: step2Context, - contextMissing: step2Missing, - actionTaken: step2Decision, - localOutcome: “Route has auth -- looks complete locally”, - globalImpact: “No rate limiting means the endpoint can be abused”, - completed: false, - }); - - // ---- Step 3: No Global Verification ---- - const step3Context = [...step2Context, “rate-limiting policy”]; - const step3Missing = [“test standards”]; - const step3Decision = modelDecide(step3Context, “add search endpoint”); - - steps.push({ - step: 3, - name: “No Global Verification”, - contextAvailable: step3Context, - contextMissing: step3Missing, - actionTaken: step3Decision, - localOutcome: “Feature appears fully implemented”, - globalImpact: “No tests -- regression risk, violates project standards”, - completed: false, - }); - - // ---- Step 4: Premature Completion ---- - steps.push({ - step: 4, - name: “Premature Completion”, - contextAvailable: step3Context, - contextMissing: step3Missing, - actionTaken: 'Agent outputs: “Done. Added search endpoint.”', - localOutcome: “Agent is satisfied, task marked complete”, - globalImpact: “Task is incomplete -- missing tests, no E2E verification”, - completed: true, - }); - - return steps; -} - -// --------------------------------------------------------------------------- -// Comparison table -// --------------------------------------------------------------------------- - -function printComparisonTable(steps: StepState[]): void { - console.log(“\n” + “=”.repeat(90)); - console.log(“ FAILURE PATTERN DEMO -- 4 Steps to a Broken Deliverable”); - console.log(“=”.repeat(90)); - - for (const s of steps) { - console.log(`\n Step ${s.step}: ${s.name}`); - console.log(“ ” + “-”.repeat(60)); - console.log(` Context available : ${s.contextAvailable.join(", ")}`); - console.log(` Context missing : ${s.contextMissing.join(", ") || "(none)"}`); - console.log(` Action taken : ${s.actionTaken}`); - console.log(` Local outcome : ${s.localOutcome}`); - console.log(` Global impact : ${s.globalImpact}`); - console.log(` Marked complete? : ${s.completed ? "YES (premature)" : "No"}`); - } - - // Summary comparison - console.log(“\n” + “=”.repeat(90)); - console.log(“ COMPARISON: What the agent saw vs. what was actually needed”); - console.log(“=”.repeat(90)); - - const totalRequired = [“project structure”, “route definitions”, “auth middleware”, “rate-limiting policy”, “test standards”]; - const finalAvailable = steps[steps.length - 1].contextAvailable; - - const header = “| Criterion | Available | Missing | Status |”; - const sep = “|------------------------|-----------|---------|----------|”; - console.log(“\n” + header); - console.log(sep); - - for (const item of totalRequired) { - const avail = finalAvailable.includes(item); - const row = `| ${item.padEnd(23)}| ${(avail ? "Yes" : "No").padEnd(10)}| ${(!avail ? "Yes" : "").padEnd(8)}| ${(avail ? "OK" : "GAP").padEnd(9)}|`; - console.log(row); - } - - const gapCount = totalRequired.filter((i) => !finalAvailable.includes(i)).length; - console.log(“\n Result: Agent completed with ” + gapCount + “ of ” + totalRequired.length + “ context items missing.”); - console.log(“ This is the core failure pattern: each step looked reasonable in isolation.\n”); -} - -// --------------------------------------------------------------------------- -// Run -// --------------------------------------------------------------------------- - -printComparisonTable(simulateFailurePattern()); diff --git a/courses/learn-harness-engineering/docs/uz/lectures/lecture-01-why-capable-agents-still-fail/code/failure-signals-checklist.md b/courses/learn-harness-engineering/docs/uz/lectures/lecture-01-why-capable-agents-still-fail/code/failure-signals-checklist.md deleted file mode 100644 index 05063917..00000000 --- a/courses/learn-harness-engineering/docs/uz/lectures/lecture-01-why-capable-agents-still-fail/code/failure-signals-checklist.md +++ /dev/null @@ -1,9 +0,0 @@ -# Muvaffaqiyatsizlik Signallari Tekshiruvi - -Ushbu roʻyxatdan kuchsiz harness ishga tushirilishini tekshirishda foydalaning. - -- Agent ilovani qanday ishga tushirishni soʻradimi yoki notoʻgʻri taxmin qildimi? -- U maqsad qilingan mahsulotga toʻgʻri kelmaydigan kataloglar yoki abstraksiyalarni yaratdimi? -- U toʻliq ishlaydigan jarayonsiz, faqat vizual UI qobigʻini (shell) qilibgina toʻxtab qoldimi? -- U kelajakdagi sessiya ishni davom ettirishi uchun eslatmalar yoki artefaktlar qoldirdimi? -- Yangi qoʻshilgan sessiya nima boʻlganini besh daqiqa ichida tushunib oladimi? diff --git a/courses/learn-harness-engineering/docs/uz/lectures/lecture-01-why-capable-agents-still-fail/code/index.md b/courses/learn-harness-engineering/docs/uz/lectures/lecture-01-why-capable-agents-still-fail/code/index.md deleted file mode 100644 index e7755e5f..00000000 --- a/courses/learn-harness-engineering/docs/uz/lectures/lecture-01-why-capable-agents-still-fail/code/index.md +++ /dev/null @@ -1,7 +0,0 @@ -# 1-maʼruza uchun kod - -Ushbu jilddan quyidagilarni koʻrsatuvchi kichik misollar uchun foydalaning: - -- kuchsiz muhitda kuchli modelning yiqilishi -- yetarlicha taʼriflanmagan repo sozlamalari -- yetishmayotgan qayta aloqa sikllari (feedback loops) diff --git a/courses/learn-harness-engineering/docs/uz/lectures/lecture-01-why-capable-agents-still-fail/code/underspecified-task.md b/courses/learn-harness-engineering/docs/uz/lectures/lecture-01-why-capable-agents-still-fail/code/underspecified-task.md deleted file mode 100644 index 600d22dc..00000000 --- a/courses/learn-harness-engineering/docs/uz/lectures/lecture-01-why-capable-agents-still-fail/code/underspecified-task.md +++ /dev/null @@ -1,18 +0,0 @@ -# Yetarlicha Taʼriflanmagan Vazifa Misoli - -AI savol-javob imkoniyatiga ega desktop bilimlar bazasi (knowledge base) ilovasini quring. - -Cheklovlar: - -- Hech narsa koʻrsatilmagan -- Ishga tushirish (startup) buyrugʻi berilmagan -- Jild strukturasi boʻyicha koʻrsatma yoʻq -- Maʼlumotlar modeli aniqlanmagan -- Aniq tugatish (completion) mezonlari yoʻq - -Bunday promptdan odatiy kutiladigan natijalar: - -- agent oʻzidan oʻzi ixtiyoriy tuzilmani oʻylab topadi -- ilova komplyatsiya boʻlishi mumkin, lekin barqaror ishga tushmasligi mumkin -- UI foydalanish mumkin boʻlgan kiritish/soʻrov (ingest/query) yoʻllari hosil boʻlmasidan paydo boʻlishi mumkin -- agent koʻpincha kosmetik muvaffaqiyatdan keyin toʻxtaydi diff --git a/courses/learn-harness-engineering/docs/uz/lectures/lecture-01-why-capable-agents-still-fail/index.md b/courses/learn-harness-engineering/docs/uz/lectures/lecture-01-why-capable-agents-still-fail/index.md deleted file mode 100644 index ba5d957b..00000000 --- a/courses/learn-harness-engineering/docs/uz/lectures/lecture-01-why-capable-agents-still-fail/index.md +++ /dev/null @@ -1,118 +0,0 @@ -[English version →](../../../en/lectures/lecture-01-why-capable-agents-still-fail/) - -> Kod misollar: [code/](https://github.com/walkinglabs/learn-harness-engineering/blob/main/docs/en/lectures/lecture-01-why-capable-agents-still-fail/code/) -> Amaliy loyiha: [01-loyiha. Faqat prompt vs. qoidalar ustuvor](./../../projects/project-01-baseline-vs-minimal-harness/index.md) - -# 01-maʼruza. Kuchli model — ishonchli bajarilish degani emas - -Oʻzingizni AI olamida tajribali deb hisoblaysiz — Claude Pro obunangiz bor, GPT-4o API kalitingiz bor, SWE-bench reytingidagi raqamlarni yoddan bilasiz. Va nihoyat, haqiqiy loyihani ishonch bilan AI agentga topshirasiz. Natija nima? U bitta funksiya qoʻshadi-yu, testlarni buzadi; bitta xatoni tuzatadi-yu, yana ikkita yangisini kiritadi; 20 daqiqa ishlab “tayyor” deb faxr bilan eʼlon qiladi — siz kodga qaraysiz, u umuman soʻraganingiz emas. - -Birinchi reaksiyangiz? “Bu model yetarli emas. Yaxshiroq modelga oʻtish vaqti keldi.” Toʻxtang. Yangi obunaga pul sarflashga shoshilmang — ehtimol, muammo umuman modelda emasdir. - -Raqamlarga qaraylik. 2025-yil oxiriga kelib, SWE-bench Verifiedʼdagi eng kuchli kodlash agentlari taxminan 50–60% natija koʻrsatmoqda. Bu — sinchiklab tanlangan, aniq tavsifli va testlari mavjud vazifalarda. Kundalik dasturlash muhitiga oʻtsangiz — talablar noaniq, testlar yoʻq, biznes qoidalari hamma joyga sochilib yotgan — bu raqam yana ham pasayadi. - -Ammo bu raqamlar ortida intuitsiyaga zid bir haqiqat yashiringan. - -## Bir xil ot, turlicha taqdir - -Anthropic nazorat ostida tajriba oʻtkazdi. Bir xil prompt (“2D retro oʻyinlar yaratish dasturini qur”), bir xil model (Opus 4.5). Birinchi sinov — hech qanday qoʻshimcha vositasiz: 20 daqiqa, 9 dollar; oʻyinning asosiy funksiyalari umuman ishlamadi. Ikkinchi sinov — toʻliq harness bilan (planner + generator + evaluator — uch agentli arxitektura): 6 soat, 200 dollar; oʻyin oʻynaladigan holatda chiqdi. - -Ular modelni oʻzgartirmadi. Opus 4.5 hamon Opus 4.5 edi. Oʻzgargan narsa — ot anjomlari. - -OpenAIʼning 2025-yilgi harness engineering maqolasi buni ochiq aytadi: yaxshi tuzilgan harnessʼga ega repozitoriyadagi Codex “ishonchsiz”dan “ishonchli”ga oʻtadi. Eʼtibor bering — “biroz yaxshi” emas, sifat jihatidan tubdan boshqacha. Bu xuddi zotli otga oʻxshaydi: mos ot anjomlarisiz minib boʻladi, lekin uzoqqa bormaysiz, tez ham yura olmaysiz, yiqilishingiz esa hech kimni ajablantirmaydi. Harness — aynan oʻsha toʻliq ot anjomlari; **model ogʻirliklaridan tashqaridagi barcha muhandislik infratuzilmasi.** - -## Agentlar aslida qayerda tiqilib qoladi - -Aniqrogʻi, nima notoʻgʻri ketadi? - -Eng koʻp uchraydigan holat: vazifani aniq belgilamagansiz. “Qidiruv funksiyasini qoʻsh” deysiz, ammo agent buni siznikidan butunlay boshqacha tushunadi — nimani qidirish kerak? Toʻliq matn boʻyichami yoki strukturali? Sahifalash kerakmi? Natijalardagi mos parchalar ajratib koʻrsatilsinmi? Buni siz aytmagansiz — demak agent taxmin qiladi. Toʻgʻri taxmin qilsa — bu omad; notoʻgʻri taxminni keyin tuzatish boshidan aniq aytishdan koʻra qimmatga tushadi. Xuddi restoranda oshpazga “menga baliq bering” deganga oʻxshaydi — dimlanganmi, bugʼda pishirilganmi yoki qaynoq qozondami — buning hammasi sof tasodifga qoladi. - -Vazifa aniq boʻlsa ham, loyihada agent bilmaydigan yashirin arxitektura konvensiyalari boʻladi. Jamoangiz SQLAlchemy 2.0 sintaksisini standart qilib olgan boʻlsa-da, agent odatda 1.x kodini yozadi. Barcha API endpointʼlar OAuth 2.0 autentifikatsiyasidan foydalanishi shart — lekin bu qoida faqat sizning yodingizda va uch oy oldingi Slack xabarida bor. Agent buni koʻra olmaydi — bu unga boʻysunmaslikni xohlagani uchun emas, balki bunday qoida borligini umuman bilmaydi. - -Muhitning oʻzi ham tuzoq. Tugallanmagan dev muhit, yetishmayotgan kutubxonalar, mos kelmagan vosita versiyalari. Agent qimmatli kontekst oynasini sizning haqiqiy vazifangizni hal qilishga emas, `pip install` xatoliklari va Node versiyalari nomuvofiqligiga sarflaydi. Bu xuddi mohir duradgorni yollab, unga bolgʻa, mix yoki tekis ish stoli bermay qoʻyganga oʻxshaydi — qanchalar mohir boʻlmasin, ish qila olmaydi. - -Yana bir keng tarqalgan holat: tekshirish imkoniyatining yoʻqligi. Testlar yoʻq, lint yoʻq yoki tekshirish buyruqlari hech qachon agentga aytilmagan. Agent kod yozadi, unga qaraydi, “yaxshi-ku” degan qarorga keladi va “tayyor” deydi. Bu xuddi talabaga javob varaqasiz uy vazifasini topshirishga oʻxshaydi — u toʻgʻri qildim deb oʻylaydi, lekin baholaganingizda xatolar uyumi chiqadi. Anthropic yana bir qiziq hodisani kuzatdi: agentlar kontekst tugayotganini sezsa, shoshilib tugatadi, tekshirishni oʻtkazib yuboradi va eng maqbul yechim oʻrniga oddiy yechimni tanlaydi. Buni “kontekst xavotiri” (context anxiety) deb atashadi — xuddi imtihonda vaqt oxirlayotganini sezganingizda qolgan test savollariga tasodifiy javoblarni belgilab ketishga oʻxshash holat. - -Bir necha sessiyaga choʻzilgan uzun vazifalar yana ham yomonroq — oldingi sessiyadagi barcha topilmalar yoʻqoladi va har bir yangi sessiya loyiha strukturasini qaytadan oʻrganishi, kod tuzilishini qaytadan oʻrganishi shart. Doimiy holatga ega boʻlmagan agentlarda 30 daqiqadan oshadigan vazifalarda muvaffaqiyatsizlik darajasi keskin oshib ketadi. - -## Asosiy atamalar - -Bu ssenariylarni nazarda tutsak, quyidagi atamalar endi shunchaki jargon emas — har biri aniq muvaffaqiyatsizlik turini nomlaydi: - -- **Capability Gap (Imkoniyatlar tafovuti)**: Modelning benchmark natijalari va haqiqiy vazifalarda koʻrsatadigan natijalari oʻrtasidagi katta tafovut. SWE-bench Verifiedʼda 50–60% muvaffaqiyat darajasi haqiqiy muammolarning deyarli yarmi hal qilinmasligini bildiradi. -- **Harness**: Modeldan tashqaridagi hamma narsa — yoʻriqnomalar, vositalar, muhit, holat boshqaruvi, tekshiruv qayta aloqasi. Agar bu model ogʻirliklari boʻlmasa — bu harness. Biz “ot anjomlari” deb atayotgan narsamiz. -- **Harness-Induced Failure (Harness keltirib chiqargan muvaffaqiyatsizlik)**: Modelda yetarli imkoniyat mavjud, ammo bajarilish muhitida strukturaviy nuqsonlar bor. Anthropicʼning nazoratli tajribasi buni allaqachon isbotladi. -- **Verification Gap (Tekshiruv tafovuti)**: Agentning oʻz natijasiga ishonchi va haqiqiy toʻgʻrilik oʻrtasidagi farq. Agent “men tugatdim” deydi, aslida tugatmagan — bu eng keng tarqalgan muvaffaqiyatsizlik turi. -- **Diagnostic Loop (Diagnostik sikl)**: Bajarish, muvaffaqiyatsizlikni kuzatish, uni maʼlum bir harness qatlamiga bogʻlash, oʻsha qatlamni tuzatish, qaytadan bajarish. Bu — harness muhandisligining asosiy metodikasi. -- **Definition of Done (Bajarilganlik mezonlari)**: Mashina tomonidan tekshirilishi mumkin boʻlgan shartlar toʻplami — testlar oʻtadi, lint toza, type check muvaffaqiyatli. Aniq DoD boʻlmasa, agent oʻzicha ixtiro qiladi. - -## Yiqilganda — avval harnessʼni tuzating - -Asosiy tamoyil: **Yiqilish boʻlganda, modelni almashtirishga shoshilmang — harnessʼni tekshiring.** Agar bir xil model oʻxshash, yaxshi tuzilgan vazifalarda muvaffaqiyat qozonsa — bu harness muammosi deb taxmin qiling. Bu xuddi mashina buzilib qolganga oʻxshaydi — darhol dvigateldan ayb qidirmaysiz. Avval benzin bor-yoʻqligini tekshirasiz. - -Aniq qadamlar: - -**Har bir muvaffaqiyatsizlikni aniq qatlamga bogʻlang.** Shunchaki “model yomon” demang. Oʻzingizdan soʻrang: vazifa noaniqmidi? Kontekst yetarli emasmidi? Tekshiruv usullari yoʻqmidi? Har bir muvaffaqiyatsizlikni beshta himoya qatlamidan biriga bogʻlang: vazifa spetsifikatsiyasi, kontekst taʼminoti, bajarilish muhiti, tekshiruv qayta aloqasi, holat boshqaruvi. Bular amaliy diagnostika qatlamlari, yuqoridagi oltita glossariy atamasi emas. Shu odatni shakllantirsangiz, “model yetarli emas” iborasi loglaringizda kamayib boradi. - -**Har bir vazifa uchun aniq Definition of Done (bajarilganlik mezonlari) yozing.** “Qidiruv funksiyasini qoʻsh” demang. Mana shunday yozing: - -``` -Bajarilganlik mezonlari: -- Yangi endpoint: GET /api/search?q=xxx -- Sahifalashni qoʻllab-quvvatlaydi, default 20 ta -- Natijalar ajratib koʻrsatilgan parchalarni oʻz ichiga oladi -- Yangi kodning hammasi pytestʼdan oʻtadi -- Type check oʻtadi (mypy --strict) -``` - -**`AGENTS.md` faylini yarating.** Uni repo ildiziga joylashtiring va agentga loyihaning tech stackʼi, arxitektura konvensiyalari va tekshiruv buyruqlarini ayting. Bu — harness muhandisligidagi birinchi qadam va eng yuqori ROIʼga ega qadam. Bitta `AGENTS.md` fayli qimmatroq modelga oʻtishdan ham samaraliroq boʻlishi mumkin — hazil emas. - -**Diagnostik siklni quring.** Muvaffaqiyatsizliklarni “model yana ahmoqlik qildi” deb qaramang. Ularni harnessʼingizda nuqson borligining signali deb qabul qiling. Har bir muvaffaqiyatsizlik uchun qatlamni aniqlang, tuzating, hech qachon shu yoʻl bilan yiqilmang. Bir necha aylanishdan soʻng harnessʼingiz mustahkamlanadi va agent samaradorligi barqarorlashadi. Bu xuddi yoʻl taʼmirlashga oʻxshaydi — har toʻldirilgan chuqur keyingi qismni silliqroq qiladi. - -**Yaxshilanishlarni oʻlchang.** Oddiy log yuriting: har bir vazifa muvaffaqiyatli boʻldimi yoki yoʻqmi va qaysi qatlam yiqilishga sabab boʻldi. Bir necha aylanishdan soʻng qaysi qatlam toʻsiq ekanini koʻrasiz — diqqatingizni oʻsha yerga jamlang. - -## Million qatorlik tajriba - -OpenAI 2025-yilda jasoratli tajriba oʻtkazdi: boʻsh git repozitoriyasidan boshlab, Codex yordamida butun ichki mahsulotni qurish. Besh oydan soʻng repoda taxminan bir million qator kod paydo boʻldi — ilova mantiqi, infratuzilma, asboblar, hujjatlar, ichki dev vositalar — barchasi agent tomonidan yaratilgan. Uchta muhandis Codexʼni boshqarib, taxminan 1 500 ta PR ochib va birlashtirib bordi. Kuniga har bir kishiga oʻrtacha 3,5 ta PR. - -Asosiy cheklov: **odamlar hech qachon kodni toʻgʻridan-toʻgʻri yozmagan.** Bu hiyla emas edi — bu jamoani muhandisning asosiy ishi endi kod yozish emas, balki muhitlarni loyihalash, niyatni ifodalash va qayta aloqa sikllarini qurishga aylanganda nima oʻzgarishini aniqlashga majbur qilish uchun moʻljallangan edi. - -Boshlanishdagi rivoj kutilganidan sekinroq boʻldi. Codexning imkoniyati yetmagani uchun emas, balki muhit yetarlicha toʻliq emas edi — agent yuqori darajadagi maqsadlarni oldinga siljitish uchun zarur vositalar, abstraksiyalar va ichki strukturalarni topa olmas edi. Muhandislarning ishi quyidagicha oʻzgardi: katta maqsadlarni kichik qurilish bloklariga (dizayn, kod, koʻrib chiqish, test) boʻlib chiqish, agentga ularni yigʻishga ruxsat berish, soʻng oʻsha bloklardan foydalanib murakkabroq vazifalarni ochish. Biror narsa yiqilganda yechim deyarli hech qachon “yana qattiqroq harakat qil” emas edi — “agentda qaysi imkoniyat yetishmayapti va biz uni qanday qilib tushunarli va bajariladigan qilamiz?” edi. - -Bu tajriba mazkur maʼruzaning asosiy tezisini bevosita isbotlaydi: **bir xil model qoʻshimcha tuzilmasiz muhitda va toʻliq harnessʼga ega muhitda tubdan farq qiluvchi natija beradi.** Model oʻzgarmadi. Muhit oʻzgardi. - -> Manba: [OpenAI: Harness engineering — agent ustuvor dunyoda Codexdan foydalanish](https://openai.com/index/harness-engineering/) - -## Yanada amaliy misol - -Bir jamoa Claude Sonnetʼdan foydalanib oʻrta hajmdagi Python web ilovasiga (FastAPI + PostgreSQL + Redis, ~15 000 qator kod) yangi API endpoint qoʻshdi. - -Boshida ular faqat bir jumla berishdi: “`/api/v2/users` ostida foydalanuvchi sozlamalari endpointʼlarini qoʻsh”. Natija? Agent kontekst oynasining 40%'ini repo strukturasini oʻrganishga sarfladi, koʻrinishidan oqilona kodni yozdi, ammo loyihaning xatoliklarni boshqarish naqshlariga rioya qilmadi, eski SQLAlchemy sintaksisidan foydalandi va endpointʼda esa runtime xatoliklari mavjud edi — agent buni eʼtiborga olmay “tayyor” deb eʼlon qildi. Keyingi sessiya butun oʻrganish ishini boshidan boshlashga majbur boʻldi. - -Keyin ular `AGENTS.md` qoʻshdi (loyiha arxitekturasi va texnologiya versiyalari tasviri bilan), aniq tekshirish buyruqlari (`pytest tests/api/v2/ && python -m mypy src/`) va arxitektura qaroriga oid yozuvlar qoʻshdilar. Aynan oʻsha model uchchala mustaqil sinovda muvaffaqiyatga erishdi, kontekst samaradorligi taxminan 60%'ga yaxshilandi. - -Ular modelni oʻzgartirmadi. Ular harnessʼni oʻzgartirdi. - -## Asosiy xulosalar - -- Modelning imkoniyati va bajarilishning ishonchliligi — bular boshqa-boshqa narsalar. Zotli ot ham yaxshi ot anjomlarini talab qiladi. -- Yiqilish boʻlganda — avval harnessʼni, keyin modelni tekshiring. Modelni almashtirish — eng qimmat variant — va koʻpincha bu model muammosi ham emas. -- Har bir muvaffaqiyatsizlik — signal: harnessʼingizda strukturaviy nuqson bor. Toping, tuzating. -- Beshta himoya qatlami: vazifa spetsifikatsiyasi, kontekst taʼminoti, bajarilish muhiti, tekshiruv qayta aloqasi, holat boshqaruvi. Doktor eng keng tarqalgan sabablarni avval inkor qilganidek, ularni tizimli tekshiring. -- Bitta `AGENTS.md` fayli qimmatroq modelga oʻtishdan koʻra samaraliroq boʻlishi mumkin. Jiddiy. - -## Qoʻshimcha oʻqish uchun - -- [OpenAI: Harness Engineering — Leveraging Codex in an Agent-First World](https://openai.com/index/harness-engineering/) -- [Anthropic: Effective Harnesses for Long-Running Agents](https://www.anthropic.com/engineering/effective-harnesses-for-long-running-agents) -- [HumanLayer: Skill Issue — Harness Engineering for Coding Agents](https://humanlayer.dev/articles/harness-engineering-for-coding-agents/) -- [SWE-bench Leaderboard](https://www.swebench.com/) -- [Thoughtworks Technology Radar: Harness Engineering](https://www.thoughtworks.com/radar) - -## Mashqlar - -1. **Solishtirish tajribasi**: Oʻzingiz yaxshi biladigan kod bazasini va sezilarli oʻzgartirish vazifasini tanlang. Avval agentni hech qanday harness yordamisiz ishga tushiring va muvaffaqiyatsizliklarni qayd eting. Keyin aniq tekshirish buyruqlari bilan `AGENTS.md` qoʻshing va aynan oʻsha agent bilan qayta ishga tushiring. Natijalarni solishtiring va har bir muvaffaqiyatsizlikni beshta himoya qatlamlaridan biriga bogʻlang. - -2. **Tekshiruv tafovutini oʻlchash**: 5 ta dasturlash vazifasini tanlang. Har bir vazifadan keyin agent tugatilganini eʼlon qilganmi yoki yoʻqligini yozib oling, soʻng mustaqil testlar orqali haqiqiy toʻgʻrilikni tekshiring. Agent “tayyor” deb aytgan, lekin haqiqatda tugatmagan holatlarning ulushini hisoblang — bu sizning verification gapʼingiz. Soʻng oʻylab koʻring: qaysi tekshirish buyruqlari bu nisbatni kamaytirishi mumkin? - -3. **Diagnostik sikl mashqi**: Oʻz loyihangizda agent qayta-qayta yiqilayotgan vazifani toping. Bir marta ishga tushiring, muvaffaqiyatsizlikni qayd eting. Uni beshta qatlamdan biriga bogʻlang. Oʻsha qatlamni tuzating. Qayta ishga tushiring. Uch-besh marta takrorlang, har safar yaxshilanishlarni qayd eting. diff --git a/courses/learn-harness-engineering/docs/uz/lectures/lecture-02-what-a-harness-actually-is/code/harness-components.md b/courses/learn-harness-engineering/docs/uz/lectures/lecture-02-what-a-harness-actually-is/code/harness-components.md deleted file mode 100644 index 7252132d..00000000 --- a/courses/learn-harness-engineering/docs/uz/lectures/lecture-02-what-a-harness-actually-is/code/harness-components.md +++ /dev/null @@ -1,21 +0,0 @@ -# Harness Komponentlari Misoli - -Lokal repozitoriyda ishlaydigan kod yozuvchi agent uchun: - -- Model: - LLM ning oʻzi - -- Harness: - - tizim prompti (system prompt) - - AGENTS.md - - bash vositasi - - fayl oʻqish/yozish vositalari - - git ruxsati - - lokal fayl tizimi - - ishga tushirish (startup) skriptlari - - test buyruqlari - - toʻxtatish ilgaklari (stop hooks) - - lint tekshiruvlari - - baholovchi (evaluator) sikli - -Agar siz yuqoridagi harness boʻlaklarining birortasini oʻzgartirsangiz, siz asl agentning ishlashini (effective agent) oʻzgartirgan boʻlasiz. diff --git a/courses/learn-harness-engineering/docs/uz/lectures/lecture-02-what-a-harness-actually-is/code/harness-vs-no-harness.ts b/courses/learn-harness-engineering/docs/uz/lectures/lecture-02-what-a-harness-actually-is/code/harness-vs-no-harness.ts deleted file mode 100644 index e1d25153..00000000 --- a/courses/learn-harness-engineering/docs/uz/lectures/lecture-02-what-a-harness-actually-is/code/harness-vs-no-harness.ts +++ /dev/null @@ -1,180 +0,0 @@ -/** - * harness-vs-no-harness.ts - * - * Side-by-side comparison of the same task executor running with and without - * a harness. The harness version adds explicit rules, verification steps, - * and stop conditions. - * - * Run: npx tsx docs/lectures/lecture-02-what-a-harness-actually-is/code/harness-vs-no-harness.ts - */ - -// --------------------------------------------------------------------------- -// Types & helpers -// --------------------------------------------------------------------------- - -interface TaskResult { - name: string; - passed: boolean; - durationMs: number; - issues: string[]; -} - -function pad(s: string, len: number): string { - return s.length >= len ? s : s + “ ”.repeat(len - s.length); -} - -// --------------------------------------------------------------------------- -// Simulated task executor -// --------------------------------------------------------------------------- - -/** A simple task that can succeed or fail depending on rules. */ -type Task = { - name: string; - requiresAuth: boolean; - hasTests: boolean; - withinScope: boolean; -}; - -const tasks: Task[] = [ - { name: “Add search endpoint”, requiresAuth: true, hasTests: false, withinScope: true }, - { name: “Add delete endpoint”, requiresAuth: true, hasTests: true, withinScope: true }, - { name: “Refactor auth middleware”, requiresAuth: true, hasTests: true, withinScope: false }, - { name: “Add health check”, requiresAuth: false, hasTests: true, withinScope: true }, - { name: “Add rate limiter”, requiresAuth: true, hasTests: false, withinScope: true }, -]; - -// --------------------------------------------------------------------------- -// Run WITHOUT harness -- just execute every task, no checks -// --------------------------------------------------------------------------- - -function runWithoutHarness(taskList: Task[]): TaskResult[] { - const results: TaskResult[] = []; - - for (const t of taskList) { - const start = performance.now(); - const issues: string[] = []; - - // The “agent” does the work and calls it done. - let passed = true; - - // Problems that go undetected without a harness - if (t.requiresAuth && !t.hasTests) { - issues.push(“No tests for auth-protected endpoint”); - // Agent doesnʼt notice -- marks as pass anyway - } - if (!t.withinScope) { - issues.push(“Task is outside current scope”); - // Agent doesnʼt notice - } - - const duration = performance.now() - start; - results.push({ name: t.name, passed, durationMs: duration, issues }); - } - - return results; -} - -// --------------------------------------------------------------------------- -// Run WITH harness -- rules, verification, stop conditions -// --------------------------------------------------------------------------- - -function runWithHarness(taskList: Task[]): TaskResult[] { - const rules = { - requireTestsForAuth: true, - enforceScope: true, - }; - - const results: TaskResult[] = []; - - for (const t of taskList) { - const start = performance.now(); - const issues: string[] = []; - let passed = true; - - // Rule: auth endpoints must have tests - if (rules.requireTestsForAuth && t.requiresAuth && !t.hasTests) { - issues.push(“BLOCKED: Auth-protected endpoint missing tests”); - passed = false; - } - - // Rule: stay in scope - if (rules.enforceScope && !t.withinScope) { - issues.push(“BLOCKED: Task outside active scope -- skipped”); - passed = false; - } - - // Verification: re-check after execution - if (passed && !t.hasTests) { - issues.push(“WARNING: No tests exist for this change”); - } - - const duration = performance.now() - start; - results.push({ name: t.name, passed, durationMs: duration, issues }); - } - - return results; -} - -// --------------------------------------------------------------------------- -// Reporting -// --------------------------------------------------------------------------- - -function printComparison( - noHarness: TaskResult[], - withHarness: TaskResult[] -): void { - console.log(“\n” + “=”.repeat(80)); - console.log(“ HARNESS vs NO-HARNESS COMPARISON”); - console.log(“=”.repeat(80)); - - const header = `| ${pad("Task", 28)}| ${pad("No-Harness", 12)}| ${pad("Issues(NH)", 35)}| ${pad("Harness", 12)}| ${pad("Issues(H)", 35)}|`; - const sep = `|${"-".repeat(30)}|${"-".repeat(14)}|${"-".repeat(37)}|${"-".repeat(14)}|${"-".repeat(37)}|`; - - console.log(“\n” + header); - console.log(sep); - - for (let i = 0; i < noHarness.length; i++) { - const nh = noHarness[i]; - const wh = withHarness[i]; - const nhPass = nh.passed ? “PASS” : “FAIL”; - const whPass = wh.passed ? “PASS” : “FAIL”; - const nhIssue = nh.issues[0] ?? “(none)”; - const whIssue = wh.issues[0] ?? “(none)”; - - console.log( - `| ${pad(nh.name, 28)}| ${pad(nhPass, 12)}| ${pad(nhIssue, 35)}| ${pad(whPass, 12)}| ${pad(whIssue, 35)}|` - ); - } - - // Metrics - const nhPassed = noHarness.filter((r) => r.passed).length; - const whPassed = withHarness.filter((r) => r.passed).length; - const nhIssues = noHarness.reduce((sum, r) => sum + r.issues.length, 0); - const whIssues = withHarness.reduce((sum, r) => sum + r.issues.length, 0); - - console.log(“\n” + “=”.repeat(80)); - console.log(“ SUMMARY METRICS”); - console.log(“=”.repeat(80)); - - const metricsHeader = `| ${pad("Metric", 30)}| ${pad("No Harness", 15)}| ${pad("With Harness", 15)}|`; - const metricsSep = `|${"-".repeat(32)}|${"-".repeat(17)}|${"-".repeat(17)}|`; - console.log(“\n” + metricsHeader); - console.log(metricsSep); - console.log(`| ${pad("Tasks passed", 30)}| ${pad(String(nhPassed) + "/" + noHarness.length, 15)}| ${pad(String(whPassed) + "/" + withHarness.length, 15)}|`); - console.log(`| ${pad("Issues detected", 30)}| ${pad(String(nhIssues), 15)}| ${pad(String(whIssues), 15)}|`); - console.log(`| ${pad("False positives (passed but flawed)", 30)}| ${pad(String(nhPassed - truePassCount(noHarness)), 15)}| ${pad(String(whPassed - truePassCount(withHarness)), 15)}|`); - - console.log(“\n The harness catches problems the no-harness run silently ignores.”); - console.log(“ Without a harness, every task 'passes' even when it shouldnʼt.\n”); -} - -/** Count tasks that actually pass (have tests and are in scope). */ -function truePassCount(results: TaskResult[]): number { - return results.filter((r) => r.passed && r.issues.length === 0).length; -} - -// --------------------------------------------------------------------------- -// Run -// --------------------------------------------------------------------------- - -printComparison(runWithoutHarness(tasks), runWithHarness(tasks)); diff --git a/courses/learn-harness-engineering/docs/uz/lectures/lecture-02-what-a-harness-actually-is/code/index.md b/courses/learn-harness-engineering/docs/uz/lectures/lecture-02-what-a-harness-actually-is/code/index.md deleted file mode 100644 index 7e043a0f..00000000 --- a/courses/learn-harness-engineering/docs/uz/lectures/lecture-02-what-a-harness-actually-is/code/index.md +++ /dev/null @@ -1,8 +0,0 @@ -# 2-maʼruza uchun kod - -Ushbu jilddan quyidagilarni farqlash uchun kichik misollarga foydalaning: - -- model xatti-harakati (behavior) -- harness xatti-harakati -- faqat prompt asosidagi sozlamalar (setups) -- muhit bilan taʼminlangan agent sozlamalari diff --git a/courses/learn-harness-engineering/docs/uz/lectures/lecture-02-what-a-harness-actually-is/code/minimal-harness-loop.ts b/courses/learn-harness-engineering/docs/uz/lectures/lecture-02-what-a-harness-actually-is/code/minimal-harness-loop.ts deleted file mode 100644 index 17bf995c..00000000 --- a/courses/learn-harness-engineering/docs/uz/lectures/lecture-02-what-a-harness-actually-is/code/minimal-harness-loop.ts +++ /dev/null @@ -1,36 +0,0 @@ -type Message = { - role: “user” | “assistant”; - content: string; -}; - -type ToolResult = { - ok: boolean; - output: string; -}; - -function runTool(name: string, input: string): ToolResult { - if (name === “read_file”) { - return { ok: true, output: `contents of ${input}` }; - } - - return { ok: false, output: `unknown tool: ${name}` }; -} - -export function minimalHarness(messages: Message[]) { - const nextAction = { - tool: “read_file”, - input: “README.md” - }; - - const result = runTool(nextAction.tool, nextAction.input); - - return { - messages: [ - ...messages, - { - role: “assistant”, - content: `Tool ${nextAction.tool} returned: ${result.output}` - } - ] - }; -} diff --git a/courses/learn-harness-engineering/docs/uz/lectures/lecture-02-what-a-harness-actually-is/index.md b/courses/learn-harness-engineering/docs/uz/lectures/lecture-02-what-a-harness-actually-is/index.md deleted file mode 100644 index 2ec0a8a8..00000000 --- a/courses/learn-harness-engineering/docs/uz/lectures/lecture-02-what-a-harness-actually-is/index.md +++ /dev/null @@ -1,109 +0,0 @@ -[English version →](../../../en/lectures/lecture-02-what-a-harness-actually-is/) - -> Kod misollari: [code/](https://github.com/walkinglabs/learn-harness-engineering/blob/main/docs/en/lectures/lecture-02-what-a-harness-actually-is/code/) -> Amaliy loyiha: [Loyiha 01. Prompt-only vs. rules-first](./../../projects/project-01-baseline-vs-minimal-harness/index.md) - -# 2-maʼruza. Harness aslida nima degani - -“Harness” soʻzi AI kod yozish agentlari (coding agent) doiralarida koʻp ishlatiladi, lekin ochigʻini aytganda, koʻpchilik odamlar harness deganda “prompt fayli”ni tushunishadi. Bu harness emas. Bu xuddi faqat masalliqlar bilan — plitasiz, pichoqlarsiz, retseptlarsiz, ovqatni taqdim etish (plating) jarayonisiz restoran ochishga oʻxshaydi. Bu restoran emas. Bu muzlatkich. - -Ushbu maʼruza sizga harness haqida aniq va amalda qoʻllash mumkin boʻlgan taʼrifni beradi. Bu qandaydir akademik abstraksiya emas, balki siz bugunoq foydalanishingiz mumkin boʻlgan freymvork: harness har birining oʻz vazifalari va baholash mezonlari aniq belgilangan beshta quyi tizimdan iborat. - -## Oʻxshatishdan boshlaymiz - -Tasavvur qiling, siz hech qanday hujjatlarisiz loyihaga tashlab ketilgan yangi xodim — muhandissiz. README yoʻq, kodda izohlar yoʻq, testlarni qanday yuritishni hech kim aytmaydi, CI konfiguratsiyasi qayerdadir yashiringan. Yaxshi kod yoza olasizmi? Balki — agar yetarlicha aqlli va sabrli boʻlsangiz. Lekin “muammoni hal qilish” oʻrniga “bu loyiha oʻzi nima haqida ekanligini tushunishga” juda koʻp vaqt sarflaysiz. - -AI agentlar ham aynan shunday vaziyatga duch keladi. Va bu undan ham yomonroq — siz hech boʻlmasa hamkasbingizdan soʻrashingiz mumkin. Agent faqat siz uning oldiga qoʻygan fayllarni va u ishlata oladigan buyruqlarni koʻra oladi. U kimnidir yelkasiga turtib, “hoy, bu loyiha ORMʼning qaysi versiyasidan foydalanadi?” deb soʻray olmaydi. - -OpenAI asosiy tamoyilni “repo — bu spetsifikatsiya (spec)” deb belgilaydi — barcha zarur kontekst repozitoriy ichida, tizimlashtirilgan yoʻriqnoma fayllari, aniq tekshiruv (verification) buyruqlari va tushunarli katalog strukturasi orqali taqdim etilishi kerak. Anthropicʼning koʻp vaqt oladigan (long-running) agentlar hujjatlari holatni saqlash (state persistence), aniq tiklash (recovery) yoʻllari va tizimli jarayon kuzatuviga urgʻu beradi. Ikkala kompaniya ham turli jihatlarga eʼtibor qaratadi, lekin ular aslida bir narsani aytmoqda: **model ogʻirliklaridan (model weights) tashqaridagi barcha muhandislik infratuzilmasi, model imkoniyatlarining (capability) qanchalik roʻyobga chiqishini belgilaydi.** - -Oʻzingiz bilgan baʼzi vositalarga eʼtibor qarating: - -**Claude Code** harness fikrlash tarzini oʻzida mujassam etadi. U sizning repoʼingizdan `CLAUDE.md` ni oʻqiydi (retseptlar javoni), shell buyruqlarini bajara oladi (pichoqlar tokchasi), sizning lokal muhitingizda (environment) ishlaydi (plita), sessiya tarixini saqlaydi (tayyorgarlik stoli) va testlarni ishga tushirib natijalarni koʻra oladi (sifat nazorati oynasi). Lekin agar siz unga testlarni qanday ishga tushirishni aytmasangiz, sifat nazorati oynasi buzilgan boʻladi — ovqat toʻliq pishgan yoki pishmaganini hech kim bilmaydi. - -**Cursor** xuddi shunday mantiqqa amal qiladi. Uning `.cursorrules` fayli bu retseptlar javoni, terminal — pichoqlar tokchasi, plita uchun u loyihangiz strukturasi va lint konfiguratsiyasini oʻqiydi. Lekin Cursorʼning holat boshqaruvi (state management) nisbatan zaif — IDEʼni yopib yana ochsangiz, oldingi kontekst yoʻqoladi. - -**Codex** (OpenAIʼning coding agentʼi) har bir vazifaning ishlash muhitini (runtime) alohida ajratish uchun git worktreeʼlardan foydalanadi, bunga qoʻshimcha ravishda lokal kuzatuvchanlik (observability) steki (loglar, metrikalar, treyslar) qoʻshiladi, shu orqali har bir oʻzgarish mustaqil muhitda tekshiriladi. `AGENTS.md` va aniq tekshiruv buyruqlari mavjud repoʼlarda, u qoʻshimcha tuzilmasiz muhitlarga (bare environments) qaraganda ancha yaxshi ishlaydi. - -**AutoGPT** ogohlantiruvchi misoldir — tizimli holat boshqaruvining yoʻqligi, uzoq davom etadigan vazifalarda kontekstning toʻplanib qolishiga olib keladi, aniq tekshiruv qayta aloqasi (verification feedback) mexanizmlarining yoʻqligi esa agentʼning siklga (loop) tushib qolishiga sabab boʻladi. Koʻpchilik odamlar AutoGPT “ishlamaydi” deyishadi, lekin aslida bu ishlamaydigan AutoGPTʼning harnessʼidir — oshpazga buzuq plita bersangiz, hatto eng yaxshi masalliqlar bilan ham ovqat pishira olmaydi. - -## Asosiy tushunchalar - -- **Harness nima**: Model ogʻirliklaridan tashqaridagi barcha muhandislik infratuzilmasi. OpenAI muhandisning asosiy vazifasini uch narsaga qisqartiradi: muhitlarni loyihalash, niyatni ifodalash va qayta aloqa sikllarini (feedback loops) qurish. Anthropic oʻzining Claude Agent SDKʼsini “umumiy maqsadli agent harnessʼi” deb ataydi. -- **Repo — bu yagona haqiqat manbai (system of record)**: Agent koʻra olmaydigan har qanday narsa, amalda mavjud emas deb hisoblanadi. OpenAI repoʼni “yagona haqiqat manbai” (system of record) sifatida qabul qiladi — barcha zarur kontekst tizimlashtirilgan fayllar va tushunarli katalog strukturasi orqali shu yerda yashashi kerak. -- **Qoʻllanma emas, xarita bering**: OpenAI tajribasidan — `AGENTS.md` ensiklopediya emas, balki katalog sahifasi boʻlishi kerak. Taxminan 100 qator yetarli. Agar sigʻmasa, uni `docs/` katalogiga ajrating va kerak boʻlganda agent oʻqishiga imkon bering. -- **Cheklang, mikromenejment qilmang**: Yaxshi harness yoʻriqnomalarni birma-bir sanab oʻtish oʻrniga, agentʼni cheklash uchun bajariladigan qoidalardan (executable rules) foydalanadi. OpenAI “implementatsiyani mikromenejment qilmang, oʻzgarmas qoidalarni (invariants) taʼminlang” deydi; Anthropic shuni aniqladiki, agentlar oʻz ishlarini ishonch bilan maqtashadi va buning yechimi “ishni bajaradigan odam” bilan “ishni tekshiradigan odam”ni alohida ajratishdir. -- **Komponentlarni birma-bir olib tashlang**: Har bir harness komponentining chegaraviy hissasini oʻlchash uchun, ularni birma-bir olib tashlang va qaysi birining olib tashlanishi unumdorlikning eng katta pasayishiga olib kelishini koʻring. Anthropic bu usuldan foydalandi va shuni aniqladiki, modelʼlar kuchayib borgan sari baʼzi komponentlar oʻzining oʻta muhimligini yoʻqotadi — lekin doim yangilari paydo boʻladi. - -## Beshta quyi tizimli Harness modeli - -Oshxona analogiyasiga qaytamiz. Toʻliq oshxonada beshta funksional hudud mavjud, harness ham beshta quyi tizimga ega: - -```mermaid -flowchart LR - Rules["Loyiha qoidalari
    AGENTS.md / CLAUDE.md"] --> Agent["AI Agent"] - State["Jarayon va git
    PROGRESS.md / commitlar"] --> Agent - Agent --> Tools["Vositalar
    shell / fayllar / testlar"] - Tools --> Env["Runtime
    kutubxonalar / xizmatlar / versiyalar"] - Env --> Checks["Tekshiruv natijalari
    test / lint / build"] - Checks --> Agent -``` - -**Yoʻriqnomalar quyi tizimi (retseptlar javoni)**: Loyihaning umumiy koʻrinishi va maqsadi (bir jumlada), tech stack va uning versiyalari (Python 3.11, FastAPI 0.100+, PostgreSQL 15), birinchi marta ishga tushirish buyruqlari (`make setup`, `make test`), muhokama qilinmaydigan qatʼiy cheklovlar (“Barcha APIʼlar OAuth 2.0ʼdan foydalanishi shart”) va batafsilroq hujjatlarga havolalarni oʻz ichiga olgan `AGENTS.md` (yoki `CLAUDE.md`) faylini yarating. - -**Vosita quyi tizimi (pichoqlar tokchasi)**: Agent yetarli vositalardan foydalanish huquqiga ega ekanligiga ishonch hosil qiling. “Xavfsizlik” deb shellʼni oʻchirib qoʻymang — agar agent hatto `pip install` qila olmasa, qanday ishlashi mumkin? Lekin hamma narsani ham ochib yubormang — eng kam imtiyoz (least-privilege) tamoyiliga amal qiling. - -**Muhit quyi tizimi (plita)**: Muhit holati (environment state) oʻz-oʻzini izohlaydigan boʻlishini taʼminlang. Bogʻliqliklarni (dependencies) qulflash uchun `pyproject.toml` yoki `package.json`, runtime versiyalari uchun `.nvmrc` yoki `.python-version`, va har doim bir xil natija olish (reproducibility) uchun Docker yoki devcontainerʼlardan foydalaning. - -**Holat quyi tizimi (tayyorgarlik stoli)**: Uzoq davom etadigan vazifalar jarayonni kuzatib borishni talab qiladi. Oddiy `PROGRESS.md` faylidan foydalanib quyidagilarni yozib boring: nima tugatildi, nima jarayonda, nimaga toʻsqinlik qilinmoqda. Har bir sessiya tugashidan oldin uni yangilang va keyingi sessiya boshlanganda uni oʻqing. - -**Qayta aloqa quyi tizimi (sifat nazorati oynasi)**: Bu eng yuqori ROIʼga ega quyi tizimdir. Tekshiruv (verification) buyruqlarini `AGENTS.md` faylida aniq koʻrsating: -```text -Tekshiruv buyruqlari: -- Testlar: pytest tests/ -x -- Type check: mypy src/ --strict -- Lint: ruff check src/ -- Toʻliq tekshiruv: make check (yuqoridagilarning barchasini oʻz ichiga oladi) -``` - -Qandaydir bir quyi tizimning yoʻqligi, xuddi oshxonada qaysidir funksional hududning yoʻqligiga oʻxshaydi — siz hali ham ovqat pishirishingiz mumkin, lekin bu doim noqulay boʻladi. - -**Harness komponentlari qiymatini oʻlchash**: **Izometrik model boshqaruvi** (modelni oʻzgarmas saqlab, harness komponentlarini birma-bir olib tashlab tekshirish usuli)dan foydalaning. Modelni oʻzgarishsiz qoldiring, quyi tizimlarni birma-bir olib tashlang va qaysi birining olib tashlanishi unumdorlikning eng katta pasayishiga olib kelishini oʻlchang. Eng katta pasayish shu vazifada chegaraviy hissasi eng yuqori komponentni koʻrsatadi, lekin bu avtomatik ravishda toʻsiq nuqtasi degani emas. Haqiqiy toʻsiqni topish uchun tajribani muvaffaqiyatsizlik jurnallari va sababga bogʻlash bilan birlashtiring: vazifa noaniqmi, kontekst yetishmayaptimi, muhit qayta tiklanmayaptimi, tekshiruv qayta aloqasi yoʻqmi yoki holat boshqaruvi uzilganmi. - -## Jamoaning hayotiy misoli - -Bir jamoa TypeScript + React asosidagi frontend ilovasida (~20,000 qator kod) GPT-4oʼdan foydalandi. Ular toʻrtta bosqichdan oʻtishdi — aslini olganda oshxona jihozlarini birma-bir qoʻshib chiqishdi: - -**1-bosqich — Boʻsh oshxona**: Faqat READMEʼda loyihaning qisqacha tavsifi mavjud. 5 ta sinovdan 1 tasi muvaffaqiyatli oʻtdi (20%). Asosiy muvaffaqiyatsizliklar: notoʻgʻri paket menejerini tanladi (npm yoki yarn), komponentlarni nomlash konvensiyalariga amal qilmadi, testlarni ishga tushira olmadi. - -**2-bosqich — Retseptlar javoni oʻrnatildi**: Tech stack versiyalari, nomlash konvensiyalari va asosiy arxitektura qarorlari kiritilgan `AGENTS.md` fayli qoʻshildi. Muvaffaqiyat koʻrsatkichi 60% ga koʻtarildi. Qolgan muvaffaqiyatsizliklar asosan muhitdagi muammolar va tekshiruvning yoʻqligi bilan bogʻliq edi. - -**3-bosqich — Sifat nazorati oynasi ochildi**: `AGENTS.md` faylida tekshiruv buyruqlari koʻrsatildi: `yarn test && yarn lint && yarn build`. Muvaffaqiyat koʻrsatkichi 80% ga koʻtarildi. - -**4-bosqich — Tayyorgarlik stoli tayyor**: Agentlar har bir sinovda tugatilgan va chala ishlarni yozib boradigan jarayon fayli andozalari (progress file templates) joriy etildi. Muvaffaqiyat koʻrsatkichi 80-100% atrofida barqarorlashdi. - -Toʻrtta iteratsiya, model umuman oʻzgarmadi, muvaffaqiyat koʻrsatkichi 20% dan deyarli 100% gacha koʻtarildi. Harness muhandisligining kuchi mana shunda. Siz qimmatroq masalliqlar sotib olmadingiz — shunchaki oshxonani toʻgʻri tartibga keltirdingiz. - -## Asosiy xulosalar - -- Harness = Yoʻriqnomalar + Vositalar + Muhit + Holat + Qayta aloqa. Beshta quyi tizim, xuddi oshxonaning beshta funksional hududiga oʻxshab — barchasi zarur. -- Agar bu model ogʻirliklari boʻlmasa, bu harness. Sizning harnessʼingiz model imkoniyatlarining qanchalik roʻyobga chiqishini belgilaydi. -- Beshta quyi tizim ichida qayta aloqa quyi tizimi odatda eng kam xarajat va eng yuqori daromadga (ROI) ega. Avval tekshiruv buyruqlaringizni toʻgʻri sozlang — sifat nazorati oynasi eng munosib yangilanishdir. -- Har bir quyi tizimning chegaraviy hissasini oʻlchash uchun **izometrik model boshqaruvi**dan foydalaning; haqiqiy toʻsiqni topish uchun muvaffaqiyatsizlik jurnallari va sababga bogʻlashdan foydalaning. -- Harness xuddi kod kabi eskiradi. Uni muntazam tekshirib turing, texnik qarzni toʻlaganingiz kabi harness qarzini ham toʻlab boring. - -## Qoʻshimcha oʻqish uchun - -- [OpenAI: Harness Engineering](https://openai.com/index/harness-engineering/) -- [Anthropic: Effective Harnesses for Long-Running Agents](https://www.anthropic.com/engineering/effective-harnesses-for-long-running-agents) -- [HumanLayer: Harness Engineering for Coding Agents](https://humanlayer.dev/articles/harness-engineering-for-coding-agents/) -- [SWE-agent: Agent-Computer Interfaces](https://github.com/princeton-nlp/SWE-agent) -- [Thoughtworks: Harness Engineering on Technology Radar](https://www.thoughtworks.com/radar) - -## Mashqlar - -1. **Beshta quyi tizimli harness auditi**: Siz AI agentʼidan foydalanadigan bitta loyihani oling va beshta quyi tizimli freymvorkdan foydalanib toʻliq audit qiling. Har bir quyi tizimni 1 dan 5 gacha baholang. Eng past baholangan quyi tizimni toping, uni yaxshilashga 30 daqiqa sarflang, soʻngra agent unumdorligidagi oʻzgarishni kuzating. - -2. **Izometrik model boshqaruvi tajribasi**: Bitta model va bitta murakkab vazifani tanlang. Ketma-ket yoʻriqnomalarni olib tashlang (`AGENTS.md` ni oʻchiring), qayta aloqani olib tashlang (tekshiruv buyruqlarini bermang), holatni olib tashlang (jarayon fayllari yoʻq) — faqat bittasini olib tashlang va unumdorlikning qanchalik tushishini oʻlchang. Natijalarga asoslanib, loyihangiz uchun quyi tizimlarni muhimlik darajasiga koʻra tartiblang. - -3. **Imkoniyatlar tahlili (Affordance analysis)**: Loyihangizdagi agent “nimadir qilishni xohlaydigan, lekin qila olmaydigan” vaziyatni toping (masalan, parametrlangan (parameterized) soʻrovlardan foydalanish kerakligini biladi, lekin loyihangizning ORM andozalarini (patterns) bilmaydi). Bu bajarish boʻshligʻi (Gulf of Execution — qanday qilishni bilmaydi) yoki baholash boʻshligʻi (Gulf of Evaluation — toʻgʻri yoki yoʻqligini bilmaydi) ekanligini tahlil qiling, soʻngra uni toʻldirish uchun harnessʼni yaxshilash rejasini tuzing. diff --git a/courses/learn-harness-engineering/docs/uz/lectures/lecture-03-why-the-repository-must-become-the-system-of-record/code/index.md b/courses/learn-harness-engineering/docs/uz/lectures/lecture-03-why-the-repository-must-become-the-system-of-record/code/index.md deleted file mode 100644 index fb420f28..00000000 --- a/courses/learn-harness-engineering/docs/uz/lectures/lecture-03-why-the-repository-must-become-the-system-of-record/code/index.md +++ /dev/null @@ -1,7 +0,0 @@ -# 3-maʼruza uchun kod - -Ushbu jilddan quyidagilarga misol sifatida foydalaning: - -- agent oʻqiy oladigan repo strukturalari -- yagona haqiqat manbai (system of record) sifatidagi hujjatlar -- yomon va yaxshi bilimlarni joylashtirish diff --git a/courses/learn-harness-engineering/docs/uz/lectures/lecture-03-why-the-repository-must-become-the-system-of-record/code/repo-knowledge-layout.txt b/courses/learn-harness-engineering/docs/uz/lectures/lecture-03-why-the-repository-must-become-the-system-of-record/code/repo-knowledge-layout.txt deleted file mode 100644 index 45315dac..00000000 --- a/courses/learn-harness-engineering/docs/uz/lectures/lecture-03-why-the-repository-must-become-the-system-of-record/code/repo-knowledge-layout.txt +++ /dev/null @@ -1,13 +0,0 @@ -AGENTS.md -docs/ - ARCHITECTURE.md - PRODUCT.md - RELIABILITY.md - references/ - electron.md - sqlite.md - plans/ - active/ - completed/ -src/ -scripts/ diff --git a/courses/learn-harness-engineering/docs/uz/lectures/lecture-03-why-the-repository-must-become-the-system-of-record/code/repo-reader.ts b/courses/learn-harness-engineering/docs/uz/lectures/lecture-03-why-the-repository-must-become-the-system-of-record/code/repo-reader.ts deleted file mode 100644 index 74ea5493..00000000 --- a/courses/learn-harness-engineering/docs/uz/lectures/lecture-03-why-the-repository-must-become-the-system-of-record/code/repo-reader.ts +++ /dev/null @@ -1,189 +0,0 @@ -/** - * repo-reader.ts - * - * Reads a directory structure and scores it on discoverability. - * Checks for: AGENTS.md, docs/, architecture docs, feature tracking, - * handoff files, and other signals of a repository that serves as the - * system of record. - * - * Usage: - * npx tsx docs/lectures/lecture-03.../code/repo-reader.ts [path] - * (defaults to current working directory if no path given) - * - * Run: npx tsx docs/lectures/lecture-03-why-the-repository-must-become-the-system-of-record/code/repo-reader.ts - */ - -import * as fs from “node:fs”; -import * as path from “node:path”; - -// --------------------------------------------------------------------------- -// Scoring criteria -// --------------------------------------------------------------------------- - -interface Check { - name: string; - description: string; - maxPoints: number; - check: (dir: string) => { points: number; found: string[]; missing: string[] }; -} - -const checks: Check[] = [ - { - name: “AGENTS.md / CLAUDE.md”, - description: “Agent-readable instruction file at repo root”, - maxPoints: 15, - check: (dir) => { - const candidates = [“AGENTS.md”, “CLAUDE.md”, “.claude/CLAUDE.md”]; - const found = candidates.filter((f) => fs.existsSync(path.join(dir, f))); - return { points: found.length > 0 ? 15 : 0, found, missing: found.length > 0 ? [] : candidates }; - }, - }, - { - name: “Documentation directory”, - description: “Dedicated docs/ or documentation/ directory”, - maxPoints: 10, - check: (dir) => { - const candidates = [“docs”, “documentation”, “doc”]; - const found = candidates.filter((f) => { - const p = path.join(dir, f); - return fs.existsSync(p) && fs.statSync(p).isDirectory(); - }); - return { points: found.length > 0 ? 10 : 0, found, missing: found.length > 0 ? [] : [“docs/”] }; - }, - }, - { - name: “Architecture documentation”, - description: “Files describing system architecture”, - maxPoints: 15, - check: (dir) => { - const patterns = [ - “architecture.md”, “ARCHITECTURE.md”, “docs/architecture.md”, - “docs/architecture/”, “design.md”, “DESIGN.md”, - ]; - const found = patterns.filter((f) => fs.existsSync(path.join(dir, f))); - return { points: found.length > 0 ? 15 : 0, found, missing: found.length > 0 ? [] : [“architecture.md or docs/architecture/”] }; - }, - }, - { - name: “Feature tracking”, - description: “Feature list or task tracking file”, - maxPoints: 15, - check: (dir) => { - const patterns = [ - “feature_list.json”, “features.md”, “FEATURES.md”, - “docs/features.md”, “tasks.json”, “TODO.md”, - ]; - const found = patterns.filter((f) => fs.existsSync(path.join(dir, f))); - return { points: found.length > 0 ? 15 : 0, found, missing: found.length > 0 ? [] : [“feature_list.json or features.md”] }; - }, - }, - { - name: “Handoff / session continuity”, - description: “Files for multi-session continuity”, - maxPoints: 15, - check: (dir) => { - const patterns = [ - “HANDOFF.md”, “handoff.md”, “SESSION_NOTES.md”, - “docs/handoff.md”, “.handoff”, “PROGRESS.md”, - ]; - const found = patterns.filter((f) => fs.existsSync(path.join(dir, f))); - return { points: found.length > 0 ? 15 : 0, found, missing: found.length > 0 ? [] : [“HANDOFF.md or PROGRESS.md”] }; - }, - }, - { - name: “Testing structure”, - description: “Test directory or test configuration”, - maxPoints: 10, - check: (dir) => { - const patterns = [“test”, “tests”, “__tests__”, “spec”]; - const found = patterns.filter((f) => { - const p = path.join(dir, f); - return fs.existsSync(p) && fs.statSync(p).isDirectory(); - }); - return { points: found.length > 0 ? 10 : 0, found, missing: found.length > 0 ? [] : [“test/ or tests/”] }; - }, - }, - { - name: “Configuration files”, - description: “Project config (package.json, tsconfig, etc.)”, - maxPoints: 10, - check: (dir) => { - const patterns = [“package.json”, “tsconfig.json”, “pyproject.toml”, “Cargo.toml”, “go.mod”]; - const found = patterns.filter((f) => fs.existsSync(path.join(dir, f))); - return { points: found.length > 0 ? 10 : 0, found, missing: found.length > 0 ? [] : [“package.json or equivalent”] }; - }, - }, - { - name: “README”, - description: “Root README file”, - maxPoints: 10, - check: (dir) => { - const patterns = [“README.md”, “README.rst”, “README.txt”, “README”]; - const found = patterns.filter((f) => fs.existsSync(path.join(dir, f))); - return { points: found.length > 0 ? 10 : 0, found, missing: found.length > 0 ? [] : [“README.md”] }; - }, - }, -]; - -// --------------------------------------------------------------------------- -// Report generation -// --------------------------------------------------------------------------- - -function pad(s: string, len: number): string { - return s.length >= len ? s : s + “ ”.repeat(len - s.length); -} - -function scoreRepo(targetDir: string): void { - const resolved = path.resolve(targetDir); - - if (!fs.existsSync(resolved)) { - console.error(` Error: Directory not found: ${resolved}`); - process.exit(1); - } - - console.log(“\n” + “=”.repeat(80)); - console.log(“ REPOSITORY DISCOVERABILITY SCORE”); - console.log(“=”.repeat(80)); - console.log(` Target: ${resolved}\n`); - - const header = `| ${pad("Criterion", 30)}| ${pad("Points", 8)}| ${pad("Status", 10)}| Details`; - const sep = `|${"-".repeat(32)}|${"-".repeat(10)}|${"-".repeat(12)}|${"-".repeat(30)}`; - console.log(header); - console.log(sep); - - let totalScore = 0; - let maxScore = 0; - - for (const check of checks) { - const result = check.check(resolved); - totalScore += result.points; - maxScore += check.maxPoints; - const status = result.points > 0 ? “PASS” : “FAIL”; - const detail = result.found.length > 0 ? result.found.join(“, ”) : result.missing[0]; - - console.log(`| ${pad(check.name, 30)}| ${pad(result.points + "/" + check.maxPoints, 8)}| ${pad(status, 10)}| ${detail}`); - } - - // Final score - console.log(“\n” + “-”.repeat(80)); - console.log(` TOTAL SCORE: ${totalScore} / ${maxScore} (${Math.round((totalScore / maxScore) * 100)}%)`); - - // Grade - const pct = totalScore / maxScore; - let grade: string; - if (pct >= 0.9) grade = “A -- Repository is a strong system of record”; - else if (pct >= 0.7) grade = “B -- Good foundation, minor gaps”; - else if (pct >= 0.5) grade = “C -- Partial structure, significant gaps”; - else if (pct >= 0.3) grade = “D -- Minimal structure, hard for agents to navigate”; - else grade = “F -- Repository lacks discoverability signals”; - - console.log(` GRADE: ${grade}`); - console.log(“=”.repeat(80) + “\n”); -} - -// --------------------------------------------------------------------------- -// Main -// --------------------------------------------------------------------------- - -const target = process.argv[2] || process.cwd(); -scoreRepo(target); diff --git a/courses/learn-harness-engineering/docs/uz/lectures/lecture-03-why-the-repository-must-become-the-system-of-record/code/system-of-record-checklist.md b/courses/learn-harness-engineering/docs/uz/lectures/lecture-03-why-the-repository-must-become-the-system-of-record/code/system-of-record-checklist.md deleted file mode 100644 index d7c5762b..00000000 --- a/courses/learn-harness-engineering/docs/uz/lectures/lecture-03-why-the-repository-must-become-the-system-of-record/code/system-of-record-checklist.md +++ /dev/null @@ -1,11 +0,0 @@ -# Yagona Haqiqat Manbai (System of Record) Tekshiruv Roʻyxati - -Yangi agent faqat repozitoriyning oʻziga qarab quyidagilarni aniqlay oladimi? - -- Qanday mahsulot qurilmoqda? -- Ilova foydalanuvchilar uchun nimalar qilib berishi kerak? -- Kod bazasi qanday tashkillashtirilgan? -- Ilova qanday ishga tushadi? -- Holat (health) qanday tekshiriladi? -- Ayni vaqtda qaysi ish jarayonda? -- Qanday sifat standartlari muhim? diff --git a/courses/learn-harness-engineering/docs/uz/lectures/lecture-03-why-the-repository-must-become-the-system-of-record/index.md b/courses/learn-harness-engineering/docs/uz/lectures/lecture-03-why-the-repository-must-become-the-system-of-record/index.md deleted file mode 100644 index 9fb872ce..00000000 --- a/courses/learn-harness-engineering/docs/uz/lectures/lecture-03-why-the-repository-must-become-the-system-of-record/index.md +++ /dev/null @@ -1,137 +0,0 @@ -[English version →](../../../en/lectures/lecture-03-why-the-repository-must-become-the-system-of-record/) - -> Kod misollari: [code/](https://github.com/walkinglabs/learn-harness-engineering/blob/main/docs/en/lectures/lecture-03-why-the-repository-must-become-the-system-of-record/code/) -> Amaliy loyiha: [Loyiha 02. Agent oʻqiy oladigan ish maydoni](./../../projects/project-02-agent-readable-workspace/index.md) - -# 3-maʼruza. Repozitoriyni yagona haqiqat manbaiga aylantiring - -Jamoangizning arxitektura qarorlari Confluence, Slack, Jira va bir nechta tajribali muhandislarning boshida tarqalib ketgan. Odamlar uchun bu amallab ishlaydi — siz hamkasbingizdan soʻrashingiz, chat tarixini qidirishingiz, hujjatlarni titkilab chiqishingiz mumkin. Agar boshqa hech narsa yordam bermasa, tanaffus xonasida kimnidir burchakka taqab soʻrab olasiz. Lekin AI agent uchun repozitoriyda mavjud boʻlmagan maʼlumot shunchaki yoʻq degani. - -Bu mubolagʻa emas. Agentʼning kiruvchi maʼlumotlari (inputs) aslida nima ekanligini oʻylab koʻring: system promptʼlar va vazifa tavsiflari, repozitoriydagi fayllar tarkibi va vositalardan chiqqan natijalar. Bori shu. Sizning Slack tarixingiz, Jira tiketlari, Confluence sahifalari va juma kuni tushdan keyin qahva ustida hamkasbingiz bilan muhokama qilgan arxitektura qaroringiz — bularning birortasini agent koʻra olmaydi. U “borib birovdan soʻray” yoki “chat tarixini qidiray” deya olmaydi. U repozitoriy ichiga qamab qoʻyilgan muhandisdir — undan tashqaridagi barcha narsalar haqida u hech narsani bilmaydi. - -Shuning uchun savol shunday qoʻyiladi: bu muhandisga yaxshi xarita berasizmi? - -## Xaritada nimalar boʻlishi kerak - -OpenAI buni ochiqchasiga aytadi: **repoda yoʻq boʻlgan maʼlumot agent uchun yoʻqdir.** Ular buni “repo spetsifikatsiya sifatida (repo as spec)” tamoyili deb atashadi — repozitoriyning oʻzi eng yuqori vakolatli spetsifikatsiya hujjatidir. - -Anthropicʼning uzoq muddatli ishlovchi agentlar (long-running agents) hujjatlari ham buni tasdiqlaydi: holatni doimiy saqlash (persistent state) uzoq muddatli vazifalar davomiyligi uchun zarur shartdir. Sessiyalararo bilimlarni tiklash qobiliyati bevosita vazifalarning muvaffaqiyat koʻrsatkichini belgilaydi. Va bu holat repozitoriyda boʻlishi shart — chunki u agent ega boʻlgan yagona barqaror va ochiq xotiradir. - -Siz shunday oʻylashingiz mumkin: “Jamoamiz kichik, bilimlar hammaning boshida va bu muammosiz ishlayapti.” Toʻgʻri, odamlar uchun. Lekin agar siz agentʼdan foydalanayotgan boʻlsangiz, shu faktni qabul qiling: agent odamlardan soʻray olmaydi. U bilishi kerak boʻlgan hamma narsa yozilgan va u topa oladigan joyda saqlangan boʻlishi shart. - -Bu “koʻproq hujjat yozish” haqida emas. Bu “qaror qabul qilish maʼlumotlarini toʻgʻri joyga qoʻyish” haqida. `src/api/` katalogidagi 50 qatorlik `ARCHITECTURE.md` fayli, hech kim qaramaydigan Confluenceʼdagi 500 sahifalik dizayn hujjatidan oʻn ming marta foydaliroqdir. Bu xuddi stolingizga yopishtirib qoʻyilgan qoʻlda chizilgan ofis xaritasi bilan javonga qulflab qoʻyilgan ajoyib arxitektura chizmasini taqqoslashdek gap — birinchisi kerak boʻlganda yoningizda turadi, ikkinchisi esa texnik jihatdan mukammal boʻlsa-da, kerakli paytda foydasizdir. - -## Bilim koʻrinuvchanligi (Knowledge Visibility) - -```mermaid -flowchart LR - Slack["Slackʼdagi qoidalar"] --> Write["Ularni repo fayllariga yozing
    AGENTS.md / ARCHITECTURE.md / PROGRESS.md"] - Confluence["Confluenceʼdagi qoidalar"] --> Write - Heads["Odamlar boshidagi qoidalar"] --> Write - Jira["Jira tiketlaridagi qoidalar"] --> Write - Write --> Repo["Repozitoriy fayllari"] - Repo --> Agent["Yangi agent sessiyasi
    repoʻni toʻgʻridan-toʻgʻri oʻqiydi"] - Warning["Agar qoida repoda boʻlmasa,
    agent uni koʻra olmaydi"] --> Agent -``` - -Xaritangiz yetarlicha yaxshi yoki yoʻqligini qanday sinab koʻrasiz? “Sovuq ishga tushirish (cold-start)” testini oʻtkazing: faqat repo tarkibidan foydalanadigan butunlay yangi agent sessiyasini oching va uning beshta asosiy savolga javob bera olishini tekshiring: - -```mermaid -flowchart TB - Q1["Bu qanday tizim?"] --> A1["AGENTS.md / README"] - Q2["U qanday tuzilgan?"] --> A2["ARCHITECTURE.md / modul hujjatlari"] - Q3["Uni qanday ishga tushiraman?"] --> A3["Makefile / init.sh / package scriptʼlari"] - Q4["Uni qanday tekshiraman?"] --> A4["Test, lint va type check buyruqlari"] - Q5["Biz hozir qayerdamiz?"] --> A5["PROGRESS.md / feature list / git tarixi"] - - A1 --> Ready["Yangi sessiya odamdan soʻramasdan
    ish boshlay oladi"] - A2 --> Ready - A3 --> Ready - A4 --> Ready - A5 --> Ready -``` - -Agar u javob bera olmasa, xaritada boʻsh joylar (blank spots) mavjud. Xarita boʻsh boʻlgan joyda agent taxmin qiladi — xato taxminlar bugʼlarga aylanadi, ortiqcha taxminlar esa kontekstni bekorga sarflaydi. Va har bir yangi sessiya hamma narsani boshidan taxmin qilishga majbur. Taxmin qilishning narxi, xaritani boshidanoq toʻgʻri chizib qoʻyish narxidan doim yuqori boʻladi. - -## Asosiy tushunchalar - -- **Bilim koʻrinuvchanligi boʻshligʻi (Knowledge Visibility Gap)**: Loyiha boʻyicha umumiy bilimlarning repozitoriyda MAVJUD BOʻLMAGAN qismi. Boʻshliq qanchalik katta boʻlsa, agentʼning muvaffaqiyatsizlik koʻrsatkichi shuncha yuqori boʻladi. Bu loyiha haqidagi qancha yashirin bilimlar sizning boshingizda saqlanadi? Hammasini hisoblab chiqing va qanchasi repoga kiritilganini koʻring — farq sizning koʻrinuvchanlik boʻshligʻingizdir. -- **Yagona haqiqat manbai (System of Record)**: Loyiha qarorlari, arxitektura cheklovlari, bajarilish holati va tekshiruv (verification) standartlari uchun vakolatli manba sifatida kod repozitoriysi olinadi. Repo oxirgi soʻzni aytadi, boshqa hech qayer hisobga olinmaydi. Xuddi xaritada “yoʻl yopiq” belgisi qoʻyilganidek — siz u yoʻldan yurmaysiz. Lekin agar bu maʼlumot faqat Qari Zhangʻning boshida boʻlsa, har safar borib undan soʻrashingizga toʻgʻri keladi. -- **Sovuq ishga tushirish testi (Cold-Start Test)**: Yuqoridagi beshta savol. U qanchasiga javob bera olsa, xaritangiz shunchalik toʻliq boʻladi. -- **Kashfiyot narxi (Discovery Cost)**: Agent repoda bitta muhim maʼlumotni topish uchun qancha kontekst byudjetini sarflashi. Maʼlumot qanchalik yashiringan boʻlsa, kashfiyot narxi shunchalik yuqori boʻladi va asosiy vazifa uchun shuncha kam byudjet qoladi. Muhim maʼlumotlarni 10 ta katalog chuqurligidagi README ichiga yashirish — bu xuddi oʻt oʻchirgichni yertoʻladagi seyfga qulflab qoʻyishga oʻxshaydi; u mavjud, lekin kerak boʻlganda topa olmaysiz. -- **Bilim eskirish darajasi (Knowledge Decay Rate)**: Vaqt oʻtishi bilan eskiradigan bilimlar ulushi. Hujjatlarning kod bilan mos kelmay qolishi eng katta dushmandir — bu hujjat umuman yoʻqligidan ham yomonroq. -- **ACID analogiyasi**: Maʼlumotlar bazasi tranzaksiyasi tamoyillarini (Atomicity, Consistency, Isolation, Durability) agent holatini boshqarishga qoʻllash. Buni quyida batafsil yoritamiz. - -## Yaxshi xarita qanday chiziladi - -**1-tamoyil: Bilim kodning yonida yashaydi.** API endpoint autentifikatsiyasi haqidagi qoida qandaydir katta global hujjatning ichida yashirinmasdan, API kodining yonida turishi kerak. Har bir modul katalogida uning vazifalari, interfeyslari va maxsus cheklovlarini tushuntiruvchi qisqa hujjat joylashtiring. Xuddi kutubxona tokchalaridagi yorliqlar kabi — tarix kitoblari kerakmi, toʻgʻridan-toʻgʻri “Tarix” deb yozilgan tokchaga borasiz. Butun kutubxonani qidirishga hojat yoʻq. - -**2-tamoyil: Standartlashtirilgan kirish faylidan foydalaning.** `AGENTS.md` (yoki `CLAUDE.md`) bu agentʼning “kirish sahifasi” (landing page). U barcha maʼlumotlarni oʻz ichiga olishi shart emas, lekin u agentʼga uchta savolga tezda javob topish imkonini berishi kerak: “Bu nima loyiha”, “Uni qanday ishga tushiraman” va “Uni qanday tekshiraman”. 50-100 qator yetarli. - -**3-tamoyil: Minimal, lekin toʻliq.** Har bir bilim parchasining aniq foydalanish holati boʻlishi kerak. Agar qoidani olib tashlash agentʼning qaror qabul qilish sifatiga taʼsir qilmasa, u qoida mavjud boʻlmasligi kerak. Biroq, sovuq ishga tushirish testidagi har bir savolning javobi boʻlishi shart. Bu nozik muvozanat — juda koʻp emas, juda kam ham emas, ayni meʼyorida. - -**4-tamoyil: Kod bilan birga yangilang.** Bilimlar yangilanishini kod oʻzgarishlariga bogʻlang. Eng oddiy usul: arxitektura hujjatlarini tegishli modul katalogiga qoʻying. Kodni oʻzgartirganingizda, oʻz-oʻzidan hujjatni koʻrasiz. Kod oʻzgarishlaridan keyin, CI hujjatlarni yangilash kerak yoki yoʻqligini tekshirishni eslatib turishi mumkin. - -**Aniq repo strukturasi**: - -``` -project/ -├── AGENTS.md # Kirish: loyiha tavsifi, ishga tushirish buyruqlari, qatʼiy cheklovlar -├── src/ -│ ├── api/ -│ │ ├── ARCHITECTURE.md # API qatlami arxitekturasi qarorlari -│ │ └── ... -│ ├── db/ -│ │ ├── CONSTRAINTS.md # Maʼlumotlar bazasi amallari uchun qatʼiy cheklovlar -│ │ └── ... -│ └── ... -├── PROGRESS.md # Joriy jarayon: tugatilgan, jarayonda, bloklangan -└── Makefile # Standart buyruqlar: setup, test, lint, check -``` - -## ACID tamoyillari bilan Agent holatini boshqarish - -Bu analogiya maʼlumotlar bazasi tranzaksiyalarini boshqarishdan olingan — ehtimol, siz buni narsalarni ortiqcha murakkablashtirish deb oʻylashingiz mumkin, lekin u aslida sizga juda amaliy freymvorkni beradi: - -- **Atomicity (Boʻlinmaslik)**: Har bir “mantiqiy amal” (masalan, “yangi endpoint qoʻshish va testlarni yangilash”) uchun bitta git commit qilinadi. Agar oʻrtada xatolik yuz bersa, orqaga qaytarish uchun `git stash` ishlatiladi. Yoki hammasi, yoki hech narsa — “yarmi bajarildi” degan narsa yoʻq. -- **Consistency (Muvofiqlik)**: “Muvofiq holat”ni (consistent state) tekshirish mezonlarini belgilang — barcha testlar oʻtadi, lint xatosi nolga teng. Agent har bir amaldan keyin tekshiruvni ishga tushiradi; muvofiq boʻlmagan oraliq holatlar commit qilinmaydi. Xuddi bank oʻtkazmasi kabi — mablagʻ kiritilmasdan turib yechib olinmaydi. -- **Isolation (Izolyatsiya)**: Bir nechta agentlar parallel ishlaganda, poyga holatlarini (race conditions) oldini olish uchun holat fayllarini (state files) toʻgʻri loyihalang. Oddiy usul: har bir agent oʻzining alohida progress faylidan foydalanadi yoki izolyatsiya uchun git branchʼlaridan foydalaning. Ikkita oshpaz bitta qozonga bir vaqtda ziravor sola olmaydi — ovqat shoʻr boʻlib ketsa, kim javob beradi? -- **Durability (Chidamlilik)**: Loyihaning muhim bilimlari git orqali kuzatiladigan fayllarda saqlanadi. Vaqtinchalik holat sessiya xotirasida qolishi mumkin, ammo sessiyalararo bilimlar fayllarda doimiy saqlanishi kerak. Sizning boshingizda nima borligi muhim emas — faqat qogʻozda bor narsa hisobga olinadi. - -## Haqiqiy oʻzgarish hikoyasi - -Bir jamoa ~30 ta mikroservisga ega e-tijorat platformasini boshqargan. Arxitektura qarorlari (servislararo aloqa protokollari, maʼlumotlar muvofiqligi strategiyalari, API versiyalash qoidalari) quyidagilarda tarqalib ketgan edi: Confluence (qisman eskirgan), Slack (qidirish qiyin), bir nechta tajribali muhandislarning boshlarida (kengaytirib boʻlmaydi) va kod izohlarida tarqoq holda (tizimli emas). - -AI agentlarini joriy etgandan soʻng, vazifalarning 70% inson aralashuvini talab qildi. Deyarli har bir muvaffaqiyatsizlik agentning “hamma biladi, lekin hech kim yozmagan” yashirin cheklovni buzishi bilan bogʻliq edi. Bu xuddi yangi xodimga hech kim “tushlik buyurtmasini guruh chatiga yozishing kerak” deb aytmaganiga oʻxshaydi — ular xato qilishadi, dakki eshitishadi, ammo gap eshitganidan keyin ham hech kim ularga qoidani tushuntirmaydi. - -Jamoa oʻzgarishlarni amalga oshirdi: -1. Repo ildizida loyiha tavsifi, tech stack versiyalari va global qatʼiy cheklovlar kiritilgan `AGENTS.md` fayli yaratildi. -2. Har bir mikroservis katalogida uning vazifalari, interfeyslari va bogʻliqliklarini taʼriflovchi `ARCHITECTURE.md` qoʻshildi. -3. Aniq “SHART/MUMKIN EMAS” tili bilan markazlashgan qatʼiy cheklovlar (`CONSTRAINTS.md`) yaratildi. -4. Har bir servis katalogiga joriy ish holatini kuzatuvchi `PROGRESS.md` qoʻshildi. - -Oʻzgarishlardan soʻng: xuddi shu agent sovuq ishga tushirishda barcha asosiy loyiha savollariga javob bera oldi va vazifani yakunlash sifati sezilarli darajada yaxshilandi. - -## Asosiy xulosalar - -- Repoda boʻlmagan bilim agent uchun mavjud emas. Muhim qarorlarni repoga kiritish eng asosiy harness sarmoyasidir — adashib qolmaslik uchun yaxshi xarita chizing. -- Repo sifatini baholash uchun “sovuq ishga tushirish testi”dan foydalaning: yangi sessiya faqat repo tarkibidan foydalanib beshta asosiy savolga javob bera oladimi? -- Bilim kodning yonida, minimal lekin toʻliq boʻlishi, va kod bilan birga yangilanishi kerak. Bu koʻproq hujjat yozish emas — maʼlumotni toʻgʻri joyga qoʻyish demakdir. -- Agent holati uchun ACID tamoyillaridan foydalaning: boʻlinmas (atomic) commitʼlar, muvofiqlikni (consistency) tekshirish, parallel ishlash izolyatsiyasi, doimiy muhim bilimlar. -- Bilim eskirishi eng katta dushmandir. Kodga mos kelmaydigan hujjat hujjat umuman yoʻqligidan xavfliroq — u agentni oʻzini toʻgʻri qilyapman deb oʻylab notoʻgʻri yoʻnalishga boshlaydi. - -## Qoʻshimcha oʻqish uchun - -- [OpenAI: Harness Engineering](https://openai.com/index/harness-engineering/) -- [Anthropic: Effective Harnesses for Long-Running Agents](https://www.anthropic.com/engineering/effective-harnesses-for-long-running-agents) -- [Infrastructure as Code — Martin Fowler](https://martinfowler.com/bliki/InfrastructureAsCode.html) -- [ADR: Architecture Decision Records](https://adr.github.io/) -- [The Twelve-Factor App](https://12factor.net/) - -## Mashqlar - -1. **Sovuq ishga tushirish testi**: Loyihangizda mutlaqo yangi agent sessiyasini oching (ogʻzaki kontekstsiz, faqat repo tarkibi bilan). Undan beshta savol soʻrang: Bu qanday tizim? U qanday tuzilgan? Uni qanday ishga tushiraman? Uni qanday tekshiraman? Joriy jarayon qanday? U qaysi savollarga javob bera olmasligini qayd eting va u javob bera olmagunicha repoʻni yaxshilang. - -2. **Bilimlarni tashqariga chiqarishni hisoblash**: Loyihangizda ishlab chiqish uchun muhim boʻlgan barcha qarorlar va cheklovlarni roʻyxatga oling. Ularning har birini repo ichida yoki tashqarisida deb belgilang. Bilim koʻrinuvchanligi boʻshligʻingizni (repo tashqarisidagi ulushni) hisoblang. Uni 10% dan kamaytirish uchun reja tuzing. - -3. **ACID baholash**: Loyihangizdagi holat boshqaruvini ushbu maʼruzadagi ACID analogiyasidan foydalanib baholang. Atomicity — agent amallarini toza orqaga qaytarish mumkinmi? Consistency — “muvofiq holat”ni tekshirish bormi? Isolation — parallel ishlayotgan agentlar bir-biriga xalaqit bermaydimi? Durability — barcha sessiyalararo bilimlar fayllarga doimiy saqlanganmi? diff --git a/courses/learn-harness-engineering/docs/uz/lectures/lecture-04-why-one-giant-instruction-file-fails/code/AGENTS-short.md b/courses/learn-harness-engineering/docs/uz/lectures/lecture-04-why-one-giant-instruction-file-fails/code/AGENTS-short.md deleted file mode 100644 index 8a907397..00000000 --- a/courses/learn-harness-engineering/docs/uz/lectures/lecture-04-why-one-giant-instruction-file-fails/code/AGENTS-short.md +++ /dev/null @@ -1,14 +0,0 @@ -# AGENTS.md - -## Shu Yerdan Boshlang - -- `docs/ARCHITECTURE.md` ni oʻqing -- `docs/PRODUCT.md` ni oʻqing -- Ilovani ishga tushirish uchun `npm run dev` dan foydalaning -- Ishni tugatildi deb belgilashdan oldin `npm run check` dan foydalaning - -## Qatʼiy Qoidalar - -- `docs/ARCHITECTURE.md` ni oʻqimasdan Electron main/preload/renderer chegaralarini (boundaries) oʻzgartirmang -- Tekshiruvsiz (verification) turib funksiyani (feature) tugatildi deb belgilamang -- Keyingi sessiya uchun toza holat qoldiring diff --git a/courses/learn-harness-engineering/docs/uz/lectures/lecture-04-why-one-giant-instruction-file-fails/code/anti-patterns.md b/courses/learn-harness-engineering/docs/uz/lectures/lecture-04-why-one-giant-instruction-file-fails/code/anti-patterns.md deleted file mode 100644 index 8500d3ad..00000000 --- a/courses/learn-harness-engineering/docs/uz/lectures/lecture-04-why-one-giant-instruction-file-fails/code/anti-patterns.md +++ /dev/null @@ -1,7 +0,0 @@ -# Yoʻriqnoma Fayli Antipatternʼlari (Xato Usullari) - -- Barcha repozitoriy bilimlarini bitta faylga joylashtirish -- Ayni bitta qoidani turli joylarda takrorlash -- Hech kim tekshirmaydigan eskirgan qoidalarni kodlashtirish -- Juda kam qoʻllaniladigan shartli yoʻriqnomalarni yozish -- Katta vosita qoʻllanmalarini (tool manuals) ishga tushirish kontekstiga tiqib yuborish diff --git a/courses/learn-harness-engineering/docs/uz/lectures/lecture-04-why-one-giant-instruction-file-fails/code/index.md b/courses/learn-harness-engineering/docs/uz/lectures/lecture-04-why-one-giant-instruction-file-fails/code/index.md deleted file mode 100644 index 704a488d..00000000 --- a/courses/learn-harness-engineering/docs/uz/lectures/lecture-04-why-one-giant-instruction-file-fails/code/index.md +++ /dev/null @@ -1,7 +0,0 @@ -# 4-maʼruza uchun kod - -Ushbu jilddan quyidagilarga misol sifatida foydalaning: - -- monolit (yaxlit katta) yoʻriqnoma fayllari -- qisqa kirish nuqtalari (entrypoints) -- maʼlumotni asta-sekin koʻrsatish (progressive disclosure) andozalari diff --git a/courses/learn-harness-engineering/docs/uz/lectures/lecture-04-why-one-giant-instruction-file-fails/code/split-vs-monolithic.ts b/courses/learn-harness-engineering/docs/uz/lectures/lecture-04-why-one-giant-instruction-file-fails/code/split-vs-monolithic.ts deleted file mode 100644 index b6a4cec2..00000000 --- a/courses/learn-harness-engineering/docs/uz/lectures/lecture-04-why-one-giant-instruction-file-fails/code/split-vs-monolithic.ts +++ /dev/null @@ -1,209 +0,0 @@ -/** - * split-vs-monolithic.ts - * - * Creates a monolithic instruction file (~200 lines) and then shows how - * splitting into 4 focused files dramatically reduces the context needed - * for any single query. Simulates an “agent” searching for a specific rule - * and measures how many lines it must read in each approach. - * - * Run: npx tsx docs/lectures/lecture-04-why-one-giant-instruction-file-fails/code/split-vs-monolithic.ts - */ - -// --------------------------------------------------------------------------- -// Simulated monolithic instruction file (200 lines of rules) -// --------------------------------------------------------------------------- - -const monolithicInstructions: { lineNumber: number; section: string; content: string }[] = []; - -// Section 1: Project Overview (lines 1-50) -for (let i = 1; i <= 50; i++) { - monolithicInstructions.push({ - lineNumber: i, - section: “Project Overview”, - content: i === 25 ? “This project uses React 18 with TypeScript strict mode.” : `Overview detail line ${i}`, - }); -} - -// Section 2: Code Style Rules (lines 51-100) -for (let i = 51; i <= 100; i++) { - monolithicInstructions.push({ - lineNumber: i, - section: “Code Style”, - content: - i === 72 - ? “RULE: Always use explicit return types on exported functions.” - : i === 78 - ? “RULE: Use const assertions for immutable arrays.” - : `Style rule detail line ${i}`, - }); -} - -// Section 3: Testing Standards (lines 101-150) -for (let i = 101; i <= 150; i++) { - monolithicInstructions.push({ - lineNumber: i, - section: “Testing”, - content: - i === 120 - ? “RULE: Every new endpoint must have integration tests.” - : i === 135 - ? “RULE: Test files must mirror the source file structure.” - : `Testing detail line ${i}`, - }); -} - -// Section 4: Deployment Rules (lines 151-200) -for (let i = 151; i <= 200; i++) { - monolithicInstructions.push({ - lineNumber: i, - section: “Deployment”, - content: - i === 175 - ? “RULE: Never deploy on Fridays. Deploy window is Tue-Thu 10am-3pm.” - : `Deployment detail line ${i}`, - }); -} - -// --------------------------------------------------------------------------- -// Split instruction files (4 focused files) -// --------------------------------------------------------------------------- - -const splitInstructions: Record = { - “01-project-overview.md”: monolithicInstructions.filter((l) => l.section === “Project Overview”), - “02-code-style.md”: monolithicInstructions.filter((l) => l.section === “Code Style”), - “03-testing.md”: monolithicInstructions.filter((l) => l.section === “Testing”), - “04-deployment.md”: monolithicInstructions.filter((l) => l.section === “Deployment”), -}; - -// --------------------------------------------------------------------------- -// Simulated queries -- the agent needs to find specific rules -// --------------------------------------------------------------------------- - -interface Query { - description: string; - targetRule: string; - relevantSection: string; -} - -const queries: Query[] = [ - { - description: “Find the rule about return types”, - targetRule: “explicit return types”, - relevantSection: “Code Style”, - }, - { - description: “Find the deployment window rule”, - targetRule: “deploy on Fridays”, - relevantSection: “Deployment”, - }, - { - description: “Find the integration test rule”, - targetRule: “integration tests”, - relevantSection: “Testing”, - }, - { - description: “Find the test file structure rule”, - targetRule: “mirror the source file”, - relevantSection: “Testing”, - }, -]; - -// --------------------------------------------------------------------------- -// Search simulation -// --------------------------------------------------------------------------- - -function searchMonolithic(query: Query): { linesRead: number; found: boolean } { - // Agent must scan from the top, reading each line until it finds the rule. - // In the worst case it reads all lines. - let linesRead = 0; - let found = false; - - for (const line of monolithicInstructions) { - linesRead++; - if (line.content.toLowerCase().includes(query.targetRule.toLowerCase())) { - found = true; - break; - } - } - - return { linesRead, found }; -} - -function searchSplit(query: Query): { linesRead: number; found: boolean; fileAccessed: string } { - // Agent knows which file to look in based on the section. - // It only reads lines from that one file. - const fileMap: Record = { - “Project Overview”: “01-project-overview.md”, - “Code Style”: “02-code-style.md”, - Testing: “03-testing.md”, - Deployment: “04-deployment.md”, - }; - - const targetFile = fileMap[query.relevantSection]; - const lines = splitInstructions[targetFile]; - let linesRead = 0; - let found = false; - - for (const line of lines) { - linesRead++; - if (line.content.toLowerCase().includes(query.targetRule.toLowerCase())) { - found = true; - break; - } - } - - return { linesRead, found, fileAccessed: targetFile }; -} - -// --------------------------------------------------------------------------- -// Reporting -// --------------------------------------------------------------------------- - -function pad(s: string, len: number): string { - return s.length >= len ? s : s + “ ”.repeat(len - s.length); -} - -function run(): void { - console.log(“\n” + “=”.repeat(90)); - console.log(“ MONOLITHIC vs SPLIT INSTRUCTION FILES”); - console.log(“=”.repeat(90)); - - console.log(“\n Monolithic file: 1 file, ” + monolithicInstructions.length + “ lines total”); - console.log(“ Split files: 4 files, ~” + Math.round(monolithicInstructions.length / 4) + “ lines each\n”); - - const header = `| ${pad("Query", 42)}| ${pad("Monolithic (lines)", 20)}| ${pad("Split (lines)", 15)}| ${pad("File Accessed", 22)}| Savings`; - const sep = `|${"-".repeat(44)}|${"-".repeat(22)}|${"-".repeat(17)}|${"-".repeat(24)}|${"-".repeat(10)}`; - console.log(header); - console.log(sep); - - let totalMono = 0; - let totalSplit = 0; - - for (const q of queries) { - const mono = searchMonolithic(q); - const split = searchSplit(q); - totalMono += mono.linesRead; - totalSplit += split.linesRead; - - const savings = Math.round(((mono.linesRead - split.linesRead) / mono.linesRead) * 100); - console.log( - `| ${pad(q.description, 42)}| ${pad(String(mono.linesRead), 20)}| ${pad(String(split.linesRead), 15)}| ${pad(split.fileAccessed, 22)}| ${savings}%` - ); - } - - console.log(sep); - const avgMono = Math.round(totalMono / queries.length); - const avgSplit = Math.round(totalSplit / queries.length); - console.log( - `| ${pad("AVERAGE", 42)}| ${pad(String(avgMono), 20)}| ${pad(String(avgSplit), 15)}| ${pad("(targeted file)", 22)}| ${Math.round(((avgMono - avgSplit) / avgMono) * 100)}%` - ); - - console.log(“\n” + “=”.repeat(90)); - console.log(“ KEY INSIGHT”); - console.log(“=”.repeat(90)); - console.log(“ With a monolithic file, the agent must scan up to ” + monolithicInstructions.length + “ lines for every query.”); - console.log(“ With split files, it reads only the relevant ” + Math.round(monolithicInstructions.length / 4) + “-line file.”); - console.log(“ This means less context window usage, fewer hallucinations, and faster execution.\n”); -} - -run(); diff --git a/courses/learn-harness-engineering/docs/uz/lectures/lecture-04-why-one-giant-instruction-file-fails/index.md b/courses/learn-harness-engineering/docs/uz/lectures/lecture-04-why-one-giant-instruction-file-fails/index.md deleted file mode 100644 index 435cdcab..00000000 --- a/courses/learn-harness-engineering/docs/uz/lectures/lecture-04-why-one-giant-instruction-file-fails/index.md +++ /dev/null @@ -1,132 +0,0 @@ -[English version →](../../../en/lectures/lecture-04-why-one-giant-instruction-file-fails/) - -> Kod misollari: [code/](https://github.com/walkinglabs/learn-harness-engineering/blob/main/docs/en/lectures/lecture-04-why-one-giant-instruction-file-fails/code/) -> Amaliy loyiha: [Loyiha 02. Agent oʻqiy oladigan ish maydoni](./../../projects/project-02-agent-readable-workspace/index.md) - -# 4-maʼruza. Yoʻriqnomalarni fayllar boʻylab ajrating - -Siz harness muhandisligiga jiddiy yondashdingiz — tabriklaymiz. Siz `AGENTS.md` faylini yaratdingiz va oʻzingiz oʻylay olgan barcha qoidalar, cheklovlar va olingan saboqlarni uning ichiga joyladingiz. Bir oydan soʻng fayl 300 qatorga, ikki oydan soʻng 450 qatorga, uch oydan soʻng esa 600 qatorga yetdi. Keyin siz agentʼning samaradorligi aslida yomonlashayotganini seza boshlaysiz — oddiy bugʼni tuzatish uchun agent juda koʻp kontekstni hech qanday aloqasi boʻlmagan deploy yoʻriqnomalarini oʻqishga sarflaydi; 300-qatorda koʻmilgan muhim xavfsizlik cheklovi mutlaqo eʼtiborsiz qoldiriladi; kod yozish uslubi boʻyicha bir-biriga zid uchta qoida mavjudligi sababli, agent har safar oʻzboshimchalik bilan ulardan birini tanlaydi. - -Bu “ulkan yoʻriqnoma fayli” tuzogʻidir. Bu xuddi chamadonni haddan tashqari toʻldirishga oʻxshaydi — hamma narsa foydali koʻrinadi, shuning uchun zanjirsimon yopqich yorilib ketgudek boʻlguncha tiqib tashlaysiz. Ichki kiyimingizni almashtirish uchun butun sumkani boʻshatishingiz kerak boʻladi. Siz toʻla chamadonni koʻtarib yurasiz, lekin aslida uning ichidagilarning faqat uchdan bir qismini ishlatasiz, xolos. - -## Muammoning ildizidagi yopiq doira - -Eng keng tarqalgan yopiq doira shunday davom etadi: agent xato qiladi, siz “buning oldini olish uchun qoida qoʻsh” deysiz, uni `AGENTS.md` fayliga qoʻshasiz, u vaqtinchalik ishlaydi, soʻngra agent boshqa bir xatoga yoʻl qoʻyadi, yana boshqa qoida qoʻshasiz, shu zaylda takrorlanib, fayl hajmi nazoratdan chiqib ketadi. - -Bu sizning aybingiz emas. Bu juda tabiiy reaksiya — har safar biror narsa xato ketganda “qoida qoʻshish” xuddi har safar uydan chiqayotganingizda “har ehtimolga qarshi” sumkaga yana bitta narsa tashlab olishdek mantiqli tuyuladi. Lekin uning toʻplanib boruvchi taʼsiri halokatlidir. Keling, aniq nimalar xato ketishini koʻrib chiqaylik. - -**Kontekst byudjeti yeb tugatiladi.** Agentʼning kontekst oynasi cheklangan. Aytaylik, agentʼingiz 200K tokenlik oynaga (Claudeʼning standarti) ega. Shishib ketgan yoʻriqnoma fayli 10-20K tokenni yeb qoʻyishi mumkin. Hali joy koʻpdek koʻrinyaptimi? Biroq murakkab vazifa oʻnlab manba fayllarini oʻqishni talab qilishi mumkin, vositalarning ishlash natijalari ham kontekstni oladi va suhbat tarixi toʻplanib boradi. Agent kodni tushunishi kerak boʻlgan vaqtga kelib, byudjet allaqachon tugab qolgan boʻladi — bu “har ehtimolga qarshi” narsalar bilan toʻldirilgan chamadonda noutbukingiz uchun joy qolmaganiga oʻxshaydi. - -**Oʻrtada yoʻqotish (Lost in the middle).** “Lost in the Middle” tadqiqoti (Liu va boshqalar, 2023) shuni aniq isbotladiki, LLMʼlar uzun matnlarning oʻrtasidagi maʼlumotlardan boshidagi yoki oxiridagi maʼlumotlarga qaraganda sezilarli darajada kamroq samaradorlik bilan foydalanadi. Sizning `AGENTS.md` faylingiz 600 qator, 300-qatorda esa “barcha maʼlumotlar bazasi soʻrovlari parametrlangan soʻrovlardan foydalanishi shart” deb yozilgan — bu qatʼiy xavfsizlik cheklovi. Lekin u oʻrtada koʻmilib yotibdi va agent katta ehtimol bilan unga eʼtibor bermaydi. Xuddi toʻlib toshgan chamadoningiz tagidagi quyoshdan himoya qiluvchi krem (sunscreen) kabi — uning oʻsha yerda ekanligini bilasiz, uch marta qidirasiz, topa olmaysiz va oxirida boshqasini sotib olasiz. - -**Ustuvorlik mojarolari.** Fayl muhokama qilinmaydigan qatʼiy cheklovlarni (“hech qachon eval() ni ishlatmang”), muhim dizayn yoʻriqnomalarini (“funksional uslubni afzal koʻring”) va maxsus tarixiy saboqni (“oʻtgan haftada WebSocket xotira sizib chiqishini (memory leak) toʻgʻriladik, shunga oʻxshash andozalarga eʼtibor bering”) aralashtirib yuboradi. Ushbu uchta qoidaning ahamiyati umuman har xil, ammo ular faylda bir xil koʻrinadi. Agentʼda farqlash uchun hech qanday ishonchli signal yoʻq — xuddi chamadonga aralashtirib tashlangan pasportingiz va quvvatlash kabelingiz singari, qaysi biri muhimroq ekanini farqlash imkonsiz. - -**Xizmat koʻrsatishdagi eskirish (Maintenance decay).** Katta fayllarga texnik xizmat koʻrsatish tabiatan qiyin. Eskirgan yoʻriqnomalar deyarli oʻchirib tashlanmaydi — chunki ularni oʻchirish oqibatlari noaniq (“balki boshqa narsa bu qoidaga bogʻliqdir?”), yangi yoʻriqnomalar qoʻshish esa bepuldek tuyuladi. Natija: fayl faqat oʻsadi, hech qachon qisqarmaydi va signal-shovqin nisbati doimiy ravishda pasayib boradi. Bu xuddi dasturiy taʼminotda texnik qarz toʻplanishiga oʻxshaydi. - -**Qarama-qarshiliklar yigʻilishi.** Turli vaqtlarda qoʻshilgan yoʻriqnomalar bir-biriga zid kela boshlaydi — biri “TypeScript strict rejimini ishlating” deydi, boshqasi “baʼzi eski fayllarda (legacy files) any tipiga ruxsat beriladi” deydi. Agent har safar ulardan birini tavakkaliga tanlaydi. Xuddi onangiz “issiqroq kiyin” deb, otangiz “juda qalin kiyinma” deyishi va siz eshik oldida kimga quloq solishni bilmay turishingizdek gap. - -## Asosiy tushunchalar - -- **Yoʻriqnoma shishuvi (Instruction Bloat)**: Yoʻriqnoma fayli kontekst oynasining 10-15% dan ortigʻini egallaganda, u kod oʻqish va vazifani muhokama qilish uchun ajratilgan byudjetni yeb yuborishni boshlaydi. 600 qatorli `AGENTS.md` fayli 10,000-20,000 token ishlatishi mumkin — bu agent hatto ish boshlamasidan oldin 128K oynaning 8-15% qismini yeb qoʻyganini bildiradi. -- **Oʻrtada yoʻqotish effekti (Lost in the Middle Effect)**: Liu va boshqalarning 2023-yildagi tadqiqoti LLMʼlar uzun matnlarning oʻrtasidagi maʼlumotlardan boshidagi yoki oxiridagi maʼlumotlarga qaraganda sezilarli darajada kamroq samaradorlik bilan foydalanishini isbotladi. 600 qatorli faylning 300-qatorida koʻmilgan muhim cheklov deyarli eʼtiborsiz qoldirilish ehtimoli juda yuqori. -- **Yoʻriqnomaning signal-shovqin nisbati (Instruction Signal-to-Noise Ratio (SNR))**: Fayldagi yoʻriqnomalarning joriy vazifaga aloqador boʻlgan qismi. Kichik bugʼni tuzatish jarayonida 50 qatorlik deploy yoʻriqnomalarini oʻqishga majbur boʻlish — bu past SNR demakdir. -- **Marshrutlash fayli (Routing File)**: Asosiy vazifasi hamma narsani oʻz ichiga olish emas, balki agentʼni batafsil hujjatlarga yoʻnaltirish boʻlgan qisqa kirish fayli. 50-200 qator boʻlishi yetarli. -- **Bosqichma-bosqich oshkor qilish (Progressive Disclosure)**: Avval umumiy maʼlumotni, keyin kerak boʻlganda batafsil maʼlumotni bering. Yaxshi harness dizayni xuddi yaxshi UI dizayniga oʻxshaydi — barcha variantlarni birdaniga foydalanuvchiga toʻkib tashlamang. -- **Ustuvorlik noaniqligi (Priority Ambiguity)**: Barcha yoʻriqnomalar bir xil formatda va bir joyda turganda, agent muhokama qilinmaydigan qatʼiy cheklovlarni tavsiyaviy yoʻriqnomalardan ajrata olmaydi. - -## Yoʻriqnoma arxitekturasi - -```mermaid -flowchart LR - Mono["Bitta 600-qatorli AGENTS.md"] --> MonoLoad["Hatto kichik bug tuzatishda ham
    deploy qoidalari va eski eslatmalar oʻqilishi shart"] - MonoLoad --> MonoRisk["Oʻrtada koʻmilgan muhim qoidalarni
    oʻtkazib yuborish oson"] - - Router["Qisqa AGENTS.md"] --> Topics["API / DB / testing hujjatlarini
    faqat joriy vazifa uchun kerak boʻlganda yuklash"] - Topics --> RoutedResult["Kod oʻqish va tekshirish uchun
    koʻproq kontekst qoladi"] -``` - -```mermaid -flowchart TB - File["600-qatorli yoʻriqnoma fayli"] --> Top["Yuqori qism
    quick start + qatʼiy cheklovlar"] - File --> Mid["Oʻrta qism
    300-qatordagi muhim xavfsizlik qoidasi"] - File --> Bot["Quyi qism
    aniq end-of-file tekshiruv roʻyxati"] - Top --> Seen["Esda saqlash ehtimoli yuqori"] - Bot --> Seen - Mid --> Missed["Yoʻqolish yoki eʼtibordan chetda qolish ehtimoli yuqori"] -``` - -## Qanday qilib ajratiladi - -Asosiy tamoyil: tez-tez kerak boʻladigan maʼlumotlarni yonida saqlang, vaqti-vaqti bilan kerak boʻladigan maʼlumotlarni chetroqqa qoʻying va umuman ishlatilmaydigan narsalardan voz keching. - -`AGENTS.md` kirish fayli 50-200 qatordan iborat boʻlib qoladi va unda faqat eng koʻp ishlatiladigan narsalar saqlanadi — loyiha boʻyicha qisqacha maʼlumot (bir yoki ikki jumla), birinchi marta ishga tushirish buyruqlari (`make setup && make test`), global qatʼiy cheklovlar (15 tadan ortiq boʻlmagan qatʼiy qoidalar) va mavzuviy hujjatlarga havolalar (bir qatorli izoh + qoʻllanilish sharti). - -```markdown -# AGENTS.md - -## Loyiha haqida -Python 3.11 FastAPI backend, PostgreSQL 15 maʼlumotlar bazasi. - -## Tez boshlash (Quick Start) -- Oʻrnatish: `make setup` -- Test: `make test` -- Toʻliq tekshiruv: `make check` - -## Qatʼiy cheklovlar -- Barcha APIʼlar OAuth 2.0 autentifikatsiyasidan foydalanishi shart -- Barcha maʼlumotlar bazasi soʻrovlari SQLAlchemy 2.0 sintaksisidan foydalanishi shart -- Barcha PRʼlar pytest + mypy --strict + ruff check dan oʻtishi shart - -## Mavzuli hujjatlar -- [API dizayn andozalari](docs/api-patterns.md) — Endpointʼlar qoʻshganda oʻqish majburiy -- [Maʼlumotlar bazasi qoidalari](docs/database-rules.md) — Maʼlumotlar bazasi amallarini oʻzgartirganda kerak boʻladi -- [Testlash standartlari](docs/testing-standards.md) — Test yozish uchun qoʻllanma -``` - -Har bir mavzu hujjati `docs/` katalogida yoki tegishli modulning yonida mavzu boʻyicha tashkillashtirilgan holda 50-150 qatordan iborat boʻladi. Agent ularni faqat kerak boʻlganda oʻqiydi. Xuddi chamadondagi taqsimlagichlar (packing cubes) kabi — ichki kiyimlar bir boʻlimda, yuvinish vositalari ikkinchisida, quvvatlagichlar uchinchisida. Biror narsani qidirib topish uchun butun sumkani boʻshatish shart emas. - -Baʼzi maʼlumotlarni toʻgʻridan-toʻgʻri kod ichiga joylashtirgan maʼqulroq — type definitionʼlar, interfeys izohlari, konfiguratsiya fayllaridagi tushuntirishlar. Agent kod oʻqiyotganda ularni tabiiy ravishda koʻradi, yoʻriqnomalarda uni takrorlashga hojat yoʻq. - -Har bir yoʻriqnomaning manbasi (“bu qoida nima uchun qoʻshilgan?”), qoʻllanilish sharti (“bu qoida qachon kerak boʻladi?”) va eskirish sharti (“qanday vaziyatlarda bu qoidani olib tashlash mumkin?”) boʻlishi kerak. Muntazam ravishda audit oʻtkazing, eskirgan, keraksiz yoki qarama-qarshi bandlarni oʻchirib tashlang. Yoʻriqnomalarni xuddi koddagi kutubxonalar (dependencies) kabi boshqaring — ishlatilmaydigan kutubxonalar oʻchirib tashlanishi kerak, aks holda ular faqat tizimni sekinlashtiradi. - -Agar yoʻriqnoma kirish faylida boʻlishi shart boʻlsa, uni boshiga yoki oxiriga joylashtiring — hech qachon oʻrtasiga emas. “Oʻrtada yoʻqotish” (lost in the middle) effekti bizga LLMʼlar markazdagi maʼlumotlardan koʻra boshidagi va oxiridagi maʼlumotlardan samaraliroq foydalanishini koʻrsatadi. Lekin yaxshiroq yondashuv — yoʻriqnomalarni kerak boʻlganda yuklanishi uchun mavzu hujjatlariga koʻchirishdir. - -OpenAI va Anthropic ham ajratish yondashuvini bilvosita qoʻllab-quvvatlaydi. OpenAI kirish fayllari “qisqa va yoʻnaltiruvchi” boʻlishi kerak deydi, Anthropic koʻp vaqt oladigan agentʼlarni boshqarish maʼlumotlari “loʻnda va yuqori ustuvorlikka ega” boʻlishi kerak deydi. Ikkalasi ham bir xil narsani aytmoqda: hamma narsani bitta faylga tiqib tashlamang. Chamadonga shunchaki kuch bilan tiqmasdan, uni tartibga solish kerak. - -## Hayotiy misol - -SaaS jamoasining `AGENTS.md` fayli 50 qatordan 600 qatorgacha shishib ketdi. Fayl tarkibiga tech stack versiyalari, kod yozish standartlari, tarixiy bugʼlarni tuzatish boʻyicha eslatmalar, API qoʻllanmalari, deploy jarayonlari va jamoa aʼzolarining shaxsiy xohish-istaklari aralashib ketgan — xuddi tikish choklaridan yorilayotgan chamadonga oʻxshardi. - -Agentʼning samaradorligi sezilarli darajada pasaya boshladi: oddiy bugʼlarni tuzatish jarayonida agent juda koʻp kontekstni aloqasi yoʻq deploy yoʻriqnomalarini oʻqishga sarfladi; “barcha maʼlumotlar bazasi soʻrovlari parametrlangan soʻrovlardan foydalanishi shart” degan xavfsizlik cheklovi 300-qatorda koʻmilib yotgani uchun tez-tez eʼtiborsiz qoldirildi; kod uslubidagi bir-biriga zid uchta qoida agentʼning tasodifiy xatti-harakatlanishiga sabab boʻldi. - -Jamoa “chamadonni qayta tartibga solish” ishini bajardi: -1. `AGENTS.md` 80 qatorga qisqartirildi: faqat loyiha tavsifi, ishga tushirish buyruqlari va 15 ta global qatʼiy cheklovlar qoldirildi. -2. Mavzu hujjatlari yaratildi: `docs/api-patterns.md` (120 qator), `docs/database-rules.md` (60 qator), `docs/testing-standards.md` (80 qator). -3. Marshrutlash faylida mavzu hujjatlari havolalari qoʻshildi. -4. Tarixiy eslatmalar test holatlariga oʻgirildi yoki oʻchirib tashlandi. - -Refaktoringdan soʻng: xuddi oʻsha vazifalar toʻplamini muvaffaqiyatli bajarish koʻrsatkichi 45% dan 72% gacha koʻtarildi. Xavfsizlik cheklovlariga rioya qilish 60% dan 95% gacha oshdi — chunki u faylning oʻrtasidan marshrutlash faylining eng tepasiga koʻchirildi, endi u “oʻrtada yoʻqolib” qolmaydi. - -## Asosiy xulosalar - -- “Qoida qoʻshish” — qisqa muddatli yengillik, ammo uzoq muddatli zahar. Qoida qoʻshishdan oldin oʻzingizga savol bering: buni mavzuviy hujjatda saqlagan maʼqul emasmi? Chamadonga shunchaki narsa tiqishni toʻxtating. -- Kirish fayli ensiklopediya emas, balki marshrutlash faylidir (router). Faqat umumiy maʼlumot, qatʼiy cheklovlar va havolalar oʻrin olgan 50-200 qator. -- “Oʻrtada yoʻqotish” effektidan foydalaning: muhim maʼlumotlar eng yuqoriga yoki eng pastga qoʻyiladi; ahamiyati pastroq maʼlumotlar mavzu hujjatlariga oʻtkaziladi. -- Yoʻriqnoma shishuvini (instruction bloat) texnik qarz kabi boshqaring. Muntazam tekshirib boring, har bir yoʻriqnomaning manbasi, qoʻllanilish sharti va eskirish sharti boʻlishi shart. -- Ajratishdan soʻng, SNR yaxshilanadi va agent aloqador boʻlmagan yoʻriqnomalarni qayta ishlash oʻrniga kontekst byudjetini haqiqiy vazifalarga koʻproq sarflaydi. - -## Qoʻshimcha oʻqish uchun - -- [OpenAI: Harness Engineering](https://openai.com/index/harness-engineering/) -- [Anthropic: Effective Harnesses for Long-Running Agents](https://www.anthropic.com/engineering/effective-harnesses-for-long-running-agents) -- [Lost in the Middle: How Language Models Use Long Contexts](https://arxiv.org/abs/2307.03172) -- [HumanLayer: Harness Engineering for Coding Agents](https://humanlayer.dev/articles/harness-engineering-for-coding-agents/) -- [Nielsen Norman Group: Progressive Disclosure](https://www.nngroup.com/articles/progressive-disclosure/) - -## Mashqlar - -1. **SNR auditi**: Oʻzingizning joriy kirish yoʻriqnoma faylingizni oling va barcha yoʻriqnoma yozuvlarini roʻyxatga kiriting. 5 xil keng tarqalgan vazifa turlarini tanlang va har bir yoʻriqnomaning oʻsha vazifaga aloqador yoki yoʻqligini belgilang. Har bir vazifa turi uchun SNR ni hisoblang. Aksariyat vazifalar uchun shovqin (noise) hisoblangan yoʻriqnomalar mavzu hujjatlariga koʻchirilishi kerak. - -2. **Bosqichma-bosqich oshkor qilish refaktoringi**: Agar sizda 300 qatordan ortiq yoʻriqnoma fayli boʻlsa, uni quyidagilarga ajrating: (a) 100 qatordan kam boʻlgan marshrutlash fayli, (b) 3-5 ta mavzu hujjatlari. Xuddi oʻsha vazifalar toʻplamini (kamida 5 ta) oldin va keyin ishga tushiring, muvaffaqiyat koʻrsatkichlarini taqqoslang. - -3. **Oʻrtada yoʻqotishni tekshirish**: Uzun yoʻriqnoma faylining mos ravishda yuqori, oʻrta va quyi qismlariga muhim cheklovni joylashtiring va har safar bitta vazifalar toʻplamini ishlating (har bir pozitsiya uchun kamida 5 ta urinish). Bajarish darajasida farq bor-yoʻqligini kuzating. Joylashuv effektining qanchalik kuchliligini koʻrib hayratda qolishingiz mumkin. diff --git a/courses/learn-harness-engineering/docs/uz/lectures/lecture-05-why-long-running-tasks-lose-continuity/code/continuity-checklist.md b/courses/learn-harness-engineering/docs/uz/lectures/lecture-05-why-long-running-tasks-lose-continuity/code/continuity-checklist.md deleted file mode 100644 index 11d76372..00000000 --- a/courses/learn-harness-engineering/docs/uz/lectures/lecture-05-why-long-running-tasks-lose-continuity/code/continuity-checklist.md +++ /dev/null @@ -1,6 +0,0 @@ -# Uzluksizlik (Continuity) Tekshiruv Roʻyxati - -- Yangi qoʻshilgan agent besh daqiqa ichida soʻnggi qilingan ishlarni aniqlay oladimi? -- Hozirgi barqaror ishga tushirish (startup) yoʻli hujjatlashtirilganmi? -- Tugallanmagan ishlar aniq belgilanganmi? -- Keyingi eng yaxshi qadam eski chat loglarini oʻqimasdan turib koʻrinib turibdimi? diff --git a/courses/learn-harness-engineering/docs/uz/lectures/lecture-05-why-long-running-tasks-lose-continuity/code/index.md b/courses/learn-harness-engineering/docs/uz/lectures/lecture-05-why-long-running-tasks-lose-continuity/code/index.md deleted file mode 100644 index 2335bcb0..00000000 --- a/courses/learn-harness-engineering/docs/uz/lectures/lecture-05-why-long-running-tasks-lose-continuity/code/index.md +++ /dev/null @@ -1,7 +0,0 @@ -# 5-maʼruza uchun kod - -Ushbu jilddan quyidagilarga misol sifatida foydalaning: - -- uzilib qolgan koʻp sessiyali vazifalar -- yetishmayotgan uzluksizlik (continuity) artefaktlari -- uzluksizlikni qayta tiklash (continuity recovery) andozalari diff --git a/courses/learn-harness-engineering/docs/uz/lectures/lecture-05-why-long-running-tasks-lose-continuity/code/session-handoff.md b/courses/learn-harness-engineering/docs/uz/lectures/lecture-05-why-long-running-tasks-lose-continuity/code/session-handoff.md deleted file mode 100644 index 9e0ef8a4..00000000 --- a/courses/learn-harness-engineering/docs/uz/lectures/lecture-05-why-long-running-tasks-lose-continuity/code/session-handoff.md +++ /dev/null @@ -1,17 +0,0 @@ -# Sessiyani Topshirish (Session Handoff) Misoli - -## Yakunlandi - -- Markdown import qilish qoʻllab-quvvatlandi -- Rendererʼga oddiy hujjatlar roʻyxati qoʻshildi - -## Buzilgan yoki Tekshirilmagan - -- Import `.md` uchun ishlamoqda, lekin katta `.txt` fayllar uchun yiqilmoqda -- Ilova ishga tushmoqda, lekin detallarni koʻrish (detail view) hali ulanmagan - -## Keyingi Eng Yaxshi Qadam - -- `.txt` import qilish yoʻlini toʻgʻrilash -- Import jarayonini end-to-end tekshirish -- Keyin hujjat detali panelini (document detail panel) qoʻshish diff --git a/courses/learn-harness-engineering/docs/uz/lectures/lecture-05-why-long-running-tasks-lose-continuity/code/session-simulator.ts b/courses/learn-harness-engineering/docs/uz/lectures/lecture-05-why-long-running-tasks-lose-continuity/code/session-simulator.ts deleted file mode 100644 index 018f6983..00000000 --- a/courses/learn-harness-engineering/docs/uz/lectures/lecture-05-why-long-running-tasks-lose-continuity/code/session-simulator.ts +++ /dev/null @@ -1,162 +0,0 @@ -/** - * session-simulator.ts - * - * Simulates two sessions working on a multi-step task. - * Run 1: No handoff file -- Session B starts from scratch and duplicates work. - * Run 2: With handoff file -- Session B picks up where Session A left off. - * - * Run: npx tsx docs/lectures/lecture-05-why-long-running-tasks-lose-continuity/code/session-simulator.ts - */ - -// --------------------------------------------------------------------------- -// Types -// --------------------------------------------------------------------------- - -interface TaskStep { - id: number; - name: string; - durationMs: number; -} - -interface SessionResult { - session: string; - stepsCompleted: number; - totalDurationMs: number; - duplicatedSteps: number; - output: string[]; -} - -// --------------------------------------------------------------------------- -// Task definition -// --------------------------------------------------------------------------- - -const taskSteps: TaskStep[] = [ - { id: 1, name: “Read project structure”, durationMs: 50 }, - { id: 2, name: “Understand existing auth module”, durationMs: 80 }, - { id: 3, name: “Design new search endpoint”, durationMs: 60 }, - { id: 4, name: “Implement search endpoint”, durationMs: 100 }, - { id: 5, name: “Write integration tests”, durationMs: 70 }, - { id: 6, name: “Update documentation”, durationMs: 40 }, -]; - -// --------------------------------------------------------------------------- -// Simulated session executor -// --------------------------------------------------------------------------- - -/** Simulate a session doing work. */ -function runSession( - sessionName: string, - startStep: number, - endStep: number -): SessionResult { - const output: string[] = []; - let totalDuration = 0; - - for (let i = startStep; i <= endStep; i++) { - const step = taskSteps.find((s) => s.id === i)!; - totalDuration += step.durationMs; - output.push(` [${sessionName}] Step ${step.id}: ${step.name} (${step.durationMs}ms)`); - } - - return { - session: sessionName, - stepsCompleted: endStep - startStep + 1, - totalDurationMs: totalDuration, - duplicatedSteps: 0, - output, - }; -} - -// --------------------------------------------------------------------------- -// Run 1: No handoff file -// --------------------------------------------------------------------------- - -function simulateNoHandoff(): { sessionA: SessionResult; sessionB: SessionResult } { - // Session A does steps 1-3 before timing out - const sessionA = runSession(“Session A”, 1, 3); - - // Session B has no context, starts from scratch - const sessionB = runSession(“Session B”, 1, 6); - sessionB.duplicatedSteps = 3; // Redoes steps 1-3 that A already did - - return { sessionA, sessionB }; -} - -// --------------------------------------------------------------------------- -// Run 2: With handoff file -// --------------------------------------------------------------------------- - -function simulateWithHandoff(): { sessionA: SessionResult; sessionB: SessionResult } { - // Session A does steps 1-3 and writes a handoff file - const sessionA = runSession(“Session A”, 1, 3); - - // Session B reads handoff, starts from step 4 - const sessionB = runSession(“Session B”, 4, 6); - sessionB.duplicatedSteps = 0; - - return { sessionA, sessionB }; -} - -// --------------------------------------------------------------------------- -// Reporting -// --------------------------------------------------------------------------- - -function pad(s: string, len: number): string { - return s.length >= len ? s : s + “ ”.repeat(len - s.length); -} - -function printRun(title: string, result: { sessionA: SessionResult; sessionB: SessionResult }): void { - console.log(“\n” + “=”.repeat(80)); - console.log(` ${title}`); - console.log(“=”.repeat(80)); - - console.log(“\n Session A output:”); - for (const line of result.sessionA.output) { - console.log(line); - } - - console.log(“\n Session B output:”); - for (const line of result.sessionB.output) { - console.log(line); - } - - console.log(“\n Session B notes:”); - if (result.sessionB.duplicatedSteps > 0) { - console.log(` - Redid ${result.sessionB.duplicatedSteps} steps that Session A already completed`); - console.log(` - Wasted ${result.sessionB.totalDurationMs - (taskSteps.slice(3).reduce((s, t) => s + t.durationMs, 0))}ms on duplicate work`); - } else { - console.log(“ - Read handoff file, continued from step 4”); - console.log(“ - No duplicate work performed”); - } -} - -function printComparison(): void { - const noHandoff = simulateNoHandoff(); - const withHandoff = simulateWithHandoff(); - - printRun(“RUN 1: NO HANDOFF FILE”, noHandoff); - printRun(“RUN 2: WITH HANDOFF FILE”, withHandoff); - - // Comparison table - console.log(“\n” + “=”.repeat(80)); - console.log(“ COMPARISON TABLE”); - console.log(“=”.repeat(80) + “\n”); - - const header = `| ${pad("Metric", 35)}| ${pad("No Handoff", 15)}| ${pad("With Handoff", 15)}|`; - const sep = `|${"-".repeat(37)}|${"-".repeat(17)}|${"-".repeat(17)}|`; - console.log(header); - console.log(sep); - - const noTotal = noHandoff.sessionA.totalDurationMs + noHandoff.sessionB.totalDurationMs; - const withTotal = withHandoff.sessionA.totalDurationMs + withHandoff.sessionB.totalDurationMs; - - console.log(`| ${pad("Session A steps completed", 35)}| ${pad(String(noHandoff.sessionA.stepsCompleted), 15)}| ${pad(String(withHandoff.sessionA.stepsCompleted), 15)}|`); - console.log(`| ${pad("Session B steps completed", 35)}| ${pad(String(noHandoff.sessionB.stepsCompleted), 15)}| ${pad(String(withHandoff.sessionB.stepsCompleted), 15)}|`); - console.log(`| ${pad("Duplicated steps", 35)}| ${pad(String(noHandoff.sessionB.duplicatedSteps), 15)}| ${pad(String(withHandoff.sessionB.duplicatedSteps), 15)}|`); - console.log(`| ${pad("Total work (ms)", 35)}| ${pad(String(noTotal), 15)}| ${pad(String(withTotal), 15)}|`); - console.log(`| ${pad("Time saved by handoff", 35)}| ${pad("-", 15)}| ${pad(String(noTotal - withTotal) + "ms", 15)}|`); - - console.log(“\n A handoff file eliminates duplicate work and ensures continuity across sessions.\n”); -} - -printComparison(); diff --git a/courses/learn-harness-engineering/docs/uz/lectures/lecture-05-why-long-running-tasks-lose-continuity/index.md b/courses/learn-harness-engineering/docs/uz/lectures/lecture-05-why-long-running-tasks-lose-continuity/index.md deleted file mode 100644 index 4c3fcf53..00000000 --- a/courses/learn-harness-engineering/docs/uz/lectures/lecture-05-why-long-running-tasks-lose-continuity/index.md +++ /dev/null @@ -1,180 +0,0 @@ -[English version →](../../../en/lectures/lecture-05-why-long-running-tasks-lose-continuity/) - -> Kod misollari: [code/](https://github.com/walkinglabs/learn-harness-engineering/blob/main/docs/en/lectures/lecture-05-why-long-running-tasks-lose-continuity/code/) -> Amaliy loyiha: [Loyiha 03. Koʻp sessiyali uzluksizlik](./../../projects/project-03-multi-session-continuity/index.md) - -# 5-maʼruza. Sessiyalar oʻrtasida kontekstni saqlab qoling - -Siz Claude Codeʼdan toʻliq bitta funksiyani (feature) yaratishni soʻraysiz. U 30 daqiqa ishlaydi, ishning koʻp qismini bajaradi, lekin kontekst tugab bormoqda. Siz davom ettirish uchun yangi sessiya boshlaysiz — va u oʻtgan safar qanday qarorlar qabul qilinganini, nima uchun B varianti A variantdan afzal koʻrilganini, qaysi fayllar allaqachon oʻzgartirilganini yoki testlar qanday holatda ekanini eslay olmasligini bilib olasiz. U loyihani qayta oʻrganish uchun 15 daqiqa sarflaydi va oldingi yondashuvga zid ishlarni qilishi mumkin. - -Tasavvur qiling, siz har tong uygʻonganda hamma narsani unutib qoʻyadigan usta boʻlsangiz. Siz butun qurilish maydoni bilan qaytadan tanishib chiqishingiz kerak boʻladi — qaysi devor yarim qurilgan, nima uchun koʻk gʻishtlar oʻrniga qizil gʻishtlar tanlangan, vodoprovod quvurlari qayergacha yetib kelgan. Eng yomoni, kecha oʻrnatib boʻlingan derazani — uning oʻrnatilganligini eslay olmaganingiz uchungina — buzib tashlashingiz mumkin. - -Bu AI kod yozish agentlari sessiyalararo vazifalarda duch keladigan aynan shu qiyin vaziyatdir. Ushbu maʼruza nima uchun uzoq davom etadigan vazifalarda agentlar xotirasini yoʻqotishini (“black out”) va qanday qilib holatni tizimli saqlash (state persistence) orqali ularni ishonchli kundalik yuritadigan ustaga aylantirish mumkinligini tushuntiradi — u hamon amneziyaga chalingan, ammo kundalik hamma narsani eslab qoladi. - -## Kontekst oynalari cheksiz emas - -Kontekst oynalari (context windows) cheklangan. Buni modelni yangilash orqali hal qilib boʻlmaydi — garchi oyna oʻlchami 1M tokengacha oshsa ham, murakkab vazifalar baribir ularni yeb tugatadi. Chunki agentlar shunchaki kod yozmaydi; ular kod bazasini tushunadi, oʻzlarining qaror qabul qilish tarixini kuzatib boradi, vositalarning natijalarini qayta ishlaydi va suhbat kontekstini saqlab turadi. Barcha bu maʼlumotlar oyna oʻsishidan koʻra tezroq koʻpayadi. - -Chuqurroq muammo shundaki: agent ishlab chiqaradigan maʼlumotlar bir xil darajada muhim emas. Oraliq mantiqiy xulosalar qadamlari qarorlarning “nega” ekanligini oʻz ichiga oladi — nima uchun A oʻrniga B varianti tanlangan, nima uchun u emas bu kutubxona, nima uchun maʼlum bir optimallashtirish oʻtkazib yuborilgan. Yakuniy natija faqat “nima” ekanligini — yaʼni kodning oʻzini oʻz ichiga oladi. Zichlash (compaction) strategiyalari odatda oxirgisini saqlab qoladi, lekin birinchisini yoʻqotadi. Keyingi sessiya kodni koʻradi, lekin nega aynan shunday yozilganini bilmaydi va qasddan qabul qilingan dizayn qarorini “optimallashtirib” yuborishi mumkin. - -Anthropic oʻzining uzoq vaqt oladigan (long-running) agent tadqiqotlarida ajoyib narsani aniqladi: agentlar kontekst tugab borayotganini sezganda, ularda “erta yakunlash” (premature convergence) xulq-atvori paydo boʻladi — joriy ishni tugatishga shoshish, tekshiruv qadamlarini oʻtkazib yuborish yoki optimal yechim oʻrniga oddiyini tanlash. Bu xuddi imtihonda vaqt tugab borayotganini tushunib, qolgan test savollariga tavakkaliga javob belgilab chiqishga oʻxshaydi. Anthropic buni “kontekst xavotiri (context anxiety)” deb ataydi. - -## Sessiya uzluksizligi oqimi - -Uzluksizlik artefaktlarisiz har bir yangi sessiya falokatdir: - -```mermaid -flowchart LR - S1["1-sessiya
    funksiya yarim tayyor"] --> End1["Kontekst deyarli toʻldi
    sessiya tugadi"] - End1 --> S2["2-sessiya yangidan boshlanadi"] - S2 --> Guess["Jildlarni qayta oʻqish, testlarni qayta ishga tushirish,
    kod nega bunday yozilganini taxmin qilish"] - Guess --> Drift["Ish takrorlanadi
    va tiklash sekinlashadi"] -``` - -Uzluksizlik artefaktlari yordamida yangi sessiyalar ishni tezda davom ettirishi mumkin: - -```mermaid -flowchart LR - Work["1-sessiya ishi"] --> Progress["PROGRESS.md
    tugatildi / jarayonda / keyingi qadam"] - Work --> Decisions["DECISIONS.md
    nega aynan shu yondashuv tanlangani"] - Work --> Verify["Tekshiruv eslatmalari
    qaysi testlar oʻtgan va yiqilgani"] - Work --> Commit["Git nazorat nuqtasi
    reponing aynan joriy holati"] - - Progress --> Rebuild["2-sessiya tiklanishi"] - Decisions --> Rebuild - Verify --> Rebuild - Commit --> Rebuild - - Rebuild --> Resume["Yangi sessiya ishni tezda davom ettiradi"] -``` - -## Asosiy tushunchalar - -- **Kontekst oynalari cheklangan**: Qanday oyna oʻlchami daʼvo qilinmasin (128K, 200K, 1M), uzoq davom etadigan vazifalar oxir-oqibat ularni yeb tugatadi. Tugagandan soʻng, zichlash (maʼlumot yoʻqotish) yoki qayta ishga tushirish (yangi sessiya) talab qilinadi. Ikkalasida ham nimanidir yoʻqotasiz. -- **Uzluksizlik artefaktlari (Continuity artifacts)**: Yangi sessiyaga oxirgi sessiya toʻxtagan joydan hech qanday noaniqliksiz davom ettirish imkonini beruvchi doimiy saqlangan holat fayllari. Asosiy shakli: jarayon jurnali + tekshiruv qaydnomasi + keyingi qadamlar. Oʻsha ustaning kundaligi kabi. -- **Tiklash narxi (Rebuild cost)**: Yangi sessiyaning bajariladigan holatga kelishi uchun kerak boʻladigan vaqt. Yaxshi harnessʼlar tiklash narxini 15 daqiqadan 3 daqiqagacha qisqartirishi mumkin. -- **Siljish (Drift)**: Agentʼning tushunchasi va kod repozitoriysining haqiqiy holati oʻrtasidagi farq. Har bir sessiya chegarasi siljishni keltirib chiqaradi; nazoratsiz u tobora ortib boradi. -- **Kontekst xavotiri (Context anxiety)**: Anthropic tomonidan kuzatilgan hodisa — agentlar idrok qilinayotgan kontekst chegaralariga yaqinlashganda erta yakunlash xatti-harakatlarini namoyish etadilar, maʼlumot yoʻqotilishining oldini olish uchun vazifalarni erta yakunlaydilar. Bu asossiz resurs xavotiridir. -- **Zichlash va qayta ishga tushirish (Compaction vs reset)**: Zichlash joriy sessiya ichidagi kontekstni xulosalaydi (“nima” saqlanadi, lekin “nega” yoʻqolishi mumkin); qayta ishga tushirish esa doimiy saqlangan holatdan tiklanadigan yangi sessiyani ochadi (toza, lekin artefaktlarning toʻliqligiga bogʻliq). - -## Uzluksizlik buzilganda nima sodir boʻladi - -Oldingi sessiya uchta yondashuvni tahlil qilib B variantni tanlashga salmoqli kontekst byudjetini sarfladi. Bu sessiyadagi agent oʻsha tahlil haqida bilmaydi va chala maʼlumot asosida qaytadan qaror qabul qilishi — ehtimol A variantni tanlashi mumkin. Xuddi amneziyaga chalingan usta qizil gʻisht nima uchun tanlanganini eslay olmasdan, bugun koʻk gʻishtga qarab uni chiroyliroq deb oʻylaydi. Soʻngra kecha qurilgan devorni buzib, uni qaytadan quradi. - -Undan ham yomoni takroriy ishlardir. Agent qaysidir ishlarning oldin bajarilgan yoki yoʻqligiga ishonch hosil qila olmaydi va uni qaytadan bajaradi. Yoki eng yomoni — yarmini bajaradi, mavjud kod bilan ziddiyatga duch keladi va ishni boshidan qilishi kerak boʻladi. Qurilish maydonchasida ikkita jamoa bir vaqtda bitta devorni qura olmaydi — lekin jarayon (progress) qaydlari boʻlmasa, yangi jamoa u yerda kimdir ishlayotganini bilmaydi. - -Bir nechta sessiyalar davomida implementatsiya yoʻnalishi asl talablardan sekin-asta uzoqlashib ketgan boʻlishi mumkin (drift). Har bir yangi sessiya loyiha maqsadlarini biroz boshqacha tushunadi. Xuddi “buzuq telefon” oʻyini kabi — oʻnta odam orqali xabar uzatilgach, “menga qahva olib kel” degan soʻz “menga qahva mashinasi sotib ol” ga aylanib ketishi mumkin. - -Shuningdek, tekshiruv tafovuti (verification gap) ham mavjud. Oldingi sessiyaning tekshiruv natijalari (qaysi testlar oʻtgan, qaysilari yiqilgan, nega yiqilgani) yozib olinmagan. Yangi sessiya joriy holatni tushunish uchun barcha tekshiruvlarni qaytadan oʻtkazishi kerak. Har bir sessiya hamma narsani noldan tashxis qiladi va har safar qimmatli kontekstni behuda sarflaydi. - -OpenAI va Anthropic ikkalasi ham oʻz hujjatlarida holatni tizimli saqlash (structured state persistence) ni taʼkidlaydi. OpenAIʼning harness muhandisligi haqidagi maqolasi repozitoriyni “operatsion qaydnoma” (operational record) sifatida koʻradi — har bir amalning natijalari repoda kuzatilishi mumkin boʻlgan iz qoldirishi kerak. Anthropicʼning koʻp vaqt oladigan agentlar hujjatlari maxsus “topshirish fayllari”ni (handoff files) tavsiya qiladi — ular joriy holat, maʼlum muammolar va keyingi qadamlarni oʻz ichiga olgan tizimli hujjatlardir. - -## Amneziyali usta uchun kundalik - -Asosiy yondashuv: **Agentʼga xotirasini yoʻqotadigan iqtidorli muhandis sifatida munosabatda boʻling.** Ishini yakunlashidan (“clock out”) oldin u muhim maʼlumotlarni yozib qoldirishi kerak, shunda keyingi “smenadagi” agent ishni tezda davom ettira oladi. - -**1-vosita: Jarayon fayli (PROGRESS.md).** Eng asosiy uzluksizlik artefakti — kundalikning oʻzagi: - -```markdown -# Loyiha jarayoni - -## Joriy holat -- Oxirgi commit: abc1234 (feat: user preferences endpoint qoʻshildi) -- Test holati: 42/43 oʻtdi (test_pagination_edge_case yiqildi) -- Lint: oʻtdi - -## Tugatildi -- [x] Foydalanuvchi modeli va maʼlumotlar bazasi migratsiyasi -- [x] Asosiy CRUD endpointʼlar -- [x] Auth middleware integratsiyasi - -## Jarayonda -- [ ] Pagination funksiyasi (90% - edge case test yiqilmoqda) - -## Maʼlum muammolar -- test_pagination_edge_case boʻsh natija qaytarganda 500 xatosini bermoqda -- Oʻchirilgan foydalanuvchilar roʻyxatda chiqishi yoki yoʻqligini aniqlash kerak - -## Keyingi qadamlar -1. Pagination edge case bugʻini toʻgʻrilash -2. "include deleted users" soʻrov parametrini qoʻshish -3. API hujjatlarini yangilash -``` - -**2-vosita: Qarorlar jurnali (DECISIONS.md).** Muhim dizayn qarorlari va ularning sabablarini yozib boring. Batafsil dizayn hujjatlariga hojat yoʻq — shunchaki “qanday qaror, nega, qachon” — kundalikdagi eslatmalar (memos) kabi: - -```markdown -# Dizayn qarorlari - -## 2024-01-15: User preferences keshini saqlash uchun Redisʼdan foydalanish -- Sabab: Yuqori oʻqish chastotasi (har bir API chaqiruvi uchun), kichik maʼlumot hajmi -- Rad etilgan alternativa: PostgreSQL materialized view (tez-tez oʻzgarish xizmat koʻrsatish narxini oqlamaydi) -- Cheklov: Keshning yashash vaqti (TTL) 5 daqiqa, yozish vaqtida faol bekor qilish (invalidation) -``` - -**3-vosita: Checkpoint sifatida Git commitʼlar.** Har bir butun ishni tugatgandan soʻng commit qiling. Commit xabarlari nima qilingani va nega qilinganini tushuntirishi kerak. Bular bepul, avtomatik versiyalanadigan holat snapshotʼlari. - -**4-vosita: init.sh yoki harness inisializatsiya jarayoni.** `AGENTS.md` faylida “ishni boshlash” va “ishni tugatish” tartiblarini aniq koʻrsating: - -```markdown -## Sessiya boshlanganda (clock in) -1. Joriy holatni bilish uchun PROGRESS.mdʼni oʻqish -2. Muhim qarorlar uchun DECISIONS.mdʼni oʻqish -3. Repo muvofiq (consistent) holatda ekanligini tasdiqlash uchun make check ishga tushirish -4. Ishni PROGRESS.mdʼdagi "Keyingi qadamlar" boʻlimidan davom ettirish - -## Sessiya tugashidan oldin (clock out) -1. PROGRESS.mdʼni yangilash -2. Repo muvofiq holatda ekanligini tasdiqlash uchun make check ishga tushirish -3. Barcha tugatilgan ishlarni commit qilish -``` - -**Aralash strategiya (Mixed strategy)**: Hamma vazifa ham kontekstni qayta oʻrnatishni (reset) talab qilavermaydi. Qisqa vazifalar (30 daqiqadan kam) bitta sessiya ichida yakunlanishi mumkin. Uzoq davom etadigan vazifalar uzluksizlik uchun jarayon fayllari (progress files) va qaror jurnallaridan foydalanishi shart. Qaror mezoni: agar vazifa oynaning 60% dan koʻprogʻini talab qilsa, topshirishga (handoff) tayyorgarlik koʻrishni boshlang. - -### Kontekst xavotirini (Context Anxiety) chuqurroq oʻrganish - -Anthropicʼning 2026-yil mart oyidagi tadqiqotlari kontekst xavotirining oʻziga xos koʻrinishlarini ochib berdi: Sonnet 4.5 da kontekst oyna chegarasiga yaqinlashganda, agent kuchli “erta yakunlash” xatti-harakatini namoyish etadi. Bu xuddi imtihonda vaqt tugab qolganini tushunib, test variantlarini koʻr-koʻrona belgilashga oʻxshaydi. - -Buni hal qilishning ikkita strategiyasi bor: - -**Zichlash (Compaction)**: Bitta sessiya ichida oldingi suhbatni qisqacha xulosalash. Afzalligi: uzluksizlikni saqlaydi, agent “nima”ligini koʻra oladi. Kamchiligi: xulosalarda koʻpincha “nega” yoʻqoladi — nima uchun A oʻrniga B tanlangani, nima uchun aynan bitta optimallashtirish tashlab ketilgani. Eng yomoni shundaki, zichlash kontekst xavotirini yoʻqotmaydi — agent qachonlardir kontekst katta boʻlganini biladi va ruhiy jihatdan baribir vazifani tezroq yopishga harakat qiladi. - -**Kontekstni qayta ishga tushirish (Context reset)**: Kontekstni butunlay tozalash, yangi sessiya ochish, doimiy saqlangan artefaktlardan tiklash. Afzalligi: toza mental holat — yangi sessiyada “vaqtim tugab qolyapti” degan xavotir yoʻq. Kamchiligi: topshirish artefaktlarining (handoff artifacts) toʻliqligiga bogʻliq. Agar kundalikda muhim maʼlumot yetishmasa, yangi sessiya notoʻgʻri yoʻnalishda ketib, vaqtni behuda sarflashi mumkin. - -Anthropicʼning real maʼlumotlari: Sonnet 4.5 uchun kontekst xavotiri shu qadar jiddiyki, faqat zichlash yetarli emas — kontekstni qayta ishga tushirish (context reset) harness dizaynining muhim qismiga aylanadi. Ammo Opus 4.5 uchun bu xatti-harakat ancha kamaygan, va zichlash resetʼlarga tayanmasdan kontekstni boshqara oladi. Bu shuni anglatadiki: **harness dizayni maqsadli modelni chuqur tushunishga tayanishi kerak, hammabop (one-size-fits-all) andozaga emas.** - -> Manba: [Anthropic: Uzoq vaqt ishlovchi dasturlar uchun harness dizayni (Harness design for long-running application development)](https://www.anthropic.com/engineering/harness-design-long-running-apps) - -## Hayotiy misol - -Agentʼga foydalanuvchi autentifikatsiyasiga ega blog tizimini yaratish topshirildi — 12 ta funksionallik (feature point), bajarish uchun 5 ta sessiya hisoblangan. - -**Kundaliksiz boshlangʻich holat**: 1-sessiya foydalanuvchi modeli va asosiy routeʼlarni amalga oshirdi. 2-sessiya auth middlewareʼning interfeys shartnomasi (contract) esdan chiqqan holatda boshlandi va oldingi dizayn maqsadini taxmin qilish uchun ~15 daqiqa sarfladi. 3-sessiyaga kelib, toʻplangan siljish (drift) agentʼni allaqachon tugatilgan funksiyalarni qaytadan yaratishga olib keldi. 5-sessiyaga kelib, repoda juda koʻp ortiqcha kodlar bor edi, lekin asosiy auth funksiyasi haligacha E2E testlaridan (end-to-end tests) oʻtmagan edi. 12 ta funksionallikdan faqat 7 tasi tugallandi, shundan 3 tasida yashirin toʻgʻrilik muammolari mavjud edi. Xuddi kundalik tutmaydigan ustaga oʻxshaydi — beshinchi kunga kelib, qurilish maydonchasida betartiblik, baʼzi devorlar ikki marta qurilgan, qurilishi kerak boʻlganlari esa umuman boshlanmagan. - -**Kundalik bilan holat**: Jarayon fayllari, qarorlar jurnallari, tekshiruv (verification) qaydlari va git checkpointʼlaridan foydalanilgan. Holat haqidagi hisobot har bir sessiya oxirida avtomatik ravishda yangilanadi. 2-sessiyani tiklash narxi (rebuild cost) ~3 daqiqaga tushdi. 5-sessiyaga kelib, barcha 12 ta funksionallik tugallandi va tekshirildi. - -Miqdoriy taqqoslash: tiklash vaqti ~78% ga qisqardi, funksiyalarning tugallanish darajasi 58% dan 100% ga koʻtarildi, yashirin nuqsonlar darajasi 43% dan 8% gacha pasaydi. Usta hamon amneziyaga chalingan, ammo kundaligi tufayli har bir kun noldan emas, balki kecha toʻxtagan joyidan boshlanadi. - -## Asosiy xulosalar - -- Kontekst oynalari cheklangan resursdir. Uzoq vazifalar sessiyalarga boʻlinadi, va sessiyalarda maʼlumot yoʻqoladi — har kuni hamma narsani unutadigan usta kabi, bu obyektiv haqiqatdir. -- Yechim kattaroq oynalar emas — bu holatni saqlashni yaxshilash. Jarayon fayllari + qaror jurnallari + git checkpointʼlari — amneziyali ustaga ishonchli kundalik bering. -- Agentʼga xotirasini yoʻqotgan muhandis kabi munosabatda boʻling: u “ishni tugatishidan” (clock out) oldin, nima qilingani, nega qilingani va keyingi nima ekanini yozib qoldirsin. -- Tiklash narxi (rebuild cost) asosiy koʻrsatkichdir. Yaxshi harnessʼlar yangi sessiyani 3 daqiqa ichida ishga tushiradigan holatga keltirishi kerak. -- Aralash strategiya (Mixed strategy): qisqa vazifalar bitta sessiyada, uzoq davom etadigan vazifalar uzluksizlik uchun tizimli artefaktlar bilan sessiyalararo. - -## Qoʻshimcha oʻqish uchun - -- [Anthropic: Effective Harnesses for Long-Running Agents](https://www.anthropic.com/engineering/effective-harnesses-for-long-running-agents) -- [OpenAI: Harness Engineering](https://openai.com/index/harness-engineering/) -- [Lost in the Middle: How Language Models Use Long Contexts](https://arxiv.org/abs/2307.03172) -- [Claude Code Documentation](https://docs.anthropic.com/en/docs/claude-code) -- [HumanLayer: Harness Engineering for Coding Agents](https://humanlayer.dev/articles/harness-engineering-for-coding-agents/) - -## Mashqlar - -1. **Uzluksizlik yoʻqolishini oʻlchash**: Kamida 3 ta sessiyani talab qiladigan ishlab chiqish vazifasini tanlang. Hech qanday uzluksizlik artefaktlarini bermasdan, har bir sessiya boshida agent “oʻtgan safar nima boʻlganini” tushunish uchun qancha kontekst sarflashini yozib boring. Har bir sessiyadan soʻng jarayon faylini (progress file) yarating va keyingi sessiya ishni shu yerdan boshlashiga imkon bering. Jarayon fayllari boʻlgan va boʻlmagan holatdagi tiklash narxlarini solishtiring. - -2. **Topshirish (handoff) andozasi dizayni**: Toʻrtta maydonga (field) ega minimal topshirish andozasini (handoff template) tuzing: repo holati (commit hash), runtime holati (testlardan oʻtish koʻrsatkichi), toʻsiqlar (blockers), keyingi qadamlar. Faqat shu andozadan foydalangan holda mutlaqo yangi agent sessiyasi loyiha holatini tiklashiga imkon bering. Tiklash paytida duch kelingan noaniqliklarni qayd eting, andozani yaxshilash uchun buni takrorlang. - -3. **Aralash strategiya tajribasi**: 5 sessiyali rivojlanish vazifasida uchta strategiyani solishtiring: (a) har safar yangi sessiyalar + jarayon fayllaridan boshlash, (b) imkon qadar bitta sessiyada koʻproq ish qilish (kontekstni zichlash), (c) aralash strategiya (sessiya ichida qisqa vazifalar, sessiyalar orasida uzun vazifalar + jarayon fayllari). Tiklash vaqtini, xususiyatlarning bajarilish darajasini va qarorlar izchilligini solishtiring. diff --git a/courses/learn-harness-engineering/docs/uz/lectures/lecture-06-why-initialization-needs-its-own-phase/code/index.md b/courses/learn-harness-engineering/docs/uz/lectures/lecture-06-why-initialization-needs-its-own-phase/code/index.md deleted file mode 100644 index d832b901..00000000 --- a/courses/learn-harness-engineering/docs/uz/lectures/lecture-06-why-initialization-needs-its-own-phase/code/index.md +++ /dev/null @@ -1,8 +0,0 @@ -# 6-maʼruza uchun kod - -Ushbu jilddan quyidagilarga misol sifatida foydalaning: - -- inisializatsiya agenti natijalari -- inisializatsiya skriptlari -- jarayon (progress) fayllari -- birinchi marta ishga tushirish uchun qoliplar (scaffolding) diff --git a/courses/learn-harness-engineering/docs/uz/lectures/lecture-06-why-initialization-needs-its-own-phase/code/init-check.ts b/courses/learn-harness-engineering/docs/uz/lectures/lecture-06-why-initialization-needs-its-own-phase/code/init-check.ts deleted file mode 100644 index 88556d23..00000000 --- a/courses/learn-harness-engineering/docs/uz/lectures/lecture-06-why-initialization-needs-its-own-phase/code/init-check.ts +++ /dev/null @@ -1,296 +0,0 @@ -/** - * init-check.ts - * - * Programmatically checks initialization prerequisites: - * - Node.js version - * - Dependencies installed (node_modules exists) - * - Data directory exists - * - Config files present - * - * Simulates running with and without an explicit init phase, - * showing how missing prerequisites cause silent failures later. - * - * Run: npx tsx docs/lectures/lecture-06-why-initialization-needs-its-own-phase/code/init-check.ts - */ - -import * as fs from “node:fs”; -import * as path from “node:path”; -import * as childProcess from “node:child_process”; - -// --------------------------------------------------------------------------- -// Types -// --------------------------------------------------------------------------- - -interface CheckItem { - name: string; - category: string; - check: () => { pass: boolean; detail: string }; - impactIfMissing: string; -} - -interface CheckResult { - name: string; - category: string; - passed: boolean; - detail: string; - impactIfMissing: string; -} - -// --------------------------------------------------------------------------- -// Checks -// --------------------------------------------------------------------------- - -function createChecks(targetDir: string): CheckItem[] { - return [ - { - name: “Node.js version >= 18”, - category: “Runtime”, - check: () => { - try { - const version = process.version; // e.g. “v20.11.0” - const major = parseInt(version.replace(“v”, “”).split(“.”)[0], 10); - return { - pass: major >= 18, - detail: `Detected: ${version} (need >= v18)`, - }; - } catch { - return { pass: false, detail: “Could not detect Node.js version” }; - } - }, - impactIfMissing: “TypeScript features and built-in APIs unavailable”, - }, - { - name: “package.json exists”, - category: “Config”, - check: () => { - const p = path.join(targetDir, “package.json”); - const exists = fs.existsSync(p); - return { - pass: exists, - detail: exists ? `Found: ${p}` : “Not found in target directory”, - }; - }, - impactIfMissing: “Cannot install dependencies or run scripts”, - }, - { - name: “Dependencies installed (node_modules)”, - category: “Dependencies”, - check: () => { - const p = path.join(targetDir, “node_modules”); - const exists = fs.existsSync(p); - return { - pass: exists, - detail: exists ? `Found: ${p}` : “node_modules/ not found -- run npm install”, - }; - }, - impactIfMissing: “All imports fail at runtime”, - }, - { - name: “TypeScript available”, - category: “Toolchain”, - check: () => { - try { - const result = childProcess.execSync(“npx tsc --version”, { - encoding: “utf-8”, - timeout: 10000, - stdio: [“pipe”, “pipe”, “pipe”], - }).trim(); - return { pass: true, detail: `Detected: ${result}` }; - } catch { - return { pass: false, detail: “TypeScript not available via npx” }; - } - }, - impactIfMissing: “Cannot compile TypeScript files”, - }, - { - name: “tsconfig.json exists”, - category: “Config”, - check: () => { - const p = path.join(targetDir, “tsconfig.json”); - const exists = fs.existsSync(p); - return { - pass: exists, - detail: exists ? `Found: ${p}` : “Not found”, - }; - }, - impactIfMissing: “TypeScript compiler uses defaults, may not match project needs”, - }, - { - name: “Source directory exists”, - category: “Structure”, - check: () => { - const candidates = [“src”, “lib”, “app”]; - const found = candidates.filter((d) => { - const p = path.join(targetDir, d); - return fs.existsSync(p) && fs.statSync(p).isDirectory(); - }); - return { - pass: found.length > 0, - detail: found.length > 0 ? `Found: ${found.join(", ")}` : “No src/ lib/ or app/ directory found”, - }; - }, - impactIfMissing: “Agent cannot locate source files to modify”, - }, - { - name: “Test directory exists”, - category: “Structure”, - check: () => { - const candidates = [“test”, “tests”, “__tests__”, “spec”]; - const found = candidates.filter((d) => { - const p = path.join(targetDir, d); - return fs.existsSync(p) && fs.statSync(p).isDirectory(); - }); - return { - pass: found.length > 0, - detail: found.length > 0 ? `Found: ${found.join(", ")}` : “No test directory found”, - }; - }, - impactIfMissing: “Agent cannot find or run existing tests”, - }, - { - name: “Git repository initialized”, - category: “Version Control”, - check: () => { - const gitDir = path.join(targetDir, “.git”); - const exists = fs.existsSync(gitDir); - return { - pass: exists, - detail: exists ? “Git repository detected” : “No .git directory found”, - }; - }, - impactIfMissing: “No rollback capability, no change history”, - }, - ]; -} - -// --------------------------------------------------------------------------- -// Simulation: with and without init phase -// --------------------------------------------------------------------------- - -interface SimResult { - scenario: string; - checksRun: boolean; - failuresBeforeWork: number; - workAttempted: boolean; - workSucceeded: boolean; - timeWastedMs: number; -} - -function simulateWithoutInit(): SimResult { - // Agent skips init, goes straight to work. - // Encounters failures one at a time as it hits missing prerequisites. - const checks = createChecks(process.cwd()); - let failures = 0; - let timeWasted = 0; - - for (const check of checks) { - const result = check.check(); - if (!result.pass) { - failures++; - timeWasted += 200; // Agent spends time discovering each missing piece - } - } - - return { - scenario: “NO INIT PHASE”, - checksRun: false, - failuresBeforeWork: 0, // Didnʼt check upfront - workAttempted: true, - workSucceeded: failures === 0, - timeWastedMs: timeWasted, - }; -} - -function simulateWithInit(): SimResult { - // Agent runs init phase first, discovers all issues upfront. - const checks = createChecks(process.cwd()); - let failures = 0; - - for (const check of checks) { - if (!check.check().pass) { - failures++; - } - } - - return { - scenario: “WITH INIT PHASE”, - checksRun: true, - failuresBeforeWork: failures, - workAttempted: failures === 0, - workSucceeded: failures === 0, - timeWastedMs: 0, - }; -} - -// --------------------------------------------------------------------------- -// Reporting -// --------------------------------------------------------------------------- - -function pad(s: string, len: number): string { - return s.length >= len ? s : s + “ ”.repeat(len - s.length); -} - -function run(): void { - const targetDir = process.cwd(); - console.log(“\n” + “=”.repeat(80)); - console.log(“ INITIALIZATION PREREQUISITE CHECK”); - console.log(“=”.repeat(80)); - console.log(` Target: ${targetDir}\n`); - - const checks = createChecks(targetDir); - const results: CheckResult[] = checks.map((c) => { - const r = c.check(); - return { - name: c.name, - category: c.category, - passed: r.pass, - detail: r.detail, - impactIfMissing: c.impactIfMissing, - }; - }); - - // Detailed report - const header = `| ${pad("Check", 35)}| ${pad("Category", 12)}| ${pad("Status", 6)}| Detail`; - const sep = `|${"-".repeat(37)}|${"-".repeat(14)}|${"-".repeat(8)}|${"-".repeat(40)}`; - console.log(header); - console.log(sep); - - for (const r of results) { - const status = r.passed ? “PASS” : “FAIL”; - console.log(`| ${pad(r.name, 35)}| ${pad(r.category, 12)}| ${pad(status, 6)}| ${r.detail}`); - } - - const passCount = results.filter((r) => r.passed).length; - const failCount = results.filter((r) => !r.passed).length; - - console.log(“\n” + “-”.repeat(80)); - console.log(` Results: ${passCount} passed, ${failCount} failed out of ${results.length} checks`); - - if (failCount > 0) { - console.log(“\n FAILED CHECKS -- Impact if not resolved:”); - for (const r of results.filter((r) => !r.passed)) { - console.log(` - ${r.name}: ${r.impactIfMissing}`); - } - } - - // Comparison: with vs without init - const noInit = simulateWithoutInit(); - const withInit = simulateWithInit(); - - console.log(“\n” + “=”.repeat(80)); - console.log(“ INIT PHASE COMPARISON”); - console.log(“=”.repeat(80) + “\n”); - - const cHeader = `| ${pad("Metric", 35)}| ${pad("No Init Phase", 18)}| ${pad("With Init Phase", 18)}|`; - const cSep = `|${"-".repeat(37)}|${"-".repeat(20)}|${"-".repeat(20)}|`; - console.log(cHeader); - console.log(cSep); - console.log(`| ${pad("Prerequisites checked upfront", 35)}| ${pad("No", 18)}| ${pad("Yes", 18)}|`); - console.log(`| ${pad("Work attempted despite issues", 35)}| ${pad(String(noInit.workAttempted), 18)}| ${pad(String(withInit.workAttempted), 18)}|`); - console.log(`| ${pad("Time wasted discovering issues late", 35)}| ${pad(noInit.timeWastedMs + "ms", 18)}| ${pad(withInit.timeWastedMs + "ms", 18)}|`); - console.log(`| ${pad("Work succeeded", 35)}| ${pad(String(noInit.workSucceeded), 18)}| ${pad(String(withInit.workSucceeded), 18)}|`); - - console.log(“\n An explicit init phase catches problems before they waste time.\n”); -} - -run(); diff --git a/courses/learn-harness-engineering/docs/uz/lectures/lecture-06-why-initialization-needs-its-own-phase/code/init.sh b/courses/learn-harness-engineering/docs/uz/lectures/lecture-06-why-initialization-needs-its-own-phase/code/init.sh deleted file mode 100644 index 2c6e1b0c..00000000 --- a/courses/learn-harness-engineering/docs/uz/lectures/lecture-06-why-initialization-needs-its-own-phase/code/init.sh +++ /dev/null @@ -1,10 +0,0 @@ -#!/usr/bin/env bash -set -euo pipefail - -echo "[init] installing dependencies" -npm install - -echo "[init] starting the docs site is optional" -echo "[init] use npm run docs:dev for course docs" - -echo "[init] project-specific startup would go here" diff --git a/courses/learn-harness-engineering/docs/uz/lectures/lecture-06-why-initialization-needs-its-own-phase/code/initializer-output-checklist.md b/courses/learn-harness-engineering/docs/uz/lectures/lecture-06-why-initialization-needs-its-own-phase/code/initializer-output-checklist.md deleted file mode 100644 index e861d5c0..00000000 --- a/courses/learn-harness-engineering/docs/uz/lectures/lecture-06-why-initialization-needs-its-own-phase/code/initializer-output-checklist.md +++ /dev/null @@ -1,7 +0,0 @@ -# Inisializatsiya Agenti Natijalari Tekshiruvi - -- Aniq asosiy ishga tushirish (startup) buyrugʻi bormi? -- Aniq asosiy tekshirish (verification) buyrugʻi bormi? -- Birinchi jarayon (progress) artefakti bormi? -- Birinchi barqaror commit bormi? -- Keyingi sessiyalar uchun koʻrinadigan funksiyalar yuzasi (feature surface) bormi? diff --git a/courses/learn-harness-engineering/docs/uz/lectures/lecture-06-why-initialization-needs-its-own-phase/index.md b/courses/learn-harness-engineering/docs/uz/lectures/lecture-06-why-initialization-needs-its-own-phase/index.md deleted file mode 100644 index 41474c61..00000000 --- a/courses/learn-harness-engineering/docs/uz/lectures/lecture-06-why-initialization-needs-its-own-phase/index.md +++ /dev/null @@ -1,157 +0,0 @@ -[English version →](../../../en/lectures/lecture-06-why-initialization-needs-its-own-phase/) - -> Kod misollari: [code/](https://github.com/walkinglabs/learn-harness-engineering/blob/main/docs/en/lectures/lecture-06-why-initialization-needs-its-own-phase/code/) -> Amaliy loyiha: [Loyiha 03. Koʻp sessiyali uzluksizlik](./../../projects/project-03-multi-session-continuity/index.md) - -# 6-maʼruza. Har bir agent sessiyasidan oldin inisializatsiya qiling - -Siz yangi agent sessiyasini boshlaysiz va “qidiruv funksiyasini qoʻsh” deysiz. U darhol kod yozishga oʻtadi — tahsinga loyiq ishtiyoq. 20 daqiqadan soʻng, test freymvorki toʻgʻri sozlanmaganini bilib qoladi va uni toʻgʻrilash uchun yana 10 daqiqa sarflaydi. Keyin maʼlumotlar bazasi migratsiya skripti formati notoʻgʻri ekani aniqlanadi, yana ovoragarchilik. Oxir-oqibat qidiruv funksiyasi qoʻshiladi, lekin butun sessiya samarasiz boʻldi — chunki vaqtning katta qismi qidiruv funksiyasini yozishga emas, balki “bu loyiha oʻzi qanday ishlashini tushunish”ga sarflandi. - -Yaxshiroq yondashuv: agentni ishlashga qoʻyib berishdan oldin, bazaviy muhitni tayyorlash, tekshiruv buyruqlarini (verification commands) muvaffaqiyatli oʻtkazish va loyiha strukturasini tushunish uchun alohida bosqich ajrating. Bu xuddi uy qurishga oʻxshaydi — siz poydevor quyib, devorlarni bir vaqtning oʻzida qurmaysiz. Agar shunday qilsangiz, devorlar poydevor qotmasidan oldin koʻtariladi va butun binoni buzib, qaytadan boshlashga toʻgʻri keladi. Avval poydevor quying, uning qotishini kuting, soʻngra devorlarni quring — toza va samarali. - -Ushbu maʼruza nima uchun inisializatsiya (initialization) kod yozish bilan aralashib ketmasdan, alohida bosqich boʻlishi kerakligini tushuntiradi. - -## Poydevor va devorlar: Ikkita tubdan farq qiluvchi ish - -Inisializatsiya va implementatsiya bir-biridan butunlay farq qiladigan optimallashtirish maqsadlariga ega. Implementatsiya bosqichi quyidagilar uchun optimallashtiriladi: tekshirilgan funksiyalar (features) miqdori va sifatini maksimal darajaga koʻtarish. Inisializatsiya bosqichi esa: keyingi barcha implementatsiyalarning ishonchliligi va samaradorligini maksimal darajaga koʻtarish uchun optimallashtiriladi. - -Inisializatsiya va implementatsiyani aralashtirganingizda, agent koʻp maqsadli optimallashtirish muammosiga duch keladi — infratuzilma qurish bilan bir vaqtda kod yozishga urinadi. Aniq ustuvorliksiz, agent tabiiy ravishda kod yozishga tortiladi (chunki bu bevosita koʻrinadigan natija), shu bilan birga infratuzilmani eʼtiborsiz qoldiradi (chunki uning qiymati faqat keyingi sessiyalarda koʻrinadi). Bu qurilish jamoasiga poydevor quyishni va bir vaqtning oʻzida devor qurishni buyurishga oʻxshaydi — ular, ehtimol, devor qurishga shoshishadi, chunki devorlar koʻrinadigan va koʻrsatish mumkin boʻlgan narsadir. Lekin yomon poydevorga ega boʻlgan uyni keyinchalik tizimli muammolar kutadi. - -## Inisializatsiya sikli - -```mermaid -flowchart TB - subgraph Wrong["Aralash sessiya (notoʻgʻri)"] - W1["Funksional ishni darhol boshlash"] --> W2["Vazifa oʻrtasida muhit va test boʻshliqlarini kashf etish"] - W2 --> W3["Tekshirilmagan kod toʻplanib qolishi"] - W3 --> W4["Keyingi sessiya loyiha holatini qayta kashf etishga majbur"] - end - - subgraph Right["Maxsus inisializatsiya (toʻgʻri)"] - R1["1-sessiya: muhit ishga tushishga tayyor"] --> R2["Namuna test oʻtmoqda"] - R2 --> R3["Bootstrap shartnomasi + vazifalar roʻyxati yozilgan"] - R3 --> R4["Toza checkpoint commit qilingan"] - R4 --> R5["Keyingi sessiyalar toʻgʻridan-toʻgʻri tekshirilgan vazifalarni boshlaydi"] - end -``` - -## Ularni aralashtirib yuborsangiz nima boʻladi - -Eng toʻgʻridan-toʻgʻri muammo: poydevor toʻgʻri qotmaydi. Agent oʻz kuchining 80 foizini funksiya kodiga, 20 foizini esa shoshilinch qandaydir infratuzilmani sozlashga sarflaydi. Test freymvorki sozlangan, lekin hech qachon tekshirilmagan, lint qoidalari qoʻyilgan, lekin oʻta yumshoq, jarayon fayli (progress file) tuzilmagan. Bu kamchiliklar birinchi sessiyada darhol bilinmaydi (chunki agent oʻzi nima qilganini hali eslaydi), lekin ikkinchi sessiyada namoyon boʻladi — yangi agent qanday ishlashni, qanday test qilishni yoki ishlarning qayerda ekanligini bilmaydi. Qaltis poydevor, titroq bino. - -Yana bir yashirin xarajat bu “tekshirilmagan toʻplanish” (unverified accumulation) — test freymvorki sozlanishidan oldin yozilgan kod, bu tekshiruvi yoʻq kod degani. Oxir-oqibat oʻsha kod uchun testlar yozishga qaytganingizda, boshidanoq dizayn notoʻgʻri qilinganini koʻrishingiz mumkin — agar shuni avvalroq bilganingizda, uni boshqacha amalga oshirgan boʻlardingiz. Bu hoʻl beton ustiga kafel yotqizishga oʻxshaydi — polning tekis emasligini anglab yetganingizda, barcha kafelni qoʻporib, qaytadan terishga toʻgʻri keladi. - -Sessiya byudjeti ham bekorga sarflanadi. Inisializatsiya ishlari (muhitlarni sozlash, testlarni oʻrnatish, loyiha tuzilishini tushunish) katta byudjetni yeb yuboradi, bu esa asosiy kodni yozishga kamroq joy qoldiradi. Natija: birinchi sessiya ishlarning faqat yarmini tugatadi va ikkinchi sessiya loyihani tushunishni noldan boshlashiga toʻgʻri keladi. Poydevor uchun byudjet sarflandi, lekin poydevor ham mustahkam emas — ikkala maqsadga ham erishilmadi. - -Eng osongina koʻzdan qochadigan muammo — bu oshkor qilinmagan taxminlar, minalar kabi yashirin tahdidlar (implicit assumption landmines). Agentning inisializatsiya davomida qabul qilgan qarorlari (qaysi test freymvorki ishlatilgan, kataloglar qanday tartiblangan, bogʻliqliklar qanday boshqarilgan) — agar ochiq-oydin yozib qoldirilmasa, keyingi sessiyalar bu tanlovlarni tushunolmaydi. Eng yomoni shundaki, keyingi sessiyalar unga zid qarorlar qabul qilishi mumkin. Birinchi qurilish jamoasi beton poydevordan foydalandi, ikkinchi jamoa buni bilmasdan turib uning ustiga yogʻoch qoqdi — poydevor yorilib ketdi. - -Anthropicʼning uzoq muddatli dasturlarni yaratish boʻyicha tadqiqoti inisializatsiyani implementatsiyadan ajratishni ochiq-oydin tavsiya qiladi. Ularning tajriba maʼlumotlari: maxsus inisializatsiya bosqichidan foydalangan loyihalar, aralash usulga nisbatan, koʻp sessiyali (multi-session) ssenariylarda funksiyalarning 31% koʻproq tugallanish koʻrsatkichini namoyish etgan. Asosiy tushuncha — inisializatsiya bosqichiga sarflangan vaqt, keyingi 3-4 sessiyada toʻliq oqlanadi. Poydevor qanchalik mustahkam boʻlsa, devorlar shuncha tez koʻtariladi. - -OpenAIʼning Codex harness muhandislik qoʻllanmasi ham “repo operatsion yozuv sifatida” (repository as operational record) tamoyilini taʼkidlaydi — birinchi ishga tushishdanoq aniq operatsion strukturani oʻrnating, aks holda har bir yangi sessiya loyiha qoidalarini (conventions) qaytadan aniqlashiga toʻgʻri keladi. - -## Asosiy tushunchalar - -- **Inisializatsiya bosqichi (Initialization Phase)**: Agent ishlash siklidagi (lifecycle) birinchi bosqich — kod yozilmaydi, faqatgina keyingi barcha implementatsiya bosqichlari uchun sharoitlar yaratiladi. Uning natijasi kod emas, infratuzilmadir. -- **Bootstrap shartnomasi (Bootstrap Contract)**: Yangi agent sessiyasi loyihani hech qanday noaniqliklarsiz boshqara olishi uchun kerak boʻlgan shartlar — loyihani ishga tushirish (start), test qilish (test), taraqqiyotni koʻrish (progress) va keyingi qadamlarni tanlash (next steps) mumkin boʻlishi kerak. Toʻrtta shart, barchasi zarur. -- **Sovuq ishga tushirish (Cold Start) va Issiq ishga tushirish (Warm Start)**: Sovuq ishga tushirish — bu boʻsh katalogdan boshlanib, agent loyiha tuzilishini taxmin qilishiga toʻgʻri keladi; issiq ishga tushirish esa, infratuzilma allaqachon mavjud boʻlgan andoza (template) yoki joriy loyihadan boshlanishidir. Issiq ishga tushirish har doim sovuq ishga tushirishdan ustunroq — bu suv va elektr quvvati boʻlgan maydonda ish boshlash bilan, sahroda ish boshlash oʻrtasidagi farq kabidir. -- **Topshirishga tayyorlik (Handoff Readiness)**: Loyiha istalgan vaqtda yangi agent qabul qilib ololadigan holatda turishi kerak. Ogʻzaki tushuntirish kerak emas — faqat repo ichidagilarning oʻzi yetarli. -- **Birinchi tekshiruvgacha vaqt (Time to First Verification)**: Loyiha boshlanganidan to birinchi funksionallik (feature point) tekshiruvdan oʻtgunga qadar boʻlgan vaqt. Bu inisializatsiya samaradorligini oʻlchash uchun asosiy metrikadir. -- **Keyingi bosqichda qoʻllanish yaroqliligi (Downstream Usability)**: Inisializatsiya sifatining eng yaxshi koʻrsatkichi — keyingi sessiyalarning yashirin bilimlarga tayanmasdan vazifalarni muvaffaqiyatli bajarish ulushidir. - -## Inisializatsiyani qanday qilib toʻgʻri bajarish mumkin - -**Inisializatsiyaga maxsus alohida bosqich sifatida qarang.** Birinchi sessiya faqat inisializatsiya bilan shugʻullanadi — biznes mantigʻiga oid kod umuman yozilmaydi. Inisializatsiya natijasi: - -**1. Ishga tayyor muhit.** Loyiha ishga tushadi, kutubxonalar (dependencies) oʻrnatiladi, muhit muammolari yoʻq. Poydevor quyildi, hech qanday yoriq yoʻq. - -**2. Tekshiruvga tayyor test freymvorki.** Kamida bitta namuna (example) test oʻtishi kerak. Bu test freymvorkining oʻzi toʻgʻri sozlanganini isbotlaydi — goʻyoki ogʻirlikni koʻtara olishini isbotlash uchun poydevor ustiga ustun oʻrnatgandek. - -**3. Bootstrap shartnomasi hujjati.** Keyingi sessiyalarga nima qilishni ochiq-oydin tushuntiruvchi hujjat: -```markdown -# Inisializatsiya shartnomasi - -## Boshlash buyruqlari -- Bogʻliqliklarni oʻrnatish: `make setup` -- Dev serverni ishga tushirish: `make dev` -- Testlarni ishga tushirish: `make test` -- Toʻliq tekshiruv: `make check` - -## Joriy holat -- Barcha bogʻliqliklar oʻrnatilgan va qulflangan (locked) -- Test freymvorki sozlangan (Vitest + React Testing Library) -- Namuna test oʻtmoqda (1/1) -- Lint qoidalari sozlangan (ESLint + Prettier) - -## Loyiha strukturasi -- src/ — Asosiy kod -- src/components/ — React komponentlari -- src/api/ — API mijoz (client) -- tests/ — Test fayllari -``` - -**4. Vazifalarni qismlarga ajratish.** Butun loyihani tartiblangan vazifalar roʻyxatiga boʻling, har bir vazifa aniq qabul qilish mezonlariga (acceptance criteria) ega boʻlsin: -```markdown -# Vazifalarni taqsimlash - -## 1-vazifa: Asosiy foydalanuvchi autentifikatsiyasi -- JWT auth middlewareʼni amalga oshirish -- Kirish/Roʻyxatdan oʻtish endpointʼlarini qoʻshish -- Qabul qilish: pytest tests/test_auth.py barchasi oʻtishi kerak - -## 2-vazifa: Foydalanuvchi profili sahifasi -- Profil CRUDʼni amalga oshirish -- Profilni tahrirlash formasini qoʻshish -- Qabul qilish: pytest tests/test_profile.py barchasi oʻtishi kerak - -## 3-vazifa: Qidiruv funksiyasi -- ... -``` - -**5. Git commit checkpoint (nazorat nuqtasi) sifatida.** Inisializatsiya tugagandan soʻng, toza commit qiling. Keyingi barcha ishlar shu nazorat nuqtasidan boshlanadi. - -**Issiq ishga tushirish (Warm start) strategiyasi**: Boʻsh katalogdan ish boshlamang. Standart katalog strukturasini, bogʻliqliklarni sozlashni va test freymvorklarini oldindan oʻrnatish uchun loyiha andozasidan (create-react-app, fastapi-template va h.k.) foydalaning. Odatdagi inisializatsiya qadamlarini andoza ichiga kiritib yuboring, toki agent faqatgina oʻz loyihasiga xos boʻlgan inisializatsiya ishlarini qilsin. Bu xuddi suv va chirogʻi bor joyda ish boshlash bilan — sahroning oʻrtasidan qurilishni boshlash oʻrtasidagi farq kabidir. - -**Inisializatsiyani yakunlash mezoni**: “Qanchalik koʻp kod yozilganligi” emas, balki bootstrap shartnomasining toʻrtta talabiga javob berganligi bilan oʻlchanadi — boshlash mumkin, test qilish mumkin, joriy holatni koʻrish mumkin va keyingi qadamlarni aniqlab olish mumkin. Inisializatsiyani tasdiqlash uchun quyidagi tekshiruv roʻyxatidan foydalaning: - -```markdown -## Inisializatsiyani qabul qilish roʻyxati -- [ ] `make setup` noldan muvaffaqiyatli ishlaydi -- [ ] `make test` da kamida bitta test oʻtdi -- [ ] Yangi agent sessiyasi faqat repo fayllariga tayanib “qanday ishga tushirish” va “qanday test qilish” ni ayta oladi -- [ ] Kamida 3 ta vazifa yozilgan vazifalar taqsimoti fayli mavjud -- [ ] Hamma narsa gitʼga commit qilingan -``` - -## Hayotiy misol - -React frontend loyihasi uchun ikkita inisializatsiya yondashuvi: - -**Aralash usul (poydevor quyish va devorlarni bir vaqtda qurish)**: 1-sessiyada agent loyiha tuzilishini yaratdi va bir vaqtning oʻzida birinchi funksiyani yozdi. Sessiya oxirida repoda ishlaydigan kod bor edi, ammo: loyihani boshlash/test qilish uchun aniq buyruqlar yoʻriqnomasi yoʻq, jarayonni kuzatib boradigan fayl yoʻq, vazifalar taqsimlanmagan edi. 2-sessiya loyiha strukturasi, test freymvorki va build jarayonini tushunib olishga ~20 daqiqa vaqt sarfladi — xuddi qurilish maydoniga kelib poydevor qayerda qotganini yoki suv quvurlari qayerdan oʻtganini bilmaydigan jamoa shuni bilish uchun bitta-bitta kovlab izlashiga oʻxshaydi. - -**Maxsus inisializatsiya (avval poydevor)**: 1-sessiya faqat inisializatsiya bilan shugʻullandi — andozadan katalog strukturasini yaratdi, test freymvorkini sozladi (Vitest + React Testing Library), bitta namunaviy test yozib, uni tekshirib oldi, bootstrap shartnomasi hujjatini va vazifalar roʻyxatini tuzdi, hamda bularni initial checkpoint sifatida commit qildi. 2-sessiyaning tiklash vaqti (rebuild time) 3 daqiqadan oshmadi, u roʻyxatdagi vazifalardan toʻgʻridan toʻgʻri ishni boshladi — yangi jamoa maydonga kelib chizmaga bir koʻz tashlab, qayerdan davom ettirishni aniq biladi. - -Loyiha sikllarini toʻliq taqqoslash: aralash usuldagi umumiy tiklash vaqti (barcha sessiyalar uchun) maxsus inisializatsiya yondashuviga qaraganda ~60% ga koʻp boʻldi. Inisializatsiyaga sarflangan ortiqcha 20 daqiqa keyingi sessiyalarda necha marta oʻzini oqladi. Xuddi mustahkam poydevor devorlarni tezroq qurishga yordam berganidek — sekin qadam bu aslida tezlashishdir. - -## Asosiy xulosalar - -- Inisializatsiya va implementatsiya turli xil optimallashtirish maqsadlariga ega — ularni aralashtirish ikkalasini ham orqaga tortadi. Avval poydevor quying, keyin devorlarni quring. -- Inisializatsiyaning natijasi bu kod emas, u infratuzilmadir: ishga tushishga tayyor muhit, tekshiruvdan oʻtadigan testlar, bootstrap shartnomasi va vazifalar taqsimoti. -- Inisializatsiyani bootstrap shartnomasining toʻrtta mezoniga koʻra tasdiqlang: ishga tushirish, test qilish, jarayonni koʻrish va keyingi qadamlarni aniqlay olish. -- Issiq ishga tushirish (warm start) sovuq ishga tushirishdan (cold start) doim ustun. Infratuzilmani oldindan sozlab olish uchun loyiha andozalaridan (project templates) foydalaning. -- Inisializatsiyaga kiritilgan vaqt keyingi 3-4 sessiyada toʻliq qoplanadi. Bu ortiqcha xarajat emas — bu boshlangʻich investitsiyadir. Poydevor qanchalik mustahkam boʻlsa, bino shunchalik tez koʻtariladi. - -## Qoʻshimcha oʻqish uchun - -- [Anthropic: Effective Harnesses for Long-Running Agents](https://www.anthropic.com/engineering/effective-harnesses-for-long-running-agents) -- [OpenAI: Harness Engineering](https://openai.com/index/harness-engineering/) -- [HumanLayer: Harness Engineering for Coding Agents](https://humanlayer.dev/articles/harness-engineering-for-coding-agents/) -- [Infrastructure as Code — Martin Fowler](https://martinfowler.com/bliki/InfrastructureAsCode.html) -- [SWE-agent: Agent-Computer Interfaces](https://github.com/princeton-nlp/SWE-agent) - -## Mashqlar - -1. **Bootstrap shartnomasi dizayni**: Oʻzingiz ishlayotgan loyiha uchun toʻliq bootstrap shartnomasini yozib chiqing. Soʻng mutlaqo yangi agent sessiyasini ochib, faqat repodagi maʼlumotlarni (ogʻzaki kontekstsiz) koʻrsatib, undan loyihani ishga tushirishni, testlarni ishga tushirishni va joriy jarayonni tushunishni talab qiling. U qanday muammolarga duch kelishini yozib oling — har bir duch kelingan muammo, sizning bootstrap shartnomangizda yetishmayotgan qoidani anglatadi. - -2. **Taqqoslash tajribasi**: Oʻrtacha murakkablikdagi yangi loyihani tanlang. A yondashuv: agentʼga bir vaqtning oʻzida inisializatsiya qilishni va birinchi implementatsiyani amalga oshirishni ishonib topshiring. B yondashuv: birinchi sessiyani faqat inisializatsiyaga sarflang va ikkinchi sessiyadan implementatsiyani boshlang. 4 sessiyadan soʻng natijalarni solishtiring: birinchi tekshiruvgacha ketgan vaqt, tiklash narxi (rebuild cost) va funksiyalarni bajarish darajasi. - -3. **Inisializatsiyani tasdiqlash varaqasi (Checklist)**: Loyihangiz uchun inisializatsiyani tasdiqlash roʻyxatini tuzing. Yangi agent sessiyasi har bir tekshiruv elementini bajarsin va qay biri muvaffaqiyatli, qaysi biri muvaffaqiyatsiz boʻlganini qayd etsin. Muvaffaqiyatsiz tugagan elementlar — sizning harnessʼingizdagi boʻshliqlardir; ularni toʻgʻrilash kerak. diff --git a/courses/learn-harness-engineering/docs/uz/lectures/lecture-07-why-agents-overreach-and-under-finish/code/index.md b/courses/learn-harness-engineering/docs/uz/lectures/lecture-07-why-agents-overreach-and-under-finish/code/index.md deleted file mode 100644 index ac3955b6..00000000 --- a/courses/learn-harness-engineering/docs/uz/lectures/lecture-07-why-agents-overreach-and-under-finish/code/index.md +++ /dev/null @@ -1,8 +0,0 @@ -# 7-maʼruza uchun kod - -Ushbu jilddan quyidagilarga misol sifatida foydalaning: - -- bitta urinishdagi (one-shot) muvaffaqiyatsizliklar -- haddan tashqari katta vazifa promptlari -- vazifani bosqichma-bosqich shakllantirish (incremental task shaping) -- tizimli funksiyalar sirti (structured feature surfaces) diff --git a/courses/learn-harness-engineering/docs/uz/lectures/lecture-07-why-agents-overreach-and-under-finish/code/next-task-template.md b/courses/learn-harness-engineering/docs/uz/lectures/lecture-07-why-agents-overreach-and-under-finish/code/next-task-template.md deleted file mode 100644 index 8c79e3d5..00000000 --- a/courses/learn-harness-engineering/docs/uz/lectures/lecture-07-why-agents-overreach-and-under-finish/code/next-task-template.md +++ /dev/null @@ -1,6 +0,0 @@ -# Keyingi Vazifa Andozasi - -- Joriy eng yuqori ustuvorlikka ega funksiya: -- Nima uchun aynan bu funksiya keyingisi: -- Nimani oʻtdi (passing) deb hisoblash mumkin: -- Bu bosqichda nima oʻzgarmasligi shart: diff --git a/courses/learn-harness-engineering/docs/uz/lectures/lecture-07-why-agents-overreach-and-under-finish/code/scope-surface-example.md b/courses/learn-harness-engineering/docs/uz/lectures/lecture-07-why-agents-overreach-and-under-finish/code/scope-surface-example.md deleted file mode 100644 index 227454af..00000000 --- a/courses/learn-harness-engineering/docs/uz/lectures/lecture-07-why-agents-overreach-and-under-finish/code/scope-surface-example.md +++ /dev/null @@ -1,17 +0,0 @@ -# Skoup Sirti (Scope Surface) Misoli - -Vazifa: - -- Electron bilimlar ilovasiga (knowledge app) indekslashni qoʻshish - -Yomon skoup shakli: - -- “Indekslashni implement qilish” - -Yaxshi skoup shakli: - -- Import qilingan hujjatlarni tahlil qilish (parse) -- Hujjatlarni boʻlaklarga (chunks) ajratish -- Boʻlak metamaʼlumotlarini (metadata) saqlash -- UIʼda indekslash holatini koʻrsatish -- Qayta indekslash (reindex) funksiyasini qoʻshish diff --git a/courses/learn-harness-engineering/docs/uz/lectures/lecture-07-why-agents-overreach-and-under-finish/code/scope-tracker.ts b/courses/learn-harness-engineering/docs/uz/lectures/lecture-07-why-agents-overreach-and-under-finish/code/scope-tracker.ts deleted file mode 100644 index 0a7fdee7..00000000 --- a/courses/learn-harness-engineering/docs/uz/lectures/lecture-07-why-agents-overreach-and-under-finish/code/scope-tracker.ts +++ /dev/null @@ -1,151 +0,0 @@ -/** - * scope-tracker.ts - * - * Reads a feature list and a change log. Enforces single-active-feature - * policy. Given a log of changes, flags any changes outside the active - * feature scope. Demonstrates how scope drift happens and how the tracker - * catches it. - * - * Run: npx tsx docs/lectures/lecture-07-why-agents-overreach-and-under-finish/code/scope-tracker.ts - */ - -// --------------------------------------------------------------------------- -// Types -// --------------------------------------------------------------------------- - -interface Feature { - id: string; - name: string; - status: “active” | “pending” | “done”; -} - -interface ChangeLogEntry { - step: number; - file: string; - description: string; - featureId: string; // The feature this change claims to belong to -} - -// --------------------------------------------------------------------------- -// Sample data -// --------------------------------------------------------------------------- - -const features: Feature[] = [ - { id: “F-001”, name: “Search endpoint”, status: “active” }, - { id: “F-002”, name: “Delete endpoint”, status: “pending” }, - { id: “F-003”, name: “Rate limiting”, status: “pending” }, - { id: “F-004”, name: “User dashboard”, status: “pending” }, -]; - -// A realistic change log where the agent gradually drifts from the active feature -const changeLog: ChangeLogEntry[] = [ - { step: 1, file: “src/routes/search.ts”, description: “Add search route handler”, featureId: “F-001” }, - { step: 2, file: “src/routes/search.ts”, description: “Add query parameter validation”, featureId: “F-001” }, - { step: 3, file: “src/routes/search.ts”, description: “Add search results pagination”, featureId: “F-001” }, - { step: 4, file: “src/routes/delete.ts”, description: “Add delete route handler”, featureId: “F-002” }, // DRIFT - { step: 5, file: “src/middleware/rate-limit.ts”, description: “Add rate limiter middleware”, featureId: “F-003” }, // DRIFT - { step: 6, file: “src/routes/search.ts”, description: “Integrate rate limiter into search”, featureId: “F-001” }, - { step: 7, file: “src/dashboard/ui.tsx”, description: “Create dashboard layout component”, featureId: “F-004” }, // DRIFT - { step: 8, file: “src/routes/search.ts”, description: “Add search response formatting”, featureId: “F-001” }, - { step: 9, file: “src/routes/delete.ts”, description: “Add delete confirmation logic”, featureId: “F-002” }, // DRIFT - { step: 10, file: “src/routes/search.ts”, description: “Add search tests”, featureId: “F-001” }, -]; - -// --------------------------------------------------------------------------- -// Scope tracker -// --------------------------------------------------------------------------- - -interface ScopeCheckResult { - step: number; - file: string; - description: string; - featureId: string; - inScope: boolean; - activeFeature: string; -} - -function trackScope( - featureList: Feature[], - changes: ChangeLogEntry[] -): ScopeCheckResult[] { - const activeFeatures = featureList.filter((f) => f.status === “active”); - const activeIds = new Set(activeFeatures.map((f) => f.id)); - const activeNames = activeFeatures.map((f) => f.name).join(“, ”); - - return changes.map((change) => ({ - step: change.step, - file: change.file, - description: change.description, - featureId: change.featureId, - inScope: activeIds.has(change.featureId), - activeFeature: activeNames, - })); -} - -// --------------------------------------------------------------------------- -// Reporting -// --------------------------------------------------------------------------- - -function pad(s: string, len: number): string { - return s.length >= len ? s : s + “ ”.repeat(len - s.length); -} - -function run(): void { - const results = trackScope(features, changeLog); - - console.log(“\n” + “=”.repeat(100)); - console.log(“ SCOPE TRACKER -- Single Active Feature Enforcement”); - console.log(“=”.repeat(100)); - - console.log(“\n Active feature: ” + features.filter((f) => f.status === “active”).map((f) => `${f.id} (${f.name})`).join(“, ”)); - console.log(“ Pending features: ” + features.filter((f) => f.status === “pending”).map((f) => `${f.id} (${f.name})`).join(“, ”)); - - // Detailed change log - console.log(“\n” + “-”.repeat(100)); - const header = `| ${pad("Step", 5)}| ${pad("File", 35)}| ${pad("Description", 40)}| ${pad("Feature", 8)}| ${pad("In Scope", 10)}|`; - const sep = `|${"-".repeat(7)}|${"-".repeat(37)}|${"-".repeat(42)}|${"-".repeat(10)}|${"-".repeat(12)}|`; - console.log(header); - console.log(sep); - - let inScopeCount = 0; - let driftCount = 0; - - for (const r of results) { - const scopeLabel = r.inScope ? “OK” : “DRIFT”; - if (r.inScope) inScopeCount++; - else driftCount++; - - const marker = r.inScope ? “ ” : “>>”; - console.log(`${marker}| ${pad(String(r.step), 5)}| ${pad(r.file, 35)}| ${pad(r.description, 40)}| ${pad(r.featureId, 8)}| ${pad(scopeLabel, 10)}|`); - } - - // Summary - console.log(“\n” + “=”.repeat(100)); - console.log(“ SCOPE DRIFT SUMMARY”); - console.log(“=”.repeat(100) + “\n”); - - const sHeader = `| ${pad("Metric", 40)}| ${pad("Value", 15)}|`; - const sSep = `|${"-".repeat(42)}|${"-".repeat(17)}|`; - console.log(sHeader); - console.log(sSep); - console.log(`| ${pad("Total changes", 40)}| ${pad(String(results.length), 15)}|`); - console.log(`| ${pad("Changes within active scope (F-001)", 40)}| ${pad(String(inScopeCount), 15)}|`); - console.log(`| ${pad("Changes outside active scope (DRIFT)", 40)}| ${pad(String(driftCount), 15)}|`); - console.log(`| ${pad("Features touched (total)", 40)}| ${pad(String(new Set(results.map((r) => r.featureId)).size), 15)}|`); - - // Drift detail - const driftFeatures = [...new Set(results.filter((r) => !r.inScope).map((r) => r.featureId))]; - if (driftFeatures.length > 0) { - console.log(“\n DRIFTED FEATURES:”); - for (const fid of driftFeatures) { - const feat = features.find((f) => f.id === fid); - const driftChanges = results.filter((r) => r.featureId === fid); - console.log(` ${fid} (${feat?.name}): ${driftChanges.length} unauthorized changes`); - } - } - - console.log(“\n Without a scope tracker, the agent silently worked on ” + driftFeatures.length + “ unrelated features.”); - console.log(“ The tracker catches this drift and enforces the single-active-feature policy.\n”); -} - -run(); diff --git a/courses/learn-harness-engineering/docs/uz/lectures/lecture-07-why-agents-overreach-and-under-finish/index.md b/courses/learn-harness-engineering/docs/uz/lectures/lecture-07-why-agents-overreach-and-under-finish/index.md deleted file mode 100644 index fcfbcb50..00000000 --- a/courses/learn-harness-engineering/docs/uz/lectures/lecture-07-why-agents-overreach-and-under-finish/index.md +++ /dev/null @@ -1,134 +0,0 @@ -[English version →](../../../en/lectures/lecture-07-why-agents-overreach-and-under-finish/) - -> Kod misollari: [code/](https://github.com/walkinglabs/learn-harness-engineering/blob/main/docs/en/lectures/lecture-07-why-agents-overreach-and-under-finish/code/) -> Amaliy loyiha: [Loyiha 04. Runtime qayta aloqa va skoup nazorati](./../../projects/project-04-incremental-indexing/index.md) - -# 7-maʼruza. Agentlar uchun aniq vazifa chegaralarini chizing - -Siz Claude Codeʼga “bu loyihaga foydalanuvchi autentifikatsiyasini qoʻsh” deysiz, u esa maʼlumotlar bazasi sxemasini oʻzgartirishdan, marshrutlarni (routes) yozishdan, frontend komponentlarini oʻzgartirishdan boshlaydi va — shu yoʻl-yoʻlakay — xatoliklarni qayta ishlash middlewareʼini (error-handling middleware) refaktoring qilib ketadi. Ikki soatdan keyin tekshirasiz: 12 ta fayl oʻzgartirilgan, 800 qator yangi kod yozilgan, lekin bittayam funksionallik (feature) boshidan oxirigacha (end-to-end) ishlamayapti. - -Qornidan kattaroq tishlash — bu ibora AI agentlariga juda mos tushadi. Agentlar “bir oz qoʻshimcha ish qilish” impulsi bilan tugʻiladi — ular bir-biriga bogʻliq narsalarni koʻrishadi va ularni shunchaki yoʻl-yoʻlakay hal qilib ketishadi, xuddi supermarketga bir shisha soya sousi uchun kirib, toʻla aravacha bilan chiqib kelgan odam kabi. Muammo shundaki, haddan tashqari koʻp narsa sotib olgan odam shunchaki pulini isrof qiladi; agentlarning bir vaqtning oʻzida juda koʻp ishlarni qilishi esa u ishlarning birortasi ham toʻgʻri oxiriga yetmaydi deganidir. - -Anthropicʼning “Uzoq vaqt ishlovchi agentlar uchun samarali harnessʼlar (Effective harnesses for long-running agents)” muhandislik blogida aniq yozilgan: promptʼlar (koʻrsatmalar) juda keng boʻlganda, agentlar “avval bitta ishni tugatish” oʻrniga “bir vaqtning oʻzida bir nechta ishni boshlash”ga moyil boʻlishadi. OpenAIʼning Codex muhandislik amaliyotlari ham xuddi shuni aniqladi — aniq skoup nazorati (scope control) boʻlmagan vazifalarning tugallanish koʻrsatkichlari keskin tushib ketadi. Bu modelning muammosi emas — bu harness muammosi. Siz chegarani chizib bermagansiz. - -## Diqqat — cheklangan resurs - -Bu qandaydir metafora emas — bu matematika. Aytaylik, agentʼning kontekst sigʻimi C ga teng va u bir vaqtning oʻzida k ta vazifani faollashtiradi. Har bir vazifaga oʻrtacha C/k mantiqiy fikrlash resursi toʻgʻri keladi. Qachonki C/k bitta vazifani yakunlash uchun kerak boʻladigan eng kam chegaradan (minimum threshold) tushib ketsa, ularning birortasi ham tugatilmaydi. Sizning oshqozoningizning ham sigʻimi maʼlum — bir vaqtning oʻzida oʻnta manti tiqsangiz, ularning hammasini hazm qila olmaysiz, shunchaki oʻn marta hazmsizlikka duch kelasiz, xolos. - -Claude Codeʼning haqiqiy xatti-harakati buni aniq koʻrsatadi. Unga “foydalanuvchi roʻyxatdan oʻtishini qoʻshish”ni soʻrang va u quyidagilarni qilishi mumkin: - -1. User modelini yaratish -2. Roʻyxatdan oʻtish marshrutini yozish -3. Elektron pochtani tasdiqlash kerakligini payqab, pochta xizmatini qoʻshish -4. Parollarni heshlash (hashing) kerakligini koʻrib, bcrypt ni olib kirish -5. Xatoliklarni qayta ishlash tizimi bir xil emasligini sezib, global xatolik middlewareʼini refaktoring qilish -6. Test fayllarining strukturasi tartibsiz ekanligini koʻrib, katalogni qayta tashkil qilish - -Oltita qadam oʻtgach, ularning har biri chala bajarilgan boʻladi. Hech qanday end-to-end tekshiruv (verification) yoʻq, chala pishgan kodlar oʻrtasida murakkab bogʻlanishlar mavjud boʻladi va chala ishlarni davom ettirishi kerak boʻlgan keyingi sessiya mutlaqo adashib qoladi. Xuddi bir vaqtning oʻzida oltita taom pishirayotgan odamga oʻxshaydi — hamma ovqat tovadadir, lekin birortasi ham tortilmagan (plated). Ularning hammasi kuyadi. - -Anthropicʼning eksperimental maʼlumotlari buni bevosita tasdiqlaydi: “kichik keyingi qadam” (small next step) strategiyasidan (WIP=1 ga teng) foydalanadigan agentlar keng promptʼlardan foydalanadigan agentlarga qaraganda 37% yuqori vazifa yakunlash darajasini koʻrsatadi. Eng qizigʻi shundaki, agentlar tomonidan yozilgan kod qatorlarining soni haqiqatda yakunlangan funksiyalar bilan zaif manfiy (teskari) korrelyatsiyaga ega — kod qanchalik koʻp yozilsa, shuncha kam funksiyalar yakunlanadi. Oʻzi chaynay oladiganidan kattaroq tishlash, maʼlumotlar bilan isbotlangan. - -## WIP=1 Ish jarayoni (Workflow) - -```mermaid -flowchart LR - Queue["Funksiyalar navbati"] --> Pick["Aynan bitta vazifani tanlash"] - Pick --> Active["Faqat bitta faol band (item)"] - Active --> Verify["End-to-end tekshiruvni ishga tushirish"] - Verify -->|oʻtdi| Commit["Commit qilish va keyingi vazifani ochish"] - Verify -->|yiqildi| Active - Commit --> Queue -``` - -```mermaid -flowchart TB - Budget["Mavjud fikrlash byudjeti = C"] --> One["WIP = 1
    Har bir vazifa uchun C / 1"] - Budget --> Many["WIP = 5
    Har bir vazifa uchun C / 5"] - - One --> Finish["Bitta funksiya (feature) oʻtish (passing) holatiga yetib boradi"] - Many --> Partial["Beshta chala implementatsiya"] - Partial --> VCR["Past darajadagi tekshirilgan yakunlash darajasi (VCR)
    keyingi sessiyada koʻp qayta ishlash"] -``` - -## Asosiy tushunchalar - -- **Haddan oshish (Overreach)**: Agent bitta sessiyada optimal miqdordan koʻra koʻproq vazifalarni faollashtirishi. Buni oʻlchash mumkin — bittayam end-to-endʼdan oʻtmagan holatda 5 ta funksiyani bajarish bu haddan oshishdir. -- **Oxiriga yetkazmaslik (Under-finish)**: Barcha faollashtirilgan vazifalar ichidan, end-to-end tekshiruvidan muvaffaqiyatli oʻtgan vazifalarning ulushi belgilangan meʼyordan (threshold) tushib ketishi. Kod yozilgan, ammo testlar oʻtmagan holat bu — oxiriga yetkazmaslikdir. -- **WIP chegarasi (WIP Limit / Work-in-Progress Limit)**: Kanban metodologiyasidan olingan. Asosiy gʻoya: bir vaqtning oʻzida qancha vazifa jarayonda ekanligini cheklash. Agentlar uchun WIP=1 eng xavfsiz standart (default) hisoblanadi — keyingisini boshlashdan oldin bittasini tugatish. Shved stoli (buffet) kabi — likopchangizga hamma narsani uyavermang, bitta likopchani tugating, soʻngra keyingisi uchun qaytib boring. -- **Bajarilganlik dalili (Completion Evidence)**: Vazifaning “jarayonda” (in progress) bosqichidan “tugatildi” (done) bosqichiga oʻtishi uchun qanoatlantirishi kerak boʻlgan tekshirilishi mumkin boʻlgan shart. Busiz agentlar “testlardan muvaffaqiyatli oʻtdi” oʻrniga “kod yaxshi koʻrinyapti”ni qabul qilishadi. -- **Skoup yuzasi (Scope Surface)**: Har bir tugun ishlash birligi va chekkalar (edges) qaramliklar (dependencies) boʻlgan DAG (Directed Acyclic Graph) strukturasi. Holatlar toʻrtta bilan cheklangan: not_started (boshlanmagan), active (faol), blocked (toʻsilgan), passing (oʻtgan). -- **Tugatish bosimi (Completion Pressure)**: Harnessʼning WIP chegaralari va bajarilganlik dalillari talablari orqali agentga oʻtkazadigan cheklovchi kuchi boʻlib, u agentni yangisini boshlashdan oldin joriy vazifani yakunlashiga majbur qiladi. - -## Haddan oshish va oxiriga yetkazmaslik oʻzaro bogʻliqdir (Symbiotic) - -Bu ikkala muammo mustaqil emas — ular bir-birini kuchaytiradi. Haddan oshish diqqatni susaytiradi, susaygan diqqat oxiriga yetkazmaslikka olib keladi va qoldirilgan chala yozilgan kod tizim murakkabligini oshiradi, bu esa keyingi vazifada yanada koʻproq haddan oshishni keltirib chiqaradi. Yopiq doira (vicious cycle). - -Kanban atamalarida: Little qonuni bizga L = lambda * W ekanligini aytadi. Agar jarayondagi ish L (bir vaqtda qilinayotgan ishlar) juda koʻp boʻlsa, har bir vazifa uchun ketadigan vaqt W muqarrar ravishda oshadi. Agentlar uchun bu shuni anglatadiki, har bir funksiyaning boshlanishidan tortib to tasdiqlangan holatda yakunlanishiga qadar boʻlgan vaqt uzayadi va muvaffaqiyatsizlik ehtimoli ortib boradi. - -Bu insonlar olamida ham qadimiy muammo — Steve McConnell oʻzining *Rapid Development* kitobida skoupning kengayib ketishi (scope creep) loyiha muvaffaqiyatsizligining asosiy sababi ekanligini hujjatlashtirgan. Lekin odamlarda hech boʻlmaganda “men yetarlicha ishladim” degan ichki sezgi bor. Agentlarda esa bu umuman yoʻq. Keyingi gʻoyani oʻylab topish model uchun deyarli hech qanday qoʻshimcha tokenlarga tushmaydi — “shu yerga kelgan ekanman, buni ham toʻgʻrilab ketay” deb yozish deyarli sezilmaydi — lekin har bir qoʻshimcha oʻzgartirish agentning diqqatini chalgʻitadi. Bu xuddi shved stoliga oʻxshaydi — har bir qoʻshimcha likopchaning xarajati nolga yaqin. Ammo sizning oshqozoningiz sigʻimi cheklangan. - -## Buni qanday toʻgʻri qilish kerak - -### 1. WIP=1 ni qatʼiy talab qiling - -Bu eng bevosita va samarali usul. Harnessʼingizda agentga ochiq-oydin shunday deng: **har qanday vaqtda faqat bitta vazifa “faol” (active) holatda boʻlishiga ruxsat beriladi.** Claude Codeʼning CLAUDE.md yoki Codexʼning AGENTS.md faylida shunday yozing: - -``` -## Ish qoidalari -- Bir vaqtning oʻzida bitta funksiya (feature) ustida ishlang -- Keyingi funksiyani faqat joriysi end-to-end tekshiruvdan oʻtgandan keyingina boshlang -- A funksiyasini amalga oshirish vaqtida B funksiyasini "qoʻshimchasiga refaktoring qilmang" -``` - -Xuddi shved stolida ovqatlanishdek — bir vaqtning oʻzida bitta likopcha oling, koʻproq olish uchun qaytib borishdan oldin uni tugating. - -### 2. Har bir vazifa uchun aniq bajarilganlik dalilini belgilang - -Tugatildi (done) degani bu “kod yozildi” emas — bu “xatti-harakat tekshiruvdan oʻtdi” degani. Funksiyalar roʻyxatingizda (feature list) har bir yozuvda (entry) tekshiruv buyrugʻi (verification command) boʻlishi kerak: - -``` -F01: Foydalanuvchi roʻyxatdan oʻtishi - Tekshiruv: curl -X POST /api/register -d '{"email":"test@example.com","password":"123456"}' | jq .status == 201 - Holat: passing -``` - -### 3. Skoup yuzasini tashqariga chiqaring (Externalize) - -Barcha vazifa holatlarini yozib borish uchun mashina oʻqiy oladigan fayldan (JSON yoki Markdown) foydalaning. Har qanday yangi sessiya ushbu faylni oʻqiy oladi va darhol bilib oladi: qaysi vazifa faol? Qanday xatti-harakat bajarilgan (done) deb hisoblanadi? Qaysi tekshiruvlar (verifications) oʻtdi? - -### 4. Tekshirilgan yakunlash darajasini (VCR) kuzatib boring - -Harness VCR (Verified Completion Rate) ni doimiy kuzatib borishi kerak = tekshirilgan vazifalar / faollashtirilgan vazifalar. Agar VCR < 1.0 boʻlsa, yangi vazifa faollashtirilishini bloklang. - -## Hayotiy misol - -8 ta funksiyaga ega REST API loyihasi, taqqoslangan ikkita strategiya: - -**Shved stoli rejimi (cheklovsiz)**: Agent 1-sessiyada 5 ta funksiyani bir vaqtda faollashtiradi. 12 ta faylda ~800 qator kod yozadi. End-to-end testlaridan oʻtish darajasi: 20% — faqat foydalanuvchi roʻyxatdan oʻtish qismi ishlaydi. Qolgan 4 ta funksiya: maʼlumotlar bazasi sxemasi yaratilgan lekin validatsiya (validation) mantigʻi yoʻq, marshrutlar (routes) belgilangan lekin xato formatda javob qaytarmoqda. Bu xuddi oltita taomni bir vaqtda pishirayotgan odamga oʻxshaydi — ulardan faqat bittasigina zoʻrgʻa yeyishga yaroqli boʻlgan. 3-sessiya oxiriga kelib, 8 ta funksiyadan faqat 3 tasi tugatilgan. - -**Bitta likopcha rejimi (WIP=1)**: Agent 1-sessiyada faqat foydalanuvchi roʻyxatdan oʻtishi ustida ishlaydi. 4 ta faylda ~200 qator kod yozadi. End-to-end testlar: 100% oʻtmoqda. Toza, tekshirilgan implementatsiyani commit qiladi. 4-sessiya oxiriga kelib, 8 ta funksiyadan 7 tasi yakunlanadi (8-chisi tashqi qaramlik (external dependency) sababli toʻxtatib qoʻyilgan). - -Natija: umumiy yozilgan kod kamroq (WIP=1 da 800 qator, shved stoli rejimida 1200 qator), lekin samaraliroq. Yakunlash darajasi: 87.5% va 37.5%. Bir vaqtning oʻzida bitta tishlamdan olsangiz, haqiqatda koʻproq yeysiz. - -## Asosiy xulosalar - -- **WIP=1 bu agent harnessʼlari uchun standart xavfsiz sozlamadir** — bittasini tugating, keyin boshqasini boshlang; parallel bajarishga urinmang. Bir tishlashda semirib qolmaysiz. -- **Bajarilganlik dalili bajarilishi (executable) mumkin boʻlishi shart** — “kod yaxshi koʻrinyapti” degani hisobga oʻtmaydi; “curl 201 qaytardi” degani oʻtadi. -- **Skoup yuzasi (scope surface) tashqi fayl sifatida yozilishi shart** — nafaqat suhbatda aytib oʻtilishi, balki repoda mashina oʻqiy oladigan formatda yozib borilishi kerak. -- **Haddan oshish va oxiriga yetkazmaslik oʻzaro bogʻliqdir** — birini hal qilish ikkinchisini ham hal qiladi. -- **“Kamroq qil, lekin tugat” har doim “koʻproq qil, lekin chala tashlab ket”dan ustunroqdir** — agent tomonidan yozilgan kod qatorlari va funksiyalarning tugallanish darajasi bir-biriga teskari korrelyatsiyaga ega (negatively correlated). Sifat har doim miqdordan ustun turadi. - -## Qoʻshimcha oʻqish uchun - -- [Effective harnesses for long-running agents - Anthropic](https://www.anthropic.com/engineering/effective-harnesses-for-long-running-agents) — Anthropicʼning muhandislik blogi, “kichik keyingi qadam” strategiyasini chuqur muhokama qiladi. -- [Harness Engineering - OpenAI](https://openai.com/index/harness-engineering/) — OpenAIʼning harness muhandisligi haqidagi toʻliq qoʻllanmasi. -- [Kanban: Successful Evolutionary Change - David Anderson](https://www.goodreads.com/book/show/1070822.Kanban) — WIP cheklovlari boʻyicha klassik manba. -- [Rapid Development - Steve McConnell](https://www.goodreads.com/book/show/125171.Rapid_Development) — Skoup kengayib ketishi (scope creep) loyiha muvaffaqiyatsizligining asosiy sababi ekanligini koʻrsatuvchi empirik maʼlumotlar. - -## Mashqlar - -1. **Vazifani atomizatsiya qilish (Task Atomization)**: Katta bir talabni tanlang (masalan, “foydalanuvchilarni boshqarish tizimini amalga oshirish”) va uni kamida 5 ta atomik ishlash birligiga boʻling. Har bir birlik uchun quyidagilarni aniqlang: (a) yagona xatti-harakat tavsifi, (b) bajariladigan (executable) tekshiruv buyrugʻi, (c) qaramliklar (dependencies). Boʻlinish WIP=1 cheklovini qanoatlantirishi yoki yoʻqligini tekshiring. - -2. **Taqqoslash tajribasi**: Bir xil loyihani ikki marta ishga tushirib koʻring — birinchi marta hech qanday cheklovsiz, ikkinchi marta WIP=1 ga qatʼiy amal qilgan holda. Taqqoslang: tekshirilgan yakunlash darajasi (VCR), umumiy yozilgan kod qatorlari soni, samarali kod nisbati. - -3. **Bajarilganlik dalili auditi**: Yaqinda qilingan agent sessiyasi natijalarini koʻrib chiqing, har bir kod oʻzgarishini “tugallangan xatti-harakat”, “tugallanmagan xatti-harakat” yoki “qolip (scaffolding)” sifatida tasniflang. Har bir tugallanmagan xatti-harakat uchun yetishmayotgan tekshiruv buyruqlarini qoʻshing. diff --git a/courses/learn-harness-engineering/docs/uz/lectures/lecture-08-why-feature-lists-are-harness-primitives/code/feature-list-validator.ts b/courses/learn-harness-engineering/docs/uz/lectures/lecture-08-why-feature-lists-are-harness-primitives/code/feature-list-validator.ts deleted file mode 100644 index dc780841..00000000 --- a/courses/learn-harness-engineering/docs/uz/lectures/lecture-08-why-feature-lists-are-harness-primitives/code/feature-list-validator.ts +++ /dev/null @@ -1,218 +0,0 @@ -/** - * feature-list-validator.ts - * - * Reads a feature_list.json, validates its schema, and checks for features - * marked “pass” without verification evidence. Outputs a structured report. - * Can run against any project directory that has a feature_list.json. - * - * Usage: - * npx tsx docs/lectures/lecture-08.../code/feature-list-validator.ts [path-to-dir] - * (defaults to the directory containing this script) - * - * Run: npx tsx docs/lectures/lecture-08-why-feature-lists-are-harness-primitives/code/feature-list-validator.ts - */ - -import * as fs from “node:fs”; -import * as path from “node:path”; - -// --------------------------------------------------------------------------- -// Types -// --------------------------------------------------------------------------- - -interface FeatureEntry { - id?: string; - category?: string; - description?: string; - verification?: string[]; - passes?: boolean; - // Allow unknown fields for flexibility - [key: string]: unknown; -} - -interface ValidationResult { - featureId: string; - schemaValid: boolean; - schemaErrors: string[]; - hasVerification: boolean; - markedPassWithoutEvidence: boolean; - passes: boolean; - verificationCount: number; -} - -// --------------------------------------------------------------------------- -// Schema validation -// --------------------------------------------------------------------------- - -function validateSchema(entry: FeatureEntry, index: number): string[] { - const errors: string[] = []; - const label = entry.id ?? `entry ${index + 1}`; - - if (!entry.id || typeof entry.id !== “string”) { - errors.push(`[${label}] Missing or invalid 'id' field`); - } - if (!entry.category || typeof entry.category !== “string”) { - errors.push(`[${label}] Missing or invalid 'category' field`); - } - if (!entry.description || typeof entry.description !== “string”) { - errors.push(`[${label}] Missing or invalid 'description' field`); - } - if (entry.verification !== undefined && !Array.isArray(entry.verification)) { - errors.push(`[${label}] 'verification' must be an array if present`); - } - if (entry.passes !== undefined && typeof entry.passes !== “boolean”) { - errors.push(`[${label}] 'passes' must be a boolean if present`); - } - - return errors; -} - -// --------------------------------------------------------------------------- -// Evidence validation -// --------------------------------------------------------------------------- - -function checkEvidence(entry: FeatureEntry): { - hasVerification: boolean; - markedPassWithoutEvidence: boolean; -} { - const hasVerification = Array.isArray(entry.verification) && entry.verification.length > 0; - const markedPassWithoutEvidence = entry.passes === true && !hasVerification; - - return { hasVerification, markedPassWithoutEvidence }; -} - -// --------------------------------------------------------------------------- -// Process feature list -// --------------------------------------------------------------------------- - -function processFeatureList(entries: FeatureEntry[]): ValidationResult[] { - return entries.map((entry, index) => { - const schemaErrors = validateSchema(entry, index); - const evidence = checkEvidence(entry); - - return { - featureId: (entry.id as string) ?? `entry-${index + 1}`, - schemaValid: schemaErrors.length === 0, - schemaErrors, - hasVerification: evidence.hasVerification, - markedPassWithoutEvidence: evidence.markedPassWithoutEvidence, - passes: entry.passes === true, - verificationCount: Array.isArray(entry.verification) ? entry.verification.length : 0, - }; - }); -} - -// --------------------------------------------------------------------------- -// Reporting -// --------------------------------------------------------------------------- - -function pad(s: string, len: number): string { - return s.length >= len ? s : s + “ ”.repeat(len - s.length); -} - -function run(): void { - // Resolve target directory - const scriptDir = path.dirname(new URL(import.meta.url).pathname); - const targetDir = process.argv[2] - ? path.resolve(process.argv[2]) - : scriptDir; - - const filePath = path.join(targetDir, “feature_list.json”); - - console.log(“\n” + “=”.repeat(90)); - console.log(“ FEATURE LIST VALIDATOR”); - console.log(“=”.repeat(90)); - console.log(` Reading: ${filePath}\n`); - - if (!fs.existsSync(filePath)) { - console.error(` ERROR: feature_list.json not found at ${filePath}`); - console.error(“ Usage: npx tsx feature-list-validator.ts [path-to-directory-containing-feature_list.json]\n”); - process.exit(1); - } - - let entries: FeatureEntry[]; - try { - const raw = fs.readFileSync(filePath, “utf-8”); - entries = JSON.parse(raw); - } catch (err) { - console.error(` ERROR: Could not parse feature_list.json: ${err}`); - process.exit(1); - } - - if (!Array.isArray(entries)) { - console.error(“ ERROR: feature_list.json must contain a JSON array at the top level.”); - process.exit(1); - } - - // For demo purposes, also validate an extended test set - const demoEntries: FeatureEntry[] = [ - ...entries, - { - id: “qna-002”, - category: “import”, - description: “User can import a PDF document.”, - verification: [“Upload a PDF file”, “Verify it appears in the document list”], - passes: true, - }, - { - id: “qna-003”, - category: “grounded_qa”, - description: “System hallucination rate is below 5%.”, - verification: [], // Empty -- no evidence - passes: true, // Marked as pass WITHOUT evidence - }, - { - id: “missing-fields”, - // Missing 'category' and 'description' - passes: true, - } as FeatureEntry, - ]; - - const results = processFeatureList(demoEntries); - - // Print report - const header = `| ${pad("Feature ID", 14)}| ${pad("Schema", 8)}| ${pad("Passes", 7)}| ${pad("Verifications", 14)}| ${pad("Evidence?", 12)}| Notes`; - const sep = `|${"-".repeat(16)}|${"-".repeat(10)}|${"-".repeat(9)}|${"-".repeat(16)}|${"-".repeat(14)}|${"-".repeat(30)}`; - console.log(header); - console.log(sep); - - for (const r of results) { - const schemaLabel = r.schemaValid ? “OK” : “INVALID”; - const passesLabel = r.passes ? “PASS” : “FAIL”; - const evidenceLabel = r.hasVerification ? “Present” : “MISSING”; - const notes = r.markedPassWithoutEvidence - ? “FLAGGED: passes without evidence!” - : r.schemaErrors.length > 0 - ? r.schemaErrors[0] - : “”; - - const marker = r.markedPassWithoutEvidence ? “>>” : r.schemaValid ? “ ” : “!!”; - console.log( - `${marker}| ${pad(r.featureId, 14)}| ${pad(schemaLabel, 8)}| ${pad(passesLabel, 7)}| ${pad(String(r.verificationCount), 14)}| ${pad(evidenceLabel, 12)}| ${notes}` - ); - } - - // Summary - const total = results.length; - const schemaOk = results.filter((r) => r.schemaValid).length; - const passing = results.filter((r) => r.passes).length; - const flagged = results.filter((r) => r.markedPassWithoutEvidence).length; - const withEvidence = results.filter((r) => r.hasVerification).length; - - console.log(“\n” + “-”.repeat(90)); - console.log(“ SUMMARY”); - console.log(“-”.repeat(90)); - console.log(` Total features: ${total}`); - console.log(` Schema valid: ${schemaOk}/${total}`); - console.log(` Marked as passing: ${passing}/${total}`); - console.log(` With verification evidence: ${withEvidence}/${total}`); - console.log(` Flagged (pass without evidence): ${flagged}`); - - if (flagged > 0) { - console.log(`\n WARNING: ${flagged} feature(s) marked as "pass" without any verification evidence.`); - console.log(“ These features need verification before they can be trusted.\n”); - } else { - console.log(“\n All passing features have verification evidence. Feature list is healthy.\n”); - } -} - -run(); diff --git a/courses/learn-harness-engineering/docs/uz/lectures/lecture-08-why-feature-lists-are-harness-primitives/code/feature_list.json b/courses/learn-harness-engineering/docs/uz/lectures/lecture-08-why-feature-lists-are-harness-primitives/code/feature_list.json deleted file mode 100644 index 8b973e31..00000000 --- a/courses/learn-harness-engineering/docs/uz/lectures/lecture-08-why-feature-lists-are-harness-primitives/code/feature_list.json +++ /dev/null @@ -1,15 +0,0 @@ -[ - { - "id": "qna-001", - "category": "grounded_qa", - "description": "User can ask a question about an imported document and receive an answer with visible citations.", - "verification": [ - "Import a markdown document", - "Open the Q&A panel", - "Ask a question about known content", - "Verify the answer is returned", - "Verify one or more citations are displayed" - ], - "passes": false - } -] diff --git a/courses/learn-harness-engineering/docs/uz/lectures/lecture-08-why-feature-lists-are-harness-primitives/code/index.md b/courses/learn-harness-engineering/docs/uz/lectures/lecture-08-why-feature-lists-are-harness-primitives/code/index.md deleted file mode 100644 index e101f044..00000000 --- a/courses/learn-harness-engineering/docs/uz/lectures/lecture-08-why-feature-lists-are-harness-primitives/code/index.md +++ /dev/null @@ -1,8 +0,0 @@ -# 8-maʼruza uchun kod - -Ushbu jilddan quyidagilarga misol sifatida foydalaning: - -- oʻtish holati tekshiruvlari (pass-state gating) -- end-to-end tekshiruv (verification) -- kuchsiz va kuchli tugallanish (completion) mezonlari -- baholovchi (evaluator) sikli misollari diff --git a/courses/learn-harness-engineering/docs/uz/lectures/lecture-08-why-feature-lists-are-harness-primitives/code/pass-gate-policy.md b/courses/learn-harness-engineering/docs/uz/lectures/lecture-08-why-feature-lists-are-harness-primitives/code/pass-gate-policy.md deleted file mode 100644 index 1b122861..00000000 --- a/courses/learn-harness-engineering/docs/uz/lectures/lecture-08-why-feature-lists-are-harness-primitives/code/pass-gate-policy.md +++ /dev/null @@ -1,8 +0,0 @@ -# Oʻtish Holati Qoidasi (Pass Gate Policy) - -Funksiya (feature) faqat quyidagi hollarda `passes: false` holatidan `passes: true` holatiga oʻtishi mumkin: - -- kutilgan jarayon (workflow) sinovdan oʻtgan boʻlsa -- muvaffaqiyat dalili yozib qoʻyilgan boʻlsa -- test qilingan yoʻlda hech qanday toʻsqinlik qiluvchi xatolik boʻlmasa -- implementatsiya ilovani buzilgan yoki noaniq holatda tashlab ketmasa diff --git a/courses/learn-harness-engineering/docs/uz/lectures/lecture-08-why-feature-lists-are-harness-primitives/index.md b/courses/learn-harness-engineering/docs/uz/lectures/lecture-08-why-feature-lists-are-harness-primitives/index.md deleted file mode 100644 index 3393e5ab..00000000 --- a/courses/learn-harness-engineering/docs/uz/lectures/lecture-08-why-feature-lists-are-harness-primitives/index.md +++ /dev/null @@ -1,137 +0,0 @@ -[English version →](../../../en/lectures/lecture-08-why-feature-lists-are-harness-primitives/) - -> Kod misollari: [code/](https://github.com/walkinglabs/learn-harness-engineering/blob/main/docs/en/lectures/lecture-08-why-feature-lists-are-harness-primitives/code/) -> Amaliy loyiha: [Loyiha 04. Runtime qayta aloqa va skoup nazorati](./../../projects/project-04-incremental-indexing/index.md) - -# 8-maʼruza. Agent nima qilishini cheklash uchun funksiyalar roʻyxatidan foydalaning - -Siz agentdan e-tijorat saytini qurishni soʻraysiz. Ishini tugatgandan soʻng, u sizga “tugatdim” deydi. Kodni koʻrib chiqasiz — foydalanuvchi autentifikatsiyasi ishlayapti, lekin savatdagi (shopping cart) “sotib olish” (checkout) tugmasi hech narsa qilmaydi va toʻlov jarayoni (payment flow) umuman ulanmagan. Muammo shundaki: siz unga hech qachon “tugatish” (done) nimani anglatishini aytmagansiz, shuning uchun u oʻzining shaxsiy standartini — “Men juda koʻp kod yozdim va u ancha tugallanganga oʻxshaydi” degan standartni ishlatdi. - -Koʻpchilikning nazarida funksiyalar roʻyxati (feature lists) shunchaki eslatma (memo) — esdan chiqarmaslik uchun yoziladi, soʻngra bir chetga yigʻishtirib qoʻyiladi. Lekin harness olamida funksiyalar roʻyxati odamlar uchun eslatma emas — u butun harnessʼning umurtqa pogʻonasidir (backbone). Rejalashtiruvchi (scheduler) qaysi vazifani tanlashda unga suyanadi, tekshiruvchi (verifier) yakunlanganligini baholashda unga suyanadi, topshirish hisobotchisi (handoff reporter) xulosalar yaratishda unga suyanadi. Umurtqani sindirsangiz butun tana falaj boʻladi. - -Anthropic ham, OpenAI ham alohida taʼkidlaydi: **artefaktlar tashqariga chiqarilishi (externalized) shart.** Funksiya (feature) holati (state) tuzilmagan suhbat matni ichida emas, balki repodagi mashina oʻqiy oladigan faylda yashashi kerak. - -## Agentlar “Tugatildi” nimani anglatishini bilmaydi - -Na Claude Code va na Codex sizning “tugatildi” deganda nimani nazarda tutayotganingizni avtomatik ravishda bilmaydi. Siz “savat (shopping cart) funksiyasini qoʻsh” deysiz va modelning talqini “Cart komponentini va addToCart metodini yozish” boʻlishi mumkin. Siz esa “foydalanuvchi mahsulotlarni koʻra oladi, savatga qoʻsha oladi va toʻlov jarayonini (checkout) boshidan oxirigacha (end-to-end) tugata oladi” deb nazarda tutgansiz. Funksiyalar roʻyxati boʻlmasa, bu tushunmovchilik saqlanib qolaveradi. Agent oʻzining yashirin (implicit) standartidan foydalanadi — odatda “kodda aniq sintaksis xatolari yoʻq” degan maʼnoda. Sizga kerak boʻlgan narsa bu end-to-end xatti-harakat tekshiruvidir (behavioral verification). Xuddi doʻstingizdan meva sotib olishni soʻraganingiz kabi — “ozgina meva olib kel” deysiz va u limon koʻtarib keladi. Uning mevasi bilan sizning mevangiz bir xil meva emas. - -Ushbu keng tarqalgan jarayon eslatmasini (progress note) koʻrib chiqing: - -``` -User auth qilindi, savat asosan tugatildi, toʻlov qismi (payments) hali kerak -``` -Yangi agent sessiyasi ushbu eslatmadan quyidagi savollarga javob bera oladimi? “Asosan tugatildi” deganda nima nazarda tutilgan? Savat qaysi testlardan oʻtdi? Toʻlovlar (payments) qilinishiga nima toʻsqinlik qilyapti (blocking)? Bularning barchasiga javob — “hech kim bilmaydi”. Xuddi shifokorga “oshqozonim ogʻriyapti, lekin oxirgi paytda yaxshi edim” deyishga oʻxshaydi — u sizga qanday dori yozib bera oladi? - -Natija: yangi sessiya loyiha holatini (project state) tushunish uchun 20 daqiqa sarflaydi va allaqachon bajarilgan funksiyalarni qaytadan yozib chiqishi mumkin. Anthropicʼning muhandislik maʼlumotlari shuni koʻrsatadiki, jarayon boʻyicha yaxshi qaydlar sessiyani ishga tushishdan keyingi tashxis vaqtini 60-80% ga qisqartiradi. - -## Funksiya holati mashinasi (Feature State Machine) - -```mermaid -flowchart LR - Feature["Bitta funksiya (feature) qatori"] --> Behavior["Xatti-harakat
    masalan: POST /cart/items 201 qaytarishi"] - Feature --> Check["Tekshiruv buyrugʻi
    aynan qanday tekshiruv ishga tushishi"] - Feature --> State["Holat
    not_started / active / blocked / passing"] - - Behavior --> Complete["Faqatgina uchala maydon bilan birga
    funksiya qatori yaroqli boʻladi"] - Check --> Complete - State --> Complete -``` - -```mermaid -flowchart LR - List["feature_list.json / features.md"] --> Scheduler["Keyingi not_started bandini (item) tanlash"] - Scheduler --> Agent["Agent aynan shu band (item) ustida ishlaydi"] - Agent --> Verifier["Oʻsha bandning (item) tekshiruv buyrugʻini ishga tushirish"] - Verifier -->|oʻtdi| Passing["Uni passing (oʻtdi) deb belgilash
    va dalilini (evidence) yozish"] - Verifier -->|yiqildi| Active["Uni active (faol) qilib qoldirish"] - Verifier -->|qaramlik (dependency) muammosi| Blocked["Uni blocked (toʻsilgan) deb belgilash"] - Passing --> Handoff["Topshirish eslatmasi
    va joriy jarayonni yangilash"] - Active --> Agent -``` - -## Asosiy tushunchalar - -- **Funksiyalar roʻyxati harness primitivlaridir (primitives)**: Ular shunchaki “ixtiyoriy rejalashtirish vositalari” emas, balki barcha boshqa harness komponentlari tayanadigan bazaviy maʼlumotlar strukturasidir. Maʼlumotlar bazasi jadvali (database table) strukturalariga oʻxshab — siz “asosiy kalitlarni (primary keys) tashlab ketaylik” deya olmaysiz. -- **Uchlik struktura (Triple structure)**: Har bir funksiya (feature) bandi bu `(xatti-harakat tavsifi, tekshiruv buyrugʻi, joriy holat)` uchligidir. Har qanday elementning yoʻqligi ushbu bandni tugallanmagan (incomplete) qilib qoʻyadi. -- **Holat mashinasi modeli (State machine model)**: Har bir funksiya bandida toʻrtta holat mavjud — `not_started`, `active`, `blocked`, `passing`. Holat oʻzgarishlari harness tomonidan boshqariladi, agent tomonidan erkin oʻzgartirilmaydi. -- **Oʻtganlik holati boʻyicha tekshiruv (Pass-state gating)**: Funksiya (feature) `active` dan `passing` ga oʻtishining yagona yoʻli bu — tekshiruv buyrugʻi (verification command) muvaffaqiyatli ishlashidir. Buni orqaga qaytarib boʻlmaydi — bir marta `passing` boʻlgach, orqaga qaytmaydi. Xuddi imtihondan oʻtganingiz kabi, siz uni oʻtdingiz, bahoni orqaga qaytarib oʻzgartira olmaysiz. -- **Yagona haqiqat manbai (Single source of truth)**: “Nima qilinishi kerakligi” haqidagi barcha maʼlumotlar yagona funksiyalar roʻyxatidan (feature list) olinishi shart. Funksiyalar roʻyxati va suhbat tarixi oʻrtasida hech qanday qarama-qarshiliklar boʻlmasligi kerak. -- **Orqa bosim (Back-pressure)**: Hali oʻtmagan (not passed) funksiyalarning soni, harness agentga oʻtkazadigan bosimdir. Nol bosim = loyiha tugallandi degani. - -## Nega funksiyalar roʻyxatlari “Primitivlar” (Primitives) boʻlishi kerak - -Hujjatlar odamlar oʻqishi uchun; primitivlar tizimlar bajarishi uchundir. Hujjatlarga eʼtiborsizlik qilish mumkin; primitivlarni esa aylanib oʻtib boʻlmaydi. - -Buni maʼlumotlar bazasi trigger cheklovlari va dastur darajasidagi (application-layer) tekshiruvlarga oʻxshating: birinchisi maʼlumotlar bazasi dvigateli tomonidan amalga oshiriladi, hech qanday SQL uni aylanib oʻtolmaydi; ikkinchisi esa ilova (application) kodining toʻgʻriligiga tayanadi va tasodifan chetlab oʻtilishi mumkin. Funksiyalar roʻyxati harness primitivi sifatida maxsus 4 ta harness komponentiga xizmat qiladi: - -1. **Rejalashtiruvchi (Scheduler)**: Holatlarni oʻqiydi, keyingi `not_started` funksiyani (feature) tanlaydi. Zavoddagi ishlab chiqarishni rejalashtirish tizimiga oʻxshaydi. -2. **Tekshiruvchi (Verifier)**: Tekshiruv buyruqlarini (verification commands) bajaradi, holatni oʻtkazishga ruxsat berish-bermaslikni hal qiladi. Sifat nazoratiga oʻxshaydi. -3. **Topshirish hisobotchisi (Handoff reporter)**: Funksiyalar roʻyxatidan sessiya topshirish (handoff) xulosalarini avtomatik tarzda yaratadi. Avtomatik smena oʻzgarishi hisobotiga oʻxshaydi. -4. **Jarayonni kuzatuvchi (Progress tracker)**: Holat taqsimotini hisoblaydi, loyiha salomatligi metrikalarini taqdim etadi. Dashboard kabi. - -## Buni qanday toʻgʻri qilish kerak - -### 1. Minimal funksiyalar roʻyxati formatini belgilang - -Sizga murakkab tizim kerak emas — strukturalangan Markdown yoki JSON fayli kifoya qiladi. Asosiy shart shuki, har bir yozuvda (entry) quyidagi uchlik (triple) boʻlishi kerak: - -```json -{ - "id": "F03", - "behavior": "POST /cart/items ni {product_id, quantity} bilan joʻnatganda 201 qaytaradi", - "verification": "curl -X POST http://localhost:3000/api/cart/items -H 'Content-Type: application/json' -d '{\"product_id\":1,\"quantity\":2}' | jq .status == 201", - "state": "passing", - "evidence": "commit abc123, test output logi" -} -``` - -### 2. Holat oʻzgarishini (State Transitions) boshqarishni Harnessʼga topshiring - -Agent bitta funksiya holatini toʻgʻridan-toʻgʻri `passing` ga oʻzgartira olmaydi. U faqat tekshiruv soʻrovini (verification request) yuborishi mumkin; harness tekshiruv buyrugʻini ishga tushirib, unga ruxsat berish yoki bermaslikni oʻzi hal qiladi. Bu “oʻtish holatini nazorat qilish” (pass-state gating) deb ataladi. - -### 3. Qoidalarni CLAUDE.md fayliga yozing - -``` -## Funksiyalar roʻyxati (Feature List) qoidalari -- Funksiyalar roʻyxati fayli: /docs/features.md -- Bir vaqtda faqat bitta funksiya (feature) faol (active) boʻladi -- Uni oʻtdi (passing) deb belgilashdan oldin tekshiruv buyrugʻi oʻtgan boʻlishi shart -- Funksiya (feature) roʻyxati holatlarini oʻzingiz oʻzgartirmang — ularni tekshiruv (verification) skripti avtomatik ravishda yangilaydi -``` - -### 4. Darajalashni (Granularity) toʻgʻrilash - -Har bir funksiya (feature) bandi “bitta sessiyada tugatilishi mumkin” boʻlgan darajada belgilanishi kerak. Juda keng boʻlsa — tugamaydi; juda kichik boʻlsa — boshqarish qiyinlashadi. “Foydalanuvchi savatga mahsulot qoʻshishi mumkin” — bu toʻgʻri darajalash (granularity). “Savat (shopping cart)ni amalga oshirish (implement qilish)” — juda keng. “Cart modelida name maydonini (field) yaratish” — juda tor. Xuddi goʻshtni kesishga oʻxshaydi — butun boʻlak ham emas, qiyma ham emas. - -## Hayotiy misol - -10 ta funksiyaga ega boʻlgan e-tijorat (e-commerce) platformasi. Taqqoslangan ikkita kuzatish (tracking) yondashuvi: - -**Eslatma rejimi (Memo mode)**: Agent hech qanday strukturaga ega boʻlmagan eslatmalardan (unstructured notes) foydalanadi. 3 sessiyadan keyin eslatmalar quyidagi koʻrinishga keladi: “foydalanuvchi auth va mahsulotlar roʻyxati bajarildi, savat asosan tugatildi lekin bugʼlari bor, toʻlovlar (payments) boshlanmadi”. Yangi sessiya holatni tushunib olishi uchun 20 daqiqa kerak boʻladi va oxir-oqibat allaqachon yakunlangan funksiyalarni qaytadan yozib chiqadi. Bu xuddi bozordagi xaridingiz roʻyxatida “sut, non va anavi narsa” deb yozilganiga oʻxshaydi — doʻkonga borgach, baribir nimani olishingiz kerakligini bilmaysiz. - -**Umurtqa pogʻona rejimi (Backbone mode)**: Har bir funksiyaning (feature) aniq holati va tekshiruv buyrugʻi (verification command) bor. Yangi sessiya funksiyalar roʻyxatini oʻqiydi va 3 daqiqa ichida hamma narsani tushunib oladi: F01-F05 — `passing`, F06 — `active`, F07-F10 — `not_started`. Ishni toʻgʻridan-toʻgʻri F06 dan boshlaydi, hech qanday ishni boshidan qilmaydi (zero rework). - -Miqdoriy natija (Quantified result): Strukturalangan funksiyalar roʻyxatidan (structured feature lists) foydalangan loyihalar erkin kuzatish (free-form tracking) tizimidagiga qaraganda 45% ga yuqori funksiyalarni tugallash (feature completion) darajasini koʻrsatdi va hech qanday funksiya takror amalga oshirilmadi. - -## Asosiy xulosalar - -- **Funksiyalar roʻyxatlari — bu odamlar uchun eslatma emas, ular harnessʼning umurtqa pogʻonasidir (backbone).** Rejalashtiruvchi, tekshiruvchi va topshirish hisobotchisi barchasi shunga tayanadi. -- **Har bir funksiya (feature) bandida uchlik (triple) boʻlishi shart**: xatti-harakat (behavior) tavsifi + tekshiruv buyrugʻi (verification command) + joriy holat (current state). Birgina elementning yetishmasligi uni tugallanmagan qilib qoʻyadi — xuddi bir oyogʻi yoʻq uch oyoqli stul kabi. -- **Holat (State) oʻzgarishlari harness tomonidan boshqariladi** — agent holatlarni oʻzboshimchalik bilan oʻzgartira olmaydi. Tekshiruvdan oʻtish = oldinga oʻtishning yagona yoʻli. -- **Funksiyalar roʻyxati bu loyihaning yagona haqiqat manbai (single source of truth)** — barcha “nima qilish kerak” maʼlumotlari yagona roʻyxatdan kelib chiqadi. -- **Darajalanishni (granularity) “bitta sessiyada tugatilishi mumkin” degan qoidaga asosan toʻgʻrilang.** - -## Qoʻshimcha oʻqish uchun - -- [Building Effective Agents - Anthropic](https://www.anthropic.com/research/building-effective-agents) — Funksiyalar roʻyxatini (feature list) agent skoupini (scope) boshqarish uchun “asosiy maʼlumotlar strukturasi” ekanligini aniq koʻrsatib beradi. -- [Harness Engineering - OpenAI](https://openai.com/index/harness-engineering/) — "artefaktlarni tashqariga chiqarish" (externalizing artifacts) tamoyilini alohida taʼkidlaydi. -- [Design by Contract - Bertrand Meyer](https://www.goodreads.com/book/show/130439.Object_Oriented_Software_Construction) — Shartnoma dizayni tamoyillari (Contract design principles), funksiyalar roʻyxatining nazariy asosi. -- [How Google Tests Software](https://www.goodreads.com/book/show/13563030-how-google-tests-software) — Test piramidasi (Test pyramid) va xulq-atvor (behavioral) spetsifikatsiyasi muhandislik amaliyotlari. - -## Mashqlar - -1. **Funksiyalar roʻyxati dizayni (Feature List Design)**: Minimal funksiyalar roʻyxati JSON sxemasini belgilang. Qatnashishi kerak: id, xatti-harakat (behavior) tavsifi, tekshiruv buyrugʻi (verification command), joriy holat, dalilga (evidence) ishora. Undan foydalanib 5 ta funksiyaga (feature) ega haqiqiy loyihani taʼriflang. - -2. **Tekshiruvning qatʼiyligini taqqoslash**: 3 ta funksiyani tanlang va ular uchun ham “yumshoq” (loose) tekshiruv (masalan, “kodda sintaksis xatolari yoʻq”), ham “qatʼiy” (strict) tekshiruv (masalan, “end-to-end test oʻtmoqda”) ishlab chiqing. Har bir yondashuv boʻyicha soxta musbat (false positive) koʻrsatkichini solishtiring. - -3. **Yagona manba tamoyili (Single Source Principle) auditi**: Mavjud boʻlgan agent loyihasini koʻrib chiqing va funksiyalar roʻyxatiga (feature list) zid boʻlgan skoup (scope) maʼlumotlarini qidiring (suhbatlardagi yashirin talablar, koddagi TODO izohlar va h.k.). Barcha maʼlumotlarni funksiyalar roʻyxatiga birlashtirish rejasini ishlab chiqing. diff --git a/courses/learn-harness-engineering/docs/uz/lectures/lecture-09-why-agents-declare-victory-too-early/code/clean-state-checklist.md b/courses/learn-harness-engineering/docs/uz/lectures/lecture-09-why-agents-declare-victory-too-early/code/clean-state-checklist.md deleted file mode 100644 index 8f702277..00000000 --- a/courses/learn-harness-engineering/docs/uz/lectures/lecture-09-why-agents-declare-victory-too-early/code/clean-state-checklist.md +++ /dev/null @@ -1,6 +0,0 @@ -# Toza Holat Tekshiruvi - -- Ilova inson aralashuvisiz ishga tushadi -- Joriy jarayon (progress) yozib qoldirilgan -- Yarmi bajarilgan import yoki indekslash ishlari hujjatlashtirilmasdan qolib ketmagan -- Keyingi agent zudlik bilan standart ishga tushirish va tekshirish yoʻlini ishga tushira oladi diff --git a/courses/learn-harness-engineering/docs/uz/lectures/lecture-09-why-agents-declare-victory-too-early/code/index.md b/courses/learn-harness-engineering/docs/uz/lectures/lecture-09-why-agents-declare-victory-too-early/code/index.md deleted file mode 100644 index 49051a28..00000000 --- a/courses/learn-harness-engineering/docs/uz/lectures/lecture-09-why-agents-declare-victory-too-early/code/index.md +++ /dev/null @@ -1,8 +0,0 @@ -# 9-maʼruza uchun kod - -Ushbu jilddan quyidagilarga misol sifatida foydalaning: - -- loglar qayta aloqa (feedback) sifatida -- runtime-state koʻrinuvchanligi (visibility) -- toza holat tekshiruvlari -- qayta tiklash (recovery) misollari diff --git a/courses/learn-harness-engineering/docs/uz/lectures/lecture-09-why-agents-declare-victory-too-early/code/victory-detector.ts b/courses/learn-harness-engineering/docs/uz/lectures/lecture-09-why-agents-declare-victory-too-early/code/victory-detector.ts deleted file mode 100644 index b14027b4..00000000 --- a/courses/learn-harness-engineering/docs/uz/lectures/lecture-09-why-agents-declare-victory-too-early/code/victory-detector.ts +++ /dev/null @@ -1,241 +0,0 @@ -/** - * victory-detector.ts - * - * Simulates an agent that claims task completion, then checks the claimed - * state against actual verification. Outputs: claimed vs actual, highlighting - * gaps between what the agent said and whatʼs really true. - * - * Run: npx tsx docs/lectures/lecture-09-why-agents-declare-victory-too-early/code/victory-detector.ts - */ - -// --------------------------------------------------------------------------- -// Types -// --------------------------------------------------------------------------- - -interface Task { - name: string; - claimedComplete: boolean; - checks: VerificationCheck[]; -} - -interface VerificationCheck { - description: string; - claimed: boolean; // What the agent says - actual: boolean; // What is actually true - severity: “critical” | “warning”; -} - -// --------------------------------------------------------------------------- -// Simulated tasks with claimed vs actual states -// --------------------------------------------------------------------------- - -const tasks: Task[] = [ - { - name: “Add search endpoint”, - claimedComplete: true, - checks: [ - { - description: “Route handler exists”, - claimed: true, - actual: true, - severity: “critical”, - }, - { - description: “Authentication middleware applied”, - claimed: true, - actual: false, // Agent forgot auth - severity: “critical”, - }, - { - description: “Input validation added”, - claimed: true, - actual: true, - severity: “critical”, - }, - { - description: “Unit tests written”, - claimed: true, - actual: false, // Agent skipped tests - severity: “critical”, - }, - { - description: “Integration tests pass”, - claimed: true, - actual: false, // No tests to run - severity: “critical”, - }, - { - description: “Documentation updated”, - claimed: true, - actual: false, // Agent skipped docs - severity: “warning”, - }, - ], - }, - { - name: “Fix pagination bug”, - claimedComplete: true, - checks: [ - { - description: “Bug reproduction case confirmed”, - claimed: true, - actual: true, - severity: “critical”, - }, - { - description: “Root cause identified”, - claimed: true, - actual: true, - severity: “critical”, - }, - { - description: “Fix applied”, - claimed: true, - actual: true, - severity: “critical”, - }, - { - description: “Edge cases tested”, - claimed: true, - actual: false, // Only happy path tested - severity: “warning”, - }, - { - description: “Regression tests added”, - claimed: true, - actual: false, - severity: “critical”, - }, - ], - }, - { - name: “Refactor auth module”, - claimedComplete: true, - checks: [ - { - description: “Old code removed”, - claimed: true, - actual: false, // Old code still present - severity: “warning”, - }, - { - description: “All existing tests still pass”, - claimed: true, - actual: false, // Two tests broke - severity: “critical”, - }, - { - description: “New auth flow works”, - claimed: true, - actual: true, - severity: “critical”, - }, - { - description: “Migration guide written”, - claimed: true, - actual: false, - severity: “warning”, - }, - ], - }, -]; - -// --------------------------------------------------------------------------- -// Verification -// --------------------------------------------------------------------------- - -interface TaskVerification { - taskName: string; - claimedComplete: boolean; - actuallyComplete: boolean; - totalChecks: number; - claimedPassing: number; - actualPassing: number; - gaps: { description: string; severity: string }[]; -} - -function verifyTask(task: Task): TaskVerification { - const claimedPassing = task.checks.filter((c) => c.claimed).length; - const actualPassing = task.checks.filter((c) => c.actual).length; - const gaps = task.checks.filter((c) => c.claimed && !c.actual); - - return { - taskName: task.name, - claimedComplete: task.claimedComplete, - actuallyComplete: gaps.length === 0, - totalChecks: task.checks.length, - claimedPassing, - actualPassing, - gaps: gaps.map((g) => ({ description: g.description, severity: g.severity })), - }; -} - -// --------------------------------------------------------------------------- -// Reporting -// --------------------------------------------------------------------------- - -function pad(s: string, len: number): string { - return s.length >= len ? s : s + “ ”.repeat(len - s.length); -} - -function run(): void { - console.log(“\n” + “=”.repeat(90)); - console.log(“ VICTORY DETECTOR -- Claimed vs Actual Verification”); - console.log(“=”.repeat(90)); - - const verifications = tasks.map(verifyTask); - - for (const v of verifications) { - console.log(“\n Task: ” + v.taskName); - console.log(“ ” + “-”.repeat(70)); - console.log(` Agent claimed: ${v.claimedComplete ? "COMPLETE" : "NOT COMPLETE"}`); - console.log(` Actually is: ${v.actuallyComplete ? "COMPLETE" : "INCOMPLETE"}`); - - if (!v.actuallyComplete) { - console.log(` MISMATCH: Agent declared victory but ${v.gaps.length} check(s) failed!`); - } - - // Detailed checks for this task - const task = tasks.find((t) => t.name === v.taskName)!; - console.log(“\n Check detail:”); - const header = ` | ${pad("Check", 40)}| ${pad("Claimed", 9)}| ${pad("Actual", 9)}| ${pad("Gap?", 6)}|`; - const sep = ` |${"-".repeat(42)}|${"-".repeat(11)}|${"-".repeat(11)}|${"-".repeat(8)}|`; - console.log(header); - console.log(sep); - - for (const check of task.checks) { - const claimedLabel = check.claimed ? “PASS” : “FAIL”; - const actualLabel = check.actual ? “PASS” : “FAIL”; - const gapLabel = check.claimed && !check.actual ? “GAP” : “”; - const marker = gapLabel ? “>>” : “ ”; - console.log(`${marker}| ${pad(check.description, 40)}| ${pad(claimedLabel, 9)}| ${pad(actualLabel, 9)}| ${pad(gapLabel, 6)}|`); - } - } - - // Overall summary - console.log(“\n” + “=”.repeat(90)); - console.log(“ OVERALL SUMMARY”); - console.log(“=”.repeat(90) + “\n”); - - const sHeader = `| ${pad("Task", 28)}| ${pad("Claimed", 10)}| ${pad("Actual", 10)}| ${pad("Gaps", 6)}| ${pad("Accuracy", 10)}|`; - const sSep = `|${"-".repeat(30)}|${"-".repeat(12)}|${"-".repeat(12)}|${"-".repeat(8)}|${"-".repeat(12)}|`; - console.log(sHeader); - console.log(sSep); - - for (const v of verifications) { - const accuracy = Math.round((v.actualPassing / v.totalChecks) * 100); - const cLabel = v.claimedComplete ? “Done” : “Pending”; - const aLabel = v.actuallyComplete ? “Done” : “Incomplete”; - console.log(`| ${pad(v.taskName, 28)}| ${pad(cLabel, 10)}| ${pad(aLabel, 10)}| ${pad(String(v.gaps.length), 6)}| ${pad(accuracy + "%", 10)}|`); - } - - const totalGaps = verifications.reduce((s, v) => s + v.gaps.length, 0); - const falseVictories = verifications.filter((v) => v.claimedComplete && !v.actuallyComplete).length; - - console.log(“\n False victories: ” + falseVictories + “ of ” + verifications.length + “ tasks”); - console.log(“ Total undetected gaps: ” + totalGaps); - console.log(“\n The agent declared 'done' on every task, but verification reveals ” + totalGaps + “ unmet criteria.”); - console.log(“ Without explicit verification, premature victory declarations go unchecked.\n”); -} - -run(); diff --git a/courses/learn-harness-engineering/docs/uz/lectures/lecture-09-why-agents-declare-victory-too-early/index.md b/courses/learn-harness-engineering/docs/uz/lectures/lecture-09-why-agents-declare-victory-too-early/index.md deleted file mode 100644 index c5974c44..00000000 --- a/courses/learn-harness-engineering/docs/uz/lectures/lecture-09-why-agents-declare-victory-too-early/index.md +++ /dev/null @@ -1,145 +0,0 @@ -[English version →](../../../en/lectures/lecture-09-why-agents-declare-victory-too-early/) - -> Ushbu maʼruza uchun kod misollari: [code/](https://github.com/walkinglabs/learn-harness-engineering/blob/main/docs/en/lectures/lecture-09-why-agents-declare-victory-too-early/code/) -> Amaliy loyiha: [Loyiha 05. Agentga oʻz ishini oʻzi tekshirishiga imkon bering](./../../projects/project-05-grounded-qa-verification/index.md) - -# 9-maʼruza. Agentlarni vaqtidan oldin gʻalabani eʼlon qilishdan saqlash - -Siz agentdan “parolni tiklash” funksiyasini qoʻshishni soʻraysiz. U maʼlumotlar bazasi sxemasini oʻzgartiradi, API endpointʼini yozadi, elektron pochta andozasini (email template) qoʻshadi, unit testlarni ishga tushiradi (hammasi oʻtadi) va soʻngra sizga ishonch bilan “tugatdim” deydi. Haqiqatda ishlatib koʻrsangiz—parolni tiklash havolasi yuborilmaydi (elektron pochta xizmati sozlamalari yoʻq), maʼlumotlar bazasi migratsiyasi yarmida yiqiladi (sxema noaniqligi), va end-to-end oqimi (flow) biror marta ham ishga tushirib koʻrilmagan. - -Bu tuygʻu sizga begona boʻlmasa kerak — u imtihon qogʻozini toʻldirib chiqib, birinchi boʻlib oʻziga ishonch bilan topshiradigan, lekin baholar eʼlon qilinganda yiqiladigan talabaga oʻxshaydi. Qogʻozning toʻla boʻlishi javoblar toʻgʻri ekanligini anglatmaydi. - -Bu tasodifiy voqea emas. 2017-yildagi ICML konferensiyasida Guo va boshqalarning mashhur maqolasi shuni isbotladi: **zamonaviy neyron tarmoqlari tizimli ravishda oʻziga oʻta ishonadi (overconfident)** — modellar tomonidan eʼlon qilingan ishonch ularning haqiqiy aniqligidan sezilarli darajada yuqori. Xuddi shu narsa AI kod yozish agentlariga ham tegishli: ular oʻzlarini “tugatdim” deb his qilishadi, lekin aslida ular tugatishdan yiroqdirlar. Sizning harnessʼingiz agentning “hissiyotlari”ni tashqi, ishlashga asoslangan (execution-based) tekshiruv (verification) bilan almashtirishi kerak. - -## Sirpanchiq qiyalik (The Slippery Slope) - -Vaqtidan oldin “tugadim” deyish deyarli har doim bir xil andozada (pattern) kechadi: kod yuzaki qaraganda mantiqli koʻrinadi — sintaksis toʻgʻri, mantiq joyidadek, va statik tahlil (static analysis) aniq xatolarni koʻrsatmaydi. Lekin harness qamrovli runtime tekshiruvni majbur qilmaydi, shu sababli agent uni haqiqatda ishga tushirib koʻrishni oʻtkazib yuboradi yoki faqat qisman testlarni ishga tushiradi. U unit testlarni ishga tushiradi, lekin integratsiya testlarini (integration tests) oʻtkazib yuboradi; u testlarni ishga tushiradi, lekin qamrovni (coverage) tekshirmaydi. Oxir-oqibat, “kod yaxshi koʻrinyapti” degan dalil “funksiya (feature) tugatildi” degan xulosaga asos qilib olinadi. Va imtihon qogʻozi topshiriladi. - -Har bir bosqichda maʼlumot yoʻqoladi. Vazifa spetsifikatsiyasidan kod implementatsiyasiga, undan esa runtime xatti-harakatigacha, har bir oʻzgarish biroz ogʻish (bias) keltirib chiqarishi mumkin va har bir oʻtkazib yuborilgan tekshiruv bu maʼlumot assimetriyasini kuchaytiradi. - -## Uch qatlamli tugatish tekshiruvi (Termination Check) - -```mermaid -flowchart LR - Claim["Agent: tugatdim deydi"] --> L1["Birinchi ishga tushirish
    lint / type check"] - L1 --> L2["Keyin ishga tushirish
    testlar va startup tekshiruvlar"] - L2 --> L3["Va nihoyat
    toʻliq foydalanuvchi oqimi (flow)"] - L3 --> Done["Tugatildi deyilishi uchun uchalasidan ham oʻtishi kerak"] -``` - -```mermaid -flowchart LR - A["Kod yozilgan
    unit testlar yashil"] --> B["Lekin dastur aslida ishga tushmagan
    toʻliq oqim (flow) tekshirilmagan"] - B --> C["Config, DB, tashqi xizmat muammolari
    barchasi yashirin qolgan"] - C --> D["Shu sababli agent vaqtidan oldin gʻalabani eʼlon qiladi"] -``` - -## Asosiy tushunchalar - -- **Vaqtidan oldin “tugadim” deyish (Premature Completion Declaration)**: Agent vazifani bajarilgan deb eʼlon qiladi, lekin qanoatlantirilmagan toʻgʻrilik (correctness) shartlari hali ham mavjud. Asosiy muammo: agent faqat kod darajasidagi mahalliy ishonchga suyanadi, holbuki tizim darajasidagi toʻgʻrilik global tekshiruvni (verification) talab qiladi. -- **Ishonchni kalibrlash ogʻishi (Confidence Calibration Bias)**: Agentning vazifani yakunlagani haqidagi oʻziga boʻlgan ishonchi va uning amaldagi sifat darajasi oʻrtasidagi tizimli farq. Koʻp faylli murakkab vazifalarda bu ogʻish doim musbat — agent har doim haqiqiy natijasidan koʻra oʻziga koʻproq ishonadi. Xuddi imtihondan chiqib oʻz bahosini har doim oshirib taxmin qiladigan talaba kabi. -- **Tugatish mezonlari (Termination Criteria)**: Harnessʼda belgilangan aniq, bajariladigan (executable) baholash shartlari toʻplami. Agent ishni tugatdim deyishidan oldin barcha shartlarni qanoatlantirishi shart. “Tugatildi” degan soʻz subyektiv his-tuygʻudan obyektiv tushunchaga aylanadi. -- **Verifikatsiya-Validatsiya qoʻsh darvozasi (Verification-Validation Dual Gate)**: Birinchi verifikatsiya qatlami “kod koʻrsatilgan xatti-harakatni toʻgʻri amalga oshirdimi” deganini tekshiradi; ikkinchi validatsiya qatlami “tizim darajasidagi xatti-harakat end-to-end talablarga javob beradimi” deganini tekshiradi. Tugatilgan deb hisoblanishi uchun ikkalasi ham oʻtishi shart. -- **Runtime qayta aloqa signallari (Runtime Feedback Signals)**: Dastur ishlashi davomidagi loglar, jarayon holatlari va health checkʼlar. Bu harnessʼning sifatni obyektiv baholashi uchun asosdir. -- **Tugatishning ustuvorlik cheklovi (Completion Priority Constraint)**: Avval funksional toʻgʻrilikni (correctness) tekshiring, keyin samaradorlik (performance) ni hal qiling, va nihoyat uslubni (style) toʻgʻrilang. Asosiy funksiya tekshiruvdan oʻtmaguncha refaktoring qilish taqiqlanadi. - -## Unit testlardan oʻtish ≠ Vazifa yakunlandi - -Bu eng keng tarqalgan va eng xavfli tuzoqdir. Agent kodni yozdi, unit testlarni ishga tushirdi, hammasi yashil boʻldi va “tugatdim” dedi. Lekin unit testlarning dizayn falsafasi — tekshirilayotgan qismni izolyatsiya qilish va qaramliklarni (dependencies) mock qilish (soxtalashtirish) — aynan komponentlararo muammolarni topishga qodir emasligini keltirib chiqaradi: - -**Interfeys mos kelmasligi (Interface Mismatch)**: Render jarayonidan preload skriptiga uzatilgan fayl yoʻli nisbiy yoʻl (relative path), ammo preload skripti mutlaq yoʻl (absolute path) kutmoqda. Ularning mos unit testlari mockʼlardan foydalangan va oʻtgan. Muammo faqat end-to-end testlar paytida maʼlum boʻladi. Bu xuddi musiqiy guruhdagi har bir sozanda oʻzi uchun mukammal mashq qilishiga oʻxshaydi. Ammo hammalari birgalikda chalganida, musiqalar bir-biriga mos tushmayotganini anglab yetadilar. - -**Holatning notoʻgʻri tarqalishi (State Propagation Errors)**: Maʼlumotlar bazasi migratsiyasi jadval sxemasini oʻzgartiradi, biroq ORM kesh qatlami eski sxema boʻyicha maʼlumotlarni keshda saqlashda davom etmoqda. Unit testlar har doim mutlaqo yangi mock muhitini taqdim etadi, bu esa ushbu qatlamlararo holat nomuvofiqligini (cross-layer state inconsistency) koʻrsatib berolmaydi. - -**Muhitga bogʻliqlik (Environment Dependency)**: Kod test muhitida toʻgʻri ishlaydi (chunki u yerda hamma narsa mock qilingan), lekin konfiguratsiyadagi farqlar, tarmoq kechikishlari (network latency) yoki xizmatning ishlamay qolishi sababli haqiqiy muhitda yiqiladi. Xuddi repititsiya zalida zoʻr kuylab, sahnada esa audio uskunalaridagi muammolarga duch kelishga oʻxshaydi. - -### “Shu bahonada refaktoring ham qilib ketay” — tugatishni baholash uchun zahardir - -Claude Codeʼda shunday keng tarqalgan xulq-atvor bor: asosiy funksiya (core feature) tekshiruvdan oʻtmasidan avval u kodni refaktoring qilishni, samaradorlikni oshirishni (performance optimization) va uslubni yaxshilashni boshlaydi. Knuthʼning mashhur “Vaqtidan oldin optimallashtirish (premature optimization) — barcha yomonliklarning ildizidir” maqoli agent ssenariysida oʻzgacha ahamiyat kasb etadi — refaktoring tekshirilgan va tekshirilmagan kod oʻrtasidagi chegarani oʻzgartiradi, ehtimol, ilgari toʻgʻri ishlagan kod yoʻllarini ham buzib yuborishi mumkin. Bu xuddi matematika imtihonida insho savollarini hali yechib tugatmasdan turib, test javoblaringizni chiroyliroq yozish uchun oqqa koʻchirishga oʻxshaydi — siz nafaqat vaqtni behuda sarflaysiz, balki ularni notoʻgʻri koʻchirib qoʻyishingiz ham mumkin. - -### Oʻz-oʻzini baholashdagi tizimli ogʻish (Systematic Bias) - -Anthropic 2026-yildagi tadqiqotlarida ancha jiddiyroq xato modelini aniqladi: **agentdan oʻz ishini oʻzi baholashini soʻrashganida, u har doim oʻta ijobiy baho beradi — garchi bu holatni inson kuzatuvchisi yaqqol past sifatda qilingan deb hisoblasa ham.** Bu oʻquvchidan oʻz imtihon qogʻozini oʻzi tekshirishini soʻrashga oʻxshaydi — ular oʻz javoblariga doim alohida yumshoqlik bilan yondashadilar. - -Bu muammo ayniqsa subyektiv vazifalarda (masalan, dizayn estetikasi) jiddiy namoyon boʻladi — “ushbu layout qanchalik chiroyli”ligi baholanishi kerak boʻlganda, agent oʻz ishini ijobiy tomonga ogʻdirib baholaydi. Hatto tasdiqlash mumkin boʻlgan aniq natijali vazifalarda ham, agentning samaradorligi oʻziga nisbatan xolisona boʻlmagan baholash tufayli pasayadi. - -Buning yechimi agentni “yanada obyektiv qilish” emas — kod yozayotgan model bilan tekshirayotgan model bitta boʻlsa, u doimo oʻziga yon bosishga moyil boʻladi. **Yechim “bajaruvchi” (worker) va “tekshiruvchi”ni (checker) bir-biridan ajratishdir.** Xuddi oʻquvchi oʻz imtihonini oʻzi tekshirmasligi kabi — sizga mustaqil tekshiruvchi kerak. - -Mustaqil baholovchi (evaluator) agentni aynan “injiq” (picky) boʻlishga sozlash, kod yaratuvchi agentga oʻzini-oʻzi baholatishdan koʻra ancha samaralidir. Anthropicʼdan tajriba maʼlumotlari: - -| Arxitektura | Runtime | Narx | Asosiy funksiyalar ishlayaptimi? | -|--------------|---------|------|------------------------| -| Bitta Agent (bare run) | 20 daq | $9 | Yoʻq (oʻyin obyektlari javob qaytarmaydi) | -| Uchta Agent (rejalashtiruvchi + yaratuvchi + tekshiruvchi) | 6 soat | $200 | Ha (oʻyin toʻliq ishlaydi) | - -Bu ayni bitta model (Opus 4.5) va bitta prompt (“2D retro oʻyin muharririni yarat”). Faqatgina harness farq qiladi — “qoʻshimcha tuzilmasiz muhit (bare environment)” dan “rejalashtiruvchi talablarni kengaytiradi → yaratuvchi birin-ketin funksiyalarni bajaradi → tekshiruvchi Playwright yordamida haqiqiy klik orqali testlarni (click testing) amalga oshiradi” formatiga oʻtilgan. - -> Manba: [Anthropic: Uzoq vaqt ishlovchi dasturlar uchun harness dizayni (Harness design for long-running application development)](https://www.anthropic.com/engineering/harness-design-long-running-apps) - -## Vaqtidan oldin “tugadim” deyishni qanday toʻxtatish mumkin - -### 1. Tugatish bahosini tashqariga chiqaring (Externalize) - -Ishning tugatilganini agent oʻzi hal qilmasligi kerak. Harness agentning oʻziga boʻlgan ishonchidan emas, balki runtime signallaridan foydalanib tugatishni alohida oʻzi validatsiya (validation) qilishi shart. Buni `CLAUDE.md` fayliga aniq qilib yozib qoʻying: - -``` -## Definition of Done (Bajarilganlik mezonlari) -- Funksiya yakunlandi (Feature complete) = "kod yozildi" degani emas, balki end-to-end tekshiruvdan oʻtdi deganidir -- Talab qilinadigan tekshiruv darajalari: - 1. Unit testlar oʻtadi - 2. Integratsiya testlari oʻtadi - 3. End-to-end oqimi (flow) tekshiruvdan oʻtadi -- Agar 1-daraja yiqilsa, 2-darajaga oʻtish mumkin emas -- Agar 2-daraja yiqilsa, 3-darajaga oʻtish mumkin emas -``` - -### 2. Uch qatlamli tugatish validatsiyasini (Termination Validation) yarating - -- **1-qatlam: Sintaksis va Statik Tahlil**. Eng arzon narxli, eng kam maʼlumot beruvchi, ammo oʻtishi shart. Bu eng bazaviy tekshiruv — boshqa narsalarga qarashimizdan oldin kod xatosiz yozilganiga ishonch hosil qilishingiz kerak. -- **2-qatlam: Runtime Xatti-harakatlarni Tekshirish**. Testlarni ishga tushirish, ilova startup checkʼlari, eng muhim (critical) marshrutlar validatsiyasi. Bu tugallanganlikning asosiy dalilidir. Shunchaki yozib qoldirishning oʻzi yetarli emas; u ishlashi kerak. -- **3-qatlam: Tizim darajasida tasdiqlash**. End-to-end testlari, integratsiya validatsiyasi, foydalanuvchi ssenariysini imitatsiya qilish. Vaqtidan oldin tugadim deyishlarga qarshi soʻnggi mudofaa chizigʻi. Shunchaki ishlashning oʻzi yetarli emas; u toʻgʻri ishlashi kerak. - -### 3. Agentlar uchun yaxshi “Qizil ruchkadagi belgilar” (Red Pen Markups) tayyorlang - -OpenAI oʻzlarining Codex amaliyotlarida juda samarali usulni oʻylab topdi: **agentlar uchun xato xabarlari (error messages) toʻgʻrilash boʻyicha yoʻriqnomalarni oʻz ichiga olishi kerak**. Erinchoq tekshiruvchi kabi shunchaki katta qizil xoch chizib qoʻymang; yaxshi oʻqituvchi boʻlib, uning yoniga “buni mana bunday oʻzgartirishingiz kerak” deb yozib qoldiring. `"Test failed"` deb emas, balki `"Test failed: POST /api/reset-password 500 qaytardi. Muhit oʻzgaruvchilarida (environment variables) elektron pochta xizmati sozlamalari (config) mavjudligini tekshiring. Andoza (template) fayli templates/reset-email.html papkasida boʻlishi kerak."` Ushbu aniq, amalda bajarilishi mumkin boʻlgan (actionable) qayta aloqa agentga oʻz xatolarini inson aralashuvisiz tuzatish imkonini beradi. - -### 4. Runtime signallarni yigʻib oling - -Samarali runtime signallarga quyidagilar kiradi: -- Dastur muvaffaqiyatli ishga tushib (startup), tayyor holatga kela oldimi? -- Muhim funksiya (feature) yoʻllari runtime vaqtida muvaffaqiyatli ishladimi? -- Maʼlumotlar bazasiga yozishlar, fayl operatsiyalari va boshqa side effectʼlar toʻgʻrimi? -- Vaqtinchalik resurslar tozalandimi? - -## Hayotiy misol - -**Vazifa**: Foydalanuvchi parolini tiklash (password reset) funksiyasini qoʻshish. Bunga maʼlumotlar bazasi amallari, elektron pochta yuborish va API endpointʼlarini oʻzgartirish kiradi. - -**Vaqtidan oldin topshirish yoʻli**: Agent maʼlumotlar bazasi sxemasini oʻzgartiradi, API endpointʼini yozadi, elektron pochta andozasini qoʻshadi, unit testlarni ishga tushiradi (hammasi oʻtadi) va ishni tugatganini eʼlon qiladi. Imtihon qogʻozi toʻliq toʻldirilgan. - -**Aslida qayerdan ball olib tashlangan (Point deductions)**: (1) End-to-end oqimi sinovdan oʻtmagan — haqiqiy havolani yuborish va parolni tiklash ishlashi tasdiqlanmagan. (2) Maʼlumotlar bazasi migratsiyasi yarmi ishlagandan soʻng yiqilib, sxema nomuvofiqligini keltirib chiqargan. (3) Target muhitda elektron pochta xizmati sozlamalari (config) kiritilmagan. - -**Harness aralashuvi**: Tugatish validatsiyasi (Termination validation) ishga tushdi — (1) Tiklash (reset) endpointʼiga kirish mumkinligini tekshirish uchun toʻliq ilovani ishga tushirish; (2) Toʻliq tiklash oqimini (reset flow) bajarish; (3) Maʼlumotlar bazasi holati toʻgʻriligini (consistency) tekshirish. Barcha xatoliklar shu sessiyaning oʻzida aniqlandi, bu esa keyingi yuzaga keladigan tuzatish narxini 5-10 barobargacha tejadi. Haqiqiy muammolarni mustaqil baholovchi (evaluator) topdi. - -## Asosiy xulosalar - -- **Agentlar tizimli ravishda oʻziga haddan ortiq ishonadi** — ishonchni kalibrlash ogʻishi (confidence calibration bias) bu obyektiv haqiqatdir. Imtihon qogʻozini toʻldirish bu hammasini toʻgʻri yechish degani emas. -- **Tugatish bahosi tashqariga chiqarilishi kerak** — uni harness mustaqil ravishda tekshiradi; agentning “hissiyotlari”ga ishonmang. Oʻquvchilar oʻzlarining imtihon qogʻozlarini oʻzlari baholay olmaydi. -- **Uchala validatsiya qatlami ham zarur** — sintaksisdan oʻtish, xatti-harakat (behavior) oʻtishi, tizim darajasidan oʻtish, hammasi qatlam-baqatlam amalga oshishi kerak. -- **Xato xabarlari (error messages) yaxshi oʻqituvchining qizil ruchkasi belgilariga oʻxshashi kerak** — agent oʻz xatosini oʻzi tuzatishi uchun xabar ichida uni qanday tuzatish qadamlari boʻlishi kerak. -- **Asosiy funksiya (core feature) tasdiqlanmaguncha refaktoring qilish taqiqlanadi** — tugatishning ustuvorlik cheklovi (completion priority constraint) vaqtidan oldin optimallashtirishning (premature optimization) oldini olishning kalitidir. - -## Qoʻshimcha oʻqish uchun - -- [On Calibration of Modern Neural Networks - Guo et al.](https://arxiv.org/abs/1706.04599) — Zamonaviy chuqur tarmoqlarning tizimli ravishda oʻta oʻziga ishonganini isbotlovchi maqola -- [Building Effective Agents - Anthropic](https://www.anthropic.com/research/building-effective-agents) — Tugatish bahosi uchun runtime dalillarining muhimligi -- [Harness Engineering - OpenAI](https://openai.com/index/harness-engineering/) — Vaqtidan oldin “tugadim” deyish agentlarning asosiy muvaffaqiyatsizlik turlaridan biridir -- [The Art of Software Testing - Myers](https://www.goodreads.com/book/show/137543.The_Art_of_Software_Testing) — Testlash metodlarining iyerarxiyasi va samaradorligi haqida klassik manba - -## Mashqlar - -1. **Tugatish validatsiyasi funksiyasining (Termination Validation Function) dizayni**: Maʼlumotlar bazasi migratsiyasi va API oʻzgartirishni oʻz ichiga olgan vazifa uchun toʻliq tugatish validatsiyasini loyihalang. Talab qilinadigan runtime signallarni va har bir signal uchun oʻtish/yiqilish shartlarini yozib chiqing. Uni haqiqiy vazifada ishlating va qanday yashirin muammolarni topganini yozib boring. - -2. **Kalibrlash ogʻishini oʻlchash**: 10 xil kod yozish vazifasini tanlang, va agentning oʻz ishini tugatganligi haqidagi ishonch darajasini ishning haqiqiy sifati bilan solishtirib yozib boring. Ogʻish (bias) qiymatini hisoblang va uning vazifa murakkabligi bilan bogʻliqligini tahlil qiling. - -3. **Koʻp qatlamli mudofaa tajribasi**: Bitta vazifalar roʻyxatida uchta konfiguratsiyani ishga tushiring — (a) faqat statik tahlil (static analysis), (b) unit testlarni qoʻshish, (c) toʻliq uch qatlamli validatsiya. Vaqtidan oldin “tugatdim” deyilish ulushini va tutilmagan (uncaught) defektlar sonini solishtiring. diff --git a/courses/learn-harness-engineering/docs/uz/lectures/lecture-10-why-end-to-end-testing-changes-results/code/architecture-rules.md b/courses/learn-harness-engineering/docs/uz/lectures/lecture-10-why-end-to-end-testing-changes-results/code/architecture-rules.md deleted file mode 100644 index 886fe7d7..00000000 --- a/courses/learn-harness-engineering/docs/uz/lectures/lecture-10-why-end-to-end-testing-changes-results/code/architecture-rules.md +++ /dev/null @@ -1,6 +0,0 @@ -# Electron Arxitektura Qoidalari - -- Renderer kodi bevosita fayl tizimiga kira olmaydi. -- Preload — bu renderer va Electron main oʻrtasidagi yagona koʻprik. -- Qidirish va indekslash mantiqlari (logic) UI komponentlarida emas, balki xizmatlar (service) modullarida joylashadi. -- Logging (log yozish) strukturaviy boʻlishi va xizmat chegaralaridan (service boundaries) uzatilishi kerak. diff --git a/courses/learn-harness-engineering/docs/uz/lectures/lecture-10-why-end-to-end-testing-changes-results/code/e2e-runner.ts b/courses/learn-harness-engineering/docs/uz/lectures/lecture-10-why-end-to-end-testing-changes-results/code/e2e-runner.ts deleted file mode 100644 index d35d8dcd..00000000 --- a/courses/learn-harness-engineering/docs/uz/lectures/lecture-10-why-end-to-end-testing-changes-results/code/e2e-runner.ts +++ /dev/null @@ -1,253 +0,0 @@ -/** - * e2e-runner.ts - * - * A minimal E2E test harness. Defines test cases as user action sequences - * (import doc -> index -> ask question -> verify citation). Simulates - * running them and shows the difference between “unit tests pass” and - * “full pipeline works”. - * - * Run: npx tsx docs/lectures/lecture-10-why-end-to-end-testing-changes-results/code/e2e-runner.ts - */ - -// --------------------------------------------------------------------------- -// Types -// --------------------------------------------------------------------------- - -interface PipelineStep { - name: string; - unitTestPasses: boolean; - // Simulated actual behavior in the pipeline - actualBehavior: “works” | “fails” | “partial”; - failureReason?: string; -} - -interface TestCase { - name: string; - steps: PipelineStep[]; -} - -interface TestResult { - testCase: string; - unitTestsPassed: number; - unitTestsTotal: number; - unitTestResult: “PASS” | “FAIL”; - e2eResult: “PASS” | “FAIL”; - e2eFailureStep?: string; - e2eFailureReason?: string; -} - -// --------------------------------------------------------------------------- -// Test cases -- realistic scenarios where unit tests pass but E2E fails -// --------------------------------------------------------------------------- - -const testCases: TestCase[] = [ - { - name: “Import document and ask question”, - steps: [ - { - name: “Parse uploaded document”, - unitTestPasses: true, - actualBehavior: “works”, - }, - { - name: “Store document chunks”, - unitTestPasses: true, - actualBehavior: “works”, - }, - { - name: “Index chunks for retrieval”, - unitTestPasses: true, - actualBehavior: “partial”, // Indexes but with wrong embedding dimensions - failureReason: “Embedding dimension mismatch between indexer and retriever”, - }, - { - name: “Retrieve relevant chunks”, - unitTestPasses: true, // Unit test uses mock data with correct dimensions - actualBehavior: “fails”, - failureReason: “Empty results due to dimension mismatch from previous step”, - }, - { - name: “Generate answer with citations”, - unitTestPasses: true, // Unit test provides pre-retrieved chunks - actualBehavior: “fails”, - failureReason: “No chunks retrieved, so answer has no citations”, - }, - ], - }, - { - name: “Delete document and verify removal”, - steps: [ - { - name: “Find document by ID”, - unitTestPasses: true, - actualBehavior: “works”, - }, - { - name: “Delete document record”, - unitTestPasses: true, - actualBehavior: “works”, - }, - { - name: “Remove indexed chunks”, - unitTestPasses: true, - actualBehavior: “fails”, // Orphaned chunks remain in the index - failureReason: “Index cleanup query timed out, chunks remain orphaned”, - }, - { - name: “Verify document not in search results”, - unitTestPasses: true, // Unit test mocks the search - actualBehavior: “fails”, - failureReason: “Orphaned chunks from previous step still appear in results”, - }, - ], - }, - { - name: “Multi-user concurrent access”, - steps: [ - { - name: “User A imports document”, - unitTestPasses: true, - actualBehavior: “works”, - }, - { - name: “User B imports document”, - unitTestPasses: true, - actualBehavior: “works”, - }, - { - name: “User A queries their document”, - unitTestPasses: true, - actualBehavior: “partial”, // Cross-contamination of results - failureReason: “No user-scoping on retrieval, returns chunks from User Bʼs doc”, - }, - { - name: “Verify only User Aʼs results returned”, - unitTestPasses: true, - actualBehavior: “fails”, - failureReason: “Results include documents from other users”, - }, - ], - }, -]; - -// --------------------------------------------------------------------------- -// Run tests -// --------------------------------------------------------------------------- - -function runUnitTests(tc: TestCase): { passed: number; total: number } { - const passed = tc.steps.filter((s) => s.unitTestPasses).length; - return { passed, total: tc.steps.length }; -} - -function runE2ETest(tc: TestCase): { pass: boolean; failureStep?: string; failureReason?: string } { - // Pipeline: if any step actually fails, the whole E2E fails - for (const step of tc.steps) { - if (step.actualBehavior === “fails”) { - return { - pass: false, - failureStep: step.name, - failureReason: step.failureReason ?? “Unknown failure”, - }; - } - // “partial” means the step technically completes but creates problems downstream - // We let it continue but track it - } - // Check if any step was “partial” (which may cause downstream issues) - const partialSteps = tc.steps.filter((s) => s.actualBehavior === “partial”); - if (partialSteps.length > 0) { - // The partial steps may or may not cause overall failure - // In our simulation, partial steps always lead to failure downstream - // unless thereʼs an explicit “fails” step that already caught it - // This case means all steps were either “works” or “partial” - return { - pass: false, - failureStep: partialSteps[partialSteps.length - 1].name, - failureReason: partialSteps[partialSteps.length - 1].failureReason ?? “Partial completion”, - }; - } - - return { pass: true }; -} - -// --------------------------------------------------------------------------- -// Reporting -// --------------------------------------------------------------------------- - -function pad(s: string, len: number): string { - return s.length >= len ? s : s + “ ”.repeat(len - s.length); -} - -function run(): void { - console.log(“\n” + “=”.repeat(95)); - console.log(“ E2E TEST RUNNER -- Unit Tests vs Full Pipeline”); - console.log(“=”.repeat(95)); - - const results: TestResult[] = testCases.map((tc) => { - const unit = runUnitTests(tc); - const e2e = runE2ETest(tc); - - return { - testCase: tc.name, - unitTestsPassed: unit.passed, - unitTestsTotal: unit.total, - unitTestResult: unit.passed === unit.total ? “PASS” : “FAIL”, - e2eResult: e2e.pass ? “PASS” : “FAIL”, - e2eFailureStep: e2e.failureStep, - e2eFailureReason: e2e.failureReason, - }; - }); - - // Per-test-case detail - for (let i = 0; i < testCases.length; i++) { - const tc = testCases[i]; - const r = results[i]; - - console.log(“\n Test Case: ” + tc.name); - console.log(“ ” + “-”.repeat(70)); - - const stepHeader = ` | ${pad("Step", 40)}| ${pad("Unit Test", 11)}| ${pad("E2E Actual", 12)}|`; - const stepSep = ` |${"-".repeat(42)}|${"-".repeat(13)}|${"-".repeat(14)}|`; - console.log(stepHeader); - console.log(stepSep); - - for (const step of tc.steps) { - const utLabel = step.unitTestPasses ? “PASS” : “FAIL”; - let e2eLabel: string; - if (step.actualBehavior === “works”) e2eLabel = “PASS”; - else if (step.actualBehavior === “partial”) e2eLabel = “PARTIAL*”; - else e2eLabel = “FAIL”; - - const marker = step.actualBehavior !== “works” ? “>>” : “ ”; - console.log(`${marker}| ${pad(step.name, 40)}| ${pad(utLabel, 11)}| ${pad(e2eLabel, 12)}|`); - } - - if (r.e2eFailureStep) { - console.log(`\n E2E Failure at: ${r.e2eFailureStep}`); - console.log(` Reason: ${r.e2eFailureReason}`); - } - } - - // Summary comparison - console.log(“\n” + “=”.repeat(95)); - console.log(“ COMPARISON: Unit Tests vs E2E Tests”); - console.log(“=”.repeat(95) + “\n”); - - const header = `| ${pad("Test Case", 35)}| ${pad("Unit Tests", 15)}| ${pad("E2E Result", 12)}| Discrepancy`; - const sep = `|${"-".repeat(37)}|${"-".repeat(17)}|${"-".repeat(14)}|${"-".repeat(30)}`; - console.log(header); - console.log(sep); - - for (const r of results) { - const utLabel = `${r.unitTestsPassed}/${r.unitTestsTotal} ${r.unitTestResult}`; - const discrepancy = r.unitTestResult === “PASS” && r.e2eResult === “FAIL”; - const discLabel = discrepancy ? “UNIT PASS BUT E2E FAIL” : “Consistent”; - - console.log(`| ${pad(r.testCase, 35)}| ${pad(utLabel, 15)}| ${pad(r.e2eResult, 12)}| ${discLabel}`); - } - - const falseConfidence = results.filter((r) => r.unitTestResult === “PASS” && r.e2eResult === “FAIL”).length; - console.log(“\n False confidence count: ” + falseConfidence + “ of ” + results.length); - console.log(“ Unit tests pass but E2E reveals integration failures that units cannot catch.\n”); -} - -run(); diff --git a/courses/learn-harness-engineering/docs/uz/lectures/lecture-10-why-end-to-end-testing-changes-results/code/index.md b/courses/learn-harness-engineering/docs/uz/lectures/lecture-10-why-end-to-end-testing-changes-results/code/index.md deleted file mode 100644 index 20869faf..00000000 --- a/courses/learn-harness-engineering/docs/uz/lectures/lecture-10-why-end-to-end-testing-changes-results/code/index.md +++ /dev/null @@ -1,8 +0,0 @@ -# 10-maʼruza uchun kod - -Ushbu jilddan quyidagilarga misol sifatida foydalaning: - -- arxitektura cheklovlari -- strukturaviy testlar -- did (taste) qatʼiy qoidalari (invariants) -- toʻgʻrilashga qaratilgan lint xabarlari diff --git a/courses/learn-harness-engineering/docs/uz/lectures/lecture-10-why-end-to-end-testing-changes-results/code/review-feedback-to-rule.md b/courses/learn-harness-engineering/docs/uz/lectures/lecture-10-why-end-to-end-testing-changes-results/code/review-feedback-to-rule.md deleted file mode 100644 index d52365eb..00000000 --- a/courses/learn-harness-engineering/docs/uz/lectures/lecture-10-why-end-to-end-testing-changes-results/code/review-feedback-to-rule.md +++ /dev/null @@ -1,10 +0,0 @@ -# Misol: Tekshiruv (Review) Fikr-mulohazalarini Qoidaga Aylantirish - -Takroriy tekshiruv izohi: - -> Rendererʼdan bevosita fayl tizimi funksiyalarini (filesystem utilities) chaqirmang. Preload koʻprigidan foydalaning. - -Kiritilgan (Promoted) harness qoidasi: - -- renderer kodida `fs` dan foydalanishni oldini oluvchi lint yoki import qoidasini qoʻshish -- preload chegarasini (boundary) tushuntiruvchi toʻgʻrilash (remediation) matnini qoʻshish diff --git a/courses/learn-harness-engineering/docs/uz/lectures/lecture-10-why-end-to-end-testing-changes-results/index.md b/courses/learn-harness-engineering/docs/uz/lectures/lecture-10-why-end-to-end-testing-changes-results/index.md deleted file mode 100644 index 80057721..00000000 --- a/courses/learn-harness-engineering/docs/uz/lectures/lecture-10-why-end-to-end-testing-changes-results/index.md +++ /dev/null @@ -1,158 +0,0 @@ -[English version →](../../../en/lectures/lecture-10-why-end-to-end-testing-changes-results/) - -> Ushbu maʼruza uchun kod misollari: [code/](https://github.com/walkinglabs/learn-harness-engineering/blob/main/docs/en/lectures/lecture-10-why-end-to-end-testing-changes-results/code/) -> Amaliy loyiha: [Loyiha 05. Agentga oʻz ishini oʻzi tekshirishiga imkon bering](./../../projects/project-05-grounded-qa-verification/index.md) - -# 10-maʼruza. Faqatgina End-to-End testlash chinakam tekshiruvdir - -Siz agentdan Electron ilovasiga faylni eksport qilish funksiyasini qoʻshishni soʻraysiz. U render process komponentini, preload skriptini va xizmat qatlami mantigʻini yozadi. Har bir komponent uchun unit testlar ajoyib tarzda oʻtadi. Agent “Tugatildi” deydi. Export tugmasini bosganingizda esa — fayl yoʻli formati notoʻgʻri, progress bar ishlamayapti va katta fayllarni eksport qilish xotira sizib chiqishiga (memory leak) sabab boʻlyapti. Beshta komponent chegarasi muammolari, va unit testlar ularning bittasini ham tutib qola olmagan. - -Bu xuddi xor (choir) repetitsiyasiga oʻxshaydi — har bir ovoz alohida aytganda mukammal eshitiladi, lekin hammalari birgalikda kuylashganda, sopranolar baslardan yarim takt tezlashib ketadi, joʻr ovoz esa asosiy ohangdan yarim tonda farq qiladi. Har bir qism oʻz-oʻzidan “toʻgʻri”, lekin umumiy ovoz ohangsiz. - -Googleʼning Testlash piramidasi (Testing Pyramid) bizga shuni aytadi: juda koʻp sonli unit testlar poydevor hisoblanadi, biroq faqat ulargagina tayanilsa, siz muntazam ravishda komponentlarning oʻzaro ishlashi muammolarini koʻzdan qochirasiz. AI kod yozish agentlarida esa bu muammo yana ham jiddiyroq — agentlar asosan eng tez oʻtadigan testlarni ishga tushirishga va shundan keyin ishni tugatdim deyishga moyil. **Tizim darajasidagi nuqsonlar yoʻqligini faqat end-to-end testlar orqali isbotlash mumkin.** - -## Unit testlashning koʻrinmas nuqtalari (Blind Spots) - -Unit testlarning asosiy dizayn falsafasi bu izolyatsiyadir — qaramliklarni (dependencies) mock qilish (soxtalashtirish) va faqatgina bitta qismga qaratilishdir. Bu falsafa unit testlarni tez va aniq ishlaydigan qilsa-da, tizimli koʻrinmas nuqtalarni keltirib chiqaradi. Bu xor aʼzolarining faqat oʻz partiyalarini quloqchin taqib (naushnikda) repetitsiya qilishiga oʻxshaydi — ularga hamma narsa toʻgʻridek tuyuladi, ammo ular birlashgandagina asl muammolar namoyon boʻladi: - -**Interfeys mos kelmasligi (Interface Mismatch)**: Render process orqali preload skriptiga uzatilgan fayl yoʻli (file path) bu nisbiy yoʻl (relative path), ammo preload skripti mutlaq yoʻl (absolute path) kutmoqda. Ularning mos unit testlari mockʼlardan foydalangan va muammosiz oʻtgan. Muammo faqat end-to-end oqimi (flow) ishga tushirilganda aniqlanadi — xuddi ikki ovoz partiyasi oʻz-oʻzicha toʻgʻri mashq qilayotgandek, sahnaga chiqqanida biri 4/4 taktda, ikkinchisi 3/4 taktda aytayotganini tushungani kabi. - -**Holatning notoʻgʻri tarqalishi (State Propagation Errors)**: Maʼlumotlar bazasi migratsiyasi jadval sxemasini oʻzgartiradi, biroq ORM kesh qatlami eski sxema boʻyicha maʼlumotlarni keshda saqlashda davom etmoqda. Unit testlar har doim mutlaqo yangi mock muhitini taqdim etadi, bu esa ushbu qatlamlararo holat nomuvofiqligini (cross-layer state inconsistency) koʻrsatib berolmaydi. Bu xuddi qoʻshiq matni oʻzgartirilsa-da, kimningdir hali ham eski versiyada kuylashida davom etayotganiga oʻxshaydi. - -**Resurs yashash davridagi muammolar (Resource Lifecycle Issues)**: Fayl descriptorʼlari, bazaga ulanishlar (database connections) va tarmoq socketʼlarini egallash va boʻshatish bir qancha komponentlarga taʼsir oʻtkazishi mumkin. Unit testlar har bir test uchun mustaqil resurslarni yaratib, soʻng ularni yakunlaydi; shuning uchun resurslar ustidagi ziddiyatlarni yoki ochiq qolib ketish holatlarini (leaks) koʻrsata olmaydi. Xuddi xorda hamma mikrofonni navbati bilan ishlatsa-da, lekin hammalari birga sahnaga chiqqanida hammaga mikrofon yetmay qolishiga oʻxshaydi. - -**Muhitga bogʻliqlik (Environment Dependency)**: Kod test muhitida toʻgʻri ishlaydi (chunki u yerda hamma narsa mock qilingan), lekin konfiguratsiyadagi farqlar, tarmoq kechikishlari (network latency) yoki xizmatning ishlamay qolishi sababli haqiqiy muhitda yiqiladi. Xuddi repetitsiya zalida zoʻr kuylab, ochiq havodagi festivalda esa ovoz qaytishi (audio feedback) va shamol xalaqit bergandek. - -## End-to-end testlash nafaqat natijalarni, balki xulq-atvorni ham oʻzgartiradi - -Koʻpchilik tushunib yetmaydigan bir holat bor: qachonki agent oʻz ishining yakunida end-to-end test orqali tekshirilishini bilsa, uning kod yozish xatti-harakati oʻzgaradi. - -1. **Komponentlarning oʻzaro ishlashini hisobga olish**: Kod yozayotganda u faqat bitta funksiyani koʻzlamay, “bu interfeys yuqori oqim (upstream) bilan qanday ulanadi” degan fikrni ham eʼtiborga oladi. Xuddi hammangiz baribir birgalikda kuylashingizni bilsangiz, mashgʻulot davomida boshqa ovoz partiyalariga ham eʼtibor berasiz. -2. **Arxitektura chegaralariga rioya qilish**: Arxitektura boʻyicha cheklovlari mavjud tizimlarda, end-to-end testlash agentni ushbu chegaralarni hurmat qilishiga majbur qiladi. Notadagi “kreshendo shu yerda boʻlsin” belgisi kabi, unga qatʼiy amal qilishingiz shart. -3. **Xatoliklar bilan ishlash (Error Paths)**: End-to-end testlar odatda xatolik ssenariylarini ham oʻz ichiga oladi, shuning sababdan agent istisnolar (exceptions) bilan qanday ishlashni inobatga olishiga toʻgʻri keladi. Xuddi repititsiya paytida “agar mikrofon oʻchib qolsa nima boʻladi” degan holatni sinab koʻrgandek, bu holatda nima qilish kerakligini bilasiz. - -## Testlash piramidasi (Testing Pyramid) va tekshiruv qayta aloqasini kuchaytirish (Review Feedback Promotion) - -```mermaid -flowchart TB - subgraph Unit["Unit testlar faqat ajratilgan qismlarni tekshiradi"] - U1["Renderer testlari"] - U2["Preload testlari"] - U3["Xizmat (Service) testlari"] - end - - subgraph E2E["E2E haqiqiy tizim boʻylab ishlaydi"] - R["Renderer tugmasini bosish"] --> P["Preload koʻprigi"] - P --> S["Xizmat (Service) qatlami"] - S --> F["Fayl tizimi / Operatsion Tizim"] - F --> Result["Haqiqatda eksport qilingan fayl"] - end -``` - -```mermaid -flowchart LR - Review["Tekshiruv qayta aloqasi:
    renderer toʻgʻridan-toʻgʻri fs ni chaqirmasligi kerak"] --> Rule["Fs toʻgʻridan-toʻgʻri chaqirilganini tekshirishni qoʻshish"] - Rule --> Message["Agentga xato xabarida fayl ruxsatini
    preloadʼga oʻtkazishni aytish"] - Message --> Harness["Ushbu tekshiruvni harnessʼga qoʻshish"] - Harness --> Stronger["Keyingi safar darhol yiqiladi va xatoni ushlaydi"] -``` - -Codex muhandislik amaliyotlarida OpenAI alohida taʼkidlaydi: **agentlar uchun yozilgan xato xabarlari (error messages) toʻgʻrilash yoʻriqnomalarini oʻz ichiga olishi shart.** Faqat `"Renderer ichida fayl tizimiga toʻgʻridan-toʻgʻri murojaat bor"` demang; uning oʻrniga `"Renderer ichida fayl tizimiga toʻgʻridan-toʻgʻri murojaat bor. Barcha fayl amallari preload koʻprigi orqali ishlashi shart. Buni preload/file-ops.ts ga oʻtkazing va window.api orqali chaqiring."` deb yozing. Bu arxitektura qoidalarini avtomatik tuzatish sikliga (auto-correction loop) aylantiradi. Xuddi xor dirijyori shunchaki “notoʻgʻri aytdingiz” deyish oʻrniga, “siz bu yerda yarim takt oldinga ketib qoldingiz, altning ritmini eshiting va 32-taktda birga kiring” degani kabi. - -## Asosiy tushunchalar - -- **Komponent chegarasi nuqsonlari (Component Boundary Defects)**: A va B komponentlari oʻz unit testlaridan oʻtadi, lekin ularning birgalikdagi ishi notoʻgʻri natija beradi. Bu end-to-end testlash eng yaxshi tutadigan turdagi muammodir — xuddi har bir partiyasi alohida toʻgʻri, ammo birgalikda ohangsiz xor partiyalari kabi. -- **Test yetarlilik gradienti (Testing Adequacy Gradient)**: Unit testlari ushlagan muammolar <= Integratsiya testlari ushlagan muammolar <= End-to-end testlari ushlagan muammolar. Har bir qatlam kashf etish imkoniyatini oshiradi. -- **Arxitektura chegaralarini majburiy qoʻllash qoidalari (Architectural Boundary Enforcement Rules)**: Arxitektura hujjatlaridagi qoidalarni (“render process fayl tizimiga bevosita murojaat qilolmaydi” kabi) avtomatik ishlaydigan va ijro etiladigan testlarga oʻtkazish. “Qogʻozda yozilgan”idan “CI tizimida ishga tushiriladigani”gacha. -- **Tekshiruv qayta aloqasini kuchaytirish (Review Feedback Promotion)**: Takroriy kod tekshiruv sharhlarini avtomatlashtirilgan testlarga aylantirish. Har gal takroriy muammo paydo boʻlganda qoida qoʻshasiz, shu tariqa harness borgan sari mustahkamlanadi. Goʻyoki xor rahbari har galgidek xatoliklarni tayyorgarlik mashqlariga aylantirib yuborsa — keyingi gal shu xato yana takrorlansa, dirijyor ogʻiz ochmasdanoq mashqning oʻzi muammoni topadi. -- **Agentga yoʻnaltirilgan xato xabarlari (Agent-Oriented Error Messages)**: Xato xabari shunchaki “nima xato” ekanini aytibgina qolmay, balki agentga qanday tuzatish kerakligini ham koʻrsatib berishi kerak. Bu har bir test yiqilishini oʻz-oʻzini toʻgʻrilaydigan jarayonga aylantiradi. - -## Buni qanday qilish kerak - -### 0. Avval Arxitektura chegaralarini belgilang, keyin E2E testlar yozing - -End-to-end testlashning old sharti shundaki — avval tizim chegaralari aniq boʻlishi kerak. Agar arxitektura chigallashgan spagettiga oʻxshasa, end-to-end test faqatgina “bu spagetti ishlashini” tasdiqlaydi, qaysi joyida xato borligini aniq aytib bermaydi. Bu oʻz partiyalari boʻyicha hatto boʻlinmagan xorga oʻxshaydi — ming marta repetitsiya qilsangiz ham foydasi yoʻq. - -OpenAI tajribasi: **agentlar orqali yaratilgan kodlarda, arxitektura cheklovlari bu kelajakda jamoa kengayganda qoʻshiladigan biror shart emas, bu loyihaning birinchi kunidanoq asos qilib qoʻyilishi kerak boʻlgan narsadir.** Bunga oddiy sabab bor — agentlar repozitoriydagi mavjud patternʼlardan, ular yomon yoki samarasiz boʻlsa ham, nusxa olishaveradi. Arxitektura chegaralari belgilab qoʻyilmaganida, agentlar har bir sessiyada koʻproq oʻzgaruvchan qoidalarni (deviations) olib kirishadi. - -OpenAI “Qatlamli Domen Arxitekturasi”ni (Layered Domain Architecture) qoʻllagan — har bir biznes domen (business domain) aniq qatlamlarga ajratiladi: Types → Config → Repo → Service → Runtime → UI. Qaramliklar faqat yuqoridan pastga (strictly forward) ketadi va qatlamlararo operatsiyalar aniq Providers interfeyslari orqali amalga oshiriladi. Boshqa qanday bogʻlanishlarga ruxsat berilmasligi va ularni lint orqali cheklab turish qatʼiy taʼminlanadi. - -Asosiy qoida shundaki: **oʻzgarmas qoidalarni taʼminlang, har bir implementationʼni mikromenejment qilmang.** Masalan, “maʼlumotlarni chegara ostonasida parsing qiling” (data is parsed at the boundary) kabi qatʼiy qoida kiriting, lekin qaysi kutubxona bilan ishlash kerakligini taʼkidlamang. Xato xabarlari (error messages) tuzatish yoʻriqnomasini kiritishi kerak — shunchaki “buzuqlik” deb aytmasdan, unga qanday toʻgʻrilash kerakligini koʻrsating. - -> Manba: [OpenAI: Harness engineering: leveraging Codex in an agent-first world](https://openai.com/index/harness-engineering/) - -### 1. Harness oʻz ichida End-to-End qatlamini olishi shart - -Oʻzingizning tasdiqlash jarayoningizda (validation flow) aniq belgilab bering: komponentlararo operatsiyalar uchun, end-to-end testlaridan muvaffaqiyatli oʻtish, ishni tugatish uchun old shart hisoblanadi. - -``` -## Tekshiruv Ierarxiyasi (Validation Hierarchy) -- 1-daraja: Unit testlar (Oʻtishi shart) -- 2-daraja: Integratsiya testlari (Oʻtishi shart) -- 3-daraja: End-to-end testlar (Komponentlararo amallar qatnashganda oʻtishi shart) -- Ixtiyoriy kerakli darajadan oʻtolmaslik = Tugatilmadi -``` - -### 2. Arxitektura qoidalarini Bajariladigan Testlarga aylantiring - -Har bir arxitektura qoidasi maxsus bir testga yoki lint qoidasiga tegishli boʻlishi kerak: - -```bash -# Render process toʻgʻridan-toʻgʻri Node.js APIʼlarni chaqirmasligini tekshirish -grep -r "require('fs')" src/renderer/ && exit 1 || echo "OK: renderer ichida fsʼga toʻgʻridan-toʻgʻri murojaat yoʻq" -``` - -### 3. Agentga yoʻnaltirilgan xato xabarlarini yarating - -Muammo sodir boʻlgandagi xabarlar (failure messages) ushbu 3 maʼlumotga ega boʻlishi shart: nima xato boʻldi, nega va uni qanday tuzatish mumkin: - -``` -ERROR: src/renderer/App.tsx:12 da 'fs' toʻgʻridan-toʻgʻri chaqirilgan (import qilingan). -WHY: Xavfsizlik yuzasidan render jarayonidan toʻgʻridan-toʻgʻri Node.js APIʼlaridan foydalanib boʻlmaydi. -FIX: Fayl operatsiyalarini src/preload/file-ops.ts fayliga koʻchiring va ularni window.api.readFile() yordamida chaqiring. -``` - -### 4. Tekshiruv qayta aloqasini kuchaytirish (Review Feedback Promotion) ni jarayonga aylantiring - -Har gal kod review (kodni tekshirish) paytida yangi agent xatosi topilsa, shunga bagʻishlangan yana bitta avtomatlashtirilgan qoida yozing. Oradan bir oy oʻtgach sizning harnessʼingiz bir oy oldingidan koʻra yaxshiroq natija koʻrsatadi. Xuddi repetitsiya jurnaliga oʻxshaydi — navbatdagi darsda eʼtibor qilish uchun xatoliklar jurnali qilinadi. Takroriy eslatish orqali xatolar yoʻqoladi va musiqa uygʻunlasha boradi. - -## Hayotiy misol - -**Vazifa**: Electron ilovasiga (app) fayl eksporti funksiyasini kiritish. Bunga render jarayoni (UI), preload fayl tizimi proksisi (filesystem proxy) va xizmat koʻrsatish (service) qatlamidagi maʼlumotlarni konversiya qilish (data transformation) kiradi. - -**Partiyalarni alohida kuylash (Unit testlar oʻtgan)**: Render komponenti testlari (oʻtdi, fayl operatsiyalari mock qilingan), preload skript testlari (oʻtdi, fayl tizimi mock qilingan), service layer testlari (oʻtdi, maʼlumotlar manbasi mock qilingan). Agent muvaffaqiyat bilan yakunlanganini eʼlon qildi. - -**Birga kuylash (End-to-End testlari ochiqlagan muammolar)**: - -| Muammo | Taʼrifi | Unit Test | E2E | -|--------|-------------|-----------|-----| -| Interfeys mos kelmasligi | Fayl yoʻli formati nomuvofiq | Oʻtkazib yuborilgan | Tutilgan | -| Holatning notoʻgʻri tarqalishi | Eksport haqidagi xabarlar IPC orqali UI ga yetkazilmayapti | Oʻtkazib yuborilgan | Tutilgan | -| Resurs yashash davridagi muammolar | Katta fayl eksportida fayl deskriptorlari (handles) boʻshatilmagan | Oʻtkazib yuborilgan | Tutilgan | -| Ruxsat berish (Permission Issue) | Yuklangan (packaged) muhitida ruxsat berish konfiguratsiyasi xatosi | Oʻtkazib yuborilgan | Tutilgan | -| Xatolar (Error Propagation) | Service qatlamidagi istisnolar UI qatlamiga yetib bormagan | Oʻtkazib yuborilgan | Tutilgan | - -Yuqoridagi 5 muammoni E2E tutilganida, unit testlarning birontasi ushlab qola olmadi. Bunga 2 soniyadan 15 soniyagacha oshgan sinov vaqti evaziga erishildi — agent workflowʼida buning hech yomon yeri yoʻq. Har bir partiya alohida qanchalik yaxshi kuylanmasin, toʻliq xor mashqi bilan tenglasholmaydi. - -## Asosiy xulosalar - -- **Unit testlar komponent chegarasi nuqsonlariga muntazam ravishda koʻr** — aynan ularning izolyatsion dizayni oʻzaro taʼsir muammolarini aniqlashga toʻsqinlik qiladi. Hammaning toʻgʻri kuylashi xor ohangsiz emas degani emas. -- **End-to-end testlash nafaqat nuqsonlarni topadi, balki agentning kod yozish xulq-atvorini ham oʻzgartiradi** — uning integratsiya va chegaralarga koʻproq eʼtibor berishiga olib keladi. -- **Arxitektura chegaralari bajariladigan testlarga oʻtkazilishi kerak** — har bir commit qilingan kodda tekshiriladigan boʻlsin. -- **Xato xabarlari agent uchun yozilishi kerak** — har biriga aniq yoʻriqnomalar (qanday qilib yechish kerakligini koʻrsatish) ham boʻlishi zarur. -- **Tekshiruv qayta aloqasini kuchaytirish (Review feedback promotion) harnessni oʻzi mukammallashib boradigan qiladi** — tutilgan muammolar doimiy himoya chizigʻiga aylanadi. - -## Qoʻshimcha oʻqish uchun - -- [How Google Tests Software - Whittaker et al.](https://www.goodreads.com/book/show/13563030-how-google-tests-software) — Testlash Piramidasining klassik va ommaviy asosi -- [Harness Engineering - OpenAI](https://openai.com/index/harness-engineering/) — Arxitektura cheklovlarini avtomatik bajarish boʻyicha muhandislik amaliyotlari -- [Chaos Engineering - Netflix (Basiri et al.)](https://ieeexplore.ieee.org/document/7466237) — Tizim bardoshliligini tekshirish uchun ataylab xatoliklarni kiritish -- [QuickCheck - Claessen & Hughes](https://www.cs.tufts.edu/~nr/cs257/archive/john-hughes/quick.pdf) — Property testing metodikasi: misol-testlash va formal verifikatsiya orasidagi yondashuv - -## Mashqlar - -1. **Komponentlararo defektlarni aniqlash**: Kamida uchta komponentni qamrab oluvchi oʻzgartirish vazifasini tanlang. Avval faqat unit testlarini ishga tushirib, natijalarni qayd eting; soʻngra end-to-end testlarini ishga tushiring. Qoʻshimcha aniqlangan har bir defekt qaysi turdagi qatlamlararo oʻzaro taʼsir muammosiga tegishliligini tahlil qiling. - -2. **Arxitektura qoidalarini avtomatlashtirish (Architectural Rule Automation)**: Loyihangizdan bitta arxitektura cheklovini tanlang va uni bajariladigan tekshiruvga (agentga moʻljallangan xato xabari bilan) aylantiring. Buni harnessʼga integratsiya qiling va baseline vazifa orqali samaradorligini tekshiring. - -3. **Tekshiruv qayta aloqasini kuchaytirish (Review Feedback Promotion)**: Oʻz kod review tarixingizdan bir nechta marta takrorlangan izoh turini toping va uni besh bosqichli jarayondan foydalanib avtomatlashtirilgan tekshiruvga aylantiring. Muammoning chastotasini kuchaytirishdan oldin va keyin taqqoslang. diff --git a/courses/learn-harness-engineering/docs/uz/lectures/lecture-11-why-observability-belongs-inside-the-harness/code/evaluator-rubric.md b/courses/learn-harness-engineering/docs/uz/lectures/lecture-11-why-observability-belongs-inside-the-harness/code/evaluator-rubric.md deleted file mode 100644 index c97a7d83..00000000 --- a/courses/learn-harness-engineering/docs/uz/lectures/lecture-11-why-observability-belongs-inside-the-harness/code/evaluator-rubric.md +++ /dev/null @@ -1,8 +0,0 @@ -# Baholovchi Rubrikasi (Evaluator Rubric) Misoli - -Har bir mezon (dimension) uchun 1-5 gacha ball qoyishdan foydalaning: - -- Asoslash (Grounding): javoblar import qilingan manbalarga aniq bogʻlanganmi? -- Iqtibos sifati: manba havolalari (source references) koʻrinib turadimi va aniqmi? -- Funksionallik: foydalanuvchi savol-javob (question-answer) jarayonini toʻliq tugata oladimi? -- Mahsulot uygʻunligi (Product coherence): ish jarayoni bir butundek tuyulyaptimi? diff --git a/courses/learn-harness-engineering/docs/uz/lectures/lecture-11-why-observability-belongs-inside-the-harness/code/index.md b/courses/learn-harness-engineering/docs/uz/lectures/lecture-11-why-observability-belongs-inside-the-harness/code/index.md deleted file mode 100644 index 10d17bf4..00000000 --- a/courses/learn-harness-engineering/docs/uz/lectures/lecture-11-why-observability-belongs-inside-the-harness/code/index.md +++ /dev/null @@ -1,8 +0,0 @@ -# 11-maʼruza uchun kod - -Ushbu jilddan quyidagilarga misol sifatida foydalaning: - -- rejalashtiruvchi (planner) natijalari -- baholovchi rubrikalar (evaluator rubrics) -- yaratuvchi/baholovchi (generator/evaluator) sikllari -- yagona agent va koʻp rolli agentlarni taqqoslash diff --git a/courses/learn-harness-engineering/docs/uz/lectures/lecture-11-why-observability-belongs-inside-the-harness/code/runtime-logger.ts b/courses/learn-harness-engineering/docs/uz/lectures/lecture-11-why-observability-belongs-inside-the-harness/code/runtime-logger.ts deleted file mode 100644 index 00f66aaa..00000000 --- a/courses/learn-harness-engineering/docs/uz/lectures/lecture-11-why-observability-belongs-inside-the-harness/code/runtime-logger.ts +++ /dev/null @@ -1,260 +0,0 @@ -/** - * runtime-logger.ts - * - * A structured logging module demo. Shows ad-hoc console.log output vs - * structured JSON log output when diagnosing a failure. Includes a seeded - * failure scenario and demonstrates how structured logs pinpoint the issue - * faster. - * - * Run: npx tsx docs/lectures/lecture-11-why-observability-belongs-inside-the-harness/code/runtime-logger.ts - */ - -// --------------------------------------------------------------------------- -// Types -// --------------------------------------------------------------------------- - -interface StructuredLogEntry { - timestamp: string; - level: “info” | “warn” | “error” | “debug”; - component: string; - action: string; - durationMs?: number; - input?: unknown; - output?: unknown; - error?: string; - correlationId: string; -} - -// --------------------------------------------------------------------------- -// Simulated pipeline with a seeded failure -// --------------------------------------------------------------------------- - -const CORRELATION_ID = “req-” + Math.random().toString(36).slice(2, 8); - -// Simulated stages of a document Q&A pipeline -interface PipelineStage { - component: string; - action: string; - durationMs: number; - success: boolean; - errorMessage?: string; - input?: unknown; - output?: unknown; -} - -function runPipeline(): PipelineStage[] { - return [ - { - component: “DocumentLoader”, - action: “parse_upload”, - durationMs: 45, - success: true, - input: { filename: “report.pdf”, size: “2.3MB” }, - output: { chunks: 47 }, - }, - { - component: “ChunkIndexer”, - action: “embed_and_store”, - durationMs: 230, - success: true, - input: { chunks: 47 }, - output: { indexed: 47, embeddingDim: 1536 }, - }, - { - component: “QueryRouter”, - action: “route_query”, - durationMs: 12, - success: true, - input: { query: “What is the revenue target?” }, - output: { routedTo: “RetrievalEngine” }, - }, - { - // SEEDED FAILURE: Retrieval returns 0 results due to dimension mismatch - component: “RetrievalEngine”, - action: “semantic_search”, - durationMs: 180, - success: false, - errorMessage: “Vector dimension mismatch: query embedding dim=768, index embedding dim=1536”, - input: { query: “What is the revenue target?”, topK: 5 }, - output: { results: 0 }, - }, - { - component: “AnswerGenerator”, - action: “generate_with_citations”, - durationMs: 1500, - success: true, // Doesnʼt crash, but produces a bad answer - input: { context: [], query: “What is the revenue target?” }, - output: { answer: “I could not find relevant information.”, citations: 0 }, - }, - ]; -} - -// --------------------------------------------------------------------------- -// Ad-hoc logging (console.log style) -// --------------------------------------------------------------------------- - -function printAdHocLog(stages: PipelineStage[]): void { - console.log(“Starting document Q&A pipeline...”); - console.log(“User uploaded report.pdf”); - - for (const stage of stages) { - if (stage.success) { - console.log(`${stage.component}: ${stage.action} done (${stage.durationMs}ms)`); - } else { - console.log(`${stage.component}: something went wrong`); - } - } - - console.log(“Pipeline finished. Answer: I could not find relevant information.”); -} - -// --------------------------------------------------------------------------- -// Structured logging (JSON) -// --------------------------------------------------------------------------- - -function printStructuredLog(stages: PipelineStage[]): StructuredLogEntry[] { - const entries: StructuredLogEntry[] = []; - - for (const stage of stages) { - entries.push({ - timestamp: new Date().toISOString(), - level: stage.success ? “info” : “error”, - component: stage.component, - action: stage.action, - durationMs: stage.durationMs, - input: stage.input, - output: stage.output, - error: stage.errorMessage, - correlationId: CORRELATION_ID, - }); - } - - return entries; -} - -// --------------------------------------------------------------------------- -// Diagnose failure from structured logs -// --------------------------------------------------------------------------- - -function diagnoseFromStructured(entries: StructuredLogEntry[]): string[] { - const diagnosis: string[] = []; - - // Find errors - const errors = entries.filter((e) => e.level === “error”); - if (errors.length > 0) { - diagnosis.push(“ERRORS FOUND:”); - for (const err of errors) { - diagnosis.push(` - ${err.component}.${err.action}: ${err.error}`); - } - } - - // Check for latency spikes - const slowStages = entries.filter((e) => (e.durationMs ?? 0) > 1000); - if (slowStages.length > 0) { - diagnosis.push(“LATENCY SPIKES:”); - for (const s of slowStages) { - diagnosis.push(` - ${s.component}.${s.action}: ${s.durationMs}ms`); - } - } - - // Check for cascading failures - const emptyOutputs = entries.filter((e) => { - const out = e.output as Record | undefined; - return out && typeof out === “object” && “results” in out && (out.results as number) === 0; - }); - if (emptyOutputs.length > 0) { - diagnosis.push(“EMPTY OUTPUTS (possible cascade):”); - for (const e of emptyOutputs) { - diagnosis.push(` - ${e.component}.${e.action}: returned 0 results`); - } - } - - return diagnosis; -} - -// --------------------------------------------------------------------------- -// Reporting -// --------------------------------------------------------------------------- - -function pad(s: string, len: number): string { - return s.length >= len ? s : s + “ ”.repeat(len - s.length); -} - -function run(): void { - const pipeline = runPipeline(); - - console.log(“\n” + “=”.repeat(90)); - console.log(“ OBSERVABILITY DEMO -- Ad-hoc vs Structured Logging”); - console.log(“=”.repeat(90)); - - // --- Ad-hoc output --- - console.log(“\n” + “-”.repeat(90)); - console.log(“ SCENARIO A: Ad-hoc console.log output”); - console.log(“-”.repeat(90) + “\n”); - printAdHocLog(pipeline); - console.log(“\n Diagnosis from ad-hoc logs: ??? Hard to tell what went wrong.”); - console.log(“ The failure message is vague: 'something went wrongʻ.”); - console.log(“ No dimensions, no input/output data, no correlation ID.”); - - // --- Structured output --- - console.log(“\n” + “-”.repeat(90)); - console.log(“ SCENARIO B: Structured JSON log output”); - console.log(“-”.repeat(90) + “\n”); - - const structuredEntries = printStructuredLog(pipeline); - - const header = `| ${pad("Timestamp", 26)}| ${pad("Level", 6)}| ${pad("Component", 20)}| ${pad("Action", 25)}| ${pad("Duration", 9)}| Error?`; - const sep = `|${"-".repeat(28)}|${"-".repeat(8)}|${"-".repeat(22)}|${"-".repeat(27)}|${"-".repeat(11)}|${"-".repeat(30)}`; - console.log(header); - console.log(sep); - - for (const entry of structuredEntries) { - const hasError = entry.error ? entry.error.slice(0, 30) : “”; - const marker = entry.level === “error” ? “>>” : “ ”; - console.log( - `${marker}| ${pad(entry.timestamp, 26)}| ${pad(entry.level, 6)}| ${pad(entry.component, 20)}| ${pad(entry.action, 25)}| ${pad(String(entry.durationMs) + "ms", 9)}| ${hasError}` - ); - } - - // --- Diagnosis --- - console.log(“\n” + “-”.repeat(90)); - console.log(“ AUTOMATED DIAGNOSIS FROM STRUCTURED LOGS”); - console.log(“-”.repeat(90) + “\n”); - - const errors = structuredEntries.filter((e) => e.level === “error”); - for (const err of errors) { - console.log(` ROOT CAUSE: ${err.component}.${err.action}`); - console.log(` Error: ${err.error}`); - console.log(` Input: ${JSON.stringify(err.input)}`); - console.log(` Output: ${JSON.stringify(err.output)}`); - console.log(` Correlation ID: ${err.correlationId}`); - } - - // Downstream impact - console.log(“\n DOWNSTREAM IMPACT:”); - const answerGen = structuredEntries.find((e) => e.component === “AnswerGenerator”); - if (answerGen) { - const out = answerGen.output as Record; - console.log(` AnswerGenerator received empty context (${JSON.stringify(answerGen.input)})`); - console.log(` Produced answer: "${out.answer}" with ${out.citations} citations`); - } - - // Comparison summary - console.log(“\n” + “=”.repeat(90)); - console.log(“ COMPARISON”); - console.log(“=”.repeat(90) + “\n”); - - const cHeader = `| ${pad("Metric", 35)}| ${pad("Ad-hoc Logs", 18)}| ${pad("Structured Logs", 18)}|`; - const cSep = `|${"-".repeat(37)}|${"-".repeat(20)}|${"-".repeat(20)}|`; - console.log(cHeader); - console.log(cSep); - console.log(`| ${pad("Root cause identifiable", 35)}| ${pad("No", 18)}| ${pad("Yes", 18)}|`); - console.log(`| ${pad("Input/output traceable", 35)}| ${pad("No", 18)}| ${pad("Yes", 18)}|`); - console.log(`| ${pad("Correlation across steps", 35)}| ${pad("No", 18)}| ${pad("Yes", 18)}|`); - console.log(`| ${pad("Machine-parseable", 35)}| ${pad("No", 18)}| ${pad("Yes", 18)}|`); - console.log(`| ${pad("Time to diagnose", 35)}| ${pad("Minutes (manual)", 18)}| ${pad("Seconds (auto)", 18)}|`); - - console.log(“\n Structured logging transforms debugging from guesswork into a deterministic lookup.\n”); -} - -run(); diff --git a/courses/learn-harness-engineering/docs/uz/lectures/lecture-11-why-observability-belongs-inside-the-harness/code/sprint-contract.md b/courses/learn-harness-engineering/docs/uz/lectures/lecture-11-why-observability-belongs-inside-the-harness/code/sprint-contract.md deleted file mode 100644 index 658694df..00000000 --- a/courses/learn-harness-engineering/docs/uz/lectures/lecture-11-why-observability-belongs-inside-the-harness/code/sprint-contract.md +++ /dev/null @@ -1,12 +0,0 @@ -# Sprint Shartnomasi Misoli - -Sprint maqsadi: - -- Asoslangan (grounded) Q&A natijalariga koʻrinadigan iqtiboslarni (citations) qoʻshish - -Tugatildi degani quyidagini anglatadi: - -- Foydalanuvchi savol soʻraydi -- Ilova javob qaytaradi -- Kamida bitta iqtibos (citation) koʻrinib turadi -- Iqtibosni bosish manba manzilini hujjat koʻrinishida (document view) ochadi diff --git a/courses/learn-harness-engineering/docs/uz/lectures/lecture-11-why-observability-belongs-inside-the-harness/index.md b/courses/learn-harness-engineering/docs/uz/lectures/lecture-11-why-observability-belongs-inside-the-harness/index.md deleted file mode 100644 index 3ef3bd96..00000000 --- a/courses/learn-harness-engineering/docs/uz/lectures/lecture-11-why-observability-belongs-inside-the-harness/index.md +++ /dev/null @@ -1,154 +0,0 @@ -[English version →](../../../en/lectures/lecture-11-why-observability-belongs-inside-the-harness/) - -> Ushbu maʼruza uchun kod misollari: [code/](https://github.com/walkinglabs/learn-harness-engineering/blob/main/docs/en/lectures/lecture-11-why-observability-belongs-inside-the-harness/code/) -> Amaliy loyiha: [Loyiha 06. Toʻliq harness (Capstone)](./../../projects/project-06-runtime-observability-and-debugging/index.md) - -# 11-maʼruza. Agent runtimeʼini kuzatuvchan qiling - -## Ushbu maʼruza qanday muammoni hal qiladi? - -Siz agentdan biror funksiyani (feature) yaratishni soʻraysiz. U 20 daqiqa ishlab, bir qancha fayllarni oʻzgartiradi va keyin sizga “tugatildi, lekin ikkita test yiqilyapti” deydi. Nega yiqilayotganini soʻrasangiz — “aniq bilmayman, balki vaqt (timing) bilan bogʻliq muammodir” deydi. U qaysi muhim yoʻllarni (critical paths) oʻzgartirganini soʻrasangiz — “kodga qarab koʻray-chi...” deb javob beradi. - -Bu agentning imkoniyati yoʻqligida emas. Bu sizning harnessʼingiz yetarli kuzatuvchanlikni (observability) taʼminlab bermaganidadir. **Kuzatuvchanliksiz, agentlar qarorlarni noaniqlik sharoitida qabul qilishadi, baholashlar (evaluations) shaxsiy taxminlarga aylanadi, va takroriy urinishlar (retries) koʻr-koʻrona sargardonlik boʻlib qoladi.** OpenAI va Anthropic ikkalasi ham ishonchlilik (reliability) ni dalillar (evidence) muammosi sifatida taʼriflaydi — harness runtime xatti-harakatini va baholash signallarini keyingi qarorga yoʻl koʻrsata oladigan shaklda ochib berishi kerak. - -## Asosiy tushunchalar - -- **Runtime kuzatuvchanligi (Runtime observability)**: Tizim darajasidagi signallar — loglar, treyslar (traces), jarayon hodisalari, health checkʼlar. “Tizim nima qildi” degan savolga javob beradi. -- **Jarayon kuzatuvchanligi (Process observability)**: Harness qarorlariga oid artefaktlarning koʻrinishi — rejalar, baholash rubrikalari (scoring rubrics), qabul qilish mezonlari. “Nega bu oʻzgarish qabul qilinishi kerak” degan savolga javob beradi. -- **Vazifa treysi (Task trace)**: Tarqatilgan tizimlardagi (distributed systems) soʻrovlarni kuzatish (request tracing) kabi, vazifaning boshlanishidan oxirigacha boʻlgan toʻliq qaror yoʻlini qayd etish. Agent qoʻygan har bir qadam oʻz konteksti bilan birga yozib olinadi. -- **Sprint shartnomasi (Sprint contract)**: Kod yozish boshlanishidan oldin kelishib olinadigan qisqa muddatli shartnoma — vazifa koʻlamini (scope), tekshirish standartlarini va istisnolarni aniq koʻrsatib beradi. Jarayon kuzatuvchanligi uchun asosiy vosita. -- **Baholovchi rubrikasi (Evaluator rubric)**: Sifatni baholashni subyektiv hukmdan, dalillarga asoslangan tuzilmali ball qoʻyishga aylantiradi. Turli xil baholovchilar bir xil ish uchun oʻxshash natijalar berishini taʼminlaydi. -- **Qatlamli kuzatuvchanlik (Layered observability)**: Tizim qatlami va jarayon qatlami kuzatuvchanligining birgalikda ishlab chiqilishi va bir-birini toʻldirishi. Runtime signallari nima qilganini tushuntirsa, jarayon artefaktlari nega qilganini (intent) tushuntiradi. - -## Qatlamli Kuzatuvchanlik - -```mermaid -flowchart LR - Contract["Avval vazifani yozib oling
    nimani oʻzgartirish / nimani oʻzgartirmaslik / oʻtish shartlari"] --> Generator["Yaratuvchi (Generator)"] - Generator --> Signals["Ishlash davomida ilova loglari, treyslarni
    va health checkʼlarni yigʻish"] - Contract --> Review["Natijani bandma-band tekshirish
    xatti-harakat / testlar / chegaralar"] - Signals --> Review - Review --> Verdict["Yiqilgan joyni koʻrsatish
    va uni qayerda toʻgʻrilashni aytish"] - Verdict --> Generator -``` - -## Nega bunday boʻladi - -### Kuzatuvchanlikning yoʻqligining haqiqiy badali - -Harnessʼda kuzatuvchanlik boʻlmasa, tizimli ravishda toʻrt turdagi muammo yuzaga keladi: - -**“Toʻgʻri” va “toʻgʻriga oʻxshaydi” ni ajrata olmaslik**: Kodni review (tekshirish) qilganda qandaydir funksiya mutlaqo toʻgʻri koʻrinadi — sintaksis toʻgʻri, mantiq toʻgʻri. Lekin runtimeʼda (ishlayotganda), maʼlum bir holatda (edge case) xatolikni tutishda muammo chiqib, aniq kirish maʼlumotlarida xato natija beradi. Faqatgina runtime treyslarigina amaldagi ishlash yoʻli kutilganidan boshqacha chiqqanini koʻrsatib bera oladi. - -**Baholash sehrgarlikka aylanadi**: Baholash rubrikasi va qabul qilish mezonlarisiz, baholovchilar (odam yoki agent) faqat ichki tasavvurlariga suyanishadi. Bitta natija turli baholovchilardan turlicha baho olishi mumkin. Sifat bahosi takrorlanmaydigan boʻlib qoladi. - -**Qayta urinishlar (Retries) koʻr-koʻrona taxminga aylanadi**: Qachonki agent biror narsa nega yiqilganini bilmasa, qayta urinish yoʻnalishi mutlaqo tasodifiy boʻladi. U notoʻgʻri yoʻnalishda qayta-qayta urinishi mumkin — xatoning asl sababini eʼtiborsiz qoldirib, unga umuman aloqasi boʻlmagan joylarni tuzatib yotadi. Har bir koʻr-koʻrona urinish token va vaqt sarflaydi. - -**Sessiyalararo maʼlumot jarligi (Session handoff information cliff)**: Chala bajarilgan ish keyingi sessiyaga uzatilsa, kuzatuvchanlikning yoʻqligi yangi sessiya tizim holatini boshidan oʻrganishga majbur boʻlishini anglatadi. Anthropicʼning uzoq davom etadigan agentlar boʻyicha kuzatuvlari shuni koʻrsatadiki, bu takroriy oʻrganish (redundant diagnosis) jami sessiya vaqtining 30-50% ini yeb qoʻyishi mumkin. - -### Claude Code ishtirokidagi hayotiy vaziyat - -Faraz qiling, harness “rejalashtiruvchi-yaratuvchi-baholovchi (planner-generator-evaluator)” uch rollik jarayondan (workflow) foydalanib, “ilovaga qorongʻu rejim (dark mode) ni qoʻshish” vazifasini bajarmoqda. - -**Kuzatuvchanliksiz**: Rejalashtiruvchi noaniq tushuntirish beradi. Yaratuvchi qorongʻu rejimni shu noaniqlikka asoslanib bajaradi, biroq u rejalashtiruvchining ichki kutganlariga toʻgʻri kelmaydi. Baholovchi oʻzining yashirin standartlariga asoslanib natijani rad etadi, lekin aynan nima xato ekanini tushuntira olmaydi. Yaratuvchi shu noaniq rad etish sabablariga qarab koʻr-koʻrona qayta urinishlar qiladi. Bu sikl 3-4 marta takrorlanadi, taxminan 45 daqiqa vaqt oladi va zoʻrgʻa qabul qilsa boʻladigan natija beradi. - -**Toʻliq kuzatuvchanlik bilan**: Rejalashtiruvchi sprint shartnomasini — qaysi komponentlar oʻzgarishi, har biri uchun tekshirish standartlari va istisnolar (masalan, bosma uchun stillar chiqarilmasin) ni aniq chiqarib beradi. Yaratuvchi xuddi shu shartnoma asosida kod yozadi. Runtime kuzatuvchanligi har bir komponentning stil yuklanishi va qoʻllanish jarayonini qayd etadi. Baholovchi baholash rubrikasidan foydalanib oʻlchamlar boʻyicha baholaydi va aniq dalillarni keltiradi. Bitta iteratsiya (aylanish) yuqori sifatli natija beradi va bor-yoʻgʻi 15 daqiqa oladi. - -3 barobar samaradorlik farqi. Yagona farq — kuzatuvchanlikdir. - -### Nega agentlar buni oʻzlari yecha olishmaydi - -Siz oʻylashingiz mumkin: “Agent shunchaki oʻzi loglarni chiqarib yozib qoldirsa boʻlmaydimi?” Muammolar quyidagicha: - -1. Agent oʻzi nimani bilmasligini bilmaydi — u oʻziga kerak boʻladigan signallarni avvaldan (proactively) bilib, yozib qoldirmaydi. -2. Log formatlari har xil — turli sessiyalar har xil log formatlaridan foydalanadi, bu tizimli tahlil qilishni imkonsiz qiladi. -3. Jarayon kuzatuvchanligini loglar bilan hal qilib boʻlmaydi — sprint shartnomalari va baholash rubrikalari harness darajasidagi yordamni talab qiluvchi strukturaviy artefaktlardir. - -## Buni qanday qilib toʻgʻri qilish kerak - -### 1. Harness ichiga Runtime signallarni yigʻishni oʻrnating - -Agent oʻzi log yozib qoldirishiga tayanmang. Harness avtomatik tarzda quyidagi signallarni yigʻib olishi kerak: - -- **Ilovaning yashash sikli (Application lifecycle)**: Ishga tushish (startup), tayyor, ishlayapti, oʻchish (shutdown) holatlari. -- **Funksiyaning ishlash yoʻli (Feature path execution)**: Muhim yoʻllarning qanday ishlagani, jumladan, kirish nuqtalari, tekshiruv joylari va chiqish joylari yozuvlari. -- **Maʼlumot oqimi (Data flow)**: Komponentlar orasida uzatilayotgan maʼlumotlar qaydi. -- **Resurslardan foydalanish (Resource utilization)**: Resurslarning gʻayritabiiy ishlatilishi (masalan, xotiraning uzluksiz oʻsishi). -- **Xatolar va istisnolar (Errors and exceptions)**: Shunchaki xato xabari emas, balki toʻliq xato konteksti. - -### 2. Sprint shartnomalarini joriy qiling - -Har bir vazifa boshlanishidan oldin, yaratuvchi (generator) va baholovchi (evaluator) (ular bir xil agentning har xil chaqiruvlari boʻlishi ham mumkin) oʻzaro shartnoma tuzishadi: - -```markdown -# Sprint shartnomasi: Qorongʻu rejimni (Dark Mode) qoʻllab-quvvatlash - -## Qamrov (Scope) -- Theme toggle komponentini oʻzgartirish -- Global CSS oʻzgaruvchilarini yangilash -- Qorongʻu rejim uchun testlar qoʻshish - -## Tekshiruv standartlari (Verification Standards) -- Har bir komponent uchun vizual regressiya (visual regression) testlari oʻtadi -- Asosiy oqim (main flow) end-to-end testlardan oʻtadi -- Stilsiz kontent koʻrinib qolmasligi (FOUC) shart - -## Istisnolar (Exclusions) -- Bosmaga chiqarish (print) stillari qoʻllab-quvvatlanmaydi -- Uchinchi tomon komponentlarining (third-party component) qorongʻu rejimlari ishlanmaydi -``` - -### 3. Baholovchi rubrikasini oʻrnating (Evaluator Rubric) - -“Bu yaxshimi yoki yoʻqmi” degan savolni aniq hisoblanadigan ballarga aylantiring: - -```markdown -# Baholash rubrikasi (Scoring Rubric) - -| Oʻlcham (Dimension) | A | B | C | D | -|-----------|---|---|---|---| -| Kodning toʻgʻriligi | Barcha testlar oʻtadi | Asosiy oqim oʻtadi | Qisman oʻtadi | Build yiqiladi | -| Arxitekturaga muvofiqlik | Toʻliq mos keladi | Kichik ogʻishlar bor | Yaqqol ogʻishlar bor | Jiddiy qoidabuzilishlar bor | -| Test qamrovi | Asosiy + kutilmagan (edge) holatlar | Faqat asosiy oqim | Faqat test skeleti | Hech qanday test yoʻq | -``` - -### 4. OpenTelemetry bilan standartlashtirish - -Har bir harness sessiyasi uchun bitta treys (trace), har bir vazifa uchun bitta span va har bir tekshirish bosqichi uchun ichki spanʼlar (sub-spans) yarating. Muhim maʼlumotlarni izohlash (annotate) uchun standart atributlardan foydalaning. Shu orqali kuzatuv maʼlumotlari odatiy vositalar (Jaeger, Zipkin) bilan birlashadi. - -## Hayotiy misol - -Rejalashtiruvchi-yaratuvchi-baholovchi (planner-generator-evaluator) jarayonidan foydalanib “qorongʻu rejim qoʻshish” vazifasini bajaruvchi harness: - -**Kuzatuvsiz (Unobservable) versiya**: 3-4 marta koʻr-koʻrona urinish, 45 daqiqa, zoʻrgʻa yaroqli natija. Baholovchi “nimadir xatodek tuyulyapti” deydi, lekin aniq qayerdaligini ayta olmaydi. Yaratuvchi notoʻgʻri yoʻnalishlarda koʻp vaqt yoʻqotadi. - -**Toʻliq kuzatiladigan (Fully observable) versiya**: -- Sprint shartnomasi qamrov, standartlar va istisnolarni aniq qilib beradi -- Runtime treyslar har bir komponent uslubi qanday yuklanishini yozib oladi -- Baholash rubrikasi oʻlchamlar boʻyicha tuzilmali baho berish imkoniyatini taqdim etadi -- Bitta aylanishda (iteration) 15 daqiqada yuqori sifatli natija yuzaga keladi - -Samaradorlik 3 barobar yaxshilanadi, sifat barqarorlashadi va baholashlar takrorlanuvchan (reproducible) boʻladi. - -## Asosiy xulosalar - -- **Kuzatuvchanlik — bu harness arxitekturasining oʻziga xos xususiyatidir** — u ish oxirida qoʻshiladigan biror funksiya (feature) emas, u boshidanoq dizaynga qoʻshilishi shart boʻlgan asosiy imkoniyatdir. -- **Ikkala kuzatuv qatlami ham zarur** — runtime signallar “nima boʻlganini” tushuntirsa, jarayon artefaktlari “nima uchun aynan shunday qilinganini” tushuntiradi. -- **Sprint shartnomalari tushunmovchilikni boshidanoq yoʻqotadi** — “yaratuvchi baholovchi koʻriboq rad etadigan narsani yasashini” oldini oladi. -- **Baholash rubrikalari bahoni takrorlanuvchan (reproducible) qiladi** — turli xil baholovchilar ayni bitta natija uchun bir xil baho berishini taʼminlaydi. -- **Kuzatuvchanliksiz sessiya vaqtining 30-50% qismi ortiqcha narsalarni takror oʻrganishga sarflanadi.** - -## Qoʻshimcha oʻqish uchun - -- [Observability Engineering - Charity Majors](https://www.honeycomb.io/blog/observability-engineering-book) — Zamonaviy kuzatuv muhandisligining nazariy va amaliy asosi -- [Dapper - Google (Sigelman et al.)](https://research.google/pubs/pub36356/) — Keng miqyosli tarqatilgan treyslar (distributed tracing) boʻyicha yutuq -- [Harness Design - Anthropic](https://www.anthropic.com/engineering/harness-design-long-running-apps) — Sprint shartnomalari va baholash rubrikalarini kiritish haqida -- [Site Reliability Engineering - Google](https://sre.google/sre-book/table-of-contents/) — Ishlab chiqarish tizimlarida (production systems) kuzatuvchanlikni tizimli qoʻllash - -## Mashqlar - -1. **Kuzatuvdagi boʻshliq tahlili (Observability Gap Analysis)**: Joriy harnessʼingizning tizim qatlami va jarayon qatlamidagi kuzatuvchanlik imkoniyatlarini tekshirib chiqing. Mavjud signallar orqali ajratib boʻlmaydigan tizim holatlarini aniqlang va yechimlar taklif qiling. - -2. **Sprint shartnomasi amaliyoti**: Haqiqiy vazifa uchun sprint shartnomasini yozing. Agentga shartnoma asosida ishlashni buyuring va shartnoma bilan, shartnomasiz qilingan ishning sifati hamda samaradorligini solishtiring. - -3. **Vazifa treysini yaratish (Task Trace Construction)**: Agentning toʻliq kod yozish vazifasidagi har bir qadamini yozib oling. OpenTelemetry semantik anʼanalari bilan izohlang (annotate). Treysdagi maʼlumot toʻsiqlarini (information bottlenecks) tahlil qiling — qaysi qadamlarda agent yetarli signalga ega emasligini aniqlang. diff --git a/courses/learn-harness-engineering/docs/uz/lectures/lecture-12-why-every-session-must-leave-a-clean-state/code/benchmark-comparison-template.md b/courses/learn-harness-engineering/docs/uz/lectures/lecture-12-why-every-session-must-leave-a-clean-state/code/benchmark-comparison-template.md deleted file mode 100644 index 3c4749a8..00000000 --- a/courses/learn-harness-engineering/docs/uz/lectures/lecture-12-why-every-session-must-leave-a-clean-state/code/benchmark-comparison-template.md +++ /dev/null @@ -1,18 +0,0 @@ -# Benchmarklarni Taqqoslash Andozasi - -A Harness: - -- tugallanish darajasi -- oʻrtacha takroriy urinishlar -- inson tekshiruvidan oldin tutilgan bugʼlar - -B Harness: - -- tugallanish darajasi -- oʻrtacha takroriy urinishlar -- inson tekshiruvidan oldin tutilgan bugʼlar - -Tahlil: - -- Qaysi harness natijani oʻzgartirdi? -- Qaysi harness natijaga erishish xarajatini oʻzgartirdi? diff --git a/courses/learn-harness-engineering/docs/uz/lectures/lecture-12-why-every-session-must-leave-a-clean-state/code/benchmark-runner.ts b/courses/learn-harness-engineering/docs/uz/lectures/lecture-12-why-every-session-must-leave-a-clean-state/code/benchmark-runner.ts deleted file mode 100644 index a1a9e642..00000000 --- a/courses/learn-harness-engineering/docs/uz/lectures/lecture-12-why-every-session-must-leave-a-clean-state/code/benchmark-runner.ts +++ /dev/null @@ -1,216 +0,0 @@ -/** - * benchmark-runner.ts - * - * Reads a benchmark task definition (JSON array of tasks with pass criteria), - * “executes” each task, records timing and pass/fail, outputs a comparison - * report showing which tasks pass and which fail. - * - * Run: npx tsx docs/lectures/lecture-12-why-every-session-must-leave-a-clean-state/code/benchmark-runner.ts - */ - -// --------------------------------------------------------------------------- -// Types -// --------------------------------------------------------------------------- - -interface BenchmarkTask { - id: string; - name: string; - category: string; - passCriteria: string[]; - expectedDurationMs: number; - // Simulated actual results - actualDurationMs: number; - actualPass: boolean; - failureReason?: string; -} - -interface BenchmarkResult { - id: string; - name: string; - category: string; - criteriaTotal: number; - criteriaPassed: number; - pass: boolean; - expectedMs: number; - actualMs: number; - durationDelta: number; - failureReason?: string; -} - -// --------------------------------------------------------------------------- -// Benchmark task definitions -// --------------------------------------------------------------------------- - -const benchmarkTasks: BenchmarkTask[] = [ - { - id: “bench-001”, - name: “Import markdown document”, - category: “Document Pipeline”, - passCriteria: [“File accepted”, “Chunks created”, “Metadata stored”], - expectedDurationMs: 100, - actualDurationMs: 95, - actualPass: true, - }, - { - id: “bench-002”, - name: “Import PDF document”, - category: “Document Pipeline”, - passCriteria: [“File accepted”, “Text extracted”, “Chunks created”, “Metadata stored”], - expectedDurationMs: 200, - actualDurationMs: 310, - actualPass: false, - failureReason: “PDF text extraction failed on page 3 -- encoding issue”, - }, - { - id: “bench-003”, - name: “Ask grounded question”, - category: “Q&A Pipeline”, - passCriteria: [“Query received”, “Relevant chunks retrieved”, “Answer generated”, “Citations present”], - expectedDurationMs: 500, - actualDurationMs: 480, - actualPass: true, - }, - { - id: “bench-004”, - name: “Ask question with no relevant docs”, - category: “Q&A Pipeline”, - passCriteria: [“Query received”, “Graceful 'no results' message”, “No hallucinated citations”], - expectedDurationMs: 400, - actualDurationMs: 420, - actualPass: false, - failureReason: “Model hallucinated a citation instead of saying 'no results'”, - }, - { - id: “bench-005”, - name: “Delete imported document”, - category: “Document Pipeline”, - passCriteria: [“Document removed from list”, “Chunks removed from index”, “No orphan data”], - expectedDurationMs: 150, - actualDurationMs: 145, - actualPass: true, - }, - { - id: “bench-006”, - name: “Concurrent user access”, - category: “Security”, - passCriteria: [“Users isolated”, “No cross-user data leak”, “Performance within SLA”], - expectedDurationMs: 300, - actualDurationMs: 850, - actualPass: false, - failureReason: “Cross-user data leak detected + response time exceeded SLA (850ms > 500ms)”, - }, - { - id: “bench-007”, - name: “Session continuity after restart”, - category: “Reliability”, - passCriteria: [“State persisted”, “Session recovers”, “No data loss”], - expectedDurationMs: 200, - actualDurationMs: 180, - actualPass: true, - }, - { - id: “bench-008”, - name: “API rate limiting”, - category: “Security”, - passCriteria: [“Rate limit enforced”, “429 response after limit”, “Legitimate traffic unaffected”], - expectedDurationMs: 100, - actualDurationMs: 100, - actualPass: true, - }, -]; - -// --------------------------------------------------------------------------- -// Execution simulation -// --------------------------------------------------------------------------- - -function executeBenchmark(tasks: BenchmarkTask[]): BenchmarkResult[] { - return tasks.map((task) => ({ - id: task.id, - name: task.name, - category: task.category, - criteriaTotal: task.passCriteria.length, - criteriaPassed: task.actualPass ? task.passCriteria.length : Math.floor(task.passCriteria.length * 0.5), - pass: task.actualPass, - expectedMs: task.expectedDurationMs, - actualMs: task.actualDurationMs, - durationDelta: task.actualDurationMs - task.expectedDurationMs, - failureReason: task.failureReason, - })); -} - -// --------------------------------------------------------------------------- -// Reporting -// --------------------------------------------------------------------------- - -function pad(s: string, len: number): string { - return s.length >= len ? s : s + “ ”.repeat(len - s.length); -} - -function run(): void { - console.log(“\n” + “=”.repeat(100)); - console.log(“ BENCHMARK RUNNER -- Task Execution Report”); - console.log(“=”.repeat(100)); - - const results = executeBenchmark(benchmarkTasks); - - // Detailed results - const header = `| ${pad("ID", 10)}| ${pad("Task", 35)}| ${pad("Category", 18)}| ${pad("Pass?", 6)}| ${pad("Criteria", 10)}| ${pad("Expected", 10)}| ${pad("Actual", 10)}| ${pad("Delta", 8)}|`; - const sep = `|${"-".repeat(12)}|${"-".repeat(37)}|${"-".repeat(20)}|${"-".repeat(8)}|${"-".repeat(12)}|${"-".repeat(12)}|${"-".repeat(12)}|${"-".repeat(10)}|`; - console.log(“\n” + header); - console.log(sep); - - for (const r of results) { - const passLabel = r.pass ? “PASS” : “FAIL”; - const criteriaLabel = `${r.criteriaPassed}/${r.criteriaTotal}`; - const deltaLabel = r.durationDelta >= 0 ? `+${r.durationDelta}ms` : `${r.durationDelta}ms`; - const marker = r.pass ? “ ” : “>>”; - - console.log( - `${marker}| ${pad(r.id, 10)}| ${pad(r.name, 35)}| ${pad(r.category, 18)}| ${pad(passLabel, 6)}| ${pad(criteriaLabel, 10)}| ${pad(r.expectedMs + "ms", 10)}| ${pad(r.actualMs + "ms", 10)}| ${pad(deltaLabel, 8)}|` - ); - } - - // Failure details - const failures = results.filter((r) => !r.pass); - if (failures.length > 0) { - console.log(“\n” + “-”.repeat(100)); - console.log(“ FAILURE DETAILS”); - console.log(“-”.repeat(100)); - for (const f of failures) { - console.log(`\n [${f.id}] ${f.name}`); - console.log(` Category: ${f.category}`); - console.log(` Criteria: ${f.criteriaPassed}/${f.criteriaTotal} passed`); - console.log(` Reason: ${f.failureReason ?? "Unknown"}`); - console.log(` Timing: Expected ${f.expectedMs}ms, actual ${f.actualMs}ms (delta: ${f.durationDelta >= 0 ? "+" : ""}${f.durationDelta}ms)`); - } - } - - // Summary - console.log(“\n” + “=”.repeat(100)); - console.log(“ SUMMARY BY CATEGORY”); - console.log(“=”.repeat(100) + “\n”); - - const categories = [...new Set(results.map((r) => r.category))]; - const catHeader = `| ${pad("Category", 20)}| ${pad("Total", 8)}| ${pad("Passed", 8)}| ${pad("Failed", 8)}| ${pad("Pass Rate", 12)}|`; - const catSep = `|${"-".repeat(22)}|${"-".repeat(10)}|${"-".repeat(10)}|${"-".repeat(10)}|${"-".repeat(14)}|`; - console.log(catHeader); - console.log(catSep); - - for (const cat of categories) { - const catResults = results.filter((r) => r.category === cat); - const catPassed = catResults.filter((r) => r.pass).length; - const catFailed = catResults.filter((r) => !r.pass).length; - const rate = Math.round((catPassed / catResults.length) * 100); - - console.log(`| ${pad(cat, 20)}| ${pad(String(catResults.length), 8)}| ${pad(String(catPassed), 8)}| ${pad(String(catFailed), 8)}| ${pad(rate + "%", 12)}|`); - } - - // Overall - const totalPassed = results.filter((r) => r.pass).length; - const totalFailed = results.filter((r) => !r.pass).length; - console.log(“\n” + “-”.repeat(100)); - console.log(` OVERALL: ${totalPassed}/${results.length} passed (${Math.round((totalPassed / results.length) * 100)}%), ${totalFailed} failures`); - console.log(“=”.repeat(100) + “\n”); -} - -run(); diff --git a/courses/learn-harness-engineering/docs/uz/lectures/lecture-12-why-every-session-must-leave-a-clean-state/code/cleanup-loop.md b/courses/learn-harness-engineering/docs/uz/lectures/lecture-12-why-every-session-must-leave-a-clean-state/code/cleanup-loop.md deleted file mode 100644 index 20fbafad..00000000 --- a/courses/learn-harness-engineering/docs/uz/lectures/lecture-12-why-every-session-must-leave-a-clean-state/code/cleanup-loop.md +++ /dev/null @@ -1,9 +0,0 @@ -# Tozalash Sikli Misoli - -Takroriy tozalash vazifalari: - -- eskirgan hujjatlarni qidirish -- strukturaviy qoidabuzilishlarni qidirish -- sifat baholarini yangilash -- maqsadli tozalash PR larini ochish -- tozalashdan keyin belgilangan benchmark boʻlagini qayta ishga tushirish diff --git a/courses/learn-harness-engineering/docs/uz/lectures/lecture-12-why-every-session-must-leave-a-clean-state/code/cleanup-scanner.ts b/courses/learn-harness-engineering/docs/uz/lectures/lecture-12-why-every-session-must-leave-a-clean-state/code/cleanup-scanner.ts deleted file mode 100644 index 2706ff84..00000000 --- a/courses/learn-harness-engineering/docs/uz/lectures/lecture-12-why-every-session-must-leave-a-clean-state/code/cleanup-scanner.ts +++ /dev/null @@ -1,336 +0,0 @@ -/** - * cleanup-scanner.ts - * - * Scans a project directory for stale artifacts, dead code, structural - * violations, and outputs a cleanup report. Helps enforce the “clean state - * at end of every session“ principle. - * - * Usage: - * npx tsx docs/lectures/lecture-12.../code/cleanup-scanner.ts [path] - * (defaults to current working directory) - * - * Run: npx tsx docs/lectures/lecture-12-why-every-session-must-leave-a-clean-state/code/cleanup-scanner.ts - */ - -import * as fs from “node:fs”; -import * as path from “node:path”; - -// --------------------------------------------------------------------------- -// Types -// --------------------------------------------------------------------------- - -interface ScanResult { - category: string; - check: string; - severity: “critical” | “warning” | “info”; - found: string[]; - description: string; -} - -// --------------------------------------------------------------------------- -// Scanner checks -// --------------------------------------------------------------------------- - -interface ScannerCheck { - category: string; - name: string; - severity: “critical” | “warning” | “info”; - description: string; - scan: (dir: string) => string[]; -} - -function createChecks(): ScannerCheck[] { - return [ - { - category: “Stale Artifacts”, - name: “Temporary files (*.tmp, *.bak, *.swp)”, - severity: “warning”, - description: “Leftover temp files from editing or processing”, - scan: (dir) => findFiles(dir, [“.tmp”, “.bak”, “.swp”, “~”]), - }, - { - category: “Stale Artifacts”, - name: “Debug/log files in source”, - severity: “warning”, - description: “Log files that should not be in the source tree”, - scan: (dir) => { - const found: string[] = []; - const logPatterns = [“.log”]; - for (const p of findFiles(dir, logPatterns)) { - // Only flag log files in source directories - if (p.includes(“/src/”) || p.includes(“/lib/”) || p.includes(“/app/”)) { - found.push(p); - } - } - return found; - }, - }, - { - category: “Dead Code”, - name: “Unused imports (placeholder detection)”, - severity: “info”, - description: “Files that may contain unused code (heuristic scan)”, - scan: (dir) => { - const found: string[] = []; - const srcDirs = [“src”, “lib”, “app”]; - for (const srcDir of srcDirs) { - const srcPath = path.join(dir, srcDir); - if (fs.existsSync(srcPath)) { - scanForPatterns(srcPath, [“TODO:”, “FIXME:”, “HACK:”, “XXX:”], found, dir); - } - } - return found; - }, - }, - { - category: “Structural Violations”, - name: “Missing .gitignore”, - severity: “warning”, - description: “No .gitignore file found -- risk of committing build artifacts”, - scan: (dir) => fs.existsSync(path.join(dir, “.gitignore”)) ? [] : [“.gitignore (MISSING)”], - }, - { - category: “Structural Violations”, - name: “Build artifacts in source”, - severity: “critical”, - description: “Compiled output (dist/, build/) mixed with source”, - scan: (dir) => { - const found: string[] = []; - // Check if dist/ or build/ exists inside src/ - const dirs = [“src/dist”, “src/build”, “lib/dist”, “lib/build”]; - for (const d of dirs) { - if (fs.existsSync(path.join(dir, d))) { - found.push(d + “/”); - } - } - return found; - }, - }, - { - category: “Structural Violations”, - name: “node_modules in source tree”, - severity: “critical”, - description: “node_modules directory found outside root (nested dependency)”, - scan: (dir) => { - const found: string[] = []; - const srcDirs = [“src”, “lib”, “app”, “test”, “tests”]; - for (const srcDir of srcDirs) { - const nmPath = path.join(dir, srcDir, “node_modules”); - if (fs.existsSync(nmPath)) { - found.push(srcDir + “/node_modules/”); - } - } - return found; - }, - }, - { - category: “Session Cleanliness”, - name: “Uncommitted changes indicator”, - severity: “info”, - description: “Checks for common artifacts of incomplete sessions”, - scan: (dir) => { - const found: string[] = []; - const indicators = [ - “WIP.md”, “IN_PROGRESS.md”, “scratch.ts”, “scratch.js”, - “debug.ts”, “test-manual.ts”, “temp.ts”, - ]; - for (const f of indicators) { - if (fs.existsSync(path.join(dir, f))) { - found.push(f); - } - } - return found; - }, - }, - { - category: “Session Cleanliness”, - name: “Empty directories”, - severity: “info”, - description: “Empty directories that may be leftover from incomplete work”, - scan: (dir) => { - const found: string[] = []; - const commonDirs = [“src”, “lib”, “app”, “test”, “tests”, “docs”]; - for (const d of commonDirs) { - const dp = path.join(dir, d); - if (fs.existsSync(dp) && fs.statSync(dp).isDirectory()) { - try { - const contents = fs.readdirSync(dp); - if (contents.length === 0) { - found.push(d + “/”); - } - } catch { - // Skip directories we canʼt read - } - } - } - return found; - }, - }, - { - category: “Configuration”, - name: “Environment files in source”, - severity: “critical”, - description: “.env files that may contain secrets”, - scan: (dir) => { - const found: string[] = []; - const envFiles = [“.env”, “.env.local”, “.env.production”, “.env.staging”]; - for (const f of envFiles) { - if (fs.existsSync(path.join(dir, f))) { - found.push(f); - } - } - return found; - }, - }, - ]; -} - -// --------------------------------------------------------------------------- -// Helper functions -// --------------------------------------------------------------------------- - -function findFiles(dir: string, extensions: string[]): string[] { - const found: string[] = []; - - function walk(current: string, depth: number): void { - if (depth > 4) return; // Limit recursion depth - try { - const entries = fs.readdirSync(current, { withFileTypes: true }); - for (const entry of entries) { - // Skip common non-source directories - if (entry.isDirectory() && [“node_modules”, “.git”, “dist”, “build”, “.next”, “coverage”].includes(entry.name)) { - continue; - } - - const fullPath = path.join(current, entry.name); - const relativePath = path.relative(dir, fullPath); - - if (entry.isDirectory()) { - walk(fullPath, depth + 1); - } else if (entry.isFile()) { - if (extensions.some((ext) => entry.name.endsWith(ext))) { - found.push(relativePath); - } - } - } - } catch { - // Skip directories we canʼt read - } - } - - walk(dir, 0); - return found; -} - -function scanForPatterns(dir: string, patterns: string[], results: string[], baseDir: string): void { - try { - const entries = fs.readdirSync(dir, { withFileTypes: true }); - for (const entry of entries) { - if (entry.isDirectory()) { - if (![“node_modules”, “.git”, “dist”, “build”].includes(entry.name)) { - scanForPatterns(path.join(dir, entry.name), patterns, results, baseDir); - } - continue; - } - - const fullPath = path.join(dir, entry.name); - if (!entry.name.endsWith(“.ts”) && !entry.name.endsWith(“.tsx”) && !entry.name.endsWith(“.js”)) { - continue; - } - - try { - const content = fs.readFileSync(fullPath, “utf-8”); - const lines = content.split(“\n”); - for (let i = 0; i < Math.min(lines.length, 50); i++) { - for (const pattern of patterns) { - if (lines[i].includes(pattern)) { - const relative = path.relative(baseDir, fullPath); - results.push(`${relative}:${i + 1} contains ${pattern}`); - } - } - } - } catch { - // Skip files we canʼt read - } - } - } catch { - // Skip directories we canʼt read - } -} - -// --------------------------------------------------------------------------- -// Reporting -// --------------------------------------------------------------------------- - -function pad(s: string, len: number): string { - return s.length >= len ? s : s + “ ”.repeat(len - s.length); -} - -function run(): void { - const targetDir = process.argv[2] ? path.resolve(process.argv[2]) : process.cwd(); - - console.log(“\n” + “=”.repeat(90)); - console.log(“ CLEANUP SCANNER -- Stale Artifact & Violation Report”); - console.log(“=”.repeat(90)); - console.log(` Scanning: ${targetDir}\n`); - - const checks = createChecks(); - const results: ScanResult[] = []; - - for (const check of checks) { - const found = check.scan(targetDir); - results.push({ - category: check.category, - check: check.name, - severity: check.severity, - found, - description: check.description, - }); - } - - // Report - const header = `| ${pad("Category", 20)}| ${pad("Check", 40)}| ${pad("Severity", 10)}| ${pad("Count", 6)}| Details`; - const sep = `|${"-".repeat(22)}|${"-".repeat(42)}|${"-".repeat(12)}|${"-".repeat(8)}|${"-".repeat(30)}`; - console.log(header); - console.log(sep); - - for (const r of results) { - const count = r.found.length; - const detail = count > 0 ? r.found.slice(0, 2).join(“, ”) : “(clean)”; - const marker = count > 0 && r.severity === “critical” ? “>>” : count > 0 ? “ * ” : “ ”; - - console.log(`${marker}| ${pad(r.category, 20)}| ${pad(r.check, 40)}| ${pad(r.severity, 10)}| ${pad(String(count), 6)}| ${detail}`); - } - - // Summary - const criticals = results.filter((r) => r.severity === “critical” && r.found.length > 0); - const warnings = results.filter((r) => r.severity === “warning” && r.found.length > 0); - const infos = results.filter((r) => r.severity === “info” && r.found.length > 0); - const clean = results.filter((r) => r.found.length === 0); - - console.log(“\n” + “=”.repeat(90)); - console.log(“ CLEANUP SUMMARY”); - console.log(“=”.repeat(90) + “\n”); - - console.log(` Critical issues: ${criticals.length}`); - console.log(` Warnings: ${warnings.length}`); - console.log(` Info items: ${infos.length}`); - console.log(` Clean checks: ${clean.length}/${results.length}`); - - if (criticals.length > 0) { - console.log(“\n ACTION REQUIRED (Critical):”); - for (const c of criticals) { - console.log(` - ${c.check}: ${c.found.length} item(s) found`); - console.log(` ${c.description}`); - } - } - - const totalIssues = results.reduce((s, r) => s + r.found.length, 0); - if (totalIssues === 0) { - console.log(“\n Project is in a clean state. No issues found.\n”); - } else { - console.log(`\n Total issues found: ${totalIssues}. Run this scanner at the end of every session to maintain clean state.\n`); - } -} - -run(); diff --git a/courses/learn-harness-engineering/docs/uz/lectures/lecture-12-why-every-session-must-leave-a-clean-state/code/index.md b/courses/learn-harness-engineering/docs/uz/lectures/lecture-12-why-every-session-must-leave-a-clean-state/code/index.md deleted file mode 100644 index c685ef14..00000000 --- a/courses/learn-harness-engineering/docs/uz/lectures/lecture-12-why-every-session-must-leave-a-clean-state/code/index.md +++ /dev/null @@ -1,8 +0,0 @@ -# 12-maʼruza uchun kod - -Ushbu jilddan quyidagilarga misol sifatida foydalaning: - -- benchmark boʻlaklari (slices) -- tozalash vazifalari -- entropiyani kamaytirish misollari -- qayta ishga tushirilishi mumkin boʻlgan harness (repeatable harness runs) diff --git a/courses/learn-harness-engineering/docs/uz/lectures/lecture-12-why-every-session-must-leave-a-clean-state/index.md b/courses/learn-harness-engineering/docs/uz/lectures/lecture-12-why-every-session-must-leave-a-clean-state/index.md deleted file mode 100644 index e56e697c..00000000 --- a/courses/learn-harness-engineering/docs/uz/lectures/lecture-12-why-every-session-must-leave-a-clean-state/index.md +++ /dev/null @@ -1,186 +0,0 @@ -[English version →](../../../en/lectures/lecture-12-why-every-session-must-leave-a-clean-state/) - -> Ushbu maʼruza uchun kod misollari: [code/](https://github.com/walkinglabs/learn-harness-engineering/blob/main/docs/en/lectures/lecture-12-why-every-session-must-leave-a-clean-state/code/) -> Amaliy loyiha: [Loyiha 06. Toʻliq harness (Capstone)](./../../projects/project-06-runtime-observability-and-debugging/index.md) - -# 12-maʼruza. Har bir sessiya oxirida toza holat topshiring - -## Ushbu maʼruza qanday muammoni hal qiladi? - -Sizning agentingiz tushdan keyin ishlaydi, 20 ta faylni oʻzgartiradi, kodni commit qiladi va sessiya tugaydi. Keyingi agent sessiyasi ish boshlaydi va birdan shularni aniqlaydi: build yiqilyapti (broken), testlar qizil (yiqilgan), vaqtinchalik debug fayllar sochilib yotibdi, funksiyalar roʻyxati yangilanmagan va ishlarning qay holatdaligi butunlay noaniq. Yangi sessiya birinchi 30 daqiqasini faqatgina “oʻtgan sessiya oʻzi nima qilgandi” degan narsani tushunishga sarflaydi. - -OpenAI ham, Anthropic ham buni ochiqchasiga aytadi: **uzoq muddatli ishonchlilik faqatgina bir martalik ishning muvaffaqiyatiga emas, balki operatsion tartib-intizomga bogʻliqdir.** Sessiya yakunidagi holat (state) sifati bevosita keyingi sessiyaning samaradorligini belgilaydi. Buni Gitʼning eng yaxshi amaliyotlari kabi tasavvur qiling — har bir commit yaxlit, kompilyatsiya qilinadigan oʻzgarish boʻlishi kerak, chala yozilgan kodlar yigʻindisi emas. - -## Asosiy tushunchalar - -- **Toza holat (Clean state)**: Tizim sessiya oxirida beshta shartni qanoatlantirishi kerak — build oʻtadi, testlar oʻtadi, bajarilgan jarayon qayd etilgan, eskirgan artefaktlar qolmagan, ishga tushirish (startup) yoʻli ochiq. Ulardan birortasi boʻlmasa, sessiya “tugatildi” hisoblanmaydi. -- **Sessiya yaxlitligi (Session integrity)**: Maʼlumotlar bazasi tranzaksiyalariga oʻxshaydi — yo oʻzgarishni toʻliq saqlang (commit) va toza holat qoldiring, yoki oxirgi muvofiq holatga (consistent state) qayting (roll back). Boshqa chorasi yoʻq. -- **Sifat hujjati (Quality document)**: Har bir modulning sifat reytingini doimiy ravishda qayd etib boruvchi faol artefakt. Bu bir martalik baholash emas, balki kod bazasining (codebase) vaqt oʻtishi bilan kuchayib borayotgani yoki zaiflashayotganini koʻrsatuvchi trekkerdir. -- **Tozalash sikli (Cleanup loop)**: Kod bazasidagi entropiyani tizimli ravishda kamaytirishga qaratilgan muntazam texnik xizmat koʻrsatish sessiyasi. Favqulodda yamoq qoʻyish emas, balki odatiy operatsiyalar (routine operations). -- **Harnessni soddalashtirish (Harness simplification)**: Model qobiliyatlari oshgani sari, endi keragi yoʻq boʻlgan harness komponentlarini davriy ravishda olib tashlash. Bugun muhim boʻlgan cheklov uch oydan soʻng ortiqcha yukka aylanishi mumkin. -- **Idempotent tozalash (Idempotent cleanup)**: Tozalash operatsiyalari necha marta ishga tushishidan qatʼi nazar, bir xil natija berishi kerak. Bu xatolik/qayta urinish (failure-retry) ssenariylarida ham tozalashning xavfsiz boʻlib qolishini taʼminlaydi. - -## Toza holatning besh oʻlchami - -```mermaid -flowchart LR - Work["Funksional ish tugallandi"] --> Build{"Build oʻtmoqdami?"} - Build -->|ha| Test{"Testlar oʻtmoqdami?"} - Build -->|yoʻq| Fix["Chiqishdan oldin toʻgʻrilang"] - Test -->|ha| Record["Funksiyalar roʻyxati + jarayonni yangilang"] - Test -->|yoʻq| Fix - Record --> Cleanup["Vaqtinchalik artefaktlar / debug kodlarni olib tashlang"] - Cleanup --> Startup{"Standart startup yoʻli ishlayaptimi?"} - Startup -->|ha| Clean["Toza topshirish (Clean handoff)"] - Startup -->|yoʻq| Fix - Fix --> Build -``` - -```mermaid -flowchart LR - Dirty["Sessiya shunday tugasa:
    qizil testlar / temp fayllar / yangilanmagan jarayon"] --> Diagnose["Keyingi sessiya birinchi navbatda
    nima boʻlganini tushunib olishiga toʻgʻri keladi"] - Diagnose --> Fragile["Yangi ish tartibsiz repoda boshlanadi"] - Fragile --> More["Koʻproq debug fayllar, koʻproq buzilgan tekshiruvlar,
    noaniq jarayon koʻpayadi"] - More --> Dirty - - Clean["Sessiya shunday tugasa:
    yashil testlar / yangilangan jarayon / temp fayllar tozalangan"] --> Fast["Keyingi sessiya darhol kod yozishni boshlay oladi"] - Fast --> Stable["Dastlab reponi qutqarishga (rescue) ehtiyoj qolmaydi"] - Stable --> Clean -``` - -## Nega bunday boʻladi - -### Entropiyaning oʻsishi bu standart holatdir - -Dasturiy taʼminot evolyutsiyasining Leman qonunlariga (Lehmanʼs laws) koʻra: doimiy oʻzgarishdagi tizimlarning murakkabligi faol boshqarilmas ekan, u muqarrar ravishda oshib boraveradi. Bu ayniqsa AI kod yozish agentlari uchun toʻgʻri — har bir sessiya oʻzgarishlar olib kiradi va chiqishda tozalab ketilmasa, texnik qarz (technical debt) eksponensial tarzda koʻpayib boradi. - -Haqiqiy maʼlumotlar soʻzlaydi. Hech qanday tozalash strategiyasisiz agentlar bilan 12 hafta davomida ishlab chiqilgan loyiha: - -- 1-hafta: Build oʻtish darajasi 100%, testlar oʻtish darajasi 100%, yangi sessiyani boshlash 5 daq -- 4-hafta: Build 95%, testlar 92%, boshlash 15 daq -- 8-hafta: Build 82%, testlar 78%, boshlash 35 daq -- 12-hafta: Build 68%, testlar 61%, boshlash 60+ daq - -Xuddi shu loyiha tozalash strategiyasi bilan: - -- 1-hafta: 100%, 100%, 5 daq -- 12-hafta: 97%, 95%, 9 daq - -12 haftadan soʻng: build oʻtish darajasi oʻrtasida 29 foiz punkti farq, yangi sessiyani boshlash vaqti oʻrtasida esa 85% farq bor. Bu faqat nazariya emas — bu kuzatilgan aniq farqdir. - -### Toza holatning besh oʻlchami - -Toza holat bu shunchaki “kod kompilyatsiya boʻladi” degani emas. U beshta oʻlchamni birgalikda baholaydi: - -**Build oʻlchami**: Kod xatosiz build boʻladimi? Bu eng asosiysi — keyingi sessiya ishni build xatolarini tuzatishdan boshlamasligi kerak. - -**Test oʻlchami**: Barcha testlar oʻtmoqdami? Sessiyadan oldin mavjud boʻlgan testlar ham — bu sessiya mavjud funksiyani (functionality) buzmaslikka masʼuldir. Va u faqat “mening kompyuterimda ishlayapti” boʻlib qolmay, CIʼda ham tekshirilishi shart. - -**Jarayon (Progress) oʻlchami**: Joriy ishning jarayoni mashina oʻqiy oladigan artefaktga yozilganmi? Oʻzining qabul qilish shartlariga (passing criteria) ega tugatilgan sub-vazifalar, jarayonda boʻlgan lekin tugallanmaganlarining joriy holati va hali boshlanmaganlari kiritilishi kerak. Yaxshi jarayon qaydlari yangi sessiya ishga tushgandagi diagnostika vaqtini 60-80% gacha kamaytiradi. - -**Artefakt oʻlchami**: Eskirgan yoki noaniq vaqtinchalik artefaktlar qolmadimi? Debug loglari, vaqtinchalik fayllar, izoh qilib qoldirilgan kodlar (commented-out code), TODO eslatmalari — bularning barchasi keyingi sessiya uchun bilish kerak boʻlgan kognitiv yukni (cognitive load) oshiradi. - -**Startup oʻlchami**: Standart ishga tushirish (startup) yoʻli ochiqmi? Keyingi sessiya inson aralashuvisiz ishni boshlay oladimi? Muhitni ishga tushirish, kod bazasini yuklash, kontekstni tushunib olish, vazifani tanlash — bu yoʻllar buzilgan boʻlmasligi kerak. - -### “Keyinroq tozalab qoʻyaman” degani — hech qachon tozalab qoʻymayman degani - -Eng koʻp tushiladigan aqliy tuzoq bu “bu sessiyada tozalashga vaqtim yoʻq, keyingi safar tozalayman” deyishdir. Ammo keyingi agent sessiyasi sizning ortda nima tashlab ketganingizni bilmaydi — u faqat tartibsiz kod va noaniq holatni koʻradi. U “bu kodning qaysi qismlari ataylab yozilgan va qaysi qismlari vaqtinchalik” deganini aniqlashga ancha vaqt sarflaydi. - -Eng yomoni, har bir sessiyaning oʻz vazifasi (task objectives) bor. Yangi sessiya oldingi sessiyaning chala ishlari va tartibsizliklarini tozalash uchun emas, yangi ish qilish uchun kelgan. U bu tartibsizlikka eʼtibor bermay, uning ustiga yangi ishini boshlaydi, tartibsizlik ustiga yana tartibsizlik qoʻshadi. Bu entropiyaning ijobiy qayta aloqa siklidir (positive feedback loop). - -## Buni qanday qilib toʻgʻri qilish kerak - -### 1. Toza holatni yakunlash talabi sifatida qoʻyish - -Harnessʼda buni aniq belgilang: **sessiyani yakunlash = vazifa tekshiruvdan oʻtdi (passes verification) VA toza holat tekshiruvidan (clean state check) oʻtdi.** Bulardan birortasini bajarmaslik, sessiya tugallanmadi deganidir. CLAUDE.md fayliga shunday yozing: - -``` -## Sessiyadan chiqish tekshiruvi (Session Exit Checklist) -- [ ] Build oʻtadi (npm run build) -- [ ] Barcha testlar oʻtadi (npm test) -- [ ] Funksiyalar roʻyxati yangilangan -- [ ] Debug kodlari qolmagan (console.log, debugger, TODO) -- [ ] Standart ishga tushirish (startup) yoʻli ochiq (npm run dev) -``` - -### 2. Ikki rejimli tozalash strategiyasi (Dual-Mode Cleanup Strategy) - -Ikkita tozalash rejimini birlashtiring: - -**Zudlik bilan tozalash (Immediate cleanup - har bir sessiya oxirida)**: Sessiya davomida yaratilgan vaqtinchalik artefaktlarni tozalash, funksiyalar roʻyxati holatini yangilash, build va testlar oʻtishini taʼminlash. Bu “havolalarni hisoblash” (reference counting) kabi tozalashdir. - -**Vaqti-vaqti bilan tozalash (Periodic cleanup - haftalik)**: Tizimni toʻliq skanerdan oʻtkazish — toʻplanib qolgan strukturaviy muammolarni hal qilish, sifat hujjatlarini yangilash, qoidadan ogʻishlarni (drift) aniqlash uchun benchmark testlarini oʻtkazish. Bu “treysing” (tracing) kabi tozalashdir. - -### 3. Sifat hujjatini (Quality Document) olib boring - -Sifat hujjati har bir modulni doimiy ravishda baholab boradigan faol artefaktdir: - -```markdown -# Sifat Hujjati - -## Foydalanuvchi Autentifikatsiya Moduli (Sifat: A) -- Tekshiruvdan oʻtdi: Ha -- Agent tushuna oladi: Ha -- Test barqarorligi: Barqaror -- Arxitektura chegaralari: Qoidaga mos -- Kod konvensiyalari: Amal qilingan - -## Toʻlov Moduli (Sifat: C) -- Tekshiruvdan oʻtdi: Qisman (toʻlov call-backʼi sinovdan oʻtmagan) -- Agent tushuna oladi: Qiyin (mantiq 3 ta faylga sochilib ketgan) -- Test barqarorligi: Barqaror emas (2 ta beqaror (flaky) test) -- Arxitektura chegaralari: Qoidabuzilishlar mavjud -- Kod konvensiyalari: Qisman amal qilingan -``` - -Yangi sessiyalar ushbu hujjatni oʻqib chiqib, ustuvorlik nimaga qaratilishini darhol bilib oladi. Eng avvalo eng past ball olgan modulni toʻgʻrilang. - -### 4. Vaqti-vaqti bilan Harnessʼni soddalashtiring - -Anthropicʼdan muhim tushuncha: **har bir harness komponenti agent biror ishni mustaqil va ishonchli bajara olmagani uchungina mavjuddir. Ammo modellar takomillashgani sari, bu taxminlar oʻz eskiradi.** Uch oy oldin zarur boʻlgan cheklov, bugun shunchaki ortiqcha yukka aylanishi mumkin. - -Tavsiya etiladigan amaliyot: Har oyda bitta harness komponentini tanlang, uni vaqtinchalik oʻchirib qoʻying va benchmark testlarini oʻtkazing. Agar natijalar yomonlashmasa, uni butunlay olib tashlang. Agar yomonlashsa, uni qaytadan yoqing yoki yengilroq alternativa bilan almashtiring. - -### 5. Tozalash operatsiyalari Idempotent boʻlishi kerak - -Tozalash skriptlari necha marta qayta-qayta ishga tushirilmasin doim xavfsiz boʻlishi kerak: - -```bash -# Idempotent tozalash amallari -rm -f /tmp/debug-*.log # -f fayllar mavjud boʻlmasa ham xato bermasligini taʼminlaydi -git checkout -- .env.local # Maʼlum boʻlgan barqaror holatga (known state) qaytarish -npm run test # Tozalash hech narsani buzmaganini tasdiqlash -``` - -## Hayotiy misol - -Ikkita yondashuv bilan 12 hafta davomida agentlar yordamida ishlab chiqilgan Electron ilovasi (app): - -**Tozalash strategiyasisiz** (nazorat guruhi - control group): 12-haftada, build oʻtish darajasi 68%, test oʻtish darajasi 61%, yangi sessiyani boshlash vaqti 60+ daq, eskirgan artefaktlar 103 ta. - -**Tozalash strategiyasi bilan** (tajriba guruhi - experimental group): Har bir sessiya yakunida toʻliq toza-holat tekshiruvi (clean-state check) + haftalik tozalash sikli (weekly cleanup loop). 12-haftada, build oʻtish darajasi 97%, test oʻtish darajasi 95%, yangi sessiyani boshlash vaqti 9 daq, eskirgan artefaktlar 11 ta. - -12-haftaga kelib, tajriba guruhining build oʻtish darajasi 29 foiz punkti yuqori, test oʻtish darajasi 34 foiz punkti yuqori va yangi sessiyani boshlash vaqti 85% ga qisqa. - -## Asosiy xulosalar - -- **Toza holat — bu sessiyani tugatish uchun muhim va zarur shartdir** — bu ixtiyoriy “uy tozalash” (housekeeping) ishi emas, balki “bajarilganlik mezonlari (definition of done)”ning bir qismidir. -- **Barcha besh oʻlcham talab qilinadi** — build, testlar, jarayon (progress), artefaktlar, ishga tushirish (startup) — har biri alohida ochiq holda tekshirilishi kerak. -- **Sifat hujjatlari orqali kod bazasining salomatligini kuzatish imkoni tugʻiladi** — siz faqat aynan nima yomonlashayotganini bilsangizgina uni tuzata olasiz. -- **Harnessʼni vaqti-vaqti bilan soddalashtiring** — model qobiliyatlari yaxshilanib borgani sari, keraksiz cheklovlarni olib tashlang. -- **“Keyinroq tozalab qoʻyaman” = Hech qachon tozalab qoʻymayman degani** — entropiyaning oʻsishi standart jarayondir; faqat faol tozalash qilinishigina unga qarshi tura oladi. - -## Qoʻshimcha oʻqish uchun - -- [Clean Code - Robert C. Martin](https://www.goodreads.com/book/show/3735293-clean-code) — Kodni toza saqlash boʻyicha tizimli tamoyillar -- [Harness Engineering - OpenAI](https://openai.com/index/harness-engineering/) — Qayta ishlab chiqaruvchanlik (Reproducibility) harness dizaynining asosiy talablaridan biri sifatida -- [Effective Harnesses - Anthropic](https://www.anthropic.com/engineering/effective-harnesses-for-long-running-agents) — Uzoq muddatli ishonchlilik uchun toza sessiya yakunlarining hal qiluvchi roli -- [Programs, Life Cycles, and Laws of Software Evolution - Lehman](https://ieeexplore.ieee.org/document/1702314) — Tizim murakkabligining faol kuzatuvsiz (active maintenance) muqarrar ravishda oʻsishini isbotlovchi dastur evolyutsiyasi qonunlari - -## Mashqlar - -1. **Toza Holat (Clean State) Tekshiruv roʻyxati**: Barcha besh oʻlchamni oʻz ichiga oladigan kod bazangiz uchun sessiyadan chiqish tekshiruvi roʻyxatini loyihalang. Buni ketma-ket 5 ta sessiya davomida qoʻllang va har bir oʻlcham boʻyicha buzilishlarni qayd qilib boring. - -2. **Benchmark Taqqoslash**: Bir xil vazifalar roʻyxatini 2 ta turli harness variantida (toza holat talablari bor/yoʻq boʻlgan holda) foydalaning. Tugallanish darajasini, takroriy urinishlar sonini (retry count) va xatoliklar chetlab oʻtish darajasini (defect escape rate) solishtiring. - -3. **Harnessni Soddalashtirish Amaliyoti**: Bitta harness komponentini tanlang, uni vaqtinchalik oʻchirib qoʻying va benchmark vazifalarini ishlating. Uni bor va yoʻq boʻlgan holatdagi natijalarni solishtiring. Qoldirishni, olib tashlashni yoki almashtirishni hal qiling. diff --git a/courses/learn-harness-engineering/docs/uz/projects/index.md b/courses/learn-harness-engineering/docs/uz/projects/index.md deleted file mode 100644 index 0c954f22..00000000 --- a/courses/learn-harness-engineering/docs/uz/projects/index.md +++ /dev/null @@ -1,23 +0,0 @@ -# Loyihalarga xush kelibsiz - -Bu Learn Harness Engineering kursining amaliyot qismidir. Faqat maʼruzalarni oʻqish yetarli emas — muhitlarni oʻzingiz qurishingiz va turli qoidalar ostida Codex, Claude Code yoki boshqa AI agentlar qanday harakat qilishini kuzatishingiz kerak. - -## Loyiha xulosasi - -Ushbu kurs 6 ta bosqichma-bosqich, amaliy loyihalarni oʻz ichiga oladi va ular sizga noldan boshlab qanday qilib ishonchli agent ishlash muhitini qurishni oʻrgatadi: - -1. **Faqat prompt vs. Avval qoidalar (Prompt-Only vs. Rules-First)**: Agent faqat prompt bilan qanday ishlashini va minimal harness bilan qanday ishlashini taqqoslang. -2. **Agent oʻqiy oladigan ish maydoni (Agent-Readable Workspace)**: Repozitoriyni qanday qilib AI uchun qulay qilishni va ishlarni topshirish (handoff) mexanizmlarini yaratishni oʻrganing. -3. **Koʻp sessiyali uzluksizlik (Multi-Session Continuity)**: Agent ishlarni sessiyalar oʻrtasida muammosiz davom ettira olishi uchun holat fayllari va ishga tushirish (initialization) skriptlarini loyihalang. -4. **Runtime qayta aloqa va skoup nazorati (Runtime Feedback and Scope Control)**: Agentga oʻz kodini oʻzi test qilish va ishlash davomida (runtime) xatolarni tuzatish imkonini beruvchi vositalarni joriy qiling. -5. **Oʻz-oʻzini tekshirish va rollarni ajratish (Self-Verification and Role Separation)**: Gallyutsinatsiyalar va vaqtidan oldin gʻalabani eʼlon qilishning oldini olish uchun mustaqil tekshiruv mexanizmini quring. -6. **Toʻliq harness (Capstone)**: Yakuniy, kuzatiladigan (observable), end-to-end agent ish muhitini toʻplang. - -## Qanday davom ettirish kerak - -Har bir loyiha papkasi odatda quyidagilarni oʻz ichiga oladi: -- `starter/`: Sizning boshlangʻich ish maydoningiz (workspace). -- `solution/`: Namuna yechim (agar qiyin vaziyatga tushib qolsangiz). -- Sizning orqa foningiz va aniq maqsadlaringiz tushuntirilgan vazifa yoʻriqnomalari. - -`starter/` katalogidagi vazifalarni bajarish uchun oʻzingiz afzal koʻrgan AI kod yozish agentidan (masalan, Claude Code, Cursor, Trae) foydalaning. diff --git a/courses/learn-harness-engineering/docs/uz/projects/project-01-baseline-vs-minimal-harness/index.md b/courses/learn-harness-engineering/docs/uz/projects/project-01-baseline-vs-minimal-harness/index.md deleted file mode 100644 index 79e1fbc4..00000000 --- a/courses/learn-harness-engineering/docs/uz/projects/project-01-baseline-vs-minimal-harness/index.md +++ /dev/null @@ -1,25 +0,0 @@ -[English version →](../../../en/projects/project-01-baseline-vs-minimal-harness/) - -> Tegishli maʼruzalar: [1-maʼruza. Kuchli modellar doim ham ishonchli degani emas](./../../lectures/lecture-01-why-capable-agents-still-fail/index.md) · [2-maʼruza. Harness aslida nima degani](./../../lectures/lecture-02-what-a-harness-actually-is/index.md) -> Andoza fayllari: [templates/](https://github.com/walkinglabs/learn-harness-engineering/blob/main/docs/en/resources/templates/) - -# Loyiha 01. Prompt-only vs. rules-first: Bu qanchalik farq qiladi - -## Nima qilasiz - -Minimal Electron bilimlar bazasi (knowledge-base) ilovasi (app) qobigʻini quring — chap tomonda hujjatlar roʻyxati, oʻng tomonda Q&A (savol-javob) paneli va lokal maʼlumotlar katalogiga ega oyna. Vazifaning oʻzi murakkab emas. Murakkab qismi shundaki — agentga buni qanday qilib bajartirishdir. - -Buni ikki marta ishga tushirasiz. Birinchi marta: hech qanday tayyorgarliksiz, faqat prompt bilan. Ikkinchi marta: repoda oldindan joylashtirilgan `AGENTS.md`, `init.sh`, `feature_list.json` bilan. Va keyin taqqoslaysiz. - -Bu loyihaning asosiy maqsadi kod yozish emas — “avval qoidalarni tayyorlash uchun 15 daqiqa sarflash” bilan “shunchaki agentga qoʻyib berish” oʻrtasidagi tafovut qanchalik katta ekanligini anglab yetishdir. - -## Vositalar - -- Claude Code yoki Codex (bittasini tanlang va ikkala urinish uchun ham ishlatasiz) -- Git (branchʼlarni boshqarish va taqqoslash uchun) -- Node.js + Electron (loyiha steki) -- Taymer (har bir urinish qancha vaqt olganini yozib borish uchun) - -## Harness mexanizmi - -Minimal harness: `AGENTS.md` + `init.sh` + `feature_list.json` diff --git a/courses/learn-harness-engineering/docs/uz/projects/project-02-agent-readable-workspace/index.md b/courses/learn-harness-engineering/docs/uz/projects/project-02-agent-readable-workspace/index.md deleted file mode 100644 index 8de6d654..00000000 --- a/courses/learn-harness-engineering/docs/uz/projects/project-02-agent-readable-workspace/index.md +++ /dev/null @@ -1,22 +0,0 @@ -[English version →](../../../en/projects/project-02-agent-readable-workspace/) - -> Tegishli maʼruzalar: [3-maʼruza. Repozitoriyni yagona haqiqat manbaiga aylantiring](./../../lectures/lecture-03-why-the-repository-must-become-the-system-of-record/index.md) · [4-maʼruza. Yoʻriqnomalarni fayllar boʻylab ajrating](./../../lectures/lecture-04-why-one-giant-instruction-file-fails/index.md) -> Andoza fayllari: [templates/](https://github.com/walkinglabs/learn-harness-engineering/blob/main/docs/en/resources/templates/) - -# Loyiha 02. Loyihani oʻqishga qulay qiling va toʻxtagan joyidan davom ettiring - -## Nima qilasiz - -Yangi agent loyiha tuzilishini tez tushunib olishi, joriy jarayon (progress) qayerdaligini bilishi va ishni davom ettirishi uchun repoga “oʻqish qulayligi”ni (readability) qoʻshing. Aniqroq qilib aytganda: ikkita sessiya davomida hujjat import qilish, hujjat detallarini koʻrish va lokal saqlashni (local persistence) amalga oshiring. - -Siz buni ikki marta bajarasiz: birinchisi hech qanday yordamsiz, ikkinchisi repoda oldindan joylashtirilgan `ARCHITECTURE.md`, `PRODUCT.md` va `session-handoff.md` fayllari bilan. - -## Vositalar - -- Claude Code yoki Codex -- Git -- Node.js + Electron - -## Harness mexanizmi - -Agent oʻqiy oladigan ish maydoni (Agent-readable workspace) + doimiy holat fayllari (persistent state files) diff --git a/courses/learn-harness-engineering/docs/uz/projects/project-03-multi-session-continuity/index.md b/courses/learn-harness-engineering/docs/uz/projects/project-03-multi-session-continuity/index.md deleted file mode 100644 index 6b5baa89..00000000 --- a/courses/learn-harness-engineering/docs/uz/projects/project-03-multi-session-continuity/index.md +++ /dev/null @@ -1,22 +0,0 @@ -[English version →](../../../en/projects/project-03-multi-session-continuity/) - -> Tegishli maʼruzalar: [5-maʼruza. Sessiyalar oʻrtasida kontekstni saqlab qoling](./../../lectures/lecture-05-why-long-running-tasks-lose-continuity/index.md) · [6-maʼruza. Har bir agent sessiyasidan oldin inisializatsiya qiling](./../../lectures/lecture-06-why-initialization-needs-its-own-phase/index.md) -> Andoza fayllari: [templates/](https://github.com/walkinglabs/learn-harness-engineering/blob/main/docs/en/resources/templates/) - -# Loyiha 03. Agentning ishlashini sessiyalar boʻylab uzluksiz taʼminlang - -## Nima qilasiz - -Agentga skoup nazorati (scope control) va tekshirish eshiklarini (verification gates) qoʻshing. Hujjatlarni qismlarga boʻlish (chunking), metamaʼlumotlarni (metadata) ajratib olish, indekslash jarayonini koʻrsatish va iqtiboslarga asoslangan Q&A (savol-javob) oqimini amalga oshiring. Funksiyalar (features) holatini kuzatish uchun `feature_list.json` dan foydalaning — bir vaqtda bitta funksiya ustida ishlansin, tekshiruv dalilisiz (verification evidence) “pass” (oʻtdi) deb belgilash mumkin emas. - -Siz buni ikki marta bajarasiz: birinchisida hech qanday cheklovlarsiz, ikkinchisida esa qatʼiy talablar asosida. - -## Vositalar - -- Claude Code yoki Codex -- Git -- Node.js + Electron - -## Harness mexanizmi - -Jarayon jurnali (Progress log) + sessiyani topshirish (session handoff) + koʻp sessiyali uzluksizlik (multi-session continuity) diff --git a/courses/learn-harness-engineering/docs/uz/projects/project-04-incremental-indexing/index.md b/courses/learn-harness-engineering/docs/uz/projects/project-04-incremental-indexing/index.md deleted file mode 100644 index a2287d3d..00000000 --- a/courses/learn-harness-engineering/docs/uz/projects/project-04-incremental-indexing/index.md +++ /dev/null @@ -1,22 +0,0 @@ -[English version →](../../../en/projects/project-04-incremental-indexing/) - -> Tegishli maʼruzalar: [7-maʼruza. Agentlar uchun aniq vazifa chegaralarini chizing](./../../lectures/lecture-07-why-agents-overreach-and-under-finish/index.md) · [8-maʼruza. Agent nima qilishini cheklash uchun funksiyalar roʻyxatidan foydalaning](./../../lectures/lecture-08-why-feature-lists-are-harness-primitives/index.md) -> Andoza fayllari: [templates/](https://github.com/walkinglabs/learn-harness-engineering/blob/main/docs/en/resources/templates/) - -# Loyiha 04. Agent harakatlarini toʻgʻrilash uchun Runtime qayta aloqadan foydalaning - -## Nima qilasiz - -Qatlamlararo qoidabuzilishlarning (cross-layer violations) oldini olish uchun runtime kuzatuvchanligini (ishga tushish loglari, import/indekslash loglari, xatolik holatlari) va arxitektura cheklovlarini qoʻshing. Agent toʻgʻrilashi uchun runtime bug qoldiring. - -Siz buni ikki marta bajarasiz: birinchisida loglar va cheklovlarsiz, ikkinchisida tegishli vositalar va qoidalar bilan. - -## Vositalar - -- Claude Code yoki Codex -- Git -- Node.js + Electron - -## Harness mexanizmi - -Runtime qayta aloqa (Runtime feedback) + skoup nazorati (scope control) + bosqichma-bosqich indekslash (incremental indexing) diff --git a/courses/learn-harness-engineering/docs/uz/projects/project-05-grounded-qa-verification/index.md b/courses/learn-harness-engineering/docs/uz/projects/project-05-grounded-qa-verification/index.md deleted file mode 100644 index 7789c9e0..00000000 --- a/courses/learn-harness-engineering/docs/uz/projects/project-05-grounded-qa-verification/index.md +++ /dev/null @@ -1,22 +0,0 @@ -[English version →](../../../en/projects/project-05-grounded-qa-verification/) - -> Tegishli maʼruzalar: [9-maʼruza. Agentlarni vaqtidan oldin gʻalabani eʼlon qilishdan saqlash](./../../lectures/lecture-09-why-agents-declare-victory-too-early/index.md) · [10-maʼruza. Faqatgina End-to-End testlash chinakam tekshiruvdir](./../../lectures/lecture-10-why-end-to-end-testing-changes-results/index.md) -> Andoza fayllari: [templates/](https://github.com/walkinglabs/learn-harness-engineering/blob/main/docs/en/resources/templates/) - -# Loyiha 05. Agentga oʻz ishini oʻzi tekshirishiga imkon bering - -## Nima qilasiz - -Rollarni ajratishni (role separation) amalga oshiring — kod yozuvchi (generator), tekshiruvchi (evaluator), hamda ixtiyoriy ravishda rejalashtiruvchi (planner). Qoʻshilgan har bir turning taʼsirini oʻlchash uchun buni uch marta ishga tushirib koʻring. - -Diqqatga sazovor boʻlgan biror funksiya yangilanishini tanlang (koʻp bosqichli suhbat, iqtibos panelini qayta dizayn qilish yoki hujjatlarni filtrlash) va uni barcha urinishlar davomida bir xil qilib saqlang. - -## Vositalar - -- Claude Code yoki Codex -- Git -- Node.js + Electron - -## Harness mexanizmi - -Oʻz-oʻzini tekshirish (Self-verification) + asoslangan Q&A (grounded Q&A) + dalilga asoslangan tugatish (evidence-based completion) diff --git a/courses/learn-harness-engineering/docs/uz/projects/project-06-runtime-observability-and-debugging/index.md b/courses/learn-harness-engineering/docs/uz/projects/project-06-runtime-observability-and-debugging/index.md deleted file mode 100644 index ed167df5..00000000 --- a/courses/learn-harness-engineering/docs/uz/projects/project-06-runtime-observability-and-debugging/index.md +++ /dev/null @@ -1,25 +0,0 @@ -[English version →](../../../en/projects/project-06-runtime-observability-and-debugging/) - -> Tegishli maʼruzalar: [11-maʼruza. Agent ishining runtimeʼda kuzatilishini taʼminlang](./../../lectures/lecture-11-why-observability-belongs-inside-the-harness/index.md) · [12-maʼruza. Har bir sessiya yakunida toza holat qoldiring](./../../lectures/lecture-12-why-every-session-must-leave-a-clean-state/index.md) -> Andoza fayllari: [templates/](https://github.com/walkinglabs/learn-harness-engineering/blob/main/docs/en/resources/templates/) - -# Loyiha 06. Toʻliq agent harnessʼini quring (Capstone) - -## Nima qilasiz - -Bu capstone (yakunlovchi) loyihadir. Dastlabki beshta loyihada oʻrganilgan barcha narsalarni bir joyga toʻplang, toʻliq benchmark ishlating, soʻngra sifatni barqaror ushlab turish mumkinligini isbotlash uchun tozalash (cleanup) bosqichini amalga oshiring. - -Toʻliq mahsulot boʻlagini (product slice) qamrab oladigan qatʼiy koʻp funksiyali (multi-feature) vazifalar toʻplamidan foydalaning: hujjatni import qilish, indekslash, iqtiboslarga asoslangan Q&A, runtime kuzatuvchanligi va oʻqish mumkin boʻlgan qayta ishga tushiriladigan (restartable) repo holati. Avval kuchsiz asosiy (baseline) harness bilan ishga tushiring, soʻngra oʻzingizning eng kuchli harnessʼingiz bilan, oxirida esa tozalash qilib, qayta ishlating. Va nihoyat, harnessʼda ablasyon (ablation) tajribasini oʻtkazing — har bir komponentni bittadan olib tashlang va haqiqatda qaysi biri muhim ekanligini aniqlang. - -## Vositalar - -- Claude Code yoki Codex -- Git -- Node.js + Electron -- Sifat hujjati andozasi (Quality document template) -- Baholovchi rubrikasi (Evaluator rubric) -- Dastlabki beshta loyihada toʻplangan barcha harness komponentlari - -## Harness mexanizmi - -Toʻliq harness: barcha mexanizmlar + kuzatuvchanlik (observability) + ablasyon oʻrganish (ablation study) diff --git a/courses/learn-harness-engineering/docs/uz/resources/index.md b/courses/learn-harness-engineering/docs/uz/resources/index.md deleted file mode 100644 index 15d2d0f0..00000000 --- a/courses/learn-harness-engineering/docs/uz/resources/index.md +++ /dev/null @@ -1,50 +0,0 @@ -# Resurslar kutubxonasi - -Ushbu jild kurs uslublarini haqiqiy repozitoriylarda foydalanishingiz mumkin boʻlgan, nusxalashga tayyor andozalar (templates) va qisqa qoʻllanmalarga aylantiradi. - -## Qachon foydalanish kerak - -Codex, Claude Code yoki boshqa kod yozish agenti bir nechta sessiyalar davomida ishlashi kerak boʻlganda, har safar sozlamalar, holat va skoupni (scope) boshidan aniqlashga majbur boʻlmaslik uchun ishni shu yerdan boshlang. - -Bu ayniqsa quyidagi holatlarda foydali: - -- ish bir nechta sessiyalar davomida bajarilsa -- funksiyalar (features) koʻp boʻlsa va ularni chala tashlab ketish oson boʻlsa -- agentlar vaqtidan oldin “tugatdim” deb eʼlon qilishga moyil boʻlsa -- ishga tushirish (startup) qadamlari har safar qaytadan kashf etilsa - -## Shu yerdan boshlang - -Minimal oʻrnatish uchun quyidagilardan boshlang: - -- asosiy yoʻriqnomalar (root instructions): [`templates/AGENTS.md`](./templates/AGENTS.md) yoki [`templates/CLAUDE.md`](./templates/CLAUDE.md) -- funksiya holati (feature state): [`templates/feature_list.json`](./templates/feature_list.json) -- jarayon jurnali (progress log): [`templates/claude-progress.md`](./templates/claude-progress.md) -- boshlangʻich skript (bootstrap script) qoʻllanmasi: `docs/en/resources/templates/init.sh` - -Keyin quyidagilarni qoʻshing: - -- sessiyani topshirish (session handoff): [`templates/session-handoff.md`](./templates/session-handoff.md) -- toza chiqish tekshiruvi roʻyxati (clean-exit checklist): [`templates/clean-state-checklist.md`](./templates/clean-state-checklist.md) -- baholovchi rubrikasi (evaluator rubric): [`templates/evaluator-rubric.md`](./templates/evaluator-rubric.md) - -Agar siz “Harness engineering” maqolasidagi OpenAI uslubidagi toʻliqroq repozitoriy strukturasini xohlasangiz, ilgʻor toʻplamdan (advanced pack) foydalaning: - -- [`openai-advanced/index.md`](./openai-advanced/index.md) - -## Kutubxona strukturasi - -- [`templates/`](./templates/index.md): haqiqiy repoga koʻchirish uchun andozalar -- [`reference/`](./reference/index.md): metod eslatmalari, ishga tushirish oqimi (startup flow) va xatolik rejimlarining (failure-mode) xaritalari -- [`openai-advanced/`](./openai-advanced/index.md): ilgʻor repo skeleti, yagona haqiqat manbai (system-of-record) hujjatlari va agent-first boshqaruv (governance) andozalari - -## Tavsiya etiladigan Minimal toʻplam - -- `AGENTS.md` yoki `CLAUDE.md` -- `feature_list.json` -- `claude-progress.md` -- `init.sh` - -Ushbu toʻrtta fayl koʻpgina agent jarayonlarini (workflows) sezilarli darajada barqarorlashtirish uchun yetarli. - -Agar repo bir nechta domenlar, faol rejalar, sifat baholash (quality scoring) va ishonchlilik siyosatlarini (reliability policies) oʻz ichiga olgan, uzoq vaqt ishlaydigan tizimga aylansa, minimal toʻplamni haddan tashqari choʻzish oʻrniga [`openai-advanced/`](./openai-advanced/index.md) toʻplamiga oʻting. diff --git a/courses/learn-harness-engineering/docs/uz/resources/openai-advanced/index.md b/courses/learn-harness-engineering/docs/uz/resources/openai-advanced/index.md deleted file mode 100644 index 60b156f8..00000000 --- a/courses/learn-harness-engineering/docs/uz/resources/openai-advanced/index.md +++ /dev/null @@ -1,73 +0,0 @@ -# OpenAI Ilgʻor toʻplami (Advanced Pack) - -Bu jild OpenAIʼning “Harness engineering: leveraging Codex in an agent-first world” maqolasida tasvirlangan ancha qatʼiy repo tuzilishini nusxalashga tayyor boshlangʻich fayllarga (starter files) toʻplaydi. - -Qachonki minimal harness endi yetarli boʻlmasa va sizning repozitoriyingiz quyidagilarga muhtoj boʻlsa, ushbu toʻplamdan foydalaning: - -- qisqa yoʻnaltiruvchi (routing-style) `AGENTS.md` -- repo ichida yagona haqiqat manbai (system-of-record) boʻlgan mustahkam hujjatlar -- faol va tugallangan ishlash rejalari (execution plans) -- aniq mahsulot (product), ishonchlilik (reliability), xavfsizlik (security) va frontend siyosati fayllari -- mahsulot domeni va arxitektura qatlami boʻyicha sifat baholash (quality scoring) -- model tushunadigan maʼlumotnoma (reference material) jildlari -- arxitektura, bilimlarni saqlab qolish va runtime validatsiyasi uchun standart operatsion protseduralar (SOP - standard operating procedures) - -## Kiritilgan boshlangʻich (Starter) maket - -[`repo-template/`](./repo-template/index.md) ostidagi boshlangʻich toʻplam quyidagi tuzilmani aynan takrorlaydi: - -```text -AGENTS.md -ARCHITECTURE.md -docs/ -├── design-docs/ -│ ├── index.md -│ └── core-beliefs.md -├── exec-plans/ -│ ├── active/ -│ ├── completed/ -│ └── tech-debt-tracker.md -├── generated/ -│ └── db-schema.md -├── product-specs/ -│ ├── index.md -│ └── new-user-onboarding.md -├── references/ -│ ├── design-system-reference-llms.txt -│ ├── nixpacks-llms.txt -│ └── uv-llms.txt -├── DESIGN.md -├── FRONTEND.md -├── PLANS.md -├── PRODUCT_SENSE.md -├── QUALITY_SCORE.md -├── RELIABILITY.md -└── SECURITY.md -``` - -## Buni qanday qabul qilish (Adopt) mumkin - -1. Agar repongiz hali kichik boʻlsa, minimal toʻplamdan boshlang. -2. Kuchliroq tuzilma kerak boʻlganda [`repo-template/`](./repo-template/index.md) ichidagi fayllarni oʻz repozitoriyingizga koʻchiring. -3. `AGENTS.md` faylini qisqa saqlang. Unga ensiklopediya emas, balki chuqurroq hujjatlarga yoʻnaltiruvchi router sifatida qarang. -4. Sifat, ishonchlilik va reja hujjatlarini alohida tozalash kuni sifatida emas, balki oddiy ishingizning bir qismi sifatida yangilab boring. -5. Yaratilgan (generated) artefaktlar va tashqi maʼlumotnomalarni (external references) aniq saqlang, shunda agentlar chat tarixiga tayanmasdan ularni topa oladi. - -## SOP Kutubxonasi - -[`sops/`](./sops/index.md) jildi maqoladagi diagrammalarni bosqichma-bosqich ishlash protseduralariga aylantiradi: - -- qatlamli domen arxitekturasini (layered domain architecture) oʻrnatish -- koʻrinmas bilimlarni repozitoriyga kodlashtirish -- lokal kuzatuvchanlik steki (observability stack) va qayta aloqa (feedback-loop) jarayoni -- UI ishlari uchun Chrome DevTools validatsiya sikli - -## Dizayn tamoyillari - -- Qisqa kirish nuqtasi (entrypoint), chuqurroq bogʻlangan hujjatlar -- Repozitoriy yagona haqiqat manbai (system of record) sifatida -- Mexanik tekshiruvlar yodda saqlangan qoidalardan ustunroqdir -- Rejalar va sifat tarixi kodning yonida yashaydi -- Tozalash (cleanup) va soddalashtirish (simplification) — birinchi darajali masʼuliyatlardir - -Ushbu toʻplam ataylab qatʼiy (opinionated) qilingan, ammo baribir u koʻr-koʻrona nusxalanmasdan, sizning loyihangizga moslashtirilishi kerak. diff --git a/courses/learn-harness-engineering/docs/uz/resources/openai-advanced/repo-template/AGENTS.md b/courses/learn-harness-engineering/docs/uz/resources/openai-advanced/repo-template/AGENTS.md deleted file mode 100644 index a49696d6..00000000 --- a/courses/learn-harness-engineering/docs/uz/resources/openai-advanced/repo-template/AGENTS.md +++ /dev/null @@ -1,55 +0,0 @@ -# AGENTS.md - -Ushbu repozitoriy kod yozuvchi agentlarning uzoq davom etadigan (long-running) ishlari uchun optimallashtirilgan. Ushbu faylni qisqa saqlang. Uni barcha yoʻriqnomalar toʻkib tashlangan ulkan axlatxona sifatida emas, balki yagona haqiqat manbai (system-of-record) hujjatlariga yoʻnaltiruvchi marshrutizatsiya (routing) qatlami sifatida ishlating. - -## Ishga tushirish oqimi (Startup Workflow) - -Kodni oʻzgartirishdan oldin: - -1. `pwd` yordamida repo root jildini tasdiqlang. -2. Joriy tizim xaritasi (system map) va qatʼiy bogʻliqlik (hard dependency) qoidalari bilan tanishish uchun `ARCHITECTURE.md` ni oʻqing. -3. Qaysi domenlar yoki qatlamlar eng zaif ekanligini koʻrish uchun `docs/QUALITY_SCORE.md` ni oʻqing. -4. `docs/PLANS.md` ni oʻqing, soʻngra hozir qaysi biri ustida ishlayotgan boʻlsangiz, oʻsha faol rejani (active plan) oching. -5. `docs/product-specs/` dan tegishli mahsulot taʼrifini (product spec) oʻqing. -6. Ushbu repo uchun standart ishga tushirish (bootstrap) va tekshirish (verification) yoʻlini ishga tushiring. -7. Agar bazaviy tekshiruv (baseline verification) yiqilayotgan boʻlsa, skoupni (scope) kengaytirishdan avval bazaviy holatni (baseline) toʻgʻrilang. - -## Marshrutizatsiya Xaritasi (Routing Map) - -- `ARCHITECTURE.md`: domen xaritasi, qatlamlar modeli, bogʻliqlik qoidalari -- `docs/design-docs/index.md`: dizayn qarorlari va asosiy qarashlar (core beliefs) -- `docs/product-specs/index.md`: joriy mahsulot xatti-harakatlari va qabul qilish (acceptance) maqsadlari -- `docs/PLANS.md`: reja yashash sikli (lifecycle) va ijro etish rejalari siyosati (execution-plan policy) -- `docs/QUALITY_SCORE.md`: mahsulot-domeni (product-domain) va qatlamlar salomatligi -- `docs/RELIABILITY.md`: runtime signallar, benchmarklar va qayta ishga tushish (restart) kutilmalari -- `docs/SECURITY.md`: maxfiy kalitlar (secrets), izolyatsiya (sandbox), maʼlumotlar (data) va tashqi operatsiyalar qoidalari -- `docs/FRONTEND.md`: UI cheklovlari, dizayn tizimi qoidalari, foydalanish imkoniyatlari (accessibility) tekshiruvlari - -## Ishlash shartnomasi (Working Contract) - -- Bir vaqtning oʻzida faqat bitta belgilangan reja yoki funksiya boʻlagi (feature slice) ustida ishlang. -- Faqatgina kodga qarab tahlil qilish bilangina ishni tugallandi deb hisoblamang; ishga tushirib koʻrish (runnable) orqali olingan dalil (evidence) talab qilinadi. -- Agar xatti-harakatni (behavior) oʻzgartirsangiz, oʻsha sessiyaning oʻzidayoq tegishli mahsulot (product), reja yoki ishonchlilik (reliability) hujjatlarini ham yangilang. -- Agar tekshiruvda (review) qayta-qayta bir xil izohni (feedback) koʻrsangiz, uni chatda tushuntirib oʻtirishning oʻrniga mexanik qoidaga, skript tekshiruviga (check) yoki linterʼga aylantiring (promote). -- Avtomat yaratilgan (generated) materiallarni `docs/generated/` jildida va manba maʼlumotlarini (source references) `docs/references/` jildida saqlang. -- Ushbu faylni shishirib yuborishdan koʻra, kichikroq va yangi hujjatlar qoʻshishni afzal koʻring. - -## Tugallanganlik Taʼrifi (Definition Of Done) - -Oʻzgartirish quyidagilarning barchasi toʻgʻri boʻlgandagina tugatilgan deb hisoblanadi: - -- maqsad qilingan xatti-harakat (target behavior) toʻliq amalga oshirilgan -- talab qilingan tekshiruv (verification) haqiqatda bajarilgan -- dalillar (evidence) tegishli reja yoki sifat (quality) hujjatiga bogʻlangan (linked) -- taʼsir qilgan hujjatlar (affected docs) eng yangi holatda yangilangan boʻlsa -- repozitoriy standart ishga tushirish (startup) yoʻli orqali xatosiz (cleanly) qayta ishga tusha olsa - -## Sessiya Yakuni (End Of Session) - -Sessiyani yakunlashdan oldin: - -1. Faol ishlash rejasini (active execution plan) yangilang. -2. Agar biron domen yoki qatlam mazmunan oʻzgargan boʻlsa `docs/QUALITY_SCORE.md` ni yangilang. -3. Agar yangi texnik qarz (technical debt) ni keyinga qoldirsangiz, uni `docs/exec-plans/tech-debt-tracker.md` da yozib qoʻying. -4. Tugallangan rejalarni oʻz vaqtida `docs/exec-plans/completed/` papkasiga koʻchiring. -5. Reponi qayta ishga tushib keta oladigan holatda (restartable state) hamda keyingi amal aniq koʻrsatilgan qilib qoldiring. diff --git a/courses/learn-harness-engineering/docs/uz/resources/openai-advanced/repo-template/ARCHITECTURE.md b/courses/learn-harness-engineering/docs/uz/resources/openai-advanced/repo-template/ARCHITECTURE.md deleted file mode 100644 index 29f375e2..00000000 --- a/courses/learn-harness-engineering/docs/uz/resources/openai-advanced/repo-template/ARCHITECTURE.md +++ /dev/null @@ -1,55 +0,0 @@ -# ARCHITECTURE.md - -Ushbu fayl tizimning yuqori darajadagi xaritasidir. U loʻnda boʻlishi va kerak boʻlganda yanada chuqurroq hujjatlarga yoʻnaltirishi kerak. - -## Tizim shakli (System Shape) - -- Mahsulot: `[mahsulot nomi bilan almashtiring]` -- Asosiy foydalanuvchi jarayoni (workflow): `[asosiy jarayon bilan almashtiring]` -- Runtime muhitlari: `[desktop / web / cli / services / workers]` -- Mahsulotning xatti-harakati uchun yagona haqiqat manbai (Source of truth): `docs/product-specs/` - -## Domenlar xaritasi (Domain Map) - -| Domen | Maqsad | Asosiy kirish nuqtalari | Tegishli Spec (taʼrif) | -|--------|---------|----------------------|--------------| -| `[domain-a]` | `[u nimaga egalik qiladi]` | `[modullar / marshrutlar / buyruqlar]` | `[spec fayl yoʻli]` | -| `[domain-b]` | `[u nimaga egalik qiladi]` | `[modullar / marshrutlar / buyruqlar]` | `[spec fayl yoʻli]` | - -## Qatlamli Model (Layer Model) - -Agentlar ixtiyoriy (ad hoc) arxitekturani oʻylab topmasliklari uchun oʻzgarmas (fixed) yoʻnalishli modeldan foydalaning: - -`Types -> Config -> Repo -> Service -> Runtime -> UI` - -Kesishuvchi (Cross-cutting) masalalar qatlamlararo bevosita ulanmasdan, balki aniq provider yoki adapter chegaralari (boundaries) orqali ulanishi kerak. - -## Qatʼiy Bogʻliqlik Qoidalari (Hard Dependency Rules) - -- Pastki qatlamlar (lower layers) yuqori qatlamlarga bogʻliq boʻlmasligi shart. -- UI qatlami runtime yoki service interfeyslarini aylanib oʻtmasligi (bypass) kerak. -- Maʼlumotlarga kirish (Data access) doim repozitoriylar yoki shunga ekvivalent adapterlar orqali amalga oshirilishi shart. -- Umumiy utilitalar (shared utilities) doim umumiy (generic) holida qolishi va oʻz ichida domen mantigʻini (domain logic) saqlamasligi shart. -- Yangi bogʻliqliklar (dependencies) mos keluvchi reja yoki dizayn hujjatida (design doc) asoslab berilishi (justified) kerak. - -## Kesishuvchi Interfeyslar (Cross-Cutting Interfaces) - -| Masala (Concern) | Tasdiqlangan Chegara | Qaydlar (Notes) | -|--------|-------------------|-------| -| Logging va tracing | `[provider / utility yoʻli]` | `[faqatgina structured boʻlsin, ixtiyoriy console ishlatilmasin]` | -| Auth (Avtorizatsiya) | `[provider yoʻli]` | `[token/sessiya qoidalari]` | -| Tashqi APIʼlar | `[client yoki provider yoʻli]` | `[rate limit / retry boʻyicha koʻrsatmalar]` | -| Feature flagʼlar | `[flag chegarasi]` | `[egalik huquqi]` | - -## Hozirgi muammoli hududlar (Current Hot Spots) - -- `[agentlar uchun xavfsiz oʻzgartirish eng qiyin boʻlgan hudud]` -- `[chegaralari zaif yoki testlari moʻrt boʻlgan hudud]` - -## Oʻzgartirishlar uchun tekshiruv (Change Checklist) - -Qachonki arxitekturaga tegishli kodga qoʻl ursangiz: - -1. Agar domen xaritasi yoki ruxsat etilgan chegaralar (allowed boundaries) oʻzgarsa, ushbu faylni yangilang. -2. Agar ishlash sababi (reasoning) oʻzgarsa, `docs/design-docs/` ichidagi tegishli dizayn hujjatini yangilang. -3. Agar qoidani mexanik ravishda (kod orqali) himoyalash kerak boʻlsa, bajariladigan (executable) tekshiruv (check) qoʻshing yoki yangilang. diff --git a/courses/learn-harness-engineering/docs/uz/resources/openai-advanced/repo-template/docs/DESIGN.md b/courses/learn-harness-engineering/docs/uz/resources/openai-advanced/repo-template/docs/DESIGN.md deleted file mode 100644 index 3f77ac15..00000000 --- a/courses/learn-harness-engineering/docs/uz/resources/openai-advanced/repo-template/docs/DESIGN.md +++ /dev/null @@ -1,25 +0,0 @@ -# DESIGN.md - -Ushbu fayl dizayn uchun kirish nuqtasidir (entrypoint). Uni qisqa saqlang va undan `docs/design-docs/` jildidagi batafsilroq fayllarga yoʻnaltirish (route) uchun foydalaning. - -## Maqsad - -Bitta suhbatdan (chat), sprintdan yoki tekshiruvchining xotirasidan uzoqroq yashashi kerak boʻlgan doimiy mahsulot (product) va tizim dizayni (system design) qarorlarini yozib boring. - -## Buni qachon oʻqish kerak - -- joriy dizayn falsafasi (design philosophy) kerak boʻlganda -- yangi andoza (pattern) kiritmoqchi boʻlganingizda -- qaysi dizayn qarorlari qatʼiy qabul qilingan va qaysilari hali ochiqligini bilish uchun - -## Asosiy Dizayn Hujjatlari (Canonical Design Docs) - -- `docs/design-docs/index.md`: qabul qilingan, taklif qilingan va eskirgan (deprecated) hujjatlar indeksi -- `docs/design-docs/core-beliefs.md`: butun loyihaga taalluqli agent-first (agenti birinchi oʻringa qoʻyadigan) qarashlar - -## Dizayn qoidalari - -- Dizayn hujjatlarini kichik va eng yangi holatda saqlang. -- Bitta qaror sohasi uchun bitta hujjat boʻlishini afzal koʻring. -- Oʻzgartirish qachonki ularga tayanadigan boʻlsa, reja (plans) va specʼlardan dizayn hujjatlariga havolalar (links) qoʻying. -- Agar dizayn qoidasi operatsion darajada oʻta muhim (operationally critical) boʻlib qolsa, uni avtomatlashtirilgan tekshiruvga (check) aylantiring yoki `ARCHITECTURE.md` ni yangilang. diff --git a/courses/learn-harness-engineering/docs/uz/resources/openai-advanced/repo-template/docs/FRONTEND.md b/courses/learn-harness-engineering/docs/uz/resources/openai-advanced/repo-template/docs/FRONTEND.md deleted file mode 100644 index d3c1b4db..00000000 --- a/courses/learn-harness-engineering/docs/uz/resources/openai-advanced/repo-template/docs/FRONTEND.md +++ /dev/null @@ -1,23 +0,0 @@ -# FRONTEND.md - -Bu fayl agentlar UI andozalarini kutilmagan tarzda (unpredictably) oʻylab topmasligi uchun frontend kutishlarini (frontend expectations) barqarorlashtiradi. - -## UI Tamoyillari - -- Oʻziga xoslikdan koʻra, aniqlikka va ochiqlikka (clarity) ustuvorlik bering. -- Interaktiv (interaction) oqimlarini oson topiladigan (discoverable) va qayta boshlash mumkin boʻlgan (restartable) holatda tuting. -- Bir martalik (one-off) variantlar yaratgandan koʻra, qayta ishlatiladigan sanoqli komponentlardan foydalanishni afzal koʻring. -- Foydalanish imkoniyati (Accessibility) tekshiruvlari odatiy validatsiyaning bir qismi hisoblanadi, qoʻshimcha goʻzallik emas. - -## Himoya (Guardrails) - -- Dizayn tizimi (design system) yoki komponentlar kutubxonasini `docs/references/` jildida hujjatlashtiring. -- Foydalanuvchiga koʻrinadigan asosiy holatlarni qayd eting: empty (boʻsh), loading (yuklanmoqda), success (muvaffaqiyat), error (xato), retry (qayta urinish). -- Matnlar, klaviatura xatti-harakatlari va vizual iyerarxiyani barcha oqimlarda bir xil saqlang. -- Qachonki UI bugʼi toʻgʻrilansa, tegishli validatsiya qadamini qoʻshing yoki yangilang. - -## Validatsiya Kutilmalari (Verification Expectations) - -- Eng muhim (critical) foydalanuvchi jarayonlari (user journeys) uchun dalillarni yozib oling. -- Brauzer yoki runtime validatsiya qadamlarini tegishli rejada (plan) yozib qoldiring. -- Agar vizual regressiyalar koʻp uchrasa, skrinshot yoki DOM tekshiruvlarini standartlashtiring. diff --git a/courses/learn-harness-engineering/docs/uz/resources/openai-advanced/repo-template/docs/PLANS.md b/courses/learn-harness-engineering/docs/uz/resources/openai-advanced/repo-template/docs/PLANS.md deleted file mode 100644 index f684119b..00000000 --- a/courses/learn-harness-engineering/docs/uz/resources/openai-advanced/repo-template/docs/PLANS.md +++ /dev/null @@ -1,34 +0,0 @@ -# PLANS.md - -Bu fayl ijro rejalari (execution plans) qanday yaratilishi, yangilanishi, yakunlanishi va arxivlanishini belgilaydi. - -## Qachon reja tuzish majburiydir (When A Plan Is Required) - -Agar ish quyidagi shartlarga tushsa ijro rejasini tuzing: - -- bir nechta sessiyani qamrab olsa -- bittadan ortiq tizim boʻlagini (subsystem) oʻzgartirsa -- tekshiruv yoki tarqatish xavfi (rollout risk) yuqori boʻlsa -- qayd etilishi kerak boʻlgan ochiq qarorlarga bogʻliq boʻlsa - -## Reja manzillari (Plan Locations) - -- `docs/exec-plans/active/`: ayni vaqtda boshqarilayotgan (driving) rejalar -- `docs/exec-plans/completed/`: kelajakdagi agent konteksti uchun saqlab qoʻyilgan yakunlangan rejalar -- `docs/exec-plans/tech-debt-tracker.md`: keyinga qoldirilgan ishlar va qoʻshimchalar (follow-ups) - -## Minimal reja boʻlimlari - -- maqsad (objective) -- qamrov (scope) va qamrovdan tashqari qismlar (out-of-scope) -- tekshirish yoʻli (verification path) -- xavflar va toʻsqinliklar (risks and blockers) -- jarayon jurnali (progress log) -- ochiq qarorlar (open decisions) - -## Ishlash Qoidalari (Operating Rules) - -- Bitta faol rejada bitta aniq egalik qilinuvchi joriy qadam (owned current step) boʻlishi kerak. -- Ish davom etgani sari rejani yangilab boring; unga oddiy oʻzgarmas matn (static prose) sifatida qaramang. -- Agar qaror implementatsiya (kod yozish) yoʻnalishini oʻzgartirsa, uni rejaga yozib qoʻying. -- Agentlar oldingi kontekstni osongina topishi uchun yakunlangan rejalarni `completed/` jildiga koʻchiring. diff --git a/courses/learn-harness-engineering/docs/uz/resources/openai-advanced/repo-template/docs/PRODUCT_SENSE.md b/courses/learn-harness-engineering/docs/uz/resources/openai-advanced/repo-template/docs/PRODUCT_SENSE.md deleted file mode 100644 index 9e386dfb..00000000 --- a/courses/learn-harness-engineering/docs/uz/resources/openai-advanced/repo-template/docs/PRODUCT_SENSE.md +++ /dev/null @@ -1,24 +0,0 @@ -# PRODUCT_SENSE.md - -Bu fayl agentlar faqat kodga qarab ishonchli xulosa chiqara olmaydigan uzoq muddatli mahsulot yondashuvini (durable product judgment) oʻzida saqlaydi. - -## Mahsulot Asosi (Product Core) - -- Asosiy foydalanuvchi: `[almashtiring]` -- Bajarilishi kerak boʻlgan ish (Job to be done): `[almashtiring]` -- Bartaraf etilishi kerak boʻlgan asosiy toʻsiq (frustration): `[almashtiring]` -- Qabul qilish uchun sifat darajasi (Quality bar for acceptance): `[almashtiring]` - -## Mahsulot Qoidalari (Product Rules) - -- Funksiyalar sonidan koʻra, foydalanuvchiga koʻrinadigan ishonchlilikni (user-visible reliability) ustun qoʻying. -- Noaniq xatti-harakatlarni taxmin qilishga (guess) ruxsat deb emas, balki spec (spetsifikatsiya) dagi boʻshliq sifatida qabul qiling. -- Agar kod yozish foydalanuvchi koʻradigan yoki ishonadigan narsani oʻzgartirsa, tegishli specni (matching spec) yangilang. -- Aniq jarayonlar (concrete flows) uchun mahsulot speclaridan, va mahsulotdagi kesishuvchi ustuvorliklar (cross-cutting priorities) uchun ushbu fayldan foydalaning. - -## Qatʼiyan Taqiqlangan Andozalar (No-Go Patterns) - -- Yashirin buzgʻunchi operatsiyalar (destructive actions) -- Foydalanuvchiga hech qanday bildirishnomasiz jimgina xatolik yuz berishi (Silent failure) -- Koʻrinadigan holat (visible state) uchun aniq yagona haqiqat manbai (source of truth) yoʻqligi -- Bir jumlada tushuntirib boʻlmaydigan funksiyalar (features) diff --git a/courses/learn-harness-engineering/docs/uz/resources/openai-advanced/repo-template/docs/QUALITY_SCORE.md b/courses/learn-harness-engineering/docs/uz/resources/openai-advanced/repo-template/docs/QUALITY_SCORE.md deleted file mode 100644 index 70355241..00000000 --- a/courses/learn-harness-engineering/docs/uz/resources/openai-advanced/repo-template/docs/QUALITY_SCORE.md +++ /dev/null @@ -1,39 +0,0 @@ -# QUALITY_SCORE.md - -Bu hujjat repozitoriy vaqt oʻtishi bilan kuchayib boryaptimi yoki zaiflashib borayotganini kuzatib boradi. - -## Baholash shkalasi (Grading Scale) - -- `A`: tekshirilgan (verified), tushunarli, barqaror, chegaralar himoyalangan (boundaries enforced) -- `B`: ishlayapti lekin kichik boʻshliqlar bor -- `C`: qisman ishlayapti, sezilarli tushunmovchiliklar yoki beqarorliklar mavjud -- `D`: buzilgan, xavfsiz emas yoki strukturasi umuman aniq emas - -## Mahsulot Domenlari (Product Domains) - -| Domen | Baho | Tekshiruv | Agent Tushunishi | Test Barqarorligi | Asosiy Boʻshliqlar | Oxirgi Yangilanish | -|--------|-------|-------------|-----------------|---------------|----------|-------------| -| `[domain-a]` | - | - | - | - | - | - | -| `[domain-b]` | - | - | - | - | - | - | -| `[domain-c]` | - | - | - | - | - | - | - -## Arxitektura Qatlamlari (Architectural Layers) - -| Qatlam | Baho | Chegaraga Rioya | Agent Tushunishi | Asosiy Boʻshliqlar | Oxirgi Yangilanish | -|-------|-------|---------------------|-----------------|----------|-------------| -| Types | - | - | - | - | - | -| Services | - | - | - | - | - | -| Runtime | - | - | - | - | - | -| UI | - | - | - | - | - | - -## Benchmark Kadrlar (Benchmark Snapshots) - -| Sana | Harness Varianti | Tugallash Darajasi | Takroriy Urinishlar (Retries) | Tekshiruvdan Oldingi Defektlar | Qaydlar | -|------|-----------------|----------------|--------|-----------------------|------| -| YYYY-MM-DD | `[baseline / improved / simplified]` | - | - | - | - | - -## Soddalashtirish Jurnali (Simplification Log) - -| Sana | Olib Tashlangan Komponent | Natija | Qaror | -|------|-------------------|---------|----------| -| YYYY-MM-DD | `[component]` | `[degraded (yomonlashdi) / unchanged (oʻzgarmadi)]` | `[restore (tiklash) / keep removed (olib tashlanganicha qolsin)]` | diff --git a/courses/learn-harness-engineering/docs/uz/resources/openai-advanced/repo-template/docs/RELIABILITY.md b/courses/learn-harness-engineering/docs/uz/resources/openai-advanced/repo-template/docs/RELIABILITY.md deleted file mode 100644 index 37a9f8d4..00000000 --- a/courses/learn-harness-engineering/docs/uz/resources/openai-advanced/repo-template/docs/RELIABILITY.md +++ /dev/null @@ -1,32 +0,0 @@ -# RELIABILITY.md - -Bu fayl tizimning sogʻlom (healthy) ekanligi va qayta ishga tusha olishini (restartable) qanday isbotlashini belgilaydi. - -## Standart Yoʻllar (Standard Paths) - -- Boshlangʻich (Bootstrap): `[buyruq]` -- Tekshiruv (Verification): `[buyruq]` -- Ilovani yoki xizmatni ishga tushirish (Start app or service): `[buyruq]` -- Nosozliklarni qidirish yoki ishlashni tekshirish (Debug or inspect runtime): `[buyruq]` - -## Talab qilinadigan Runtime signallar (Required Runtime Signals) - -- startup va muhim yoʻllar (critical flows) uchun strukturaviy loglar (structured logs) -- asosiy xizmatlar uchun salomatlik tekshiruvlari (health checks) -- iloji boricha sekin ishlaydigan yoʻllar uchun treys yoki timing maʼlumotlari -- qayta tiklanadigan (recoverable) xatoliklar uchun foydalanuvchiga koʻrinadigan xato (error) holatlari - -## Oltin Jarayonlar (Golden Journeys) - -- `[1-jarayon]` -- `[2-jarayon]` -- `[3-jarayon]` - -Har bir oltin jarayon (golden journey) oʻzining qayta-qayta ishga tushirilishi mumkin boʻlgan (repeatable) tekshiruv yoʻliga va aniq muvaffaqiyatsizlik signallariga ega boʻlishi kerak. - -## Ishonchlilik Qoidalari (Reliability Rules) - -- Hech qaysi funksiya agar tizim ishlagandan soʻng xatosiz qayta ishga (restart cleanly) tusha olmasa tugallangan (complete) hisoblanmaydi. -- Ish ishlash jarayonidagi (runtime) xatoliklar repo-lokal signallar asosida aniqlanishi (diagnosable) kerak. -- Agar biror qaytariladigan xatolik (repeated failure mode) yuzaga kelsa, bu uchun benchmark yoki himoya toʻsigʻi (guardrail) qoʻshing. -- Tozalash ishlari (cleanup) - ishonchlilikning bir qismidir, alohida vazifa emas. diff --git a/courses/learn-harness-engineering/docs/uz/resources/openai-advanced/repo-template/docs/SECURITY.md b/courses/learn-harness-engineering/docs/uz/resources/openai-advanced/repo-template/docs/SECURITY.md deleted file mode 100644 index d461f578..00000000 --- a/courses/learn-harness-engineering/docs/uz/resources/openai-advanced/repo-template/docs/SECURITY.md +++ /dev/null @@ -1,27 +0,0 @@ -# SECURITY.md - -Bu fayl agentlar aslo taxmin qilmasligi kerak boʻlgan xavfsizlik (security) va ishonchlilik qoidalarini belgilaydi. - -## Maxfiy Kalitlar Va Ruxsatnomalar (Secrets And Credentials) - -- Hech qachon maxfiy kalitlarni (secrets) manba kodda (source code) yoki hujjatlarda ochiqchasiga (hard-code) yozmang. -- Maxfiy kalitlarni yuklashning tasdiqlangan usullarini bu yerda hujjatlashtiring. -- Tokenlar, API kalitlar va shaxsiy maʼlumotlarni loglar va skrinshotlardan olib tashlang (redact). - -## Ishonchsiz Kiritmalar (Untrusted Input) - -- Tashqi kontentni toki validatsiyadan oʻtmaguncha ishonchsiz (untrusted) deb hisoblang. -- Ruxsat etilgan tarmoq soʻrovlari (fetch) yoki ishlash (execution) chegaralarini shu yerda yozib boring. -- Agar prompt injection yoki command injection xavfi mavjud boʻlsa, buning himoyasini (guardrail) hujjatlashtiring. - -## Tashqi Amallar (External Actions) - -- Qaysi amallar alohida aniq ruxsatnomani (explicit approval) talab qilishini roʻyxat qiling. -- Agentlar avtomatik tarzda ishga tushirmasligi kerak boʻlgan istalgan production yoki buzgʻunchi (destructive) buyruqlarni yozib qoldiring. -- Debug va tekshirish (verification) ishlari uchun xavfsiz izolyatsiyalangan (sandbox-safe) oqimlarni (workflows) afzal koʻring. - -## Bogʻliqliklar va Tekshiruv Qoidalari (Dependency And Review Rules) - -- Yangi kutubxonalarga (dependencies) boʻlgan ehtiyoj faol rejada (active plan) asoslanishi kerak. -- Xavfsizlikka sezgir (security-sensitive) oʻzgarishlar maxsus validatsiya qadamlarini talab qiladi. -- Takroriy xavfsizlikka oid review sharhlari jamoaning ogʻzaki bilimi (tribal knowledge) boʻlib qolmasdan, avtomatlashtirilgan tekshiruvlarga (checks) aylanishi kerak. diff --git a/courses/learn-harness-engineering/docs/uz/resources/openai-advanced/repo-template/docs/design-docs/core-beliefs.md b/courses/learn-harness-engineering/docs/uz/resources/openai-advanced/repo-template/docs/design-docs/core-beliefs.md deleted file mode 100644 index ccf9b3f0..00000000 --- a/courses/learn-harness-engineering/docs/uz/resources/openai-advanced/repo-template/docs/design-docs/core-beliefs.md +++ /dev/null @@ -1,9 +0,0 @@ -# Asosiy Qarashlar (Core Beliefs) - -- Agentlar uchun yagona haqiqat manbai (system of record) bu repozitoriydir. -- `AGENTS.md` - bu ensiklopediya emas, balki marshrutizatordir (router). -- Ishonchdan (confidence) koʻra tasdiqlangan dalil (verification evidence) muhimroq. -- Bitta toʻliq ishlangan chegaralangan vazifa (bounded task), koʻplab chala ishlardan afzalroq. -- Takroriy inson fikr-mulohazalari (human feedback) qayta foydalanish mumkin boʻlgan harness qoidalariga aylanishi kerak. -- Tozalash va soddalashtirish - bular ishni keyinga qoldirish emas, balki mahsulotni yetkazib berish (shipping) jarayonining bir qismidir. -- Agar agent repodan biror faktni topa olmasa, u faktni operatsion jihatdan mavjud emas deb hisoblang. diff --git a/courses/learn-harness-engineering/docs/uz/resources/openai-advanced/repo-template/docs/design-docs/index.md b/courses/learn-harness-engineering/docs/uz/resources/openai-advanced/repo-template/docs/design-docs/index.md deleted file mode 100644 index 701570d0..00000000 --- a/courses/learn-harness-engineering/docs/uz/resources/openai-advanced/repo-template/docs/design-docs/index.md +++ /dev/null @@ -1,21 +0,0 @@ -# Dizayn Hujjatlari Indeksi (Design Docs Index) - -Ushbu indeksdan dizayn tarixini izlab topish mumkin boʻlgan (discoverable) xarita sifatida foydalaning. - -## Qabul qilingan (Accepted) - -- `core-beliefs.md`: agent-first (agenti birinchi oʻringa qoʻyadigan) ishlash qarashlari va doimiy loyiha normalari - -## Taklif qilingan (Proposed) - -- `[yangi dizayn hujjatlari yoʻllarini shu yerga qoʻshing]` - -## Eskirgan (Deprecated) - -- `[eski yoki oʻrniga yangisi chiqqan dizayn hujjatlarini oʻrinbosar havolalari (replacement links) bilan shu yerga koʻchiring]` - -## Xizmat koʻrsatish qoidalari (Maintenance Rules) - -- Har bir dizayn hujjati oʻz egasiga (owner) yoki yangilash shartiga (update trigger) ega boʻlishi kerak. -- Eskirgan hujjatlarni oʻz holiga tashlab qoʻymasdan, ularni oʻchiring yoki deprecated (eskirgan) deb belgilang. -- Faol ishlash rejalarini (active execution plans) oʻzlari bogʻliq boʻlgan dizayn hujjatlariga havolalash (link) ni yoddan chiqarmang. diff --git a/courses/learn-harness-engineering/docs/uz/resources/openai-advanced/repo-template/docs/exec-plans/active/index.md b/courses/learn-harness-engineering/docs/uz/resources/openai-advanced/repo-template/docs/exec-plans/active/index.md deleted file mode 100644 index d2cf6a6a..00000000 --- a/courses/learn-harness-engineering/docs/uz/resources/openai-advanced/repo-template/docs/exec-plans/active/index.md +++ /dev/null @@ -1,9 +0,0 @@ -# Faol Rejalar (Active Plans) - -Ushbu jildda har bir faol ishlash rejasi (active execution plan) uchun bitta markdown fayl saqlang. - -Tavsiya etiladigan fayl nomi andozasi (filename pattern): - -- `YYYY-MM-DD-qisqa-mavzu.md` - -Har bir faol reja shu darajada eng soʻnggi holatda (current) boʻlishi kerakki, yangi agent sessiyasi faqat repozitoriyning oʻzigagina qarab turib ishni davom ettira olsin. diff --git a/courses/learn-harness-engineering/docs/uz/resources/openai-advanced/repo-template/docs/exec-plans/completed/index.md b/courses/learn-harness-engineering/docs/uz/resources/openai-advanced/repo-template/docs/exec-plans/completed/index.md deleted file mode 100644 index 234acbf6..00000000 --- a/courses/learn-harness-engineering/docs/uz/resources/openai-advanced/repo-template/docs/exec-plans/completed/index.md +++ /dev/null @@ -1,3 +0,0 @@ -# Yakunlangan Rejalar (Completed Plans) - -Tugallangan rejalarni oʻchirib yuborish oʻrniga shu yerga koʻchiring. Yakunlangan rejalar repozitoriy xotirasi sirtining (memory surface) bir qismi boʻlib, kelajakda ishlaydigan agentlarga kod nima uchun aynan shunday koʻrinishda ekanligini tushunishga yordam beradi. diff --git a/courses/learn-harness-engineering/docs/uz/resources/openai-advanced/repo-template/docs/exec-plans/tech-debt-tracker.md b/courses/learn-harness-engineering/docs/uz/resources/openai-advanced/repo-template/docs/exec-plans/tech-debt-tracker.md deleted file mode 100644 index cd645d18..00000000 --- a/courses/learn-harness-engineering/docs/uz/resources/openai-advanced/repo-template/docs/exec-plans/tech-debt-tracker.md +++ /dev/null @@ -1,7 +0,0 @@ -# Texnik Qarz Trekeri (Tech Debt Tracker) - -Ushbu fayldan haqiqiy, tan olingan va ataylab keyinga qoldirilgan qarzlar uchun foydalaning. - -| Sana | Soha (Area) | Qarz (Debt) | Nega Kechiktirildi | Xavf (Risk) | Keyingi Trigger | -|------|------|------|--------------|------|--------------| -| YYYY-MM-DD | `[soha]` | `[qarz]` | `[sabab]` | `[xavf]` | `[qachon qayta koʻrib chiqiladi]` | diff --git a/courses/learn-harness-engineering/docs/uz/resources/openai-advanced/repo-template/docs/generated/db-schema.md b/courses/learn-harness-engineering/docs/uz/resources/openai-advanced/repo-template/docs/generated/db-schema.md deleted file mode 100644 index dafde36d..00000000 --- a/courses/learn-harness-engineering/docs/uz/resources/openai-advanced/repo-template/docs/generated/db-schema.md +++ /dev/null @@ -1,13 +0,0 @@ -# Maʼlumotlar Bazasi Sxemasi (Database Schema) - -Agentlar kodni qayta tahlil qilib (reverse-engineering) oʻtirmasdan turib oʻqiy olishi kerak boʻlgan, avtomatik yaratiluvchi (generated) yoki hosil qilinuvchi artefaktlar uchun ushbu jilddan foydalaning. - -## Manba (Source) - -- Yaratilgan (Generated from): `[buyruq yoki manba fayl yoʻli]` -- Oxirgi marta yangilangan (Last refreshed): `YYYY-MM-DD` - -## Eslatmalar (Notes) - -- Avtomatik yaratilgan boʻlimlarni (generated sections) qoʻlda tahrirlamang. -- Asosiy sxema oʻzgarganda ushbu faylni qaytadan yarating (regenerate). diff --git a/courses/learn-harness-engineering/docs/uz/resources/openai-advanced/repo-template/docs/product-specs/index.md b/courses/learn-harness-engineering/docs/uz/resources/openai-advanced/repo-template/docs/product-specs/index.md deleted file mode 100644 index edbb00ca..00000000 --- a/courses/learn-harness-engineering/docs/uz/resources/openai-advanced/repo-template/docs/product-specs/index.md +++ /dev/null @@ -1,13 +0,0 @@ -# Mahsulot Speclari Indeksi (Product Specs Index) - -Foydalanuvchiga taalluqli (user-facing) joriy xatti-harakatlar (behavior) specʼlari uchun ushbu jilddan foydalaning. - -## Faol Speclar (Active Specs) - -- `new-user-onboarding.md` - -## Qoidalar (Rules) - -- Speclar foydalanuvchiga koʻrinadigan xatti-harakatni (user-visible behavior) va qabul qilish mezonlarini (acceptance criteria) tasvirlab berishi kerak. -- Agar implementatsiya specdan uzoqlashsa (diverges), oʻsha sessiyaning oʻzidayoq ularning birortasini yangilab qoʻying. -- Yangi agent mahsulot skoupini (product scope) tezda izlab topa olishi uchun ushbu indeksni har doim yangi holatda saqlang. diff --git a/courses/learn-harness-engineering/docs/uz/resources/openai-advanced/repo-template/docs/product-specs/new-user-onboarding.md b/courses/learn-harness-engineering/docs/uz/resources/openai-advanced/repo-template/docs/product-specs/new-user-onboarding.md deleted file mode 100644 index d4c9e16f..00000000 --- a/courses/learn-harness-engineering/docs/uz/resources/openai-advanced/repo-template/docs/product-specs/new-user-onboarding.md +++ /dev/null @@ -1,26 +0,0 @@ -# Yangi Foydalanuvchini Qabul Qilish (New User Onboarding) - -## Maqsad (Goal) - -Yangi foydalanuvchining ilova bilan birinchi marta ishlashdagi tajribasini (first-run experience) tasvirlab bering. - -## Kirish Shartlari (Entry Conditions) - -- `[oqim (flow) boshlanishidan oldingi holat]` - -## Foydalanuvchi Oqimi (User Flow) - -1. `[birinchi qadam]` -2. `[ikkinchi qadam]` -3. `[uchinchi qadam]` - -## Qabul Qilish Mezonlari (Acceptance Criteria) - -- `[kuzatilishi mumkin boʻlgan natija (observable outcome)]` -- `[kuzatilishi mumkin boʻlgan natija]` -- `[kuzatilishi mumkin boʻlgan natija]` - -## Muvaffaqiyatsizlik Holatlari (Failure States) - -- `[qayta tiklanadigan (recoverable) xatolik va foydalanuvchiga bildirishnoma]` -- `[toʻsilgan (blocked) holat va zaxira (fallback) harakati]` diff --git a/courses/learn-harness-engineering/docs/uz/resources/openai-advanced/repo-template/docs/references/design-system-reference-llms.txt b/courses/learn-harness-engineering/docs/uz/resources/openai-advanced/repo-template/docs/references/design-system-reference-llms.txt deleted file mode 100644 index 6f1c612c..00000000 --- a/courses/learn-harness-engineering/docs/uz/resources/openai-advanced/repo-template/docs/references/design-system-reference-llms.txt +++ /dev/null @@ -1,9 +0,0 @@ -Purpose: store model-friendly reference material for the design system. - -Suggested contents: -- component naming rules -- spacing and typography tokens -- state variants -- accessibility expectations - -Keep this file concise and refresh it when the upstream design system changes. diff --git a/courses/learn-harness-engineering/docs/uz/resources/openai-advanced/repo-template/docs/references/nixpacks-llms.txt b/courses/learn-harness-engineering/docs/uz/resources/openai-advanced/repo-template/docs/references/nixpacks-llms.txt deleted file mode 100644 index c30cc9c2..00000000 --- a/courses/learn-harness-engineering/docs/uz/resources/openai-advanced/repo-template/docs/references/nixpacks-llms.txt +++ /dev/null @@ -1,8 +0,0 @@ -Purpose: store a clean agent-readable extract of the deployment or packaging -rules your repository depends on. - -Suggested contents: -- build entrypoints -- runtime assumptions -- environment variable expectations -- common failure signatures diff --git a/courses/learn-harness-engineering/docs/uz/resources/openai-advanced/repo-template/docs/references/uv-llms.txt b/courses/learn-harness-engineering/docs/uz/resources/openai-advanced/repo-template/docs/references/uv-llms.txt deleted file mode 100644 index f2ceba9a..00000000 --- a/courses/learn-harness-engineering/docs/uz/resources/openai-advanced/repo-template/docs/references/uv-llms.txt +++ /dev/null @@ -1,8 +0,0 @@ -Purpose: store a compact reference for your Python package and environment -workflow when `uv` or similar tooling matters to the repo. - -Suggested contents: -- install and sync commands -- lockfile policy -- virtualenv expectations -- verification commands diff --git a/courses/learn-harness-engineering/docs/uz/resources/openai-advanced/repo-template/index.md b/courses/learn-harness-engineering/docs/uz/resources/openai-advanced/repo-template/index.md deleted file mode 100644 index 5355f548..00000000 --- a/courses/learn-harness-engineering/docs/uz/resources/openai-advanced/repo-template/index.md +++ /dev/null @@ -1,21 +0,0 @@ -# Kengaytirilgan Repo Andozasi (Advanced Repo Template) - -Qachonki sizga faqatgina minimal harnessʼdan koʻra, OpenAI uslubidagi, hujjatlari agent uchun moslashtirilgan (agent-first) kuchliroq sirt kerak boʻlsa, ushbu boshlangʻich andozani (starter) haqiqiy repozitoriyga nusxalang. - -## Nusxalash Tartibi (Copy Order) - -1. `AGENTS.md` va `ARCHITECTURE.md` ni reponing root (asosiy) jildiga koʻchiring. -2. Butun `docs/` jild daraxtini (tree) koʻchiring. -3. Birinchi navbatda `docs/PRODUCT_SENSE.md`, `docs/QUALITY_SCORE.md` va `docs/RELIABILITY.md` fayllarini toʻldiring. -4. Oʻzingizning birinchi faol rejangizni (active plan) `docs/exec-plans/active/` papkasiga qoʻshing. -5. Kirish nuqtasi (entrypoint) fayllarini qisqa tuting va barcha detallarni ulangan (linked) hujjatlar ichiga yoʻnaltiring. - -## Ushbu Andoza Nimalarni Yaxshilaydi (Optimizes For) - -- repoga biriktirilgan barqaror kontekst -- bitta ulkan yoʻriqnoma faylining oʻrniga, maʼlumotlarning asta-sekin ochilishi (progressive disclosure) -- rejaning ochiq (explicit) yashash sikli (lifecycle) -- vaqt oʻtishi bilan sifatning nazorat qilib borilishi -- agentlar va odamlar uchun oʻqish mumkin boʻlgan aniq chegaralar (readable boundaries) - -Bu yerdagi har bir faylga boshlangʻich nuqta (starter) sifatida qarang. Ularga ishonishdan oldin namunalarni, misollarni va namunaviy buyruqlarni oʻzingizning real loyihangiz maʼlumotlari bilan almashtiring. diff --git a/courses/learn-harness-engineering/docs/uz/resources/openai-advanced/sops/chrome-devtools-validation-loop.md b/courses/learn-harness-engineering/docs/uz/resources/openai-advanced/sops/chrome-devtools-validation-loop.md deleted file mode 100644 index 33e0d11b..00000000 --- a/courses/learn-harness-engineering/docs/uz/resources/openai-advanced/sops/chrome-devtools-validation-loop.md +++ /dev/null @@ -1,48 +0,0 @@ -# SOP: Chrome DevTools Validatsiya Sikli (Validation Loop) - -UI ishlarini bajarishda foydalanuvchining runtime (ishlash) davomidagi harakatlariga va skrinshotlarga tayanilganda, hamda shunchaki kodni tekshirishdan koʻra DOM holati va console natijalari muhimroq boʻlganda, ushbu SOP (Standard Operating Procedure) dan foydalaning. - -## Maqsad - -UI validatsiyasini (tekshiruvini) agent qayta-qayta ishga tushira oladigan, va toki jarayon (journey) toza va xatosiz oʻtmaguncha takrorlanadigan ketma-ketlikka aylantirish. - -## Asosiy Sikl (Core Loop) - -1. Maqsad qilingan sahifa (page) yoki ilovani (app instance) tanlash. -2. Consoleʼdagi eskirgan (stale) shovqinlarni (loglarni) tozalash. -3. OLDINGI (BEFORE) holatni yozib olish (capture). -4. UI yoʻlini (path) ishga tushirish. -5. Harakatlanish davrida yuz bergan runtime hodisalarini (events) kuzatish. -6. KEYINGI (AFTER) holatni yozib olish. -7. Tuzatish kiritish va zarur boʻlsa ilovani qayta ishga tushirish. -8. Jarayon mutlaqo toza (clean) boʻlgunicha validatsiyani qayta ishga tushirish. - -## Talab qilinadigan Kiritmalar (Inputs) - -- barqaror ishga tushirish (startup) buyrugʻi -- qayta ishga tushirib koʻrsa boʻladigan (reproducible) UI jarayoni (journey) -- DOM, console yoki skrinshotlarning holatini suratga (snapshot) olib olish imkoniyati -- “Toza” (clean) deganda nima nazarda tutilganligining qoidasi - -## Bajarish SOPʼi (Execution SOP) - -1. Rejalashtirilayotgan maqsadli jarayonni (target journey) faol rejaga yozing. -2. Kuzatiladigan atamalarda nima muvaffaqiyat hisoblanishini yozing: matn koʻrinmoqda, tugma faollashgan (enabled), xato (error) yoʻqolgan, console toza, soʻrov (request) muvaffaqiyatli oʻtgan. -3. Foydalanuvchi taʼsirigacha boʻlgan (before interaction) dastlabki holatni yozib (snapshot) oling. -4. Bir vaqtning oʻzida aynan bitta yoʻlni (path) ishga tushiring. -5. Runtime hodisalarini, DOM oʻzgarishlarini va koʻrinadigan natijalarni yozib oling. -6. Agar jarayon (journey) muvaffaqiyatsiz boʻlsa, xatoga sabab boʻlgan eng kichik qatlamni (layer) toʻgʻrilang va qayta ishga tushiring (restart). -7. Xuddi shu yoʻlni qayta ishga tushiring va OLDINGI (BEFORE)/KEYINGI (AFTER) dalillarini taqqoslang. - -## Tozalik Mezonlari (Clean Criteria) - -- maqsad qilingan (intended) vizual holat koʻrinib turibdi -- kutilmagan xatolar (unexpected errors) yoʻq -- consoleʼdagi shovqinlarning (noise) sababi aniq yoki tozalangan -- aynan shu yoʻlni (path) qayta ishga tushirish aynan bir xil natija beradi - -## Yangilanishi Kerak Boʻlgan Repo Artefaktlari - -- faol ijro etish (execution) rejasi -- agar jarayon “oltin yoʻl” (golden path - ideal holat) ga aylansa `docs/RELIABILITY.md` -- agar vizual xulq-atvor (visible behavior) oʻzgargan boʻlsa mahsulot tavsifi (product spec) diff --git a/courses/learn-harness-engineering/docs/uz/resources/openai-advanced/sops/encode-knowledge-into-repo.md b/courses/learn-harness-engineering/docs/uz/resources/openai-advanced/sops/encode-knowledge-into-repo.md deleted file mode 100644 index 65792428..00000000 --- a/courses/learn-harness-engineering/docs/uz/resources/openai-advanced/sops/encode-knowledge-into-repo.md +++ /dev/null @@ -1,42 +0,0 @@ -# SOP: Koʻrinmas Bilimlarni Repo Ichiga Kodlashtirish (Encode Unseen Knowledge Into The Repo) - -Qachonki muhim kontekst haligacha Google Docs hujjatlarida, chat yozishmalarida, chiptalarda (tickets) yoki odamlarning kallasida saqlanayotgan boʻlsa, ushbu SOPʼdan foydalaning. - -## Maqsad - -Agent koʻra olmaydigan bilimlarni kod bazasida (codebase) topish mumkin boʻladigan holatga keltirish, toki yangi boshlangan sessiya (fresh session) oldingi suhbatlarga tayangan holda ish yuritishga majbur boʻlmasin. - -## Trigger Signallari - -- Agent tizim qanday ishlashi haqida qayta-qayta soʻrayversa. -- Odamlar “biz buni Slackʼda hal qilgandik” yoki “oʻtgan hafta X nima degan boʻlsa, shuni qiling” deyishsa. -- Tekshiruvlar (reviews) davomida repoda yozilmagan mahsulot (product) yoki xavfsizlik (security) qoidalari aytib oʻtilsa. -- Yangi sessiyalar aslida hal qilinib boʻlingan masalalarni qaytadan “kashf qilishga” vaqt sarflayotsa. - -## Bajarish SOPʼi (Execution SOP) - -1. Koʻrinmas bilimlar manbalarini (invisible knowledge sources) roʻyxat qilib yozing: docs (hujjatlar), chatlar, yozilmagan jamoa qoidalari (tacit team rules), ogʻzaki qarorlar. -2. Har bir manba uchun soʻrang: bu arxitekturami, mahsulotning ishlashi (product behavior), xavfsizlik siyosati, ishonchlilik kutilmasimi (reliability expectation), reja kontekstimi yoki biror maʼlumotnomami (reference material)? -3. Uni mos keluvchi repo artefaktiga joylashtiring (encode): - - arxitektura -> `ARCHITECTURE.md` - - mahsulotning ishlashi -> `docs/product-specs/` - - dizayn sabablari (design rationale) -> `docs/design-docs/` - - ish holatlari (execution state) -> `docs/exec-plans/` - - takrorlanuvchi tashqi maʼlumotlar -> `docs/references/` - - sifat yoki ishonchlilik kutilmalari -> `docs/QUALITY_SCORE.md` yoki `docs/RELIABILITY.md` -4. Noaniq va umumiy gaplarni (vague statements) amaliyotda ishlatish mumkin boʻlgan aniq jumlalarga aylantiring. -5. Repo yagona izlab topish mumkin boʻlgan haqiqat manbai (discoverable truth) boʻlib qolishi uchun, eskirgan nusxalarni (stale copies) oʻchiring yoki bekor qiling (deprecate). - -## Yaxshi Kodlashtirish (Encoding) Qoidalari - -- Badiiy mukammallik (literary completeness) uchun emas, topish oson boʻlishi uchun (discoverability) yozing. -- Fayl nomlari aniq va tushunarli boʻlgan qisqa hujjatlarni afzal koʻring. -- Bir-biriga aloqador artefaktlarni oʻzaro bogʻlang (link). -- Uchrashuv transkriptlarini emas, balki doimiy ishlaydigan qoidalarni saqlang. -- Biror qaror qabul qilingan oʻsha sessiyaning oʻzidayoq reponi yangilang. - -## Tugallanganlik Taʼrifi (Definition Of Done) - -- Yangi agent muhim qoidani inson aralashuvisiz topa olishi kerak. -- Ayni bitta fakt (qoida) bir nechta bir-biriga zid boʻlgan (contradictory) fayllarda tarqalib ketmasligi kerak. -- Yangi artefakt u boshqaradigan (governs) kod yoki ish oqimi (workflow) ning bevosita yonida joylashgan boʻlishi kerak. diff --git a/courses/learn-harness-engineering/docs/uz/resources/openai-advanced/sops/index.md b/courses/learn-harness-engineering/docs/uz/resources/openai-advanced/sops/index.md deleted file mode 100644 index ca750781..00000000 --- a/courses/learn-harness-engineering/docs/uz/resources/openai-advanced/sops/index.md +++ /dev/null @@ -1,19 +0,0 @@ -# OpenAI Ilgʻor SOPʼlari (Advanced SOPs) - -Ushbu SOPʼlar (Standart Operatsion Protseduralar) maqoladagi ishlash andozalarini (operating patterns) siz kuzatishingiz yoki moslashtirishingiz mumkin boʻlgan aniq bajarish qoʻllanmalariga (execution playbooks) aylantiradi. - -## Kiritilgan SOPʼlar - -- [`layered-domain-architecture.md`](./layered-domain-architecture.md): aniq qatlamlar va qatlamlararo kesishuvchi (cross-cutting) chegaralarni belgilash -- [`encode-knowledge-into-repo.md`](./encode-knowledge-into-repo.md): koʻrinmas bilimlarni chat, hujjatlar va xotiradan reponing lokal fayllariga oʻtkazish -- [`observability-feedback-loop.md`](./observability-feedback-loop.md): agentlarga loglar, metrikalar, treyslar va takrorlanuvchi debug siklini (repeatable debug loop) taqdim etish -- [`chrome-devtools-validation-loop.md`](./chrome-devtools-validation-loop.md): UI xatti-harakatlarini toki toza boʻlmaguncha validatsiya qilish uchun brauzer avtomatizatsiyasi va skrinshotlardan (snapshots) foydalanish - -## Ulardan Qanday Foydalanish Kerak - -1. Joriy muammongizga (bottleneck) mos keladigan SOPʼni tanlang. -2. Yetishmayotgan artefaktlar yoki vositalarni (tooling) oʻrnatish uchun tekshiruv roʻyxatidan (checklist) foydalaning. -3. Hosil boʻlgan qoidalarni oʻzingiz koʻchirib olgan `repo-template/` hujjatlariga kiriting (encode). -4. Qayta-qayta takrorlanadigan tekshiruv (review) izohlarini avtomatlashtirilgan testlar, skriptlar yoki himoya toʻsiqlariga (guardrails) aylantiring. - -Bularga koʻr-koʻrona amal qilish shart emas. Ular harnessʼni tushunishni osonlashtirish, qoidalarga majburlay olish (enforceable) va qayta-ishlanuvchan (repeatable) qilishga xizmat qiladi. diff --git a/courses/learn-harness-engineering/docs/uz/resources/openai-advanced/sops/layered-domain-architecture.md b/courses/learn-harness-engineering/docs/uz/resources/openai-advanced/sops/layered-domain-architecture.md deleted file mode 100644 index 41bae9b5..00000000 --- a/courses/learn-harness-engineering/docs/uz/resources/openai-advanced/sops/layered-domain-architecture.md +++ /dev/null @@ -1,48 +0,0 @@ -# SOP: Qatlamli Domen Arxitekturasi (Layered Domain Architecture) - -Qachonki agent doimiy ravishda chegaralarni (boundaries) buzsa, bir xil mantiqni (logic) turli qatlamlarda (layers) takrorlasa yoki bir nechta sessiyadan keyin tekshirish qiyin boʻlib ketadigan kod yozishni boshlasa, ushbu SOPʼdan foydalaning. - -## Maqsad - -Agentlar strukturani jimgina buzib yubormasdan turib tez ishlay olishi uchun domen chegaralarini (domain boundaries) yetarlicha aniq qilib belgilash. - -## Maqsadli Model (Target Model) - -Biznes domen (business domain) ichida quyidagi yoʻnalish oqimini afzal koʻring: - -`Types -> Config -> Repo -> Service -> Runtime -> UI` - -Kesishuvchi (Cross-cutting) masalalar (masalan, auth, telemetry) aniq providerʼlar yoki adapterʼlar orqali ulanishi kerak. -Umumiy (shared) yordamchi funksiyalar (utils) domendan tashqarida qolishi va oʻz ichida domen mantigʻini (domain logic) toʻplamasligi kerak. - -## Oʻrnatish Tekshiruvi (Setup Checklist) - -- `ARCHITECTURE.md` faylida joriy domenlarni taʼriflang. -- Ruxsat etilgan bogʻliqlik yoʻnalishlarini (allowed dependency directions) `ARCHITECTURE.md` ga yozib qoʻying. -- Auth, telemetry va tashqi APIʼlar kabi qatlamlararo kesishuvchi interfeyslarni (cross-cutting interfaces) yozib qoldiring. -- Hozirgi eng qiyin chegara buzilishi (boundary violation) boʻyicha bitta qisqa eslatma (note) qoʻshing. -- Lint, testlar yoki skriptlar orqali mexanik ravishda nimalar majburiy qilinishi (enforced) kerakligini hal qiling. - -## Bajarish SOPʼi (Execution SOP) - -1. Kodni qanday uslubda implement qilishga (implementation style) tegishdan oldin kod bazasini (codebase) domenlarga xaritalang (map). -2. Har bir domen uchun ruxsat etilgan qatlamlar ketma-ketligini (allowed layer sequence) aniqlang. -3. Barcha kesishuvchi (cross-cutting) masalalarni toping va ularni providerʼlar yoki adapterʼlar orqali yoʻnaltiring. -4. Tushunarsiz, bir nechta joyda ishlatiladigan mantiqni (shared logic) yo uning egasi boʻlgan domenga yoki haqiqiy umumiy yordamchi funksiyalar (generic utils) safiga koʻchiring. -5. Qoidalarni `ARCHITECTURE.md` da hujjatlashtiring. -6. Eng koʻp xarajat (cost) talab qiladigan qoidabuzilish (violation) uchun bitta bajariladigan (executable) himoya (guardrail) qoʻshing. -7. Oʻzgarishlardan soʻng sifat bahosini (quality scoring) yangilang. - -## Tugallanganlik Taʼrifi (Definition Of Done) - -- Yangi qoʻshilgan agent maʼlum bir oʻzgarish (change) qaysi qatlamga tegishliligini ayta oladi. -- UI kodi endi repo yoki tashqi tizimlarga bevosita taʼsir (external side effects) qilmaydi. -- Kesishuvchi masalalarning maxsus kirish nuqtalari (named entry points) bor. -- Kamida bitta muhim chegara (boundary) mexanik ravishda (kod orqali) himoyalangan. - -## Yangilanishi Kerak Boʻlgan Repo Artefaktlari - -- `ARCHITECTURE.md` -- `docs/QUALITY_SCORE.md` -- Qachonki asosiy sabablar (rationale) oʻzgarsa `docs/design-docs/` -- `docs/PLANS.md` yoki faol ishlash rejasi (active execution plan) diff --git a/courses/learn-harness-engineering/docs/uz/resources/openai-advanced/sops/observability-feedback-loop.md b/courses/learn-harness-engineering/docs/uz/resources/openai-advanced/sops/observability-feedback-loop.md deleted file mode 100644 index eb93f3b5..00000000 --- a/courses/learn-harness-engineering/docs/uz/resources/openai-advanced/sops/observability-feedback-loop.md +++ /dev/null @@ -1,41 +0,0 @@ -# SOP: Kuzatuvchanlik va Qayta aloqa sikli (Observability Feedback Loop) - -Qachonki nosozliklarni izlash (debugging) sekinlashsa, agentlar doimiy ravishda ishni hech qanday dalilsiz (evidence) muvaffaqiyatli yakunlaganini eʼlon qilsa, yoki tizimning ishlashi davomidagi (runtime) xatti-harakatlarni tekshirish shunchaki kodni oʻqishdan koʻra qiyinroq boʻlsa, ushbu SOPʼdan foydalaning. - -## Maqsad - -Agentga loglar, metrikalar, treyslar va ishga tushirsa boʻladigan ish yuklamalariga (runnable workloads) ega lokal qayta aloqa siklini (local feedback loop) taqdim etish; toki u faqatgina kodga qarab emas, balki real ijro etilishdan (execution) kelib chiqib mulohaza yarata olsin. - -## Minimal Stek (Minimum Stack) - -- ilova strukturaviy loglarni (structured logs) chiqarishi kerak -- imkon boʻlganda ilova metrikalar (metrics) va treyslarni (traces) chiqarishi kerak -- lokal fan-out (tarqatuvchi) yoki maʼlumot yigʻish (collection) qatlami -- loglar, metrikalar va treyslar uchun soʻrov interfeyslari (query interfaces) -- har bir oʻzgarishdan keyin qayta ishga tushirish mumkin boʻlgan takrorlanuvchi ish yuklamasi (workload) yoki foydalanuvchi jarayoni (user journey) - -## Bajarish SOPʼi (Execution SOP) - -1. Eng muhim boʻlgan “oltin” runtime jarayonlarni (golden runtime journeys) aniqlab oling. -2. Ishga tushish (startup) va muhim yoʻllarga (critical path) strukturaviy loglar (structured logs) qoʻshing. -3. Kutilish vaqti (latency), muvaffaqiyatsizliklar soni yoki zarur boʻlsa navbat chuqurligi (queue depth) uchun metrikalar qoʻshing. -4. Sekin yoki koʻp bosqichli jarayonlar uchun treyslar yoki vaqt belgilarini (timing markers) qoʻshing. -5. Signallarni lokal dev muhitidan turib soʻrov qilish mumkinligini (queryable) taʼminlang. -6. Agentga qayta ishga tushirishi uchun bitta takrorlanuvchi ish yuklamasi (workload) yoki ssenariy taqdim eting. -7. Ushbu siklni talab qiling: soʻrov (query) -> bogʻlash (correlate) -> xulosa qilish (reason) -> kod yozish (implement) -> qayta ishga tushirish (restart) -> qayta ishga tushirish (rerun) -> tekshirish (verify). - -## Debug Sessiyasi Tekshiruvi (Checklist) - -- Nima yiqildi (failed)? -- Qaysi signal bu yiqilishni (failure) isbotlaydi? -- Qaysi qatlam ushbu xatolikka ega (owns the failure)? -- Tuzatishdan keyin nima oʻzgardi? -- Ilova toza (cleanly) qayta ishga tushdimi (restart)? -- Qayta ishga tushirilgandan keyin xuddi shu ish yuklamasi tekshiruvdan oʻtdimi (pass)? - -## Tugallanganlik Taʼrifi (Definition Of Done) - -- Agent xatolik holatini (failure mode) runtime dalillari bilan tushuntirib bera oladi. -- Har bir oʻzgarishdan keyin xuddi shu ish yuklamasini (workload) qayta ishga tushirish mumkin. -- Qayta ishga tushirish (restart) va qayta ishga tushirish (rerun) normal vazifa siklining bir qismiga aylanadi. -- Ishonchlilik signallari (reliability signals) `docs/RELIABILITY.md` da hujjatlashtirilgan. diff --git a/courses/learn-harness-engineering/docs/uz/resources/reference/coding-agent-startup-flow.md b/courses/learn-harness-engineering/docs/uz/resources/reference/coding-agent-startup-flow.md deleted file mode 100644 index b5b5c598..00000000 --- a/courses/learn-harness-engineering/docs/uz/resources/reference/coding-agent-startup-flow.md +++ /dev/null @@ -1,33 +0,0 @@ -# Kod yozuvchi agentni ishga tushirish oqimi (Coding Agent Startup Flow) - -Inisializatsiya yakunlangandan keyin, har bir sessiyaning boshida ushbu oqimdan foydalaning. - -## Belgilangan ishga tushish andozasi (Fixed Startup Template) - -1. `pwd` buyrugʻini ishga tushiring va repozitoriyning asosiy (root) jildidaligini tasdiqlang. -2. `claude-progress.md` faylini oʻqing. -3. `feature_list.json` faylini oʻqing. -4. `git log --oneline -5` orqali oxirgi commitlarni koʻrib chiqing. -5. `./init.sh` skriptini ishlating. -6. Bazaviy smoke test yoki end-to-end oqimini tekshiring. -7. Agar bazaviy holat (baseline) buzilgan boʻlsa, avvalo uni tuzating. -8. Tugallanmaganlar ichidan eng yuqori ustuvorlikka ega funksiyani (feature) tanlang. -9. Toki tekshiruvdan (verified) oʻtmaguncha yoki tasdiqlangan holda toʻsilmaguncha (blocked) faqat oʻsha funksiya ustida ishlang. - -## Nima uchun bu ketma-ketlik muhim - -- `pwd` notoʻgʻri katalogda tasodifan ishlab qoʻyishni oldini oladi. -- progress va feature fayllari yangi tahrirlar boshlanishidan oldin barqaror holatni (durable state) tiklab beradi. -- oxirgi commitlar yaqinda nima oʻzgarganini tushuntiradi. -- `init.sh` xotiraga tayanish oʻrniga oʻrnatishni (setup) standartlashtiradi. -- bazaviy tekshiruv (baseline verification) yangi yozilgan kodlar uni yashirib qoʻyishidan oldinroq mavjud buzilgan holatlarni tutadi. - -## Sessiya yakunidagi oyna (End-Of-Session Mirror) - -Sessiya xuddi shu tarzda quyidagilar bilan yakunlanishi kerak: - -1. jarayonni (progress) yozib qoldirish -2. funksiya (feature) holatini yangilash -3. zarur boʻlsa topshirish xatini (handoff) yozish -4. xavfsiz (buzilmagan) ishlarni commit qilish -5. toza qayta ishga tushirish yoʻlini (clean restart path) qoldirish diff --git a/courses/learn-harness-engineering/docs/uz/resources/reference/index.md b/courses/learn-harness-engineering/docs/uz/resources/reference/index.md deleted file mode 100644 index a37aed54..00000000 --- a/courses/learn-harness-engineering/docs/uz/resources/reference/index.md +++ /dev/null @@ -1,71 +0,0 @@ -# Inglizcha maʼlumotnomalar (English Reference) - -Ushbu qaydlar andozalardan shunchaki tarqoq fayllar toʻplami emas, balki toʻliq ishlaydigan harness sifatida qanday foydalanish kerakligini tushuntiradi. - -## Ichki maʼlumotnoma qaydlari (Internal Reference Notes) - -- [`method-map.md`](./method-map.md): koʻp uchraydigan uzoq davom etadigan muvaffaqiyatsizlik rejimlari (failure modes) ni birinchi boʻlib hal qiladigan artefakt yoki siyosat bilan bogʻlaydi. -- [`initializer-agent-playbook.md`](./initializer-agent-playbook.md): inisializatsiya agenti funksiya (feature) ishini boshlashdan oldin nimalarni yozib qoldirishi kerak. -- [`coding-agent-startup-flow.md`](./coding-agent-startup-flow.md): kod yozuvchi (coding) agentlarning keyingi ishga tushishlari uchun belgilangan (fixed) sessiya boshlash oqimi. -- [`prompt-calibration.md`](./prompt-calibration.md): asosiy (root) yoʻriqnomalarni ularni ortiqcha shishib yoki moʻrt boʻlib ketishiga yoʻl qoʻymasdan qanday qilib aniq va foydali saqlash usullari. - -## Asosiy maqolalar - -Bu roʻyxat ataylab qisqa tutilgan. Harness bu model atrofidagi ishlash tizimi (execution system) degani: agent sikli (loop), vositalarni ishlatish (tool execution), izolyatsiya (sandboxing), holat (state), kontekst, tekshiruv (verification), yakunlash (termination), orkestratsiya va kuzatuvchanlik (observability). Umumiy prompt muhandisligi yoki keng agent-freymvork maqolalari asosiy roʻyxatga kirmaydi. - -Dastlabki uchta maqola kursning tayanchi boʻlib qoladi: - -- [OpenAI: Harness engineering: leveraging Codex in an agent-first world](https://openai.com/index/harness-engineering/) (2026-02-11): agent-first repozitoriylar, repo-lokal kontekst, maxsus linting va strukturaviy himoya toʻsiqlari (guardrails). -- [Anthropic: Effective harnesses for long-running agents](https://www.anthropic.com/engineering/effective-harnesses-for-long-running-agents) (2025-11-26): inisializatsiya agenti, kod yozuvchi agent, funksiyalar roʻyxati (feature list), jarayon jurnali (progress log) va kontekst oynalari oʻrtasida ishlarni topshirish (handoff). -- [Anthropic: Harness design for long-running application development](https://www.anthropic.com/engineering/harness-design-long-running-apps) (2026-03-24): rejalashtiruvchi (planner) / yaratuvchi (generator) / baholovchi (evaluator) rollari, kontekstni qayta ishga tushirish (resets), harnessʼni soddalashtirish va eskirgan taxminlar. - -Faqatgina juda aloqador boʻlgan bir nechta 2026-yilgi maqolalar qoʻshilgan: - -- [OpenAI: Unrolling the Codex agent loop](https://openai.com/index/unrolling-the-codex-agent-loop/) (2026-01-23): Codex runtime harness, vosita (tool) chaqiruvlari, kontekstning oʻsishi va siklning yakunlanishi (loop termination). -- [Anthropic: Demystifying evals for AI agents](https://www.anthropic.com/engineering/demystifying-evals-for-ai-agents) (2026-01-09): modelni va harnessʼni birgalikda baholash (evaluating), hamda evaluation harnessʼlarni agent harnessʼlaridan farqlash. -- [LangChain: Improving Deep Agents with harness engineering](https://www.langchain.com/blog/improving-deep-agents-with-harness-engineering) (2026-02-17): kod yozuvchi agentni Terminal Bench 2.0 da Top 30 dan Top 5 ga koʻtarish uchun modelni oʻzgartirmasdan, tizim promptlari, vositalar, middleware, treysing va oʻz-oʻzini tekshirishni (self-verification) yaxshilash. -- [Thoughtworks / Martin Fowler: Harness engineering for coding agent users](https://martinfowler.com/articles/harness-engineering.html) (2026-04-02): kod yozuvchi agent foydalanuvchilarining harnessʼlari feedforward yoʻriqnomalar va feedback datchiklari sifatida (deterministik va inferensial nazoratlar bilan). -- [Cursor: Continually improving our agent harness](https://cursor.com/blog/continually-improving-agent-harness) (2026-04-30): harnessʼga offline evals, onlayn metrikalar, tool-error taksonomiyasi, modelga xos sozlashlar (tuning) va suhbat oʻrtasida modelni almashtirish (mid-chat switching) orqali doimiy takomillashib boruvchi mahsulot tizimi sifatida qarash. - -## 2026-yildagi Qoʻshimcha Manbalar (Extended References) - -Bular kursning asosiy manbalari emas, biroq maxsus harness modullarini loyihalashda juda foydali boʻlishi mumkin. Ushbu boʻlimda faqat asosiy matni bevosita agent sikli, vosita ishlatish, kontekstni boshqarish, tekshiruv, izolyatsiya, boshqaruv qatlamlari yoki regressiyani boshqarish (regression governance) ni qamrab olgan manbalargina saqlangan. Sof agent mahsulotlari, platforma eʼlonlari, jamoaviy case studyʼlar va benchmarklar kiritilmagan. - -- [OpenAI: Unlocking the Codex harness: how we built the App Server](https://openai.com/index/unlocking-the-codex-harness/) (2026-02-04): harness qayta foydalanish mumkin boʻlgan App Server protokoli sifatida (tred yashash davri, resume, fork, diffs va mijoz integratsiyalari bilan). -- [OpenAI Developers: Run long horizon tasks with Codex](https://developers.openai.com/blog/run-long-horizon-tasks-with-codex) (2026-02-23): uzoq davom etadigan vazifalar uchun doimiy (durable) loyiha xotirasi, bosqichlarni (milestone) validatsiya qilish va “done-when” misollari. -- [OpenAI: The next evolution of the Agents SDK](https://openai.com/index/the-next-evolution-of-the-agents-sdk/) (2026-04-15): modelga xos boʻlgan (model-native) harnessʼlar, sandboxʼda ishga tushirish (sandbox execution) hamda fayl/buyruq ishlashi. -- [OpenAI: An open-source spec for Codex orchestration: Symphony](https://openai.com/index/open-source-codex-orchestration-symphony/) (2026-04-27): muammolar treykerini (issue tracker) yoki Linear doskasini koʻp agentli boshqaruv paneliga (control plane) aylantirish. -- [Anthropic: Building a C compiler with a team of parallel Claudes](https://www.anthropic.com/engineering/building-c-compiler) (2026-02-05): parallel agentlar jamoalari, vazifa qulflari (task locks), git sinxronizatsiyasi, konteyner izolyatsiyasi va avtonom sikllar. -- [Anthropic: Scaling Managed Agents: Decoupling the brain from the hands](https://www.anthropic.com/engineering/managed-agents) (2026-04-08): sessiya, harness va sandboxʼni almashtirsa boʻladigan (swappable) interfeyslar sifatida ajratuvchi meta-harness qarashi. -- [Anthropic: An update on recent Claude Code quality reports](https://www.anthropic.com/engineering/april-23-postmortem) (2026-04-23): mantiqiy fikrlash kuchi, kontekstni tozalash (pruning) va tizim promptlari, regressiyani nazorat qilishni talab qiluvchi harness oʻzgarishlari sifatida. -- [LangChain: Context Management for Deep Agents](https://www.langchain.com/blog/context-management-for-deepagents) (2026-01-28): kontekstni fayl tizimiga oʻtkazish (offloading), tool-call qisqartirish, xulosalash (summarization) va kontekstni boshqarish harnessʼlari uchun maqsadli evalʼlar. -- [LangChain: Tuning Deep Agents to Work Well with Different Models](https://www.langchain.com/blog/tuning-deep-agents-different-models) (2026-04-29): promptlar, vosita nomlari, middleware va sub-agent konfiguratsiyasi uchun modelga xos harness profillari. -- [LangChain: Continual learning for AI agents](https://www.langchain.com/blog/continual-learning-for-ai-agents) (2026-04-05): agentni takomillashtirishni model, harness va kontekst qatlamlariga boʻlish (treyslar orqali quvvatlanadi). -- [Microsoft: Agent Harness in Agent Framework](https://devblogs.microsoft.com/agent-framework/agent-harness-in-agent-framework/) (2026-03-12): shell/filesystem harnessʼlari, tasdiqlash oqimi (approval flow), boshqariluvchi shell ijrosi va kontekst zichlash (compaction). -- [Google: Announcing ADK for Java 1.0.0](https://developers.googleblog.com/announcing-adk-for-java-100-building-the-future-of-ai-agents-in-java/) (2026-03-30): qayta ishlatiladigan harness primitivlari sifatida plaginlar, hodisa zichlash (event compaction), HITL, sessiya/xotira xizmatlari va A2A. -- [GitHub: Automate repository tasks with GitHub Agentic Workflows](https://github.blog/ai-and-ml/automate-repository-tasks-with-github-agentic-workflows/) (2026-02-13): GitHub Actions xavfsiz chiqishlar (outputs), izolyatsiya (sandboxing), ruxsatlar (permissions) va tekshirishlar (review) ga ega agentik jarayon (workflow) ishlatuvchisi sifatida. -- [AWS: AI agents in enterprises: Best practices with Amazon Bedrock AgentCore](https://aws.amazon.com/blogs/machine-learning/ai-agents-in-enterprises-best-practices-with-amazon-bedrock-agentcore/) (2026-02-03): Runtime, Xotira, Gateway, Identifikatsiya/Siyosat, Kuzatuvchanlik va Baholash (Evaluations) boʻyicha korporativ harness qatlamlari. -- [Stripe: Minions: Stripeʼs one-shot, end-to-end coding agents](https://stripe.dev/blog/minions-stripes-one-shot-end-to-end-coding-agents) (2026-02-09) va [Part 2](https://stripe.dev/blog/minions-stripes-one-shot-end-to-end-coding-agents-part-2) (2026-02-19): devbox izolyatsiyasi, maxsus agent harnessʼlari, blueprint holat mashinalari, qoidalar fayllari, MCP vositalari nazorati, xavfsizlik tekshiruvlari va pre-push/CI qayta aloqa sikllari. -- [Cognition: What We Learned Building Cloud Agents](https://cognition.ai/blog/what-we-learned-building-cloud-agents) (2026-04-23): VM izolyatsiyasi, sessiya snapshot/resume, orkestratsiya, boshqaruv (governance), audit loglari va cloud-agent runtimeʼlari uchun integratsiyalar. -- [Cognition: Multi-Agents: Whatʼs Actually Working](https://cognition.ai/blog/multi-agents-working) (2026-04-22): generator-verifier sikllari, toza-kontekst (clean-context) tekshiruvchilari, aqlli-doʻst (smart-friend) yoʻnaltiruvchilari, menejer-xodim muvofiqlashtirishi va agentlararo aloqa chegaralari. -- [Replit: Decision-Time Guidance: Keeping Replit Agent Reliable](https://blog.replit.com/decision-time-guidance) (2026-01-20, yangilangan 2026-01-23): barcha qoidalarni tizim promptiga tiqish oʻrniga, yengil klassifikator qaror qabul qilish nuqtasida qisqa vaziyatga qarab (situational) yoʻriqnoma kiritadi. -- [Vercel: How we made v0 an effective coding agent](https://vercel.com/blog/how-we-made-v0-an-effective-coding-agent) (2026-01-07): dinamik tizim promptlari, streaming rewrite qatlami hamda deterministik/model asosidagi avto-toʻgʻrilagichlar (autofixers). -- [Vercel: Introducing deepsec](https://vercel.com/blog/introducing-deepsec-find-and-fix-vulnerabilities-in-your-code-base) (2026-05-04): skanerlash, tekshirish (investigate), qayta validatsiya (revalidate), boyitish (enrich), eksport, plagin va rad etishni tekshirish (refusal-checker) qadamlari bilan xavfsizlikka qaratilgan kod yozuvchi agent harnessʼi. -- [Sourcegraph: CodeScaleBench](https://sourcegraph.com/blog/codescalebench-testing-coding-agents-on-large-codebases-and-multi-repo-software-engineering-tasks) (2026-03-03): MCP vositalarini oʻzlashtirish, tool-use transkriptlari, benchmark QA, tekshiruvchi (verifier)/qayta ishlab chiqaruvchanlik eshiklari va prompt/muqaddima iteratsiyalarini qamrab oluvchi eval/vosita harnessʼi boʻyicha maʼlumotnoma. - -Faqatgina 2025-yilga oid umumiy manbalar asosiy roʻyxatdan chiqarildi. Dastlabki 2025 Anthropic harness maqolasi kursning asosiy manbasi boʻlgani uchungina qoldirildi. - -## Oʻqish boʻyicha tavsiya etilgan ketma-ketlik - -1. `method-map.md` -2. `initializer-agent-playbook.md` -3. `coding-agent-startup-flow.md` -4. `prompt-calibration.md` -5. OpenAI Harness engineering -6. Anthropic Effective harnesses -7. Anthropic Harness design for long-running application development -8. OpenAI Codex agent loop -9. Anthropic agent evals -10. LangChain Improving Deep Agents -11. Thoughtworks / Martin Fowler Harness engineering for coding agent users -12. Cursor Continually improving our agent harness diff --git a/courses/learn-harness-engineering/docs/uz/resources/reference/initializer-agent-playbook.md b/courses/learn-harness-engineering/docs/uz/resources/reference/initializer-agent-playbook.md deleted file mode 100644 index e9f1f1db..00000000 --- a/courses/learn-harness-engineering/docs/uz/resources/reference/initializer-agent-playbook.md +++ /dev/null @@ -1,35 +0,0 @@ -# Inisializatsiya agenti boʻyicha qoʻllanma (Initializer Agent Playbook) - -Ushbu qoʻllanmadan repozitoriydagi birinchi jiddiy sessiya uchun, qismlarga boʻlingan funksiya (incremental feature) ishlari boshlanishidan avval foydalaning. - -## Maqsad - -Keyingi sessiyalar ishga tushirish buyruqlarini (startup commands), joriy holatni (current status) yoki vazifa chegaralarini (task boundaries) boshqatdan izlab oʻtirmasdan toʻgʻridan-toʻgʻri ishlashga oʻta olishi uchun barqaror operatsion sirt (stable operating surface) yarating. - -## Talab qilinadigan Natijalar - -Inisializatsiya agenti (initializer) oʻzidan keyin kamida quyidagi artefaktlarni qoldirishi kerak: - -- `AGENTS.md` yoki `CLAUDE.md` kabi asosiy yoʻriqnoma fayli (root instruction file) -- `feature_list.json` kabi mashina oʻqiy oladigan funksiyalar roʻyxati yuzasi -- `claude-progress.md` kabi doimiy saqlanadigan jarayon (progress) artefakti -- `init.sh` kabi standart ishga tushirish yordamchisi -- bazaviy kod skeletini oʻz ichiga olgan dastlabki xavfsiz commit - -## Tekshiruv roʻyxati (Checklist) - -1. Standart ishga tushirish yoʻlini belgilang. -2. Standart tekshiruv yoʻlini (verification path) belgilang. -3. Jarayon jurnalini (progress log) yarating va boshlangʻich holatni yozib qoldiring. -4. Ishni oʻz holatlariga ega boʻlgan aniq funksiyalarga ajrating. -5. Birinchi toza bazaviy commitʼni (clean baseline commit) yarating. - -## Muvaffaqiyat testi (Success Test) - -Hech qanday avvalgi chat kontekstiga ega boʻlmagan butunlay yangi sessiya quyidagilarga javob bera olishi kerak: - -- bu repozitoriy nima ish qiladi -- uni qanday ishga tushirish kerak -- uni qanday tekshirish (verify) kerak -- nimalar tugallanmagan -- keyingi eng yaxshi qadam nima boʻlishi kerak diff --git a/courses/learn-harness-engineering/docs/uz/resources/reference/method-map.md b/courses/learn-harness-engineering/docs/uz/resources/reference/method-map.md deleted file mode 100644 index 4a489531..00000000 --- a/courses/learn-harness-engineering/docs/uz/resources/reference/method-map.md +++ /dev/null @@ -1,16 +0,0 @@ -# Uslub xaritasi (Method Map) - -Ushbu jadval kod yozuvchi agentlarning uzoq davom etadigan muvaffaqiyatsizliklarini (failure modes) asosan ularni birinchi boʻlib hal qiluvchi artefakt yoki ishlash qoidalari (operating rule) bilan bogʻlaydi. - -| Muvaffaqiyatsizlik rejimi (Failure mode) | Amaliyotda bu qanday koʻrinadi | Asosiy yechim | Yordamchi artefakt | -| --- | --- | --- | --- | -| Sovuq ishga tushishdagi chalkashlik (Cold-start confusion) | Yangi sessiya oʻz vaqtining asosiy qismini sozlamalar va holatni oʻrganishga sarflaydi | Repozitoriyni yagona haqiqat manbaiga (system of record) aylantiring | `claude-progress.md` | -| Skoupning yoyilib ketishi (Scope sprawl) | Agent bir nechta funksiyalarni boshlaydi va birortasini ham toza tugatmaydi | Faol ish koʻlamini (scope) cheklang | `feature_list.json` | -| Vaqtidan oldin tugatish (Premature completion) | Agent kod tahrirlangandan keyin, lekin ishlashga doir dalilsiz (runnable proof) yakunlanganini eʼlon qiladi | Tugatishni dalil bilan bogʻlang | `clean-state-checklist.md` | -| Moʻrt ishga tushish (Fragile startup) | Har bir sessiya loyihani qanday ishga tushirishni (boot) qaytadan oʻrganadi | Oʻrnatish va tekshirishni (verification) standartlashtiring | `init.sh` | -| Kuchsiz topshirish (Weak handoff) | Keyingi sessiya nima tasdiqlanganini, buzilganini yoki keyingi nima ekanini bilolmaydi | Aniq topshiriq xati (handoff) bilan tugating | `session-handoff.md` | -| Subyektiv baholash | Sifatni baholash did yoki xotiraga tayanib qoladi | Natijalarni belgilangan kategoriyalar boʻyicha baholang | `evaluator-rubric.md` | - -## Ishlash tamoyili (Operating Principle) - -Kuzatilgan muvaffaqiyatsizlik (failure mode) ni bevosita hal qiladigan eng kichik artefaktni qoʻshing. Barcha ishonchlilik muammolarini bitta global yoʻriqnoma fayliga juda koʻp matn tiqish orqali hal qilishdan qoching. diff --git a/courses/learn-harness-engineering/docs/uz/resources/reference/prompt-calibration.md b/courses/learn-harness-engineering/docs/uz/resources/reference/prompt-calibration.md deleted file mode 100644 index d75e5c25..00000000 --- a/courses/learn-harness-engineering/docs/uz/resources/reference/prompt-calibration.md +++ /dev/null @@ -1,23 +0,0 @@ -# Promptni sozlash (Prompt Calibration) - -Asosiy (root) yoʻriqnomalar har bir mumkin boʻlgan harakatni emas, balki ishlash tizimini (operating frame) belgilashi kerak. - -## Asosiy (Root) faylda qoldiring - -- repozitoriy maqsadi va skoupi (scope) -- ishga tushirish (startup) yoʻli -- tekshirish (verification) yoʻli -- muhokama qilinmaydigan qatʼiy cheklovlar -- talab qilinadigan holat artefaktlari (state artifacts) -- sessiya yakuni qoidalari - -## Asosiy fayldan olib tashlang - -- juda uzun tarixiy chetki holatlar (edge cases) -- maʼlum bir mavzuga xos implementatsiya detallari -- bevosita kodning yonida turishi kerak boʻlgan lokal arxitektura eslatmalari -- faqatgina bitta kichik tizim (subsystem) ga taalluqli boʻlgan misollar - -## Ish qoidasi - -Asosiy fayl (root file) mutlaqo yangi sessiyaning tezda oʻziga kelib olishiga yordam berishi kerak. Agar fayl oʻtmishdagi barcha xatoliklarni tashlaydigan axlatxonaga aylanib borayotgan boʻlsa, maʼlumotlarni kichikroq hujjatlarga boʻling va ular oʻrniga oʻsha hujjatlarga havolalar qoldiring (link to them). diff --git a/courses/learn-harness-engineering/docs/uz/resources/templates/AGENTS.md b/courses/learn-harness-engineering/docs/uz/resources/templates/AGENTS.md deleted file mode 100644 index b3381290..00000000 --- a/courses/learn-harness-engineering/docs/uz/resources/templates/AGENTS.md +++ /dev/null @@ -1,50 +0,0 @@ -# AGENTS.md - -Ushbu repozitoriy kod yozuvchi agentlarning uzoq davom etadigan (long-running) ishlari uchun moʻljallangan. Maqsad shunchaki koʻp kod yozish emas. Maqsad reponi keyingi sessiya taxmin qilmasdan ishni davom ettira oladigan toza holatda (clean state) qoldirishdir. - -## Ishga tushirish oqimi (Startup Workflow) - -Kod yozishdan oldin: - -1. `pwd` yordamida ish katalogini (working directory) tasdiqlang. -2. Oxirgi tasdiqlangan (verified) holat va keyingi qadamni bilish uchun `claude-progress.md` ni oʻqing. -3. `feature_list.json` faylini oʻqing va eng yuqori ustuvorlikka ega boʻlgan tugallanmagan funksiyani (feature) tanlang. -4. `git log --oneline -5` yordamida oxirgi commitlarni koʻrib chiqing. -5. `./init.sh` ni ishga tushiring. -6. Yangi ishni boshlashdan oldin talab qilinadigan smoke yoki end-to-end tekshiruvni ishga tushiring. - -Agar bazaviy tekshiruv (baseline verification) allaqachon yiqilayotgan boʻlsa, avval shuni toʻgʻrilang. Buzilgan boshlangʻich holatning ustiga yangi funksiyani qurishga urinmang. - -## Ishlash qoidalari (Working Rules) - -- Bir vaqtning oʻzida faqat bitta funksiya ustida ishlang. -- Shunchaki kod yozilgani uchungina funksiyani tugatildi deb belgilamang. -- Toki blokirovka (blocker) majbur qilmaguncha (masalan, yordamchi tor doiradagi tuzatishga ehtiyoj tugʻilsa), oʻzgarishlarni faqat tanlangan funksiya skoupi (scope) ichida qiling. -- Ishlayotgan vaqtda (implementation) tekshirish (verification) qoidalarini jimgina oʻzgartirib qoymang. -- Chatdagi xulosalardan koʻra repodagi doimiy (durable) artefaktlarni ustun qoʻying. - -## Talab qilinadigan Artefaktlar - -- `feature_list.json`: funksiyalar holati (feature state) uchun yagona haqiqat manbai (source of truth) -- `claude-progress.md`: sessiya jurnali (log) va joriy tekshirilgan holat -- `init.sh`: standart ishga tushirish (startup) va tekshirish (verification) yoʻli -- `session-handoff.md`: kattaroq sessiyalar uchun topshirish xati (ixtiyoriy) - -## Tugallanganlik Taʼrifi (Definition Of Done) - -Funksiya quyidagilarning barchasi bajarilgandagina tugallangan deb hisoblanadi: - -- moʻljallangan xatti-harakat (target behavior) toʻliq amalga oshirilgan -- talab qilingan tekshiruv (verification) haqiqatda bajarilgan -- dalillar (evidence) `feature_list.json` yoki `claude-progress.md` da yozib qoldirilgan -- repozitoriy standart ishga tushirish (startup) yoʻli orqali qayta ishga tushirishga qodir (restartable) boʻlib qolgan - -## Sessiya Yakuni (End Of Session) - -Sessiyani yakunlashdan oldin: - -1. `claude-progress.md` ni yangilang. -2. `feature_list.json` ni yangilang. -3. Hal etilmagan xavf yoki blockerlarni (toʻsqinliklar) yozib qoldiring. -4. Ish toza (safe) holatga kelgach, tushunarli xabar bilan commit qiling. -5. Repozitoriyni keyingi sessiya toʻgʻridan-toʻgʻri `./init.sh` ni ishga tushira oladigan darajada toza qoldiring. diff --git a/courses/learn-harness-engineering/docs/uz/resources/templates/CLAUDE.md b/courses/learn-harness-engineering/docs/uz/resources/templates/CLAUDE.md deleted file mode 100644 index 18ee1a6e..00000000 --- a/courses/learn-harness-engineering/docs/uz/resources/templates/CLAUDE.md +++ /dev/null @@ -1,43 +0,0 @@ -# CLAUDE.md - -Siz uzoq davom etadigan (long-running) implementatsiya (kod yozish) ishlari uchun moʻljallangan repozitoriydasiz. Tezlikdan koʻra ishonchli yakunlashga, sessiyalar oʻrtasidagi uzluksizlikka (continuity) va ishlarni isbotlashga (explicit verification) ustuvorlik bering. - -## Operatsion Sikl (Operating Loop) - -Har bir sessiyaning boshida: - -1. `pwd` yordamida kutilgan repozitoriy root jildida (katalogida) ekanligingizni tasdiqlang. -2. `claude-progress.md` ni oʻqing. -3. `feature_list.json` faylini oʻqing. -4. `git log --oneline -5` yordamida oxirgi commitlarni koʻrib chiqing. -5. `./init.sh` ni ishga tushiring. -6. Bazaviy smoke yoki end-to-end yoʻli (path) buzilmaganligini tekshiring. - -Shundan soʻng aynan bitta tugallanmagan funksiyani (feature) tanlang va uni tasdiqlamaguningizcha yoki nega toʻxtab qolganini (blocked) hujjatlashtirmaguningizcha faqat oʻsha funksiya ustida ishlang. - -## Qoidalar (Rules) - -- Bir vaqtning oʻzida faqat bitta funksiya faol boʻlsin. -- Ishlaydigan dalilsiz (runnable evidence) tugallanganini (completion) daʼvo qilmang. -- Chala qolgan ishlarni yashirish uchun funksiyalar roʻyxatini (feature list) qayta yozmang. -- Vazifani tugallangandek qilib koʻrsatish uchun testlarni oʻchirmang yoki yumshatmang. -- Yagona haqiqat manbai (system of record) sifatida repozitoriy artefaktlaridan foydalaning. - -## Talab qilinadigan Fayllar - -- `feature_list.json` -- `claude-progress.md` -- `init.sh` -- qisqacha topshirish muhim boʻlgan vaqtda `session-handoff.md` - -## Tugallash Eshigi (Completion Gate) - -Funksiya faqat kerakli tekshiruv (verification) muvaffaqiyatli yakunlanib va natija yozib qoʻyilgandan keyingina `passing` holatiga oʻtishi mumkin. - -## Toʻxtashdan Oldin (Before You Stop) - -1. Jarayon jurnalini (progress log) yangilang. -2. Funksiya (feature) holatini yangilang. -3. Hali ham buzilgan yoki tekshirilmagan (unverified) narsalarni yozib qoldiring. -4. Repozitoriy ishlashda davom etishga (resume) xavfsiz holatga kelgach, commit qiling. -5. Keyingi sessiya uchun toza qayta ishga tushirish (restart) yoʻlini qoldiring. diff --git a/courses/learn-harness-engineering/docs/uz/resources/templates/claude-progress.md b/courses/learn-harness-engineering/docs/uz/resources/templates/claude-progress.md deleted file mode 100644 index d2cdfd9b..00000000 --- a/courses/learn-harness-engineering/docs/uz/resources/templates/claude-progress.md +++ /dev/null @@ -1,35 +0,0 @@ -# Jarayon jurnali (Progress Log) - -## Joriy Tasdiqlangan Holat (Current Verified State) - -- Repozitoriyning asosiy (root) jildi: -- Standart ishga tushirish (startup) yoʻli: -- Standart tekshirish (verification) yoʻli: -- Joriy eng yuqori ustuvorlikka ega tugallanmagan funksiya (feature): -- Joriy blocker (toʻsqinlik qilyotgan narsa): - -## Sessiya Jurnali (Session Log) - -### Sessiya 001 - -- Sana: -- Maqsad: -- Yakunlandi: -- Yurgizilgan tekshiruvlar: -- Qoʻlga kiritilgan dalillar: -- Commitʼlar: -- Yangilangan fayllar yoki artefaktlar: -- Maʼlum boʻlgan xavf yoki hal etilmagan muammo: -- Keyingi eng yaxshi qadam: - -### Sessiya 002 - -- Sana: -- Maqsad: -- Yakunlandi: -- Yurgizilgan tekshiruvlar: -- Qoʻlga kiritilgan dalillar: -- Commitʼlar: -- Yangilangan fayllar yoki artefaktlar: -- Maʼlum boʻlgan xavf yoki hal etilmagan muammo: -- Keyingi eng yaxshi qadam: diff --git a/courses/learn-harness-engineering/docs/uz/resources/templates/clean-state-checklist.md b/courses/learn-harness-engineering/docs/uz/resources/templates/clean-state-checklist.md deleted file mode 100644 index 9315cb95..00000000 --- a/courses/learn-harness-engineering/docs/uz/resources/templates/clean-state-checklist.md +++ /dev/null @@ -1,8 +0,0 @@ -# Toza holat (Clean State) Tekshiruv roʻyxati - -- [ ] Standart ishga tushirish (startup) yoʻli hali ham ishlayapti. -- [ ] Standart tekshirish (verification) yoʻli hali ham ishga tushirilyapti. -- [ ] Joriy jarayon (progress) jarayon jurnalida yozilgan. -- [ ] Funksiya (feature) holati aslida nima oʻtganligini va nima tasdiqlanmaganini toʻgʻri koʻrsatyapti. -- [ ] Chala qolgan ish hujjatlashtirilmagan holda tashlab ketilmagan. -- [ ] Keyingi sessiya insonning qoʻlda kiritadigan tuzatishlarisiz ishlashda davom eta oladi. diff --git a/courses/learn-harness-engineering/docs/uz/resources/templates/evaluator-rubric.md b/courses/learn-harness-engineering/docs/uz/resources/templates/evaluator-rubric.md deleted file mode 100644 index fb3c2206..00000000 --- a/courses/learn-harness-engineering/docs/uz/resources/templates/evaluator-rubric.md +++ /dev/null @@ -1,24 +0,0 @@ -# Baholovchi rubrikasi (Evaluator Rubric) - -Ushbu rubrikadan implementatsiya (kod yozish) dan soʻng va yakuniy qabul qilishdan (final acceptance) oldin foydalaning. - -| Kategoriya | Savol | Ball (0-2) | Qaydlar | -| --- | --- | --- | --- | -| Toʻgʻrilik (Correctness) | Kiritilgan xatti-harakat (behavior) soʻralgan funksiyaga (feature) mos keladimi? | | | -| Tekshiruv (Verification) | Talab qilingan tekshiruvlar haqiqatda dalili bilan ishga tushirildimi? | | | -| Skoup intizomi (Scope discipline) | Sessiya faqat tanlangan funksiya chegarasidan chiqmadimi? | | | -| Ishonchlilik (Reliability) | Natija qayta ishga tushirilganda (restart) tuzatishlarsiz ishlashda davom etadimi? | | | -| Davomiylilik qulayligi (Maintainability) | Kod va hujjatlar keyingi sessiya tushunishi uchun yetarlicha aniqmi? | | | -| Topshirishga tayyorlik (Handoff readiness) | Yangi sessiya ishni faqat repodagi artefaktlardan foydalanib davom ettira oladimi? | | | - -## Xulosa (Verdict) - -- Qabul qilish (Accept) -- Qayta koʻrish (Revise) -- Toʻxtatish (Block) - -## Talab qilinadigan keyingi qadamlar (Required Follow-Up) - -- Yetishmayotgan dalil: -- Talab qilinadigan tuzatishlar: -- Keyingi tekshiruv (review) sharti: diff --git a/courses/learn-harness-engineering/docs/uz/resources/templates/feature_list.json b/courses/learn-harness-engineering/docs/uz/resources/templates/feature_list.json deleted file mode 100644 index 53b583e7..00000000 --- a/courses/learn-harness-engineering/docs/uz/resources/templates/feature_list.json +++ /dev/null @@ -1,64 +0,0 @@ -{ - "project": "replace-with-project-name", - "last_updated": "YYYY-MM-DD", - "rules": { - "single_active_feature": true, - "passing_requires_evidence": true, - "do_not_skip_verification": true - }, - "status_legend": { - "not_started": "Ish hali boshlanmagan.", - "in_progress": "Funksiya ustida ayni vaqtda ishlanmoqda (joriy vazifa).", - "blocked": "Hujjatlashtirilgan to'siq (blocker) hal etilmaguncha ishni davom ettirib bo'lmaydi.", - "passing": "Talab qilingan tekshiruvdan (verification) o'tdi va dalillar yozib qoldirildi." - }, - "features": [ - { - "id": "chat-001", - "priority": 1, - "area": "chat", - "title": "Yangi suhbat yaratish", - "user_visible_behavior": "Foydalanuvchi 'Yangi chat' tugmasini bosishi va bo'sh suhbat oynasini ko'rishi kerak.", - "status": "not_started", - "verification": [ - "Ilovani oching.", - "Yangi chat (New Chat) tugmasini bosing.", - "Yon panelda (sidebar) yangi suhbat paydo bo'lganini tekshiring.", - "Asosiy panel bo'sh suhbat holatini ko'rsatayotganini tekshiring." - ], - "evidence": [], - "notes": "" - }, - { - "id": "chat-002", - "priority": 2, - "area": "chat", - "title": "Joriy suhbatda xabar yuborish", - "user_visible_behavior": "Foydalanuvchi xabar jo'natishi va uni faol yozishmalar orasida paydo bo'lishini ko'rishi kerak.", - "status": "not_started", - "verification": [ - "Mavjud suhbatni oching.", - "Kiritish maydoniga (input) xabar yozing.", - "Xabarni jo'nating.", - "Yangi xabar yozishmalar (thread) orasida paydo bo'lganini tekshiring." - ], - "evidence": [], - "notes": "" - }, - { - "id": "chat-003", - "priority": 3, - "area": "chat", - "title": "Faol suhbatlar ro'yxatini saqlab qolish", - "user_visible_behavior": "Ilovani qayta ishga tushirganda foydalanuvchi avval yaratilgan suhbatlarni ko'rishi kerak.", - "status": "not_started", - "verification": [ - "Ikkita suhbat yarating.", - "Ilovani qayta ishga tushiring.", - "Ikkala suhbat ham yon panelda (sidebar) ko'rinib turganini tekshiring." - ], - "evidence": [], - "notes": "" - } - ] -} diff --git a/courses/learn-harness-engineering/docs/uz/resources/templates/index.md b/courses/learn-harness-engineering/docs/uz/resources/templates/index.md deleted file mode 100644 index fb5c0b9b..00000000 --- a/courses/learn-harness-engineering/docs/uz/resources/templates/index.md +++ /dev/null @@ -1,226 +0,0 @@ -# Andozalar boʻyicha qoʻllanma (Template Guide) - -Ushbu andozalar oʻzingizning loyihangizga nusxalash uchun tayyor. Ularning har biri agentning ishlash jarayonida aniq maqsadga xizmat qiladi. Ulardagi maʼlumotlarni oʻz loyihangizning buyruqlari, fayl yoʻllari, funksiyalar (features) nomlari va tekshiruv qadamlariga moslab tahrirlang. - -## Qanday boshlash kerak - -Avval quyidagi toʻrtta faylni loyihangizning asosiy (root) jildiga koʻchiring: - -1. `AGENTS.md` yoki `CLAUDE.md` -2. `init.sh` -3. `claude-progress.md` -4. `feature_list.json` - -Qolgan fayllarni loyihangiz kattalashgani sari qoʻshib borishingiz mumkin. - ---- - -## AGENTS.md - -Asosiy (root) yoʻriqnoma fayli. Bu agent sessiya boshlaganda eng birinchi oʻqiydigan narsadir. U ishlash qoidalarini belgilaydi: kod yozishdan oldin nima qilish kerak, qanday ishlash kerak va ishni qanday tugatish kerak. - -**Qanday foydalanish kerak:** - -- Loyihangizning root katalogiga koʻchiring -- Ishga tushirish (startup) qadamlarini oʻz loyihangizdagi haqiqiy yoʻllar va buyruqlar bilan almashtiring -- Ish qoidalarini jamoangizning konvensiyalariga moslab tahrirlang -- “Tugatilganlik taʼrifi” (definition of done) boʻlimini saqlab qoling — bu eng muhim qismdir - -**U agent uchun nima qiladi:** - -- Ish boshlashdan oldin jarayon va funksiya holatini oʻqishini aytadi -- Bir vaqtning oʻzida faqat bitta funksiya ustida ishlashga majbur qiladi -- Biror narsani tugatildi deb belgilashdan oldin dalil (evidence) talab qiladi -- Toza sessiya yakuni (clean end-of-session) qanday boʻlishini taʼriflaydi - -Codex yoki boshqa agentlar uchun `AGENTS.md` ishlating. Agar Claude Code bilan ishlayotgan boʻlsangiz `CLAUDE.md` ishlating — tuzilishi bir xil, faqat Claudeʼning yoʻriqnoma uslubiga moslashtirilgan. - -## init.sh - -Ishga tushirish skripti. Bogʻliqliklarni (dependencies) oʻrnatish, tekshirish va ishga tushirish buyrugʻini koʻrsatish — barchasi bitta urinishda. - -**Qanday foydalanish kerak:** - -- Loyihangizning root katalogiga koʻchiring -- Tepadagi ushbu uchta oʻzgaruvchini tahrirlang: - - `INSTALL_CMD` — sizning kutubxonalarni oʻrnatish buyrugʻingiz (masalan, `npm install`, `pip install -r requirements.txt`) - - `VERIFY_CMD` — bazaviy tekshiruv buyrugʻingiz (masalan, `npm test`, `pytest`) - - `START_CMD` — dev serverni ishga tushirish buyrugʻingiz (masalan, `npm run dev`) -- Faylni bajariladigan qiling: `chmod +x init.sh` - -**U nima qiladi:** - -1. Joriy katalogni koʻrsatadi (agent toʻgʻri joyda ishlayotganini tasdiqlashi uchun) -2. Bogʻliqliklarni oʻrnatadi -3. Tekshiruv buyrugʻini ishga tushiradi -4. Ishga tushirish buyrugʻini koʻrsatadi (yoki agar `RUN_START_COMMAND=1` berilsa, uni bajaradi) - -Agar tekshiruv yiqilsa, agent boshqa hech narsa qilishdan oldin toʻxtab, bazaviy holatni (baseline) toʻgʻrilashi kerak. - -## claude-progress.md - -Jarayon jurnali. Har bir sessiya ushbu faylga yozib qoldiradi va har bir yangi sessiya eng avval aynan shuni oʻqiydi. - -**Qanday foydalanish kerak:** - -- Loyihangizning root katalogiga koʻchiring -- “Joriy Tasdiqlangan Holat” (Current Verified State) boʻlimini loyihangiz haqidagi maʼlumotlar bilan toʻldiring -- Har bir sessiyadan soʻng, sessiya yozuvini (session record) yangilang - -**Har bir maydon (field) nima anglatadi:** - -- **Current Verified State** — loyiha qayerda ekanligining yagona haqiqat manbai (single source of truth) - - `Repository root directory` — loyiha qayerda joylashgani - - `Standard startup path` — loyihani ishga tushirish buyrugʻi - - `Standard verification path` — testlarni ishga tushirish buyrugʻi - - `Highest priority unfinished feature` — keyingi sessiya nima ustida ishlashi kerakligi - - `Current blocker` — ishlarga toʻsqinlik qilayotgan narsalar -- **Session Record** — har bir sessiya uchun bitta yozuv - - `Goal` — nima qilishni rejalashtirgan edingiz - - `Completed` — aslida nimalar qilindi - - `Verification run` — qanday testlar ishga tushirildi - - `Evidence recorded` — qanday dalillar olingan - - `Commits` — nimalar commit qilingan - - `Known risks` — nimalar buzilgan boʻlishi ehtimoli bor - - `Next best action` — keyingi sessiya ishni qayerdan boshlashi kerak - -## feature_list.json - -Funksiyalar (Feature) trekkeri. Agent amalga oshirishi kerak boʻlgan har bir funksiyaning mashina oʻqiy oladigan (machine-readable) roʻyxati, uning holati, tekshirish qadamlari va dalillari bilan. - -**Qanday foydalanish kerak:** - -- Loyihangizning root katalogiga koʻchiring -- Namunaviy funksiyalarni (features) oʻzingizniki bilan almashtiring -- Har bir funksiya quyidagilarni talab qiladi: - - `id` — qisqa, takrorlanmas identifikator - - `priority` — butun son, kichikroq = yuqoriroq ustuvorlik - - `area` — ilovaning qaysi qismi (masalan, “chat”, “import”, “search”) - - `title` — qisqa tavsif - - `user_visible_behavior` — ishlaganda foydalanuvchi nimani koʻrishi kerak - - `status` — bularning bittasi: `not_started`, `in_progress`, `blocked`, `passing` - - `verification` — ishlashini tasdiqlash boʻyicha bosqichma-bosqich koʻrsatmalar - - `evidence` — tekshiruvdan oʻtganining qayd etilgan dalili (agent tomonidan toʻldiriladi) - - `notes` — boshqa ixtiyoriy kontekstlar - -**Holat (Status) qoidalari:** - -- `not_started` — hali tegib koʻrilmagan -- `in_progress` — ayni vaqtda ustida ishlanayotgan bitta funksiya (bir vaqtda faqat bitta) -- `blocked` — hujjatlashtirilgan muammo sababli davom ettirib boʻlmaydi -- `passing` — tekshiruvdan oʻtdi va dalil yozib qoʻyildi - -Agent har qanday vaqtda `in_progress` holatida faqat bitta funksiyaga ega boʻlishi kerak. - -## session-handoff.md - -Sessiyalar oʻrtasida qisqacha topshirish (handoff) eslatmasi. Bir sessiya tugagach, keyingisi tezroq oʻrganib ishlashi uchun foydalaniladi. - -**Qanday foydalanish kerak:** - -- Loyihangizning root katalogiga koʻchiring -- Uni har bir sessiya oxirida toʻldiring (yoki agentga toʻldirtiring) - -**Har bir boʻlim nimani qamrab oladi:** - -- **Currently verified** — nimalar ishlayotgani tasdiqlangan va qanday tekshiruvlar ishga tushirilgan -- **Changes this session** — qanday kod yoki infratuzilma oʻzgardi -- **Still broken or unverified** — maʼlum boʻlgan muammolar va xavfli joylar -- **Next best action** — keyingi sessiya nima qilishi kerak va nimalarga tegmasligi kerak -- **Commands** — ishga tushirish, tekshirish va tezkor qidiruv uchun debug buyruqlari - -Kichik sessiyalar uchun bu fayl ixtiyoriy. Ammo sessiyalar uzoq davom etganda yoki loyihada bir nechta faol joylar boʻlganda muhim hisoblanadi. - -## clean-state-checklist.md - -Har bir sessiyani yakunlashdan oldin tekshiriladigan nazorat roʻyxati (checklist). Repo keyingi sessiya toza holatda boshlanishi uchun yaxshi holatda qolishini taʼminlaydi. - -**Qanday foydalanish kerak:** - -- Loyihangizning root katalogiga koʻchiring -- Sessiyani yopishdan oldin buni birma-bir oʻqib tekshiring -- Agent ham bularni sessiyani yakunlash rutinasi sifatida tekshirishi kerak - -**U nimani tekshiradi:** - -- Standart startup hali ham ishlayapti -- Standart tekshiruv (verification) hali ham ishlayapti -- Jarayon jurnali (progress log) yangilangan -- Funksiyalar roʻyxati amaldagi holatni koʻrsatmoqda (yolgʻon `passing` kiritmalar yoʻq) -- Chala bajarilgan ishlar hisobotsiz tashlab ketilmagan -- Keyingi sessiya insonning qoʻlda kiritadigan tuzatishlarisiz ishlashda davom eta oladi - -## evaluator-rubric.md - -Agent natijalari sifatini tekshirish (review) uchun baholash qogʻozi (scorecard). Buni sessiyadan soʻng yoki loyiha bosqichlarida ish sifati talabga javob berish-bermasligini baholash uchun foydalaning. - -**Qanday foydalanish kerak:** - -- Loyihangizning root katalogiga koʻchiring -- Sessiyadan soʻng (yoki bir nechta sessiyadan keyin) agentning ishini 6 ta oʻlcham boʻyicha baholang -- Har bir oʻlchamga 0-2 gacha ball qoʻyiladi - -**Olti oʻlcham (dimensions):** - -1. **Toʻgʻrilik (Correctness)** — qilingan ish maqsad qilingan xatti-harakatlarga (behavior) mos keladimi? -2. **Tekshiruv (Verification)** — talab qilingan tekshiruvlar haqiqatda bajarilgan va dalili bormi? -3. **Skoup intizomi (Scope discipline)** — agent tanlangan funksiya chegarasidan chiqmadimi? -4. **Ishonchlilik (Reliability)** — natija restart yoki qayta ishga tushirilganda (re-run) ham ishlaydimi? -5. **Davomiylilik qulayligi (Maintainability)** — kod va hujjatlar keyingi sessiya uchun yetarlicha tushunarlimi? -6. **Topshirishga tayyorlik (Handoff readiness)** — yangi sessiya faqat repodagi artefaktlardan foydalanib davom eta oladimi? - -**Xulosa variantlari:** - -- Qabul qilish (Accept) — talabga javob beradi -- Qayta koʻrish (Revise) — qabul qilishdan oldin tuzatishlar kiritish kerak -- Toʻxtatish (Block) — avvalo asosiy muammolarni hal qilish kerak - -**Muhim: baholovchi sozlashga muhtoj.** Avvalboshdan, agentlar oʻz-oʻzini baholashda noʻnoq (poor self-judges) — ular muammolarni koʻradi va baribir oʻzlarini-oʻzlari maqullab qabul qilishga koʻndirishadi. Siz buning ustida takror va takror ishlashingiz kerak: - -1. Baholovchini (evaluator) tugatilgan bitta sprint ustida ishga tushiring. -2. Uning ballarini oʻzingizning shaxsiy (insoniy) bahoingiz bilan solishtiring. -3. Ballar farq qilgan joylarda rubrikani oʻtish/yiqilish qoidalari boʻyicha yanada aniqroq qiling. -4. Qayta ishga tushiring va mosligini (alignment) tekshiring. -5. Baholovchi sizning (odam) xulosangiz bilan doimiy mos tushmagunicha buni takrorlang. - -3-5 marta sozlash aylanishlarini reja qiling. Qanday oʻzgartirishlar moslikni yaxshilaganini bilish uchun barcha oʻzgarishlarni yozib boring. - -## quality-document.md - -Sizning loyihangizdagi har bir mahsulot domeni va arxitektura qatlamini baholaydigan sifat koʻrinishi (quality snapshot). Faqatgina alohida sessiya natijasini emas, balki vaqt oʻtishi bilan butun kod bazasi salomatligini kuzatib boradi. - -**Qanday foydalanish kerak:** - -- Loyihangizning root katalogiga koʻchiring -- Sessiya boshlashdan oldin: kod bazasining qaysi joyi eng zaif ekanligini tushunish uchun uni oʻqib chiqing -- Sessiya oxirida: nima oʻzgarganiga qarab baholarni yangilang -- Vaqt oʻtishi bilan: harnessʼdagi qanday oʻzgarishlar kod salomatligini haqiqatda yaxshilaganini koʻrish uchun snapshotʼlarni solishtiring - -**U nimani baholaydi:** - -- **Mahsulot domenlari (Product domains)** (masalan, hujjatni import qilish, Q&A oqimi, indekslash): har bir domen tekshiruv (verification) holati, agent tushuna olishi, test barqarorligi va asosiy boʻshliqlar (gaps) boʻyicha baho (A-D) oladi -- **Arxitektura qatlamlari (Architectural layers)** (masalan, main process, preload, renderer, xizmatlar): har bir qatlam chegara (boundary) qoidalariga rioya etishi va agent tushuna olishi boʻyicha baho oladi - -**Nima uchun bu muhim:** - -Baholovchi rubrika (evaluator rubric) alohida agent natijalarini baholaydi. Sifat hujjati (quality document) esa butun kod bazasining oʻzini baholaydi. Ular turli xil savollarga javob beradi: - -- Baholovchi rubrika: “Agent shu sessiyada yaxshi ish qildimi?” -- Sifat hujjati: “Loyiha vaqt oʻtishi bilan kuchayib boryaptimi yoki zaiflashyaptimi?” - -**Qachon yangilash kerak:** - -- Har bir muhim sessiyadan keyin -- Benchmarklarni taqqoslashdan oldin -- Tozalash (cleanup) yoki soddalashtirish jarayonidan keyin -- Loyihaga yangi agent yoki modelni moslashtirayotganda (onboarding) - -**Harnessni soddalashtirishga aloqadorligi:** - -Sifat hujjati shuningdek harnessni soddalashtirishni qoʻllab-quvvatlaydi. Har bir harness komponenti model nimalarni mustaqil bajara olmasligi haqidagi taxminlarga asoslanadi. Modellar takomillashgani sari bu taxminlar eskiradi. Biror komponentga ehtiyoj qolgan-qolmaganini tekshirish uchun: - -1. Sifat hujjati snapshotʼini oling. -2. Bitta harness komponentini olib tashlang. -3. Benchmark vazifalarini ishlating. -4. Yana bir snapshot oling. -5. Taqqoslang — agar baholar tushmagan boʻlsa, u komponent ortiqcha yuk edi. Agar tushib ketsa, uni joyiga qaytaring. diff --git a/courses/learn-harness-engineering/docs/uz/resources/templates/init.sh b/courses/learn-harness-engineering/docs/uz/resources/templates/init.sh deleted file mode 100755 index 092add2c..00000000 --- a/courses/learn-harness-engineering/docs/uz/resources/templates/init.sh +++ /dev/null @@ -1,29 +0,0 @@ -#!/usr/bin/env bash - -set -euo pipefail - -ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" -cd "$ROOT_DIR" - -# Bu buyruqlarni loyihangizdagi haqiqiy buyruqlar bilan almashtiring. -INSTALL_CMD=(npm install) -VERIFY_CMD=(npm test) -START_CMD=(npm run dev) - -echo "==> Ishchi katalog (Working directory): $PWD" -echo "==> Bogʻliqliklarni oʻrnatish (Syncing dependencies)" -"${INSTALL_CMD[@]}" - -echo "==> Bazaviy tekshiruvni ishga tushirish (Running baseline verification)" -"${VERIFY_CMD[@]}" - -echo "==> Ishga tushirish buyrugʻi (Startup command)" -printf ' %q' "${START_CMD[@]}" -printf '\n' - -if [ "${RUN_START_COMMAND:-0}" = "1" ]; then - echo "==> Ilovani ishga tushirish (Starting the app)" - exec "${START_CMD[@]}" -fi - -echo "Agar init.sh ilovani toʻgʻridan-toʻgʻri ishga tushirishini xohlasangiz, RUN_START_COMMAND=1 qilib bering." diff --git a/courses/learn-harness-engineering/docs/uz/resources/templates/quality-document.md b/courses/learn-harness-engineering/docs/uz/resources/templates/quality-document.md deleted file mode 100644 index c9fadf4a..00000000 --- a/courses/learn-harness-engineering/docs/uz/resources/templates/quality-document.md +++ /dev/null @@ -1,43 +0,0 @@ -# Sifat Hujjati (Quality Document) - -Har bir mahsulot domeni (product domain) va arxitektura qatlamining sifat koʻrinishi (snapshot). Agentlar ham, odamlar ham kod bazasining qayeri kuchli va qayeri zaifligini tezda bilib olish uchun ushbu hujjatdan foydalanishlari mumkin. - -**Yangilash chastotasi:** Har bir muhim sessiyadan soʻng yoki ishning yangi bosqichini boshlashdan oldin. - -**Baholash shkalasi:** - -- **A**: Barcha tekshiruvlar (verification) oʻtadi, toza arxitektura, agent tushuna oladi, testlar barqaror -- **B**: Tekshiruvlar oʻtadi, asosan toza, tushunarlilik yoki test qamrovida kichik boʻshliqlar bor -- **C**: Qisman ishlaydi, maʼlum boʻshliqlar mavjud, kodning baʼzi qismlarini agentlar tushunishi qiyin -- **D**: Ishlamayapti yoki katta strukturaviy muammolar bor - ---- - -## Mahsulot domenlari (Product Domains) - -| Domen | Baho | Tekshiruv | Agent Tushunishi | Test Barqarorligi | Asosiy Boʻshliqlar | Oxirgi Yangilanish | -|--------|-------|-------------|-----------------|---------------|----------|-------------| -| Hujjat Importi | - | - | - | - | - | - | -| Hujjat Boshqaruvi | - | - | - | - | - | - | -| Hujjat Indekslash | - | - | - | - | - | - | -| Q&A Oqimi | - | - | - | - | - | - | -| Asoslangan Javoblar | - | - | - | - | - | - | - -## Arxitektura qatlamlari (Architectural Layers) - -| Qatlam | Baho | Chegaraga Rioya | Agent Tushunishi | Asosiy Boʻshliqlar | Oxirgi Yangilanish | -|-------|-------|---------------------|-----------------|----------|-------------| -| Main Process | - | - | - | - | - | -| Preload | - | - | - | - | - | -| Renderer | - | - | - | - | - | -| Services | - | - | - | - | - | - -## Oʻzgarishlar tarixi (Change History) - -### YYYY-MM-DD - -- Oʻzgarishlar: -- Bahosi koʻtarilgan domenlar: -- Bahosi tushgan domenlar: -- Yangi aniqlangan boʻshliqlar: -- Yopilgan boʻshliqlar: diff --git a/courses/learn-harness-engineering/docs/uz/resources/templates/session-handoff.md b/courses/learn-harness-engineering/docs/uz/resources/templates/session-handoff.md deleted file mode 100644 index 27663a1c..00000000 --- a/courses/learn-harness-engineering/docs/uz/resources/templates/session-handoff.md +++ /dev/null @@ -1,30 +0,0 @@ -# Sessiyani topshirish (Session Handoff) - -## Hozir tekshirildi (Verified Now) - -- Hozir nima ishlayotgani: -- Aslida qanday tekshiruv (verification) ishlagani: - -## Ushbu sessiyada oʻzgarganlar (Changed This Session) - -- Qoʻshilgan kod yoki xatti-harakat (behavior): -- Infratuzilma yoki harness oʻzgarishlari: - -## Buzilgan yoki Tekshirilmagan (Broken Or Unverified) - -- Maʼlum boʻlgan defekt (defect): -- Tekshirilmagan yoʻl (Unverified path): -- Keyingi sessiya uchun xavf: - -## Keyingi eng yaxshi qadam (Next Best Step) - -- Eng yuqori ustuvorlikka ega tugallanmagan funksiya: -- Nima uchun bu aynan keyingisi: -- Nimani oʻtdi (passing) deb hisoblash mumkin: -- Ushbu bosqichda nima oʻzgarmasligi shart: - -## Buyruqlar (Commands) - -- Ishga tushirish (Startup): -- Tekshirish (Verification): -- Maxsus debug buyrugʻi: diff --git a/courses/learn-harness-engineering/docs/uz/skills/index.md b/courses/learn-harness-engineering/docs/uz/skills/index.md deleted file mode 100644 index b4e11384..00000000 --- a/courses/learn-harness-engineering/docs/uz/skills/index.md +++ /dev/null @@ -1,49 +0,0 @@ -# Koʻnikmalar (Skills) - -Ushbu katalog kurs bilan birga taqdim etiladigan AI agent koʻnikmalarini (skills) oʻz ichiga oladi. Koʻnikmalar (Skills) — maxsus vazifalarni bajarish uchun AI kod yozuvchi agentlari (Claude Code, Codex, Cursor, Windsurf va boshqalar) tomonidan yuklanishi mumkin boʻlgan mustaqil (self-contained) prompt andozalaridir. - -## harness-creator - -AI kod yozuvchi agentlari uchun ishlab chiqarish darajasidagi (production-grade) harness muhandisligi koʻnikmasi (skill). U sizga beshta asosiy harness quyi tizimini (subsystems): yoʻriqnomalar (instructions), holat (state), tekshiruv (verification), skoup (scope) va sessiya yashash siklini (session lifecycle) yaratish, baholash (assess) va yaxshilashda yordam beradi. - -### U nima qiladi (What It Does) - -- **Harnessʼlarni noldan yaratish** — AGENTS.md, funksiyalar roʻyxati (feature lists), tekshiruv jarayonlari (verification workflows) -- **Mavjud harnessʼlarni yaxshilash** — Eng muhim oʻzgarishlarni belgilagan holda besh quyi tizim boʻyicha baholash -- **Sessiya uzluksizligini (Session continuity) loyihalash** — Xotirani saqlab qolish (Memory persistence), jarayonni kuzatish (progress tracking), topshirish jarayonlari (handoff procedures) -- **Ishlab chiqarish andozalarini qoʻllash** — Xotira, kontekst muhandisligi, vositalar xavfsizligi, koʻp agentli muvofiqlashtirish - -### Tez boshlash (Quick Start) - -Koʻnikma fayllari repozitoriyning [`skills/harness-creator/`](https://github.com/walkinglabs/learn-harness-engineering/tree/main/skills/harness-creator) qismida joylashgan. - -Undan Claude Code bilan foydalanish uchun, `harness-creator/` jildini loyihangizning koʻnikmalar (skills) yoʻliga koʻchiring yoki agentingizni SKILL.md fayliga yoʻnaltiring. - -### Maʼlumotnoma andozalari (Reference Patterns) - -Ushbu koʻnikma oʻz ichiga chuqur tahlil qilingan 6 ta maʼlumotnoma hujjatini (reference documents) oladi: - -| Andoza (Pattern) | Qachon foydalanish kerak | -|---------|-------------| -| Xotirani saqlab qolish (Memory Persistence) | Agent sessiyalar oʻrtasida unutib qoʻysa | -| Kontekst muhandisligi (Context Engineering) | Kontekst byudjetini boshqarish, kerak vaqtda (JIT) yuklash | -| Vositalar reyestri (Tool Registry) | Vositalar xavfsizligi, parallellik nazorati (concurrency control) | -| Koʻp agentli muvofiqlashtirish (Multi-Agent Coordination) | Parallellik, ixtisoslashish (specialization) jarayonlari | -| Yashash sikli va Bootstrap (Lifecycle & Bootstrap) | Hookʼlar, fon vazifalari (background tasks), inisializatsiya | -| Tuzoqlar (Gotchas) | 15 ta koʻzga tashlanmaydigan xato turlari va ularning yechimlari | - -### Andozalar (Templates) - -Koʻnikma oʻz ichiga foydalanishga tayyor andozalarni oladi: - -- `agents.md` — Ishlash qoidalari kiritilgan AGENTS.md qolipi (scaffold) -- `feature-list.json` — JSON Schema + namunaviy funksiyalar roʻyxati (example feature list) -- `init.sh` — Standart inisializatsiya skripti -- `progress.md` — Sessiya jarayoni jurnali (progress log) andozasi - -### Ushbu koʻnikma qanday yaratilgan (How This Skill Was Built) - -`harness-creator`, agent koʻnikmalarini (skills) yaratish, test qilish va iteratsiya qilish uchun Anthropicʼning rasmiy meta-koʻnikmasi boʻlgan **skill-creator** metodologiyasidan foydalanib ishlab chiqilgan. skill-creator oʻzida oʻrnatilgan eval ishga tushiruvchilar (eval runners), baholovchilar (graders) va benchmark koʻruvchisi (benchmark viewer) bilan tuzilmali jarayonni (qoralama → test → baholash → iteratsiya) taqdim etadi. - -- **skill-creator manbasi**: [anthropics/skills — skill-creator](https://github.com/anthropics/skills/tree/main/skills/skill-creator) -- **Claude Code skills hujjatlari**: [anthropics/claude-code — plugin-dev/skills](https://github.com/anthropics/claude-code/tree/main/plugins/plugin-dev/skills) diff --git a/courses/learn-harness-engineering/docs/vi/index.md b/courses/learn-harness-engineering/docs/vi/index.md deleted file mode 100644 index 4fe9babb..00000000 --- a/courses/learn-harness-engineering/docs/vi/index.md +++ /dev/null @@ -1,73 +0,0 @@ -# Chào mừng đến với Learn Harness Engineering - -Learn Harness Engineering là khóa học chuyên về kỹ thuật của các AI coding agent. Chúng tôi đã nghiên cứu sâu sắc và tổng hợp các lý thuyết cũng như thực tiễn về Harness Engineering tiên tiến nhất trong ngành. Các tài liệu tham khảo cốt lõi của chúng tôi bao gồm: -- [OpenAI: Harness engineering: leveraging Codex in an agent-first world](https://openai.com/index/harness-engineering/) -- [Anthropic: Effective harnesses for long-running agents](https://www.anthropic.com/engineering/effective-harnesses-for-long-running-agents) -- [Anthropic: Harness design for long-running application development](https://www.anthropic.com/engineering/harness-design-long-running-apps) -- [Awesome Harness Engineering](https://github.com/walkinglabs/awesome-harness-engineering) - -Thông qua thiết kế môi trường có hệ thống, quản lý trạng thái, xác minh và hệ thống kiểm soát, khóa học này dạy bạn cách làm cho các công cụ lập trình agent như Codex và Claude Code thực sự đáng tin cậy. Nó giúp bạn xây dựng tính năng, sửa lỗi và tự động hóa các tác vụ phát triển bằng cách ràng buộc trợ lý AI của bạn bằng các quy tắc và ranh giới rõ ràng. - -## Bắt đầu - -Chọn lộ trình học của bạn để bắt đầu. Khóa học được chia thành các bài giảng lý thuyết, các dự án thực hành và thư viện tài nguyên sẵn sàng sao chép. - - - -## Cơ chế cốt lõi của một Harness - -Một harness không "làm cho mô hình thông minh hơn"; thay vào đó, nó thiết lập một **hệ thống làm việc** vòng kín cho mô hình. Bạn có thể hiểu quy trình làm việc cốt lõi của nó qua sơ đồ đơn giản này: - -```mermaid -graph TD - A["Mục tiêu rõ ràng
    AGENTS.md"] --> B("Khởi tạo
    init.sh") - B --> C{"Chạy tác vụ
    AI Agent"} - C -->|Gặp sự cố| D["Phản hồi Runtime
    CLI / Logs"] - D -->|Tự động sửa| C - C -->|Mã hoàn thành| E{"Xác minh & QA
    Test suite"} - E -->|Thất bại| D - E -->|Thành công| F["Dọn dẹp & Bàn giao
    claude-progress.md"] - - classDef primary fill:#D95C41,stroke:#C14E36,color:#fff,font-weight:bold; - classDef process fill:#F4F3EE,stroke:#D1D1D1,color:#1A1A1A; - classDef check fill:#EAE8E1,stroke:#B3B3B3,color:#1A1A1A; - - class A,F primary; - class B,D process; - class C,E check; -``` - -## Những gì bạn sẽ học - -Dưới đây là một số khái niệm chính mà bạn sẽ nắm vững: - -
      -
    • Ràng buộc hành vi của agent bằng các quy tắc và ranh giới rõ ràng.
    • -
    • Duy trì ngữ cảnh qua các tác vụ dài hạn, đa phiên.
    • -
    • Ngăn chặn agent khỏi việc tuyên bố thành công quá sớm.
    • -
    • Xác minh công việc bằng các bài kiểm thử toàn bộ quy trình và tự phản ánh.
    • -
    • Làm cho runtime có thể quan sát được và có thể gỡ lỗi.
    • -
    - -## Các bước tiếp theo - -Khi bạn đã hiểu các khái niệm cốt lõi, các hướng dẫn này sẽ giúp bạn đi sâu hơn: - - diff --git a/courses/learn-harness-engineering/docs/vi/lectures/lecture-01-why-capable-agents-still-fail/code/failure-pattern-demo.ts b/courses/learn-harness-engineering/docs/vi/lectures/lecture-01-why-capable-agents-still-fail/code/failure-pattern-demo.ts deleted file mode 100644 index 311e665b..00000000 --- a/courses/learn-harness-engineering/docs/vi/lectures/lecture-01-why-capable-agents-still-fail/code/failure-pattern-demo.ts +++ /dev/null @@ -1,170 +0,0 @@ -/** - * failure-pattern-demo.ts - * - * Simulates the 4-step failure pattern that capable agents fall into: - * 1. Incomplete context - * 2. Locally reasonable changes - * 3. No global verification - * 4. Premature completion - * - * Run: npx tsx docs/lectures/lecture-01-why-capable-agents-still-fail/code/failure-pattern-demo.ts - */ - -// --------------------------------------------------------------------------- -// Types -// --------------------------------------------------------------------------- - -interface StepState { - step: number; - name: string; - contextAvailable: string[]; - contextMissing: string[]; - actionTaken: string; - localOutcome: string; - globalImpact: string; - completed: boolean; -} - -// --------------------------------------------------------------------------- -// Simulated "model" -- a simple decision function that bases its output -// solely on whatever context it has been given. -// --------------------------------------------------------------------------- - -function modelDecide(context: string[], task: string): string { - const has = (s: string) => context.some((c) => c.includes(s)); - - // The task is to "add a search endpoint to the API". - // Correct answer requires knowing about auth middleware and rate limiting. - - if (!has("auth")) { - return "Created new route handler /search without authentication checks"; - } - if (!has("rate-limit")) { - return "Added search route with auth but forgot rate limiting"; - } - if (!has("test-standards")) { - return "Implemented search with auth and rate-limit, but no tests"; - } - return "Fully implemented search endpoint with auth, rate-limit, and tests"; -} - -// --------------------------------------------------------------------------- -// Failure simulation -// --------------------------------------------------------------------------- - -function simulateFailurePattern(): StepState[] { - const steps: StepState[] = []; - - // ---- Step 1: Incomplete Context ---- - const step1Context = ["project structure", "route definitions"]; - const step1Missing = ["auth middleware", "rate-limiting policy", "test standards"]; - const step1Decision = modelDecide(step1Context, "add search endpoint"); - - steps.push({ - step: 1, - name: "Incomplete Context", - contextAvailable: step1Context, - contextMissing: step1Missing, - actionTaken: step1Decision, - localOutcome: "Looks good -- route compiles, returns data", - globalImpact: "Missing auth means unauthenticated access to search", - completed: false, - }); - - // ---- Step 2: Locally Reasonable Changes ---- - // The agent adds auth after a hint, but still lacks other context. - const step2Context = [...step1Context, "auth middleware"]; - const step2Missing = ["rate-limiting policy", "test standards"]; - const step2Decision = modelDecide(step2Context, "add search endpoint"); - - steps.push({ - step: 2, - name: "Locally Reasonable Changes", - contextAvailable: step2Context, - contextMissing: step2Missing, - actionTaken: step2Decision, - localOutcome: "Route has auth -- looks complete locally", - globalImpact: "No rate limiting means the endpoint can be abused", - completed: false, - }); - - // ---- Step 3: No Global Verification ---- - const step3Context = [...step2Context, "rate-limiting policy"]; - const step3Missing = ["test standards"]; - const step3Decision = modelDecide(step3Context, "add search endpoint"); - - steps.push({ - step: 3, - name: "No Global Verification", - contextAvailable: step3Context, - contextMissing: step3Missing, - actionTaken: step3Decision, - localOutcome: "Feature appears fully implemented", - globalImpact: "No tests -- regression risk, violates project standards", - completed: false, - }); - - // ---- Step 4: Premature Completion ---- - steps.push({ - step: 4, - name: "Premature Completion", - contextAvailable: step3Context, - contextMissing: step3Missing, - actionTaken: 'Agent outputs: "Done. Added search endpoint."', - localOutcome: "Agent is satisfied, task marked complete", - globalImpact: "Task is incomplete -- missing tests, no E2E verification", - completed: true, - }); - - return steps; -} - -// --------------------------------------------------------------------------- -// Comparison table -// --------------------------------------------------------------------------- - -function printComparisonTable(steps: StepState[]): void { - console.log("\n" + "=".repeat(90)); - console.log(" FAILURE PATTERN DEMO -- 4 Steps to a Broken Deliverable"); - console.log("=".repeat(90)); - - for (const s of steps) { - console.log(`\n Step ${s.step}: ${s.name}`); - console.log(" " + "-".repeat(60)); - console.log(` Context available : ${s.contextAvailable.join(", ")}`); - console.log(` Context missing : ${s.contextMissing.join(", ") || "(none)"}`); - console.log(` Action taken : ${s.actionTaken}`); - console.log(` Local outcome : ${s.localOutcome}`); - console.log(` Global impact : ${s.globalImpact}`); - console.log(` Marked complete? : ${s.completed ? "YES (premature)" : "No"}`); - } - - // Summary comparison - console.log("\n" + "=".repeat(90)); - console.log(" COMPARISON: What the agent saw vs. what was actually needed"); - console.log("=".repeat(90)); - - const totalRequired = ["project structure", "route definitions", "auth middleware", "rate-limiting policy", "test standards"]; - const finalAvailable = steps[steps.length - 1].contextAvailable; - - const header = "| Criterion | Available | Missing | Status |"; - const sep = "|------------------------|-----------|---------|----------|"; - console.log("\n" + header); - console.log(sep); - - for (const item of totalRequired) { - const avail = finalAvailable.includes(item); - const row = `| ${item.padEnd(23)}| ${(avail ? "Yes" : "No").padEnd(10)}| ${(!avail ? "Yes" : "").padEnd(8)}| ${(avail ? "OK" : "GAP").padEnd(9)}|`; - console.log(row); - } - - const gapCount = totalRequired.filter((i) => !finalAvailable.includes(i)).length; - console.log("\n Result: Agent completed with " + gapCount + " of " + totalRequired.length + " context items missing."); - console.log(" This is the core failure pattern: each step looked reasonable in isolation.\n"); -} - -// --------------------------------------------------------------------------- -// Run -// --------------------------------------------------------------------------- - -printComparisonTable(simulateFailurePattern()); diff --git a/courses/learn-harness-engineering/docs/vi/lectures/lecture-01-why-capable-agents-still-fail/code/failure-signals-checklist.md b/courses/learn-harness-engineering/docs/vi/lectures/lecture-01-why-capable-agents-still-fail/code/failure-signals-checklist.md deleted file mode 100644 index 9e72ad41..00000000 --- a/courses/learn-harness-engineering/docs/vi/lectures/lecture-01-why-capable-agents-still-fail/code/failure-signals-checklist.md +++ /dev/null @@ -1,9 +0,0 @@ -# Danh sách Kiểm tra Tín hiệu Lỗi - -Sử dụng danh sách kiểm tra này khi xem xét một lần chạy harness yếu. - -- Agent có hỏi hoặc suy ra không chính xác cách khởi động ứng dụng không? -- Nó có tạo ra các thư mục hoặc trừu tượng không khớp với sản phẩm dự định không? -- Nó có dừng lại sau khi tạo ra một UI shell có thể nhìn thấy nhưng không có workflow hoàn chỉnh không? -- Nó có để lại các ghi chú hoặc artifact giúp lần chạy tiếp theo tiếp tục không? -- Một phiên mới có thể hiểu chuyện gì đã xảy ra trong vòng dưới năm phút không? diff --git a/courses/learn-harness-engineering/docs/vi/lectures/lecture-01-why-capable-agents-still-fail/code/index.md b/courses/learn-harness-engineering/docs/vi/lectures/lecture-01-why-capable-agents-still-fail/code/index.md deleted file mode 100644 index ce69f8cf..00000000 --- a/courses/learn-harness-engineering/docs/vi/lectures/lecture-01-why-capable-agents-still-fail/code/index.md +++ /dev/null @@ -1,7 +0,0 @@ -# Mã cho Bài 01 - -Sử dụng thư mục này cho các ví dụ nhỏ minh họa: - -- một mô hình mạnh thất bại trong môi trường yếu -- thiết lập repo không được chỉ định rõ ràng -- thiếu vòng phản hồi diff --git a/courses/learn-harness-engineering/docs/vi/lectures/lecture-01-why-capable-agents-still-fail/code/underspecified-task.md b/courses/learn-harness-engineering/docs/vi/lectures/lecture-01-why-capable-agents-still-fail/code/underspecified-task.md deleted file mode 100644 index 8f328b97..00000000 --- a/courses/learn-harness-engineering/docs/vi/lectures/lecture-01-why-capable-agents-still-fail/code/underspecified-task.md +++ /dev/null @@ -1,18 +0,0 @@ -# Ví dụ Tác vụ Không được Chỉ định Rõ ràng - -Xây dựng ứng dụng desktop cơ sở kiến thức với hỏi đáp AI. - -Ràng buộc: - -- Không có ràng buộc nào được chỉ định -- Không có lệnh khởi động được cung cấp -- Không có hướng dẫn cấu trúc thư mục -- Không có data model được định nghĩa -- Không có tiêu chí hoàn thành rõ ràng - -Kết quả điển hình từ loại prompt này: - -- agent tự phát minh ra cấu trúc một cách tùy tiện -- ứng dụng có thể biên dịch nhưng không khởi động nhất quán -- UI có thể xuất hiện trước khi có bất kỳ đường dẫn ingest/query nào có thể sử dụng được -- agent thường dừng lại sau thành công về mặt thẩm mỹ diff --git a/courses/learn-harness-engineering/docs/vi/lectures/lecture-01-why-capable-agents-still-fail/index.md b/courses/learn-harness-engineering/docs/vi/lectures/lecture-01-why-capable-agents-still-fail/index.md deleted file mode 100644 index 51e80096..00000000 --- a/courses/learn-harness-engineering/docs/vi/lectures/lecture-01-why-capable-agents-still-fail/index.md +++ /dev/null @@ -1,117 +0,0 @@ -[English Version →](../../../en/lectures/lecture-01-why-capable-agents-still-fail/) | [中文版本 →](../../../zh/lectures/lecture-01-why-capable-agents-still-fail/) - -> Ví dụ mã nguồn: [code/](https://github.com/walkinglabs/learn-harness-engineering/blob/main/docs/vi/lectures/lecture-01-why-capable-agents-still-fail/code/) -> Dự án thực hành: [Dự án 01. Chỉ Prompt vs. Ưu tiên Quy tắc](./../../projects/project-01-baseline-vs-minimal-harness/index.md) - -# Bài 01. Mô hình Mạnh Không có nghĩa là Thực thi Đáng tin cậy - -Bạn tự coi mình là người có kinh nghiệm trong thế giới AI — đăng ký Claude Pro, có API key GPT-4o, thuộc lòng các con số trên bảng xếp hạng SWE-bench. Một ngày nào đó bạn cuối cùng giao một dự án thực sự cho một AI agent, đầy tự tin. Kết quả? Nó thêm một tính năng nhưng làm hỏng các bài test, sửa một lỗi nhưng gây ra thêm hai lỗi khác, chạy 20 phút và tự hào tuyên bố "xong" — nhưng khi bạn nhìn vào mã nguồn, nó hoàn toàn không phải là thứ bạn yêu cầu. - -Phản ứng đầu tiên của bạn? "Mô hình này không đủ tốt. Thời điểm nâng cấp." Khoan đã. Trước khi bạn mở ví, hãy cân nhắc rằng vấn đề có thể hoàn toàn không phải là mô hình. - -Hãy nhìn vào một vài con số. Tính đến cuối năm 2025, các coding agent mạnh nhất trên SWE-bench Verified đạt khoảng 50-60%. Và đó là trên các tác vụ được lựa chọn cẩn thận với mô tả vấn đề rõ ràng và các bài test hiện có. Chuyển sang môi trường phát triển hàng ngày của bạn — yêu cầu mơ hồ, không có test, các quy tắc kinh doanh ẩn rải rác khắp nơi — và con số đó chỉ giảm xuống. - -Nhưng đằng sau những con số này ẩn chứa một sự thật phản trực giác. - -## Cùng Con Ngựa, Số Phận Khác Nhau - -Anthropic đã thực hiện một thí nghiệm có kiểm soát. Cùng một prompt ("xây dựng một trình tạo trò chơi retro 2D"), cùng một mô hình (Opus 4.5). Lần chạy đầu tiên: trần trụi, không có hỗ trợ — 20 phút, $9, các tính năng cốt lõi của trò chơi hoàn toàn không hoạt động. Lần chạy thứ hai: harness đầy đủ (kiến trúc ba agent: planner + generator + evaluator) — 6 giờ, $200, trò chơi có thể chơi được. - -Họ không thay đổi mô hình. Opus 4.5 vẫn là Opus 4.5. Những gì thay đổi là bộ yên cương. - -Bài viết về harness engineering năm 2025 của OpenAI nói thẳng: Codex trong một kho lưu trữ được trang bị harness tốt đi từ "không đáng tin cậy" đến "đáng tin cậy." Lưu ý cách diễn đạt của họ — không phải "tốt hơn một chút," mà là một sự thay đổi về chất. Giống như một con ngựa thuần chủng: bạn có thể cưỡi nó không có bộ yên cương phù hợp, nhưng bạn sẽ không đi xa, không nhanh, và việc ngã ngựa không có gì là ngạc nhiên. Harness là cả bộ yên cương đó — **tất cả mọi thứ trong cơ sở hạ tầng kỹ thuật bên ngoài trọng số mô hình.** - -## Agent Thực sự Bị Kẹt Ở Đâu - -Vậy cụ thể điều gì đi sai? - -Phổ biến nhất: bạn không bao giờ xác định rõ ràng tác vụ. Bạn nói "thêm tính năng tìm kiếm," và sự hiểu biết của agent hoàn toàn khác với của bạn — tìm kiếm cái gì? Full-text hay có cấu trúc? Phân trang? Tô sáng? Bạn không chỉ định, vì vậy agent đoán. Đoán đúng là may mắn; đoán sai tốn nhiều chi phí để sửa hơn là cụ thể hóa ngay từ đầu. Giống như đi vào nhà hàng và bảo với đầu bếp "tôi muốn cá" — liệu bạn có cá kho, cá hấp hay cá lẩu là hoàn toàn phụ thuộc vào may rủi. - -Ngay cả khi bạn đã chỉ định, dự án có các quy ước kiến trúc ẩn mà agent không biết. Nhóm của bạn đã chuẩn hóa cú pháp SQLAlchemy 2.0, nhưng agent mặc định viết mã 1.x. Tất cả các API endpoint phải sử dụng xác thực OAuth 2.0, nhưng quy tắc đó chỉ tồn tại trong đầu bạn và một tin nhắn Slack từ ba tháng trước. Agent không thể nhìn thấy những điều này — không phải vì nó không muốn tuân thủ, mà là vì nó thực sự không biết những quy tắc này tồn tại. - -Môi trường cũng là một cái bẫy. Dev environment không đầy đủ, thiếu dependencies, phiên bản công cụ sai. Agent đốt cháy cửa sổ ngữ cảnh quý báu vào lỗi `pip install` và xung đột phiên bản Node thay vì giải quyết tác vụ thực tế của bạn. Giống như thuê một thợ mộc lành nghề nhưng quên cung cấp búa, đinh hoặc bàn làm việc bằng phẳng — dù tài năng đến đâu, họ cũng không thể làm được việc. - -Thậm chí còn phổ biến hơn: đơn giản là không có cách nào để xác minh. Không có test, không có lint, hoặc các lệnh xác minh không bao giờ được truyền đạt cho agent. Agent viết mã, nhìn vào nó, quyết định nó ổn, nói "xong." Giống như yêu cầu học sinh nộp bài tập mà không có đáp án — họ nghĩ họ làm đúng, nhưng khi bạn chấm điểm có hàng đống lỗi. Anthropic cũng quan sát thấy một hiện tượng thú vị: khi các agent cảm thấy ngữ cảnh đang cạn kiệt, họ vội vàng hoàn thành, bỏ qua xác minh, và chọn một giải pháp đơn giản hơn giải pháp tối ưu. Họ gọi đây là "lo lắng ngữ cảnh" — điều tương tự xảy ra khi bạn nhận ra thời gian gần hết trong bài kiểm tra và bắt đầu đoán ngẫu nhiên các câu hỏi trắc nghiệm còn lại. - -Các tác vụ dài trải dài qua nhiều phiên còn tệ hơn — tất cả những phát hiện từ phiên trước đều bị mất, và mọi phiên mới phải khám phá lại cấu trúc dự án và hiểu lại cách tổ chức mã. Các agent không có trạng thái liên tục thấy tỷ lệ thất bại tăng vọt trên các tác vụ vượt quá 30 phút. - -## Giải thích thuật ngữ chính - -Với những tình huống này, các khái niệm sau không còn chỉ là thuật ngữ nữa: - -- **Khoảng cách Năng lực (Capability Gap)**: Khoảng cách lớn giữa hiệu suất mô hình trên các điểm chuẩn và hiệu suất trên các tác vụ thực tế. Tỷ lệ vượt qua 50-60% trên SWE-bench Verified có nghĩa là gần một nửa số vấn đề thực tế không thể được giải quyết. -- **Harness**: Mọi thứ bên ngoài mô hình — hướng dẫn, công cụ, môi trường, quản lý trạng thái, phản hồi xác minh. Nếu không phải trọng số mô hình, thì đó là harness. Đó là cái chúng ta gọi là "bộ yên cương." -- **Lỗi Do Harness (Harness-Induced Failure)**: Mô hình có đủ năng lực, nhưng môi trường thực thi có khiếm khuyết cấu trúc. Thí nghiệm có kiểm soát của Anthropic đã chứng minh điều này. -- **Khoảng cách Xác minh (Verification Gap)**: Khoảng cách giữa sự tự tin của agent vào kết quả của nó và tính đúng đắn thực tế. Agent nói "tôi đã xong" khi chưa xong — đây là chế độ lỗi phổ biến nhất. -- **Vòng lặp Chẩn đoán (Diagnostic Loop)**: Thực thi, quan sát lỗi, quy cho một lớp harness cụ thể, sửa lớp đó, thực thi lại. Đây là phương pháp luận cốt lõi của harness engineering. -- **Định nghĩa Hoàn thành (Definition of Done)**: Một tập hợp các điều kiện có thể xác minh bằng máy — test qua, lint sạch, type check qua. Không có định nghĩa hoàn thành rõ ràng, agent sẽ tự bịa ra định nghĩa của mình. - -## Khi Sự Cố Xảy ra, Sửa Harness Trước - -Nguyên tắc cốt lõi: **Khi sự cố xảy ra, đừng đổi mô hình trước — kiểm tra harness.** Nếu cùng một mô hình thành công trên các tác vụ tương tự, có cấu trúc tốt, hãy giả định đó là vấn đề harness. Giống như xe hơi bị hỏng — bạn không ngay lập tức nghi ngờ động cơ. Bạn kiểm tra xăng trước. - -Các bước cụ thể: - -**Quy mọi lỗi cho một lớp cụ thể.** Đừng chỉ nói "mô hình không tốt." Hãy hỏi: tác vụ có mơ hồ không? Ngữ cảnh có không đủ không? Không có phương pháp xác minh không? Ánh xạ mỗi lỗi vào một trong năm lớp phòng thủ: đặc tả tác vụ, cung cấp ngữ cảnh, môi trường thực thi, phản hồi xác minh, quản lý trạng thái. Đây là các lớp chẩn đoán thực hành, không phải sáu thuật ngữ trong phần thuật ngữ phía trên. Xây dựng thói quen này, và bạn sẽ thấy "mô hình không đủ tốt" xuất hiện ngày càng ít hơn trong nhật ký của bạn. - -**Viết Định nghĩa Hoàn thành rõ ràng cho mỗi tác vụ.** Đừng nói "thêm tính năng tìm kiếm." Hãy nói: -``` -Tiêu chí hoàn thành: -- Endpoint GET /api/search?q=xxx mới -- Hỗ trợ phân trang, mặc định 20 mục -- Kết quả bao gồm đoạn trích được tô sáng -- Tất cả mã mới vượt qua pytest -- Type checking vượt qua (mypy --strict) -``` - -**Tạo tệp AGENTS.md.** Đặt nó trong thư mục gốc của repo để cho agent biết tech stack của dự án, các quy ước kiến trúc, và các lệnh xác minh. Đây là bước đầu tiên trong harness engineering và bước có ROI cao nhất bạn có thể thực hiện. Một tệp `AGENTS.md` có thể hiệu quả hơn việc nâng cấp lên mô hình đắt tiền hơn — tôi không nói đùa. - -**Xây dựng vòng lặp chẩn đoán.** Đừng coi lỗi là "mô hình lại ngốc rồi." Coi chúng là tín hiệu rằng harness của bạn có khiếm khuyết. Mỗi lỗi, xác định lớp, sửa nó, không bao giờ thất bại theo cách đó nữa. Sau vài vòng, harness của bạn mạnh hơn và hiệu suất agent ổn định hơn. Giống như vá đường — mỗi ổ gà bạn lấp đầy làm cho đoạn đường tiếp theo êm hơn. - -**Định lượng cải tiến.** Giữ một nhật ký đơn giản: mỗi tác vụ có thành công hay thất bại, và lớp nào gây ra lỗi. Sau vài vòng, bạn sẽ thấy lớp nào là điểm nghẽn cổ chai — tập trung năng lượng của bạn ở đó. - -## Thí nghiệm Triệu Dòng Mã - -OpenAI đã thực hiện một thí nghiệm táo bạo vào năm 2025: sử dụng Codex để xây dựng một sản phẩm nội bộ hoàn chỉnh từ một kho git trống. Năm tháng sau, kho có khoảng một triệu dòng mã — logic ứng dụng, cơ sở hạ tầng, công cụ, tài liệu, công cụ dev nội bộ — tất cả được tạo bởi agent. Ba kỹ sư điều khiển Codex, mở và hợp nhất khoảng 1,500 PR. Trung bình 3.5 PR mỗi người mỗi ngày. - -Ràng buộc chính: **con người không bao giờ viết mã trực tiếp.** Đây không phải là trò gimmick — nó được thiết kế để buộc nhóm phải tìm ra điều gì thay đổi khi công việc chính của kỹ sư không còn là viết mã, mà là thiết kế môi trường, diễn đạt ý định, và xây dựng các vòng phản hồi. - -Tiến độ ban đầu chậm hơn dự kiến. Không phải vì Codex không đủ năng lực, mà vì môi trường chưa đủ hoàn chỉnh — agent thiếu các công cụ, trừu tượng và cấu trúc nội bộ cần thiết để tiến lên các mục tiêu cấp cao. Công việc của các kỹ sư trở thành: chia nhỏ các mục tiêu lớn thành các khối xây dựng nhỏ (thiết kế, mã hóa, đánh giá, kiểm tra), để agent lắp ráp chúng, rồi sử dụng những khối đó để mở khóa các tác vụ phức tạp hơn. Khi có sự cố, câu trả lời gần như không bao giờ là "cố gắng hơn" — mà là "agent đang thiếu năng lực gì, và làm thế nào để chúng ta làm cho nó có thể hiểu và thực thi được?" - -Thí nghiệm này trực tiếp chứng minh luận điểm cốt lõi của bài giảng này: **cùng một mô hình tạo ra kết quả về cơ bản khác nhau trong môi trường trần trụi so với môi trường có harness đầy đủ.** Mô hình không thay đổi. Môi trường thay đổi. - -> Nguồn: [OpenAI: Harness engineering: leveraging Codex in an agent-first world](https://openai.com/index/harness-engineering/) - -## Một Ví dụ Thực tế Hơn - -Một nhóm sử dụng Claude Sonnet để thêm một API endpoint mới vào ứng dụng web Python cỡ trung (FastAPI + PostgreSQL + Redis, ~15,000 dòng mã). - -Ban đầu họ chỉ đưa ra một câu: "thêm các endpoint tùy chọn người dùng dưới `/api/v2/users`." Kết quả? Agent dành 40% cửa sổ ngữ cảnh để khám phá cấu trúc repo, tạo ra mã trông có vẻ hợp lý nhưng không theo các mẫu xử lý lỗi của dự án, sử dụng cú pháp SQLAlchemy cũ, và tuyên bố hoàn thành trong khi endpoint có lỗi runtime. Phiên tiếp theo phải làm lại toàn bộ công việc khám phá. - -Sau đó họ thêm `AGENTS.md` (mô tả kiến trúc dự án và phiên bản tech stack), các lệnh xác minh rõ ràng (`pytest tests/api/v2/ && python -m mypy src/`), và các bản ghi quyết định kiến trúc. Cùng một mô hình đã thành công trong cả ba lần chạy độc lập, với hiệu quả ngữ cảnh tốt hơn khoảng 60%. - -Họ không thay đổi mô hình. Họ thay đổi harness. - -## Những Điểm chính cần Nhớ - -- Năng lực mô hình và độ tin cậy thực thi là hai điều khác nhau. Một con ngựa thuần chủng vẫn cần bộ yên cương tốt. -- Khi sự cố xảy ra, kiểm tra harness trước, rồi mới đến mô hình. Đổi mô hình là lựa chọn tốn kém nhất — và thường thậm chí không phải là vấn đề mô hình. -- Mỗi lỗi là một tín hiệu: harness của bạn có khiếm khuyết cấu trúc. Tìm nó, sửa nó. -- Năm lớp phòng thủ: đặc tả tác vụ, cung cấp ngữ cảnh, môi trường thực thi, phản hồi xác minh, quản lý trạng thái. Kiểm tra chúng có hệ thống, như bác sĩ loại trừ các nguyên nhân phổ biến nhất trước. -- Một tệp `AGENTS.md` có thể hiệu quả hơn việc nâng cấp lên mô hình đắt tiền hơn. Thực sự vậy. - -## Đọc thêm - -- [OpenAI: Harness Engineering — Leveraging Codex in an Agent-First World](https://openai.com/index/harness-engineering/) -- [Anthropic: Effective Harnesses for Long-Running Agents](https://www.anthropic.com/engineering/effective-harnesses-for-long-running-agents) -- [HumanLayer: Skill Issue — Harness Engineering for Coding Agents](https://humanlayer.dev/articles/harness-engineering-for-coding-agents/) -- [SWE-bench Leaderboard](https://www.swebench.com/) -- [Thoughtworks Technology Radar: Harness Engineering](https://www.thoughtworks.com/radar) - -## Bài tập - -1. **Thí nghiệm so sánh**: Chọn một codebase bạn biết rõ và một tác vụ sửa đổi không tầm thường. Đầu tiên, chạy agent mà không có hỗ trợ harness và ghi lại các lỗi. Sau đó thêm `AGENTS.md` với các lệnh xác minh rõ ràng và chạy lại với cùng agent. So sánh kết quả, quy mỗi lỗi vào một trong năm lớp phòng thủ. - -2. **Đo lường khoảng cách xác minh**: Chọn 5 tác vụ lập trình. Sau mỗi tác vụ, ghi lại xem agent có tuyên bố hoàn thành không, sau đó xác minh tính đúng đắn thực tế bằng các bài test độc lập. Tính tỷ lệ lần agent tuyên bố xong khi thực tế chưa xong — đó là khoảng cách xác minh của bạn. Sau đó suy nghĩ: những lệnh xác minh nào sẽ giảm tỷ lệ này? - -3. **Thực hành vòng lặp chẩn đoán**: Tìm một tác vụ mà agent liên tục thất bại trong dự án của bạn. Chạy một lần, ghi lại lỗi. Quy nó cho một trong năm lớp. Sửa lớp đó. Chạy lại. Lặp lại ba đến năm vòng, ghi lại cải tiến mỗi lần. diff --git a/courses/learn-harness-engineering/docs/vi/lectures/lecture-02-what-a-harness-actually-is/code/harness-components.md b/courses/learn-harness-engineering/docs/vi/lectures/lecture-02-what-a-harness-actually-is/code/harness-components.md deleted file mode 100644 index 10b6d216..00000000 --- a/courses/learn-harness-engineering/docs/vi/lectures/lecture-02-what-a-harness-actually-is/code/harness-components.md +++ /dev/null @@ -1,21 +0,0 @@ -# Ví dụ Các Thành phần Harness - -Đối với một coding agent làm việc trong kho lưu trữ cục bộ: - -- Mô hình: - bản thân LLM - -- Harness: - - system prompt - - AGENTS.md - - bash tool - - công cụ đọc/ghi tệp - - quyền truy cập git - - hệ thống tệp cục bộ - - script khởi động - - lệnh test - - stop hook - - kiểm tra lint - - vòng lặp evaluator - -Nếu bạn thay đổi bất kỳ phần harness nào ở trên, bạn thay đổi agent thực tế. diff --git a/courses/learn-harness-engineering/docs/vi/lectures/lecture-02-what-a-harness-actually-is/code/harness-vs-no-harness.ts b/courses/learn-harness-engineering/docs/vi/lectures/lecture-02-what-a-harness-actually-is/code/harness-vs-no-harness.ts deleted file mode 100644 index d856ba36..00000000 --- a/courses/learn-harness-engineering/docs/vi/lectures/lecture-02-what-a-harness-actually-is/code/harness-vs-no-harness.ts +++ /dev/null @@ -1,180 +0,0 @@ -/** - * harness-vs-no-harness.ts - * - * Side-by-side comparison of the same task executor running with and without - * a harness. The harness version adds explicit rules, verification steps, - * and stop conditions. - * - * Run: npx tsx docs/lectures/lecture-02-what-a-harness-actually-is/code/harness-vs-no-harness.ts - */ - -// --------------------------------------------------------------------------- -// Types & helpers -// --------------------------------------------------------------------------- - -interface TaskResult { - name: string; - passed: boolean; - durationMs: number; - issues: string[]; -} - -function pad(s: string, len: number): string { - return s.length >= len ? s : s + " ".repeat(len - s.length); -} - -// --------------------------------------------------------------------------- -// Simulated task executor -// --------------------------------------------------------------------------- - -/** A simple task that can succeed or fail depending on rules. */ -type Task = { - name: string; - requiresAuth: boolean; - hasTests: boolean; - withinScope: boolean; -}; - -const tasks: Task[] = [ - { name: "Add search endpoint", requiresAuth: true, hasTests: false, withinScope: true }, - { name: "Add delete endpoint", requiresAuth: true, hasTests: true, withinScope: true }, - { name: "Refactor auth middleware", requiresAuth: true, hasTests: true, withinScope: false }, - { name: "Add health check", requiresAuth: false, hasTests: true, withinScope: true }, - { name: "Add rate limiter", requiresAuth: true, hasTests: false, withinScope: true }, -]; - -// --------------------------------------------------------------------------- -// Run WITHOUT harness -- just execute every task, no checks -// --------------------------------------------------------------------------- - -function runWithoutHarness(taskList: Task[]): TaskResult[] { - const results: TaskResult[] = []; - - for (const t of taskList) { - const start = performance.now(); - const issues: string[] = []; - - // The "agent" does the work and calls it done. - let passed = true; - - // Problems that go undetected without a harness - if (t.requiresAuth && !t.hasTests) { - issues.push("No tests for auth-protected endpoint"); - // Agent doesn't notice -- marks as pass anyway - } - if (!t.withinScope) { - issues.push("Task is outside current scope"); - // Agent doesn't notice - } - - const duration = performance.now() - start; - results.push({ name: t.name, passed, durationMs: duration, issues }); - } - - return results; -} - -// --------------------------------------------------------------------------- -// Run WITH harness -- rules, verification, stop conditions -// --------------------------------------------------------------------------- - -function runWithHarness(taskList: Task[]): TaskResult[] { - const rules = { - requireTestsForAuth: true, - enforceScope: true, - }; - - const results: TaskResult[] = []; - - for (const t of taskList) { - const start = performance.now(); - const issues: string[] = []; - let passed = true; - - // Rule: auth endpoints must have tests - if (rules.requireTestsForAuth && t.requiresAuth && !t.hasTests) { - issues.push("BLOCKED: Auth-protected endpoint missing tests"); - passed = false; - } - - // Rule: stay in scope - if (rules.enforceScope && !t.withinScope) { - issues.push("BLOCKED: Task outside active scope -- skipped"); - passed = false; - } - - // Verification: re-check after execution - if (passed && !t.hasTests) { - issues.push("WARNING: No tests exist for this change"); - } - - const duration = performance.now() - start; - results.push({ name: t.name, passed, durationMs: duration, issues }); - } - - return results; -} - -// --------------------------------------------------------------------------- -// Reporting -// --------------------------------------------------------------------------- - -function printComparison( - noHarness: TaskResult[], - withHarness: TaskResult[] -): void { - console.log("\n" + "=".repeat(80)); - console.log(" HARNESS vs NO-HARNESS COMPARISON"); - console.log("=".repeat(80)); - - const header = `| ${pad("Task", 28)}| ${pad("No-Harness", 12)}| ${pad("Issues(NH)", 35)}| ${pad("Harness", 12)}| ${pad("Issues(H)", 35)}|`; - const sep = `|${"-".repeat(30)}|${"-".repeat(14)}|${"-".repeat(37)}|${"-".repeat(14)}|${"-".repeat(37)}|`; - - console.log("\n" + header); - console.log(sep); - - for (let i = 0; i < noHarness.length; i++) { - const nh = noHarness[i]; - const wh = withHarness[i]; - const nhPass = nh.passed ? "PASS" : "FAIL"; - const whPass = wh.passed ? "PASS" : "FAIL"; - const nhIssue = nh.issues[0] ?? "(none)"; - const whIssue = wh.issues[0] ?? "(none)"; - - console.log( - `| ${pad(nh.name, 28)}| ${pad(nhPass, 12)}| ${pad(nhIssue, 35)}| ${pad(whPass, 12)}| ${pad(whIssue, 35)}|` - ); - } - - // Metrics - const nhPassed = noHarness.filter((r) => r.passed).length; - const whPassed = withHarness.filter((r) => r.passed).length; - const nhIssues = noHarness.reduce((sum, r) => sum + r.issues.length, 0); - const whIssues = withHarness.reduce((sum, r) => sum + r.issues.length, 0); - - console.log("\n" + "=".repeat(80)); - console.log(" SUMMARY METRICS"); - console.log("=".repeat(80)); - - const metricsHeader = `| ${pad("Metric", 30)}| ${pad("No Harness", 15)}| ${pad("With Harness", 15)}|`; - const metricsSep = `|${"-".repeat(32)}|${"-".repeat(17)}|${"-".repeat(17)}|`; - console.log("\n" + metricsHeader); - console.log(metricsSep); - console.log(`| ${pad("Tasks passed", 30)}| ${pad(String(nhPassed) + "/" + noHarness.length, 15)}| ${pad(String(whPassed) + "/" + withHarness.length, 15)}|`); - console.log(`| ${pad("Issues detected", 30)}| ${pad(String(nhIssues), 15)}| ${pad(String(whIssues), 15)}|`); - console.log(`| ${pad("False positives (passed but flawed)", 30)}| ${pad(String(nhPassed - truePassCount(noHarness)), 15)}| ${pad(String(whPassed - truePassCount(withHarness)), 15)}|`); - - console.log("\n The harness catches problems the no-harness run silently ignores."); - console.log(" Without a harness, every task 'passes' even when it shouldn't.\n"); -} - -/** Count tasks that actually pass (have tests and are in scope). */ -function truePassCount(results: TaskResult[]): number { - return results.filter((r) => r.passed && r.issues.length === 0).length; -} - -// --------------------------------------------------------------------------- -// Run -// --------------------------------------------------------------------------- - -printComparison(runWithoutHarness(tasks), runWithHarness(tasks)); diff --git a/courses/learn-harness-engineering/docs/vi/lectures/lecture-02-what-a-harness-actually-is/code/index.md b/courses/learn-harness-engineering/docs/vi/lectures/lecture-02-what-a-harness-actually-is/code/index.md deleted file mode 100644 index 5b6dc9e8..00000000 --- a/courses/learn-harness-engineering/docs/vi/lectures/lecture-02-what-a-harness-actually-is/code/index.md +++ /dev/null @@ -1,8 +0,0 @@ -# Mã cho Bài 02 - -Sử dụng thư mục này cho các ví dụ nhỏ phân biệt: - -- hành vi mô hình -- hành vi harness -- thiết lập chỉ dùng prompt -- thiết lập agent được hỗ trợ bởi môi trường diff --git a/courses/learn-harness-engineering/docs/vi/lectures/lecture-02-what-a-harness-actually-is/code/minimal-harness-loop.ts b/courses/learn-harness-engineering/docs/vi/lectures/lecture-02-what-a-harness-actually-is/code/minimal-harness-loop.ts deleted file mode 100644 index e56cddd8..00000000 --- a/courses/learn-harness-engineering/docs/vi/lectures/lecture-02-what-a-harness-actually-is/code/minimal-harness-loop.ts +++ /dev/null @@ -1,36 +0,0 @@ -type Message = { - role: "user" | "assistant"; - content: string; -}; - -type ToolResult = { - ok: boolean; - output: string; -}; - -function runTool(name: string, input: string): ToolResult { - if (name === "read_file") { - return { ok: true, output: `contents of ${input}` }; - } - - return { ok: false, output: `unknown tool: ${name}` }; -} - -export function minimalHarness(messages: Message[]) { - const nextAction = { - tool: "read_file", - input: "README.md" - }; - - const result = runTool(nextAction.tool, nextAction.input); - - return { - messages: [ - ...messages, - { - role: "assistant", - content: `Tool ${nextAction.tool} returned: ${result.output}` - } - ] - }; -} diff --git a/courses/learn-harness-engineering/docs/vi/lectures/lecture-02-what-a-harness-actually-is/index.md b/courses/learn-harness-engineering/docs/vi/lectures/lecture-02-what-a-harness-actually-is/index.md deleted file mode 100644 index 3630c064..00000000 --- a/courses/learn-harness-engineering/docs/vi/lectures/lecture-02-what-a-harness-actually-is/index.md +++ /dev/null @@ -1,109 +0,0 @@ -[English Version →](../../../en/lectures/lecture-02-what-a-harness-actually-is/) | [中文版本 →](../../../zh/lectures/lecture-02-what-a-harness-actually-is/) - -> Ví dụ mã nguồn: [code/](https://github.com/walkinglabs/learn-harness-engineering/blob/main/docs/vi/lectures/lecture-02-what-a-harness-actually-is/code/) -> Dự án thực hành: [Dự án 01. Chỉ Prompt vs. Ưu tiên Quy tắc](./../../projects/project-01-baseline-vs-minimal-harness/index.md) - -# Bài 02. Harness Thực sự Có nghĩa là Gì - -Từ "harness" được nhắc đến rất nhiều trong giới AI coding agent, nhưng thành thật mà nói, hầu hết mọi người đều có nghĩa là "một tệp prompt" khi họ nói harness. Đó không phải là harness. Giống như mở nhà hàng chỉ với nguyên liệu — không có bếp, không có dao, không có công thức, không có quy trình trình bày món ăn. Đó không phải là nhà hàng. Đó là tủ lạnh. - -Bài giảng này cho bạn một định nghĩa harness chính xác, có thể hành động được. Không phải sự trừu tượng học thuật, mà là một khung bạn có thể sử dụng ngay hôm nay: một harness bao gồm năm hệ thống phụ, mỗi hệ thống có trách nhiệm và tiêu chí đánh giá rõ ràng. - -## Bắt đầu Bằng một Phép loại suy - -Hãy tưởng tượng bạn là một kỹ sư mới được tuyển, được thả vào một dự án không có tài liệu. Không có README, không có chú thích trong mã, không ai cho bạn biết cách chạy test, cấu hình CI bị chôn vùi đâu đó. Bạn có thể viết mã tốt không? Có thể — nếu bạn đủ thông minh và kiên nhẫn. Nhưng bạn sẽ dành rất nhiều thời gian để "tìm hiểu dự án này là về cái gì" thay vì "giải quyết vấn đề." - -Một AI agent đối mặt với chính xác tình huống tương tự. Và còn tệ hơn — ít nhất bạn có thể hỏi đồng nghiệp. Agent chỉ có thể nhìn thấy các tệp bạn đặt trước mặt nó và các lệnh nó có thể thực thi. Nó không thể vỗ vai ai đó và hỏi "này, phiên bản ORM nào dự án này sử dụng vậy?" - -OpenAI đóng khung nguyên tắc cốt lõi là "repo LÀ spec" — tất cả ngữ cảnh cần thiết phải có trong kho lưu trữ, được truyền qua các tệp hướng dẫn có cấu trúc, lệnh xác minh rõ ràng và tổ chức thư mục rõ ràng. Tài liệu về agent chạy lâu của Anthropic nhấn mạnh tính liên tục của trạng thái, các đường phục hồi rõ ràng và theo dõi tiến độ có cấu trúc. Hai công ty tập trung vào các khía cạnh khác nhau, nhưng họ đang nói cùng một điều: **tất cả mọi thứ trong cơ sở hạ tầng kỹ thuật bên ngoài mô hình xác định mức độ năng lực của mô hình thực sự được hiện thực hóa bao nhiêu.** - -Nhìn vào một số công cụ bạn đã biết: - -**Claude Code** thể hiện tư duy harness. Nó đọc `CLAUDE.md` từ repo của bạn (kệ công thức), có thể chạy lệnh shell (giá dao), thực thi trong môi trường cục bộ của bạn (bếp), duy trì lịch sử phiên (bàn chuẩn bị), và có thể chạy test và xem kết quả (cửa sổ kiểm tra chất lượng). Nhưng nếu bạn không nói cho nó biết cách chạy test, cửa sổ kiểm tra chất lượng bị hỏng — không ai biết liệu món ăn đã chín chưa. - -**Cursor** theo logic tương tự. Tệp `.cursorrules` của nó là kệ công thức, terminal là giá dao, nó đọc cấu trúc dự án và cấu hình lint cho bếp. Nhưng quản lý trạng thái của Cursor tương đối yếu — đóng IDE và mở lại, ngữ cảnh trước đó biến mất. - -**Codex** (coding agent của OpenAI) sử dụng git worktrees để cô lập môi trường runtime của mỗi tác vụ, kết hợp với ngăn xếp quan sát cục bộ (logs, metrics, traces), vì vậy mỗi thay đổi được xác minh trong môi trường độc lập. Trong các repo có `AGENTS.md` và lệnh xác minh rõ ràng, nó hoạt động tốt hơn nhiều so với các repo "trần trụi". - -**AutoGPT** là câu chuyện cảnh báo — thiếu quản lý trạng thái có cấu trúc dẫn đến tích lũy ngữ cảnh trong các tác vụ dài, và thiếu cơ chế phản hồi chính xác khiến agent lặp vòng. Nhiều người nói AutoGPT "không hoạt động," nhưng thực ra là harness của AutoGPT không hoạt động — đưa cho đầu bếp một cái bếp hỏng và ngay cả nguyên liệu tốt nhất cũng không tạo ra bữa ăn. - -## Các Khái niệm Cốt lõi - -- **Harness là gì**: Mọi thứ trong cơ sở hạ tầng kỹ thuật bên ngoài trọng số mô hình. OpenAI chắt lọc công việc cốt lõi của kỹ sư thành ba thứ: thiết kế môi trường, diễn đạt ý định, và xây dựng vòng phản hồi. Anthropic gọi Claude Agent SDK của họ là "harness agent đa năng." -- **Repo là nguồn sự thật duy nhất**: Bất cứ thứ gì agent không thể nhìn thấy, theo mọi nghĩa thực tế, không tồn tại. OpenAI coi repo là "hệ thống ghi chép" — tất cả ngữ cảnh cần thiết phải sống ở đó, qua các tệp có cấu trúc và tổ chức thư mục rõ ràng. -- **Đưa bản đồ, không phải cẩm nang**: Kinh nghiệm của OpenAI — `AGENTS.md` nên là một trang thư mục, không phải bách khoa toàn thư. Khoảng 100 dòng là đủ. Nếu không vừa, chia vào thư mục `docs/` và để agent đọc theo yêu cầu. -- **Ràng buộc, không vi quản lý**: Một harness tốt sử dụng các quy tắc có thể thực thi để ràng buộc agent, thay vì liệt kê hướng dẫn từng cái một. OpenAI nói "thực thi các bất biến, không vi quản lý triển khai"; Anthropic phát hiện rằng các agent tự tin khen ngợi công việc của chính mình, và giải pháp là tách "người làm việc" khỏi "người kiểm tra công việc." -- **Xóa từng thành phần một**: Để định lượng đóng góp biên của từng thành phần harness, hãy xóa chúng từng cái một và xem cái nào bị xóa gây ra giảm hiệu suất lớn nhất. Anthropic đã sử dụng phương pháp này và phát hiện ra rằng khi các mô hình trở nên mạnh hơn, một số thành phần không còn quan trọng — nhưng các thành phần mới luôn xuất hiện. - -## Mô hình Harness Năm Hệ thống Phụ - -Trở lại phép loại suy nhà bếp. Một nhà bếp hoàn chỉnh có năm khu vực chức năng, và một harness có năm hệ thống phụ: - -```mermaid -flowchart LR - Rules["Quy tắc dự án
    AGENTS.md / CLAUDE.md"] --> Agent["AI Agent"] - State["Tiến độ và git
    PROGRESS.md / commits"] --> Agent - Agent --> Tools["Công cụ
    shell / files / tests"] - Tools --> Env["Runtime
    deps / services / versions"] - Env --> Checks["Kết quả kiểm tra
    test / lint / build"] - Checks --> Agent -``` - -**Hệ thống phụ Hướng dẫn (kệ công thức)**: Tạo `AGENTS.md` (hoặc `CLAUDE.md`) chứa tổng quan và mục đích dự án (một câu), tech stack và phiên bản (Python 3.11, FastAPI 0.100+, PostgreSQL 15), lệnh chạy đầu tiên (`make setup`, `make test`), các ràng buộc cứng không thể thương lượng ("Tất cả API phải sử dụng OAuth 2.0"), và các liên kết đến tài liệu chi tiết hơn. - -**Hệ thống phụ Công cụ (giá dao)**: Đảm bảo agent có đủ quyền truy cập công cụ. Đừng vô hiệu hóa shell vì "bảo mật" — nếu agent thậm chí không thể chạy `pip install`, làm sao nó được cho là hoạt động? Nhưng cũng đừng mở tất cả — tuân theo nguyên tắc ít đặc quyền nhất. - -**Hệ thống phụ Môi trường (bếp)**: Làm cho trạng thái môi trường tự mô tả. Sử dụng `pyproject.toml` hoặc `package.json` để khóa dependencies, `.nvmrc` hoặc `.python-version` cho các phiên bản runtime, Docker hoặc devcontainers để tái tạo. - -**Hệ thống phụ Trạng thái (bàn chuẩn bị)**: Các tác vụ dài cần theo dõi tiến độ. Sử dụng tệp `PROGRESS.md` đơn giản ghi lại: những gì đã xong, những gì đang tiến hành, những gì bị chặn. Cập nhật trước khi mỗi phiên kết thúc, đọc khi phiên tiếp theo bắt đầu. - -**Hệ thống phụ Phản hồi (cửa sổ kiểm tra chất lượng)**: Đây là hệ thống phụ có ROI cao nhất. Liệt kê rõ ràng các lệnh xác minh trong `AGENTS.md`: -``` -Lệnh xác minh: -- Test: pytest tests/ -x -- Type check: mypy src/ --strict -- Lint: ruff check src/ -- Xác minh đầy đủ: make check (bao gồm tất cả những trên) -``` - -Thiếu bất kỳ hệ thống phụ nào cũng giống như thiếu một khu vực chức năng trong nhà bếp — bạn vẫn có thể nấu ăn, nhưng luôn gượng gạo. - -**Định lượng giá trị thành phần harness**: Sử dụng "kiểm soát mô hình đẳng áp." Giữ nguyên mô hình, xóa từng hệ thống phụ một và đo xem cái nào bị xóa gây ra giảm hiệu suất lớn nhất. Mức giảm lớn nhất cho biết thành phần có đóng góp biên cao nhất trong tác vụ đó, không tự động là điểm nghẽn cổ chai. Để xác định điểm nghẽn thật, hãy kết hợp thí nghiệm với nhật ký lỗi và quy kết nguyên nhân: tác vụ mơ hồ, thiếu ngữ cảnh, môi trường không tái lập được, thiếu phản hồi xác minh, hoặc quản lý trạng thái bị đứt gãy. - -## Câu chuyện Thực tế của Một Nhóm - -Một nhóm sử dụng GPT-4o trên ứng dụng frontend TypeScript + React (~20,000 dòng mã). Họ trải qua bốn giai đoạn — về cơ bản là thêm thiết bị nhà bếp từng cái một: - -**Giai đoạn 1 — Nhà bếp trống**: Chỉ có mô tả dự án cơ bản trong README. 1 trong 5 lần chạy thành công (20%). Lỗi chính: chọn sai package manager (npm vs yarn), không tuân theo quy ước đặt tên component, không thể chạy test. - -**Giai đoạn 2 — Kệ công thức được cài đặt**: Thêm `AGENTS.md` với phiên bản tech stack, quy ước đặt tên, các quyết định kiến trúc chính. Tỷ lệ thành công tăng lên 60%. Các lỗi còn lại chủ yếu là vấn đề môi trường và thiếu xác minh. - -**Giai đoạn 3 — Cửa sổ kiểm tra chất lượng được mở**: Liệt kê các lệnh xác minh trong `AGENTS.md`: `yarn test && yarn lint && yarn build`. Tỷ lệ thành công tăng lên 80%. - -**Giai đoạn 4 — Bàn chuẩn bị sẵn sàng**: Giới thiệu các mẫu tệp tiến độ nơi các agent ghi lại công việc đã hoàn thành và chưa hoàn thành mỗi lần chạy. Tỷ lệ thành công ổn định ở 80-100%. - -Bốn lần lặp, mô hình hoàn toàn không thay đổi, tỷ lệ thành công từ 20% lên gần 100%. Đó là sức mạnh của harness engineering. Bạn không mua nguyên liệu đắt tiền hơn — bạn chỉ tổ chức lại nhà bếp đúng cách. - -## Những Điểm chính cần Nhớ - -- Harness = Hướng dẫn + Công cụ + Môi trường + Trạng thái + Phản hồi. Năm hệ thống phụ, như năm khu vực chức năng của nhà bếp — tất cả đều cần thiết. -- Nếu không phải trọng số mô hình, thì đó là harness. Harness của bạn xác định mức độ năng lực mô hình được hiện thực hóa bao nhiêu. -- Trong số năm hệ thống phụ, hệ thống phụ phản hồi thường có đầu tư thấp nhất và lợi nhuận cao nhất. Đặt đúng các lệnh xác minh trước — cửa sổ kiểm tra chất lượng là bản nâng cấp đáng giá nhất. -- Sử dụng "kiểm soát mô hình đẳng áp" để định lượng đóng góp biên của mỗi hệ thống phụ; dùng nhật ký lỗi và quy kết nguyên nhân để xác định điểm nghẽn thật. -- Harness mục nát như mã nguồn. Kiểm tra thường xuyên, trả nợ harness như bạn trả nợ kỹ thuật. - -## Đọc thêm - -- [OpenAI: Harness Engineering](https://openai.com/index/harness-engineering/) -- [Anthropic: Effective Harnesses for Long-Running Agents](https://www.anthropic.com/engineering/effective-harnesses-for-long-running-agents) -- [HumanLayer: Harness Engineering for Coding Agents](https://humanlayer.dev/articles/harness-engineering-for-coding-agents/) -- [SWE-agent: Agent-Computer Interfaces](https://github.com/princeton-nlp/SWE-agent) -- [Thoughtworks: Harness Engineering on Technology Radar](https://www.thoughtworks.com/radar) - -## Bài tập - -1. **Kiểm toán harness năm tuple**: Lấy một dự án nơi bạn sử dụng AI agent và thực hiện kiểm toán đầy đủ bằng khung năm tuple. Chấm điểm mỗi hệ thống phụ từ 1-5. Tìm hệ thống phụ có điểm thấp nhất, dành 30 phút cải thiện nó, sau đó quan sát sự thay đổi về hiệu suất agent. - -2. **Thí nghiệm kiểm soát mô hình đẳng áp**: Chọn một mô hình và một tác vụ đầy thách thức. Lần lượt xóa hướng dẫn (xóa AGENTS.md), xóa phản hồi (không cung cấp lệnh xác minh), xóa trạng thái (không có tệp tiến độ) — xóa chỉ một cái mỗi lần và đo lường giảm hiệu suất. Dựa trên kết quả, xếp hạng tầm quan trọng của hệ thống phụ cho dự án của bạn. - -3. **Phân tích affordance**: Tìm một tình huống mà agent trong dự án của bạn "muốn làm gì đó nhưng không thể" (ví dụ: biết nó nên sử dụng các truy vấn được tham số hóa nhưng không biết các mẫu ORM của dự án của bạn). Phân tích liệu đây là Gulf of Execution (không biết làm thế nào) hay Gulf of Evaluation (không biết có đúng không), sau đó thiết kế một cải tiến harness để thu hẹp khoảng cách đó. diff --git a/courses/learn-harness-engineering/docs/vi/lectures/lecture-03-why-the-repository-must-become-the-system-of-record/code/index.md b/courses/learn-harness-engineering/docs/vi/lectures/lecture-03-why-the-repository-must-become-the-system-of-record/code/index.md deleted file mode 100644 index 1d4cd1d0..00000000 --- a/courses/learn-harness-engineering/docs/vi/lectures/lecture-03-why-the-repository-must-become-the-system-of-record/code/index.md +++ /dev/null @@ -1,7 +0,0 @@ -# Mã cho Bài 03 - -Sử dụng thư mục này cho các ví dụ về: - -- cấu trúc repo mà agent có thể đọc được -- tài liệu như hệ thống ghi chép -- vị trí kiến thức xấu vs tốt diff --git a/courses/learn-harness-engineering/docs/vi/lectures/lecture-03-why-the-repository-must-become-the-system-of-record/code/repo-knowledge-layout.txt b/courses/learn-harness-engineering/docs/vi/lectures/lecture-03-why-the-repository-must-become-the-system-of-record/code/repo-knowledge-layout.txt deleted file mode 100644 index 45315dac..00000000 --- a/courses/learn-harness-engineering/docs/vi/lectures/lecture-03-why-the-repository-must-become-the-system-of-record/code/repo-knowledge-layout.txt +++ /dev/null @@ -1,13 +0,0 @@ -AGENTS.md -docs/ - ARCHITECTURE.md - PRODUCT.md - RELIABILITY.md - references/ - electron.md - sqlite.md - plans/ - active/ - completed/ -src/ -scripts/ diff --git a/courses/learn-harness-engineering/docs/vi/lectures/lecture-03-why-the-repository-must-become-the-system-of-record/code/repo-reader.ts b/courses/learn-harness-engineering/docs/vi/lectures/lecture-03-why-the-repository-must-become-the-system-of-record/code/repo-reader.ts deleted file mode 100644 index 46f6a62a..00000000 --- a/courses/learn-harness-engineering/docs/vi/lectures/lecture-03-why-the-repository-must-become-the-system-of-record/code/repo-reader.ts +++ /dev/null @@ -1,189 +0,0 @@ -/** - * repo-reader.ts - * - * Reads a directory structure and scores it on discoverability. - * Checks for: AGENTS.md, docs/, architecture docs, feature tracking, - * handoff files, and other signals of a repository that serves as the - * system of record. - * - * Usage: - * npx tsx docs/lectures/lecture-03.../code/repo-reader.ts [path] - * (defaults to current working directory if no path given) - * - * Run: npx tsx docs/lectures/lecture-03-why-the-repository-must-become-the-system-of-record/code/repo-reader.ts - */ - -import * as fs from "node:fs"; -import * as path from "node:path"; - -// --------------------------------------------------------------------------- -// Scoring criteria -// --------------------------------------------------------------------------- - -interface Check { - name: string; - description: string; - maxPoints: number; - check: (dir: string) => { points: number; found: string[]; missing: string[] }; -} - -const checks: Check[] = [ - { - name: "AGENTS.md / CLAUDE.md", - description: "Agent-readable instruction file at repo root", - maxPoints: 15, - check: (dir) => { - const candidates = ["AGENTS.md", "CLAUDE.md", ".claude/CLAUDE.md"]; - const found = candidates.filter((f) => fs.existsSync(path.join(dir, f))); - return { points: found.length > 0 ? 15 : 0, found, missing: found.length > 0 ? [] : candidates }; - }, - }, - { - name: "Documentation directory", - description: "Dedicated docs/ or documentation/ directory", - maxPoints: 10, - check: (dir) => { - const candidates = ["docs", "documentation", "doc"]; - const found = candidates.filter((f) => { - const p = path.join(dir, f); - return fs.existsSync(p) && fs.statSync(p).isDirectory(); - }); - return { points: found.length > 0 ? 10 : 0, found, missing: found.length > 0 ? [] : ["docs/"] }; - }, - }, - { - name: "Architecture documentation", - description: "Files describing system architecture", - maxPoints: 15, - check: (dir) => { - const patterns = [ - "architecture.md", "ARCHITECTURE.md", "docs/architecture.md", - "docs/architecture/", "design.md", "DESIGN.md", - ]; - const found = patterns.filter((f) => fs.existsSync(path.join(dir, f))); - return { points: found.length > 0 ? 15 : 0, found, missing: found.length > 0 ? [] : ["architecture.md or docs/architecture/"] }; - }, - }, - { - name: "Feature tracking", - description: "Feature list or task tracking file", - maxPoints: 15, - check: (dir) => { - const patterns = [ - "feature_list.json", "features.md", "FEATURES.md", - "docs/features.md", "tasks.json", "TODO.md", - ]; - const found = patterns.filter((f) => fs.existsSync(path.join(dir, f))); - return { points: found.length > 0 ? 15 : 0, found, missing: found.length > 0 ? [] : ["feature_list.json or features.md"] }; - }, - }, - { - name: "Handoff / session continuity", - description: "Files for multi-session continuity", - maxPoints: 15, - check: (dir) => { - const patterns = [ - "HANDOFF.md", "handoff.md", "SESSION_NOTES.md", - "docs/handoff.md", ".handoff", "PROGRESS.md", - ]; - const found = patterns.filter((f) => fs.existsSync(path.join(dir, f))); - return { points: found.length > 0 ? 15 : 0, found, missing: found.length > 0 ? [] : ["HANDOFF.md or PROGRESS.md"] }; - }, - }, - { - name: "Testing structure", - description: "Test directory or test configuration", - maxPoints: 10, - check: (dir) => { - const patterns = ["test", "tests", "__tests__", "spec"]; - const found = patterns.filter((f) => { - const p = path.join(dir, f); - return fs.existsSync(p) && fs.statSync(p).isDirectory(); - }); - return { points: found.length > 0 ? 10 : 0, found, missing: found.length > 0 ? [] : ["test/ or tests/"] }; - }, - }, - { - name: "Configuration files", - description: "Project config (package.json, tsconfig, etc.)", - maxPoints: 10, - check: (dir) => { - const patterns = ["package.json", "tsconfig.json", "pyproject.toml", "Cargo.toml", "go.mod"]; - const found = patterns.filter((f) => fs.existsSync(path.join(dir, f))); - return { points: found.length > 0 ? 10 : 0, found, missing: found.length > 0 ? [] : ["package.json or equivalent"] }; - }, - }, - { - name: "README", - description: "Root README file", - maxPoints: 10, - check: (dir) => { - const patterns = ["README.md", "README.rst", "README.txt", "README"]; - const found = patterns.filter((f) => fs.existsSync(path.join(dir, f))); - return { points: found.length > 0 ? 10 : 0, found, missing: found.length > 0 ? [] : ["README.md"] }; - }, - }, -]; - -// --------------------------------------------------------------------------- -// Report generation -// --------------------------------------------------------------------------- - -function pad(s: string, len: number): string { - return s.length >= len ? s : s + " ".repeat(len - s.length); -} - -function scoreRepo(targetDir: string): void { - const resolved = path.resolve(targetDir); - - if (!fs.existsSync(resolved)) { - console.error(` Error: Directory not found: ${resolved}`); - process.exit(1); - } - - console.log("\n" + "=".repeat(80)); - console.log(" REPOSITORY DISCOVERABILITY SCORE"); - console.log("=".repeat(80)); - console.log(` Target: ${resolved}\n`); - - const header = `| ${pad("Criterion", 30)}| ${pad("Points", 8)}| ${pad("Status", 10)}| Details`; - const sep = `|${"-".repeat(32)}|${"-".repeat(10)}|${"-".repeat(12)}|${"-".repeat(30)}`; - console.log(header); - console.log(sep); - - let totalScore = 0; - let maxScore = 0; - - for (const check of checks) { - const result = check.check(resolved); - totalScore += result.points; - maxScore += check.maxPoints; - const status = result.points > 0 ? "PASS" : "FAIL"; - const detail = result.found.length > 0 ? result.found.join(", ") : result.missing[0]; - - console.log(`| ${pad(check.name, 30)}| ${pad(result.points + "/" + check.maxPoints, 8)}| ${pad(status, 10)}| ${detail}`); - } - - // Final score - console.log("\n" + "-".repeat(80)); - console.log(` TOTAL SCORE: ${totalScore} / ${maxScore} (${Math.round((totalScore / maxScore) * 100)}%)`); - - // Grade - const pct = totalScore / maxScore; - let grade: string; - if (pct >= 0.9) grade = "A -- Repository is a strong system of record"; - else if (pct >= 0.7) grade = "B -- Good foundation, minor gaps"; - else if (pct >= 0.5) grade = "C -- Partial structure, significant gaps"; - else if (pct >= 0.3) grade = "D -- Minimal structure, hard for agents to navigate"; - else grade = "F -- Repository lacks discoverability signals"; - - console.log(` GRADE: ${grade}`); - console.log("=".repeat(80) + "\n"); -} - -// --------------------------------------------------------------------------- -// Main -// --------------------------------------------------------------------------- - -const target = process.argv[2] || process.cwd(); -scoreRepo(target); diff --git a/courses/learn-harness-engineering/docs/vi/lectures/lecture-03-why-the-repository-must-become-the-system-of-record/code/system-of-record-checklist.md b/courses/learn-harness-engineering/docs/vi/lectures/lecture-03-why-the-repository-must-become-the-system-of-record/code/system-of-record-checklist.md deleted file mode 100644 index 0a6b534d..00000000 --- a/courses/learn-harness-engineering/docs/vi/lectures/lecture-03-why-the-repository-must-become-the-system-of-record/code/system-of-record-checklist.md +++ /dev/null @@ -1,11 +0,0 @@ -# Danh sách Kiểm tra Hệ thống Ghi chép - -Một agent mới có thể khám phá những điều sau chỉ từ repo không? - -- Sản phẩm nào đang được xây dựng? -- Ứng dụng nên làm gì cho người dùng? -- Codebase được tổ chức như thế nào? -- Ứng dụng khởi động như thế nào? -- Sức khỏe được kiểm tra như thế nào? -- Công việc nào hiện đang được tiến hành? -- Tiêu chuẩn chất lượng nào quan trọng? diff --git a/courses/learn-harness-engineering/docs/vi/lectures/lecture-03-why-the-repository-must-become-the-system-of-record/index.md b/courses/learn-harness-engineering/docs/vi/lectures/lecture-03-why-the-repository-must-become-the-system-of-record/index.md deleted file mode 100644 index 3613d76d..00000000 --- a/courses/learn-harness-engineering/docs/vi/lectures/lecture-03-why-the-repository-must-become-the-system-of-record/index.md +++ /dev/null @@ -1,137 +0,0 @@ -[English Version →](../../../en/lectures/lecture-03-why-the-repository-must-become-the-system-of-record/) | [中文版本 →](../../../zh/lectures/lecture-03-why-the-repository-must-become-the-system-of-record/) - -> Ví dụ mã nguồn: [code/](https://github.com/walkinglabs/learn-harness-engineering/blob/main/docs/vi/lectures/lecture-03-why-the-repository-must-become-the-system-of-record/code/) -> Dự án thực hành: [Dự án 02. Không gian làm việc Agent đọc được](./../../projects/project-02-agent-readable-workspace/index.md) - -# Bài 03. Biến Kho lưu trữ Thành Nguồn Sự thật Duy nhất - -Các quyết định kiến trúc của nhóm bạn rải rác khắp Confluence, Slack, Jira, và một vài cái đầu của các kỹ sư cấp cao. Đối với con người, điều này hoạt động gượng gạo — bạn có thể hỏi đồng nghiệp, tìm kiếm lịch sử chat, đào qua tài liệu. Nếu thất bại, bạn có thể chặn ai đó ở căng tin. Nhưng đối với AI agent, thông tin không có trong kho lưu trữ đơn giản là không tồn tại. - -Đây không phải phóng đại. Hãy nghĩ về các đầu vào thực sự của agent là gì: system prompt và mô tả tác vụ, nội dung tệp từ kho lưu trữ, và kết quả thực thi công cụ. Chỉ vậy thôi. Lịch sử Slack, ticket Jira, trang Confluence, và quyết định kiến trúc bạn thảo luận với đồng nghiệp qua cà phê vào chiều thứ Sáu — agent không thể thấy bất cứ điều nào trong số này. Nó không thể "đi hỏi ai đó" hoặc "tìm kiếm lịch sử chat." Nó là một kỹ sư bị nhốt bên trong kho lưu trữ — tất cả những gì bên ngoài, nó không biết gì cả. - -Vậy câu hỏi trở thành: bạn có định đưa cho kỹ sư này một bản đồ tốt không? - -## Những Gì Thuộc Về Bản Đồ - -OpenAI nói thẳng: **thông tin không tồn tại trong repo, không tồn tại với agent.** Họ gọi đây là nguyên tắc "repo là spec" — bản thân kho lưu trữ là tài liệu đặc tả có thẩm quyền cao nhất. - -Tài liệu về agent chạy lâu của Anthropic phản ánh điều này: trạng thái liên tục là điều kiện cần thiết cho tính liên tục của tác vụ dài. Khả năng phục hồi kiến thức xuyên phiên trực tiếp xác định tỷ lệ thành công của tác vụ. Và trạng thái này phải tồn tại trong kho lưu trữ — vì đó là bộ lưu trữ ổn định, có thể truy cập duy nhất mà agent có. - -Bạn có thể nghĩ: "Nhóm chúng tôi nhỏ, kiến thức nằm trong đầu mọi người, và nó vẫn hoạt động tốt." Chắc chắn, đối với con người. Nhưng nếu bạn đang sử dụng agent, hãy chấp nhận sự thật này: agent không thể hỏi người. Mọi thứ nó cần biết phải được viết xuống và đặt ở nơi nó có thể tìm thấy. - -Đây không phải về "viết nhiều tài liệu hơn." Mà là về "đặt thông tin quyết định vào đúng chỗ." Một `ARCHITECTURE.md` 50 dòng trong thư mục `src/api/` hữu ích hơn mười nghìn lần so với một tài liệu thiết kế 500 trang trong Confluence mà không ai bảo trì. Giống như bản đồ văn phòng vẽ tay dán trên bàn làm việc của bạn so với bản vẽ kiến trúc đẹp bị khóa trong tủ hồ sơ — cái trước có sẵn ngay khi bạn cần; cái sau về mặt kỹ thuật ưu việt hơn nhưng vô dụng trong thời điểm đó. - -## Khả năng Hiển thị Kiến thức - -```mermaid -flowchart LR - Slack["Quy tắc trong Slack"] --> Write["Viết vào tệp repo
    AGENTS.md / ARCHITECTURE.md / PROGRESS.md"] - Confluence["Quy tắc trong Confluence"] --> Write - Heads["Quy tắc trong đầu người"] --> Write - Jira["Quy tắc trong Jira ticket"] --> Write - Write --> Repo["Tệp kho lưu trữ"] - Repo --> Agent["Phiên agent mới
    đọc trực tiếp từ repo"] - Warning["Nếu một quy tắc không có trong repo,
    agent không thể nhìn thấy nó"] --> Agent -``` - -Làm thế nào để kiểm tra xem bản đồ của bạn có đủ tốt không? Chạy "bài kiểm tra khởi động lạnh (cold-start test)": mở một phiên agent hoàn toàn mới chỉ sử dụng nội dung repo, và xem nó có thể trả lời năm câu hỏi cơ bản không: - -```mermaid -flowchart TB - Q1["Hệ thống này là gì?"] --> A1["AGENTS.md / README"] - Q2["Nó được tổ chức như thế nào?"] --> A2["ARCHITECTURE.md / tài liệu module"] - Q3["Làm thế nào để chạy nó?"] --> A3["Makefile / init.sh / package scripts"] - Q4["Làm thế nào để xác minh nó?"] --> A4["Lệnh test, lint và check"] - Q5["Chúng ta đang ở đâu bây giờ?"] --> A5["PROGRESS.md / feature list / git history"] - - A1 --> Ready["Một phiên mới có thể bắt đầu làm việc
    mà không cần hỏi con người"] - A2 --> Ready - A3 --> Ready - A4 --> Ready - A5 --> Ready -``` - -Nếu nó không thể trả lời, bản đồ có những chỗ trống. Nơi bản đồ trống, agent đoán — đoán sai trở thành lỗi, đoán quá nhiều lãng phí ngữ cảnh. Và mỗi phiên mới lại đoán lại. Chi phí đoán luôn cao hơn chi phí vẽ bản đồ đúng cách ngay từ đầu. - -## Các Khái niệm Cốt lõi - -- **Khoảng cách Hiển thị Kiến thức (Knowledge Visibility Gap)**: Tỷ lệ tổng kiến thức dự án KHÔNG có trong kho lưu trữ. Khoảng cách càng lớn, tỷ lệ thất bại của agent càng cao. Bao nhiêu kiến thức ẩn về dự án này sống trong đầu bạn? Đếm tất cả, sau đó xem bao nhiêu đã vào repo — sự khác biệt là khoảng cách hiển thị của bạn. -- **Hệ thống Ghi chép (System of Record)**: Kho lưu trữ mã là nguồn có thẩm quyền cho các quyết định dự án, ràng buộc kiến trúc, trạng thái thực thi và tiêu chuẩn xác minh. Repo có tiếng nói cuối cùng, không nơi nào khác có giá trị. Giống như bản đồ có ghi "đường đóng cửa" — bạn sẽ không đi theo con đường đó. Nhưng nếu thông tin đó chỉ tồn tại trong đầu của Anh Nam, bạn phải hỏi Anh Nam mỗi lần. -- **Bài kiểm tra Khởi động Lạnh (Cold-Start Test)**: Năm câu hỏi ở trên. Nó có thể trả lời bao nhiêu thì bản đồ của bạn hoàn chỉnh bấy nhiêu. -- **Chi phí Khám phá (Discovery Cost)**: Ngân sách ngữ cảnh agent đốt cháy để tìm một thông tin quan trọng trong repo. Thông tin càng ẩn, chi phí khám phá càng cao, và ngân sách còn lại cho tác vụ thực tế càng ít. Ẩn thông tin quan trọng trong README sâu mười cấp thư mục giống như khóa bình chữa cháy trong két an toàn tầng hầm — nó tồn tại, nhưng bạn không thể tìm thấy khi cần. -- **Tốc độ Suy giảm Kiến thức (Knowledge Decay Rate)**: Tỷ lệ các mục kiến thức trở nên lỗi thời theo đơn vị thời gian. Tài liệu không đồng bộ với mã là kẻ thù lớn nhất — tệ hơn không có tài liệu nào cả. -- **Phép loại suy ACID**: Áp dụng các nguyên tắc giao dịch cơ sở dữ liệu (Tính nguyên tử, Nhất quán, Cô lập, Bền vững) vào quản lý trạng thái agent. Chúng ta sẽ mở rộng điều này dưới đây. - -## Cách Vẽ Bản Đồ Tốt - -**Nguyên tắc 1: Kiến thức sống gần mã.** Một quy tắc về xác thực API endpoint thuộc về gần mã API, không bị chôn vùi trong một tài liệu toàn cục khổng lồ. Đặt một tài liệu ngắn trong mỗi thư mục module giải thích trách nhiệm, giao diện và các ràng buộc đặc biệt của module đó. Giống như nhãn kệ thư viện — bạn muốn sách lịch sử, đi thẳng đến kệ ghi nhãn "Lịch sử." Không cần tìm kiếm toàn bộ thư viện. - -**Nguyên tắc 2: Sử dụng tệp đầu vào được chuẩn hóa.** `AGENTS.md` (hoặc `CLAUDE.md`) là "trang đích" của agent. Nó không cần chứa tất cả thông tin, nhưng phải để agent nhanh chóng trả lời ba câu hỏi: "Dự án này là gì," "Làm thế nào để chạy nó," và "Làm thế nào để xác minh nó." 50-100 dòng là đủ. - -**Nguyên tắc 3: Tối giản nhưng đầy đủ.** Mỗi mảnh kiến thức nên có trường hợp sử dụng rõ ràng. Nếu xóa một quy tắc không ảnh hưởng đến chất lượng quyết định của agent, quy tắc đó không nên tồn tại. Nhưng mỗi câu hỏi từ bài kiểm tra khởi động lạnh phải có câu trả lời. Đây là sự cân bằng tế nhị — không quá nhiều, không quá ít, vừa đủ. - -**Nguyên tắc 4: Cập nhật cùng mã.** Ràng buộc cập nhật kiến thức với thay đổi mã. Cách tiếp cận đơn giản nhất: đặt tài liệu kiến trúc trong thư mục module tương ứng. Khi bạn sửa đổi mã, bạn tự nhiên nhìn thấy tài liệu. Sau khi thay đổi mã, CI có thể nhắc nhở bạn kiểm tra xem tài liệu có cần cập nhật không. - -**Cấu trúc repo cụ thể**: - -``` -project/ -├── AGENTS.md # Đầu vào: tổng quan dự án, lệnh chạy, ràng buộc cứng -├── src/ -│ ├── api/ -│ │ ├── ARCHITECTURE.md # Quyết định kiến trúc lớp API -│ │ └── ... -│ ├── db/ -│ │ ├── CONSTRAINTS.md # Ràng buộc cứng của hoạt động cơ sở dữ liệu -│ │ └── ... -│ └── ... -├── PROGRESS.md # Tiến độ hiện tại: xong, đang thực hiện, bị chặn -└── Makefile # Lệnh chuẩn hóa: setup, test, lint, check -``` - -## Quản lý Trạng thái Agent với Nguyên tắc ACID - -Phép loại suy này đến từ quản lý giao dịch cơ sở dữ liệu — bạn có thể nghĩ nó đang phức tạp hóa quá mức, nhưng nó thực sự cung cấp cho bạn một khung rất thực tế: - -- **Tính nguyên tử (Atomicity)**: Mỗi "hoạt động logic" (ví dụ: "thêm endpoint mới và cập nhật test") nhận một git commit. Nếu thất bại giữa chừng, `git stash` để khôi phục. Tất cả hoặc không có gì — không có "làm được một nửa." -- **Nhất quán (Consistency)**: Xác định các vị từ xác minh "trạng thái nhất quán" — tất cả test qua, lint báo cáo không có lỗi. Agent chạy xác minh sau mỗi hoạt động; các trạng thái trung gian không nhất quán không được commit. Giống như chuyển khoản ngân hàng — bạn không thể ghi nợ mà không ghi có. -- **Cô lập (Isolation)**: Khi nhiều agent hoạt động đồng thời, thiết kế các tệp trạng thái để tránh race condition. Cách tiếp cận đơn giản: mỗi agent sử dụng tệp tiến độ riêng, hoặc sử dụng git branch để cô lập. Hai đầu bếp không thể nêm cùng một nồi đồng thời — ai chịu trách nhiệm khi bị mặn quá? -- **Bền vững (Durability)**: Kiến thức dự án quan trọng sống trong các tệp được theo dõi bởi git. Trạng thái tạm thời có thể ở lại trong bộ nhớ phiên, nhưng kiến thức xuyên phiên phải được lưu trữ vào tệp. Những gì trong đầu bạn không tính — chỉ những gì trên giấy mới tính. - -## Câu chuyện Chuyển đổi Thực tế - -Một nhóm duy trì một nền tảng thương mại điện tử với ~30 microservices. Các quyết định kiến trúc (giao thức giao tiếp giữa các dịch vụ, chiến lược nhất quán dữ liệu, quy tắc phiên bản API) bị rải rác khắp: Confluence (một phần lỗi thời), Slack (khó tìm kiếm), một vài đầu của các kỹ sư cấp cao (không có khả năng mở rộng), và các chú thích mã rải rác (không có hệ thống). - -Sau khi giới thiệu AI agent, 70% tác vụ yêu cầu can thiệp của con người. Gần như mọi lỗi đều liên quan đến agent vi phạm một số ràng buộc ẩn "mọi người biết nhưng không ai viết xuống." Giống như nhân viên mới mà không ai nói với họ "bạn cần đăng lệnh ăn trưa trong nhóm chat" — họ đoán sai, bị mắng, nhưng sau khi bị mắng vẫn không ai nói với họ quy tắc. - -Nhóm đã thực hiện một cuộc chuyển đổi: -1. Tạo `AGENTS.md` trong thư mục gốc repo với tổng quan dự án, phiên bản tech stack và các ràng buộc cứng toàn cục -2. Thêm `ARCHITECTURE.md` trong mỗi thư mục microservice mô tả trách nhiệm, giao diện và phụ thuộc -3. Tạo `CONSTRAINTS.md` tập trung với các ràng buộc cứng bằng ngôn ngữ "PHẢI/KHÔNG ĐƯỢC" rõ ràng -4. Thêm `PROGRESS.md` trong mỗi thư mục dịch vụ theo dõi trạng thái công việc hiện tại - -Sau chuyển đổi: cùng agent có thể trả lời tất cả các câu hỏi dự án quan trọng khi khởi động lạnh, và chất lượng hoàn thành tác vụ cải thiện đáng kể. - -## Những Điểm chính cần Nhớ - -- Kiến thức không có trong repo không tồn tại với agent. Đặt các quyết định quan trọng vào repo là đầu tư harness cơ bản nhất — vẽ bản đồ tốt để không bị lạc. -- Sử dụng "bài kiểm tra khởi động lạnh" để đánh giá chất lượng repo: một phiên mới có thể trả lời năm câu hỏi cơ bản chỉ sử dụng nội dung repo không? -- Kiến thức phải ở gần mã, tối giản nhưng đầy đủ, và cập nhật cùng mã. Không phải về viết nhiều tài liệu hơn — mà là đặt thông tin vào đúng chỗ. -- Sử dụng nguyên tắc ACID cho trạng thái agent: commit nguyên tử, xác minh nhất quán, cô lập đồng thời, kiến thức quan trọng bền vững. -- Suy giảm kiến thức là kẻ thù lớn nhất. Tài liệu không đồng bộ với mã nguy hiểm hơn không có tài liệu — nó đưa agent đi theo hướng sai trong khi nghĩ rằng mình đúng. - -## Đọc thêm - -- [OpenAI: Harness Engineering](https://openai.com/index/harness-engineering/) -- [Anthropic: Effective Harnesses for Long-Running Agents](https://www.anthropic.com/engineering/effective-harnesses-for-long-running-agents) -- [Infrastructure as Code — Martin Fowler](https://martinfowler.com/bliki/InfrastructureAsCode.html) -- [ADR: Architecture Decision Records](https://adr.github.io/) -- [The Twelve-Factor App](https://12factor.net/) - -## Bài tập - -1. **Bài kiểm tra khởi động lạnh**: Mở một phiên agent hoàn toàn mới trong dự án của bạn (không có ngữ cảnh lời nói, chỉ nội dung repo). Hỏi nó năm câu hỏi: Hệ thống này là gì? Nó được tổ chức như thế nào? Làm thế nào để chạy nó? Làm thế nào để xác minh nó? Tiến độ hiện tại là gì? Ghi lại những gì nó không thể trả lời, sau đó cải thiện repo cho đến khi nó có thể. - -2. **Định lượng ngoại hóa kiến thức**: Liệt kê tất cả các quyết định và ràng buộc quan trọng cho công việc phát triển trong dự án của bạn. Đánh dấu mỗi cái là có trong hoặc ngoài repo. Tính khoảng cách hiển thị kiến thức của bạn (tỷ lệ ngoài repo). Lập kế hoạch để đưa nó xuống dưới 10%. - -3. **Đánh giá ACID**: Đánh giá quản lý trạng thái dự án của bạn bằng phép loại suy ACID của bài giảng này. Tính nguyên tử — các hoạt động agent có thể được khôi phục sạch không? Nhất quán — có xác minh "trạng thái nhất quán" không? Cô lập — các agent đồng thời có giẫm lên nhau không? Bền vững — tất cả kiến thức xuyên phiên có được lưu trữ không? diff --git a/courses/learn-harness-engineering/docs/vi/lectures/lecture-04-why-one-giant-instruction-file-fails/code/AGENTS-short.md b/courses/learn-harness-engineering/docs/vi/lectures/lecture-04-why-one-giant-instruction-file-fails/code/AGENTS-short.md deleted file mode 100644 index 9cb810c2..00000000 --- a/courses/learn-harness-engineering/docs/vi/lectures/lecture-04-why-one-giant-instruction-file-fails/code/AGENTS-short.md +++ /dev/null @@ -1,15 +0,0 @@ -# AGENTS.md - -## Bắt đầu Tại đây - -- Đọc `docs/ARCHITECTURE.md` -- Đọc `docs/PRODUCT.md` -- Sử dụng `npm run dev` để khởi động ứng dụng -- Sử dụng `npm run check` trước khi đánh dấu công việc hoàn thành - -## Quy tắc Cứng - -- Không thay đổi ranh giới Electron main/preload/renderer mà không đọc - `docs/ARCHITECTURE.md` -- Không đánh dấu tính năng hoàn thành mà không có xác minh -- Để lại trạng thái sạch cho phiên tiếp theo diff --git a/courses/learn-harness-engineering/docs/vi/lectures/lecture-04-why-one-giant-instruction-file-fails/code/anti-patterns.md b/courses/learn-harness-engineering/docs/vi/lectures/lecture-04-why-one-giant-instruction-file-fails/code/anti-patterns.md deleted file mode 100644 index b9a010ea..00000000 --- a/courses/learn-harness-engineering/docs/vi/lectures/lecture-04-why-one-giant-instruction-file-fails/code/anti-patterns.md +++ /dev/null @@ -1,7 +0,0 @@ -# Các Anti-Pattern Tệp Hướng dẫn - -- Đưa tất cả kiến thức kho lưu trữ vào một tệp duy nhất -- Lặp lại cùng một quy tắc ở nhiều nơi -- Mã hóa các quy tắc lỗi thời mà không ai kiểm toán -- Viết các hướng dẫn có điều kiện cụ thể đến mức ít khi áp dụng -- Nhúng các hướng dẫn công cụ dài vào ngữ cảnh khởi động diff --git a/courses/learn-harness-engineering/docs/vi/lectures/lecture-04-why-one-giant-instruction-file-fails/code/index.md b/courses/learn-harness-engineering/docs/vi/lectures/lecture-04-why-one-giant-instruction-file-fails/code/index.md deleted file mode 100644 index 8176a692..00000000 --- a/courses/learn-harness-engineering/docs/vi/lectures/lecture-04-why-one-giant-instruction-file-fails/code/index.md +++ /dev/null @@ -1,7 +0,0 @@ -# Mã cho Bài 04 - -Sử dụng thư mục này cho các ví dụ về: - -- tệp hướng dẫn nguyên khối -- tệp đầu vào ngắn -- các mẫu tiết lộ tiến triển diff --git a/courses/learn-harness-engineering/docs/vi/lectures/lecture-04-why-one-giant-instruction-file-fails/code/split-vs-monolithic.ts b/courses/learn-harness-engineering/docs/vi/lectures/lecture-04-why-one-giant-instruction-file-fails/code/split-vs-monolithic.ts deleted file mode 100644 index 59a96328..00000000 --- a/courses/learn-harness-engineering/docs/vi/lectures/lecture-04-why-one-giant-instruction-file-fails/code/split-vs-monolithic.ts +++ /dev/null @@ -1,209 +0,0 @@ -/** - * split-vs-monolithic.ts - * - * Creates a monolithic instruction file (~200 lines) and then shows how - * splitting into 4 focused files dramatically reduces the context needed - * for any single query. Simulates an "agent" searching for a specific rule - * and measures how many lines it must read in each approach. - * - * Run: npx tsx docs/lectures/lecture-04-why-one-giant-instruction-file-fails/code/split-vs-monolithic.ts - */ - -// --------------------------------------------------------------------------- -// Simulated monolithic instruction file (200 lines of rules) -// --------------------------------------------------------------------------- - -const monolithicInstructions: { lineNumber: number; section: string; content: string }[] = []; - -// Section 1: Project Overview (lines 1-50) -for (let i = 1; i <= 50; i++) { - monolithicInstructions.push({ - lineNumber: i, - section: "Project Overview", - content: i === 25 ? "This project uses React 18 with TypeScript strict mode." : `Overview detail line ${i}`, - }); -} - -// Section 2: Code Style Rules (lines 51-100) -for (let i = 51; i <= 100; i++) { - monolithicInstructions.push({ - lineNumber: i, - section: "Code Style", - content: - i === 72 - ? "RULE: Always use explicit return types on exported functions." - : i === 78 - ? "RULE: Use const assertions for immutable arrays." - : `Style rule detail line ${i}`, - }); -} - -// Section 3: Testing Standards (lines 101-150) -for (let i = 101; i <= 150; i++) { - monolithicInstructions.push({ - lineNumber: i, - section: "Testing", - content: - i === 120 - ? "RULE: Every new endpoint must have integration tests." - : i === 135 - ? "RULE: Test files must mirror the source file structure." - : `Testing detail line ${i}`, - }); -} - -// Section 4: Deployment Rules (lines 151-200) -for (let i = 151; i <= 200; i++) { - monolithicInstructions.push({ - lineNumber: i, - section: "Deployment", - content: - i === 175 - ? "RULE: Never deploy on Fridays. Deploy window is Tue-Thu 10am-3pm." - : `Deployment detail line ${i}`, - }); -} - -// --------------------------------------------------------------------------- -// Split instruction files (4 focused files) -// --------------------------------------------------------------------------- - -const splitInstructions: Record = { - "01-project-overview.md": monolithicInstructions.filter((l) => l.section === "Project Overview"), - "02-code-style.md": monolithicInstructions.filter((l) => l.section === "Code Style"), - "03-testing.md": monolithicInstructions.filter((l) => l.section === "Testing"), - "04-deployment.md": monolithicInstructions.filter((l) => l.section === "Deployment"), -}; - -// --------------------------------------------------------------------------- -// Simulated queries -- the agent needs to find specific rules -// --------------------------------------------------------------------------- - -interface Query { - description: string; - targetRule: string; - relevantSection: string; -} - -const queries: Query[] = [ - { - description: "Find the rule about return types", - targetRule: "explicit return types", - relevantSection: "Code Style", - }, - { - description: "Find the deployment window rule", - targetRule: "deploy on Fridays", - relevantSection: "Deployment", - }, - { - description: "Find the integration test rule", - targetRule: "integration tests", - relevantSection: "Testing", - }, - { - description: "Find the test file structure rule", - targetRule: "mirror the source file", - relevantSection: "Testing", - }, -]; - -// --------------------------------------------------------------------------- -// Search simulation -// --------------------------------------------------------------------------- - -function searchMonolithic(query: Query): { linesRead: number; found: boolean } { - // Agent must scan from the top, reading each line until it finds the rule. - // In the worst case it reads all lines. - let linesRead = 0; - let found = false; - - for (const line of monolithicInstructions) { - linesRead++; - if (line.content.toLowerCase().includes(query.targetRule.toLowerCase())) { - found = true; - break; - } - } - - return { linesRead, found }; -} - -function searchSplit(query: Query): { linesRead: number; found: boolean; fileAccessed: string } { - // Agent knows which file to look in based on the section. - // It only reads lines from that one file. - const fileMap: Record = { - "Project Overview": "01-project-overview.md", - "Code Style": "02-code-style.md", - Testing: "03-testing.md", - Deployment: "04-deployment.md", - }; - - const targetFile = fileMap[query.relevantSection]; - const lines = splitInstructions[targetFile]; - let linesRead = 0; - let found = false; - - for (const line of lines) { - linesRead++; - if (line.content.toLowerCase().includes(query.targetRule.toLowerCase())) { - found = true; - break; - } - } - - return { linesRead, found, fileAccessed: targetFile }; -} - -// --------------------------------------------------------------------------- -// Reporting -// --------------------------------------------------------------------------- - -function pad(s: string, len: number): string { - return s.length >= len ? s : s + " ".repeat(len - s.length); -} - -function run(): void { - console.log("\n" + "=".repeat(90)); - console.log(" MONOLITHIC vs SPLIT INSTRUCTION FILES"); - console.log("=".repeat(90)); - - console.log("\n Monolithic file: 1 file, " + monolithicInstructions.length + " lines total"); - console.log(" Split files: 4 files, ~" + Math.round(monolithicInstructions.length / 4) + " lines each\n"); - - const header = `| ${pad("Query", 42)}| ${pad("Monolithic (lines)", 20)}| ${pad("Split (lines)", 15)}| ${pad("File Accessed", 22)}| Savings`; - const sep = `|${"-".repeat(44)}|${"-".repeat(22)}|${"-".repeat(17)}|${"-".repeat(24)}|${"-".repeat(10)}`; - console.log(header); - console.log(sep); - - let totalMono = 0; - let totalSplit = 0; - - for (const q of queries) { - const mono = searchMonolithic(q); - const split = searchSplit(q); - totalMono += mono.linesRead; - totalSplit += split.linesRead; - - const savings = Math.round(((mono.linesRead - split.linesRead) / mono.linesRead) * 100); - console.log( - `| ${pad(q.description, 42)}| ${pad(String(mono.linesRead), 20)}| ${pad(String(split.linesRead), 15)}| ${pad(split.fileAccessed, 22)}| ${savings}%` - ); - } - - console.log(sep); - const avgMono = Math.round(totalMono / queries.length); - const avgSplit = Math.round(totalSplit / queries.length); - console.log( - `| ${pad("AVERAGE", 42)}| ${pad(String(avgMono), 20)}| ${pad(String(avgSplit), 15)}| ${pad("(targeted file)", 22)}| ${Math.round(((avgMono - avgSplit) / avgMono) * 100)}%` - ); - - console.log("\n" + "=".repeat(90)); - console.log(" KEY INSIGHT"); - console.log("=".repeat(90)); - console.log(" With a monolithic file, the agent must scan up to " + monolithicInstructions.length + " lines for every query."); - console.log(" With split files, it reads only the relevant " + Math.round(monolithicInstructions.length / 4) + "-line file."); - console.log(" This means less context window usage, fewer hallucinations, and faster execution.\n"); -} - -run(); diff --git a/courses/learn-harness-engineering/docs/vi/lectures/lecture-04-why-one-giant-instruction-file-fails/index.md b/courses/learn-harness-engineering/docs/vi/lectures/lecture-04-why-one-giant-instruction-file-fails/index.md deleted file mode 100644 index 0ee754e8..00000000 --- a/courses/learn-harness-engineering/docs/vi/lectures/lecture-04-why-one-giant-instruction-file-fails/index.md +++ /dev/null @@ -1,132 +0,0 @@ -[English Version →](../../../en/lectures/lecture-04-why-one-giant-instruction-file-fails/) | [中文版本 →](../../../zh/lectures/lecture-04-why-one-giant-instruction-file-fails/) - -> Ví dụ mã nguồn: [code/](https://github.com/walkinglabs/learn-harness-engineering/blob/main/docs/vi/lectures/lecture-04-why-one-giant-instruction-file-fails/code/) -> Dự án thực hành: [Dự án 02. Không gian làm việc Agent đọc được](./../../projects/project-02-agent-readable-workspace/index.md) - -# Bài 04. Chia Hướng dẫn Ra Thành Nhiều Tệp - -Bạn đã nghiêm túc với harness engineering — tốt lắm. Bạn đã tạo `AGENTS.md` và nhét mọi quy tắc, ràng buộc và bài học kinh nghiệm mà bạn có thể nghĩ ra vào đó. Một tháng sau, tệp phình lên 300 dòng, hai tháng 450 dòng, ba tháng 600 dòng. Rồi bạn nhận ra hiệu suất của agent thực sự đang kém hơn — trên một sửa lỗi đơn giản, agent đốt cháy rất nhiều ngữ cảnh để xử lý các hướng dẫn triển khai không liên quan; một ràng buộc bảo mật quan trọng bị chôn ở dòng 300 bị bỏ qua hoàn toàn; ba quy tắc phong cách mã mâu thuẫn có nghĩa là agent chọn một cái ngẫu nhiên mỗi lần. - -Đây là bẫy "tệp hướng dẫn khổng lồ." Giống như nhồi quá nhiều vào vali — mọi thứ có vẻ hữu ích, vì vậy bạn nhét tất cả vào cho đến khi khóa kéo sắp nổ tung. Tìm đồ lót của bạn có nghĩa là đổ toàn bộ túi. Bạn mang một chiếc vali đầy, nhưng thực ra bạn chỉ dùng khoảng một phần ba những gì bên trong. - -## Vòng lặp Vicious Tại Gốc rễ - -Vòng lặp vicious phổ biến nhất diễn ra như thế này: agent mắc lỗi, bạn nói "thêm quy tắc để ngăn điều này," thêm nó vào AGENTS.md, nó hoạt động tạm thời, agent mắc lỗi khác, thêm quy tắc khác, lặp lại, tệp phình ra ngoài tầm kiểm soát. - -Đây không phải lỗi của bạn. Đây là một phản ứng rất tự nhiên — "thêm quy tắc" mỗi khi có sự cố cảm thấy hợp lý, giống như tống thêm thứ gì đó vào túi mỗi khi bạn ra khỏi nhà "đề phòng bất trắc." Nhưng hiệu ứng tích lũy là thảm khốc. Hãy xem cụ thể những gì đi sai. - -**Ngân sách ngữ cảnh bị ăn mòn.** Cửa sổ ngữ cảnh của agent là hữu hạn. Giả sử agent của bạn có cửa sổ 200K token (tiêu chuẩn của Claude). Một tệp hướng dẫn phình to có thể ăn 10-20K token. Có vẻ vẫn còn nhiều chỗ? Nhưng một tác vụ phức tạp có thể cần đọc hàng chục tệp nguồn, kết quả thực thi công cụ cũng chiếm ngữ cảnh, và lịch sử hội thoại tích lũy. Đến khi agent cần hiểu mã, ngân sách đã chật chội — giống như một chiếc vali quá đầy đến mức không còn chỗ cho laptop của bạn. - -**Lạc giữa đường.** Bài báo "Lost in the Middle" (Liu et al., 2023) đã chứng minh rõ ràng rằng LLM sử dụng thông tin ở giữa các văn bản dài kém hiệu quả hơn đáng kể so với đầu hoặc cuối. AGENTS.md của bạn có 600 dòng, và dòng 300 nói "tất cả các truy vấn cơ sở dữ liệu phải sử dụng truy vấn được tham số hóa" — đó là ràng buộc cứng bảo mật. Nhưng nó bị chôn ở giữa, và agent gần như chắc chắn sẽ bỏ qua nó. Giống như chai kem chống nắng ở đáy chiếc vali quá đầy của bạn — bạn biết nó ở đó, bạn đào ba lần, không tìm thấy, cuối cùng mua cái khác. - -**Xung đột ưu tiên.** Tệp trộn lẫn các ràng buộc cứng không thể thương lượng ("không bao giờ sử dụng eval()"), các hướng dẫn thiết kế quan trọng ("ưu tiên phong cách hàm"), và một bài học lịch sử cụ thể ("đã sửa rò rỉ bộ nhớ WebSocket tuần trước, chú ý đến các mẫu tương tự"). Ba quy tắc này có mức độ quan trọng hoàn toàn khác nhau, nhưng chúng trông giống hệt nhau trong tệp. Agent không có tín hiệu đáng tin cậy để phân biệt — giống như hộ chiếu và dây sạc bị lẫn lộn trong vali, không cách nào biết cái nào cấp bách hơn. - -**Suy giảm bảo trì.** Các tệp lớn vốn dĩ khó bảo trì. Các hướng dẫn lỗi thời hiếm khi bị xóa — vì hậu quả của việc xóa là không chắc chắn ("có lẽ thứ gì đó khác phụ thuộc vào quy tắc này?"), trong khi thêm hướng dẫn mới có vẻ miễn phí. Kết quả: tệp chỉ tăng, không bao giờ giảm, và tỷ lệ tín hiệu/nhiễu liên tục giảm. Đây chính xác giống như tích lũy nợ kỹ thuật trong phần mềm. - -**Tích lũy mâu thuẫn.** Các hướng dẫn được thêm vào các thời điểm khác nhau bắt đầu mâu thuẫn nhau — một cái nói "sử dụng TypeScript strict mode," một cái khác nói "một số tệp legacy cho phép any types." Agent chọn ngẫu nhiên một cái để tuân theo mỗi lần. Giống như mẹ bạn nói "mặc thêm ấm" và bố bạn nói "đừng mặc quá nhiều," và bạn đứng ở cửa không biết lắng nghe ai. - -## Các Khái niệm Cốt lõi - -- **Phình Hướng dẫn (Instruction Bloat)**: Khi một tệp hướng dẫn chiếm hơn 10-15% cửa sổ ngữ cảnh, nó bắt đầu lấn át ngân sách để đọc mã và lý luận tác vụ. Một `AGENTS.md` 600 dòng có thể tiêu thụ 10,000-20,000 token — đó là 8-15% cửa sổ 128K bị ăn hết trước khi agent thậm chí bắt đầu. -- **Hiệu ứng Lạc Giữa Đường (Lost in the Middle Effect)**: Nghiên cứu năm 2023 của Liu et al. đã chứng minh rằng LLM sử dụng thông tin ở giữa các văn bản dài kém hiệu quả hơn đáng kể so với thông tin ở đầu hoặc cuối. Một ràng buộc quan trọng bị chôn ở dòng 300 của tệp 600 dòng có xác suất rất cao bị bỏ qua thực tế. -- **Tỷ lệ Tín hiệu/Nhiễu Hướng dẫn (Instruction SNR)**: Tỷ lệ hướng dẫn trong một tệp liên quan đến tác vụ hiện tại. Bị buộc phải đọc 50 dòng hướng dẫn triển khai trong khi sửa lỗi — đó là SNR thấp. -- **Tệp Định tuyến (Routing File)**: Một tệp đầu vào ngắn có chức năng cốt lõi là chỉ agent đến các tài liệu chi tiết hơn, không chứa tất cả mọi thứ trong chính nó. 50-200 dòng là đủ. -- **Tiết lộ Tiến triển (Progressive Disclosure)**: Đưa thông tin tổng quan trước, thông tin chi tiết khi cần. Thiết kế harness tốt giống như thiết kế UI tốt — đừng đổ tất cả các tùy chọn lên người dùng cùng một lúc. -- **Mơ hồ Ưu tiên (Priority Ambiguity)**: Khi tất cả hướng dẫn xuất hiện ở cùng định dạng và vị trí, agent không thể phân biệt các ràng buộc cứng không thể thương lượng với các hướng dẫn mềm mang tính gợi ý. - -## Kiến trúc Hướng dẫn - -```mermaid -flowchart LR - Mono["Một AGENTS.md 600 dòng"] --> MonoLoad["Ngay cả sửa lỗi nhỏ
    cũng phải đọc quy tắc triển khai và ghi chú cũ"] - MonoLoad --> MonoRisk["Quy tắc quan trọng bị chôn ở giữa
    dễ bỏ qua"] - - Router["AGENTS.md ngắn"] --> Topics["Chỉ tải tài liệu API / DB / testing
    khi tác vụ này cần chúng"] - Topics --> RoutedResult["Còn nhiều ngữ cảnh hơn cho việc đọc mã
    và xác minh"] -``` - -```mermaid -flowchart TB - File["Tệp hướng dẫn 600 dòng"] --> Top["Phần đầu
    khởi động nhanh + ràng buộc cứng"] - File --> Mid["Phần giữa
    quy tắc bảo mật quan trọng ở dòng 300"] - File --> Bot["Phần cuối
    danh sách kiểm tra cuối tệp rõ ràng"] - Top --> Seen["Khả năng nhớ lại cao"] - Bot --> Seen - Mid --> Missed["Khả năng cao bị loãng hoặc bỏ qua"] -``` - -## Cách Chia - -Nguyên tắc cốt lõi: giữ thông tin thường cần ở tầm tay, cất thông tin thỉnh thoảng cần, và bỏ lại những gì bạn sẽ không bao giờ dùng. - -Tệp đầu vào `AGENTS.md` duy trì ở 50-200 dòng, chỉ chứa các mục được sử dụng thường xuyên nhất — tổng quan dự án (một hoặc hai câu), lệnh chạy đầu tiên (`make setup && make test`), các ràng buộc cứng toàn cục (không quá 15 quy tắc không thể thương lượng), và liên kết đến các tài liệu chủ đề (mô tả một dòng + điều kiện áp dụng). - -```markdown -# AGENTS.md - -## Tổng quan Dự án -Backend FastAPI Python 3.11, cơ sở dữ liệu PostgreSQL 15. - -## Khởi động Nhanh -- Cài đặt: `make setup` -- Test: `make test` -- Xác minh đầy đủ: `make check` - -## Ràng buộc Cứng -- Tất cả API phải sử dụng xác thực OAuth 2.0 -- Tất cả truy vấn cơ sở dữ liệu phải sử dụng cú pháp SQLAlchemy 2.0 -- Tất cả PR phải vượt qua pytest + mypy --strict + ruff check - -## Tài liệu Chủ đề -- [Mẫu Thiết kế API](docs/api-patterns.md) — Đọc bắt buộc khi thêm endpoint -- [Quy tắc Cơ sở dữ liệu](docs/database-rules.md) — Bắt buộc khi sửa đổi hoạt động cơ sở dữ liệu -- [Tiêu chuẩn Testing](docs/testing-standards.md) — Tham khảo khi viết test -``` - -Mỗi tài liệu chủ đề có 50-150 dòng, được tổ chức theo chủ đề trong thư mục `docs/` hoặc cạnh module tương ứng. Agent chỉ đọc chúng khi cần. Giống như túi đựng đồ trong vali — đồ lót một túi, đồ vệ sinh một túi, dây sạc một túi. Tìm đồ không cần đổ toàn bộ túi. - -Một số thông tin được đặt tốt hơn trực tiếp trong mã — định nghĩa type, chú thích giao diện, giải thích trong tệp cấu hình. Agent tự nhiên thấy những điều này khi đọc mã, không cần sao chép trong hướng dẫn. - -Mỗi hướng dẫn nên có nguồn gốc ("tại sao quy tắc này được thêm?"), điều kiện áp dụng ("khi nào quy tắc này cần?"), và điều kiện hết hạn ("trong hoàn cảnh nào quy tắc này có thể được xóa?"). Kiểm tra thường xuyên, xóa các mục lỗi thời, dư thừa và mâu thuẫn. Quản lý hướng dẫn của bạn như bạn quản lý các phụ thuộc mã — các phụ thuộc không dùng nên được xóa, nếu không chúng chỉ làm chậm hệ thống. - -Nếu một hướng dẫn phải có trong tệp đầu vào, hãy đặt nó ở đầu hoặc cuối — không bao giờ ở giữa. Hiệu ứng "lạc giữa đường" cho chúng ta biết rằng LLM sử dụng thông tin ở các cực đoan tốt hơn đáng kể so với trung tâm. Nhưng cách tiếp cận tốt hơn là di chuyển hướng dẫn sang các tài liệu chủ đề để tải theo yêu cầu. - -Cả OpenAI và Anthropic đều ngầm hỗ trợ cách tiếp cận chia nhỏ. OpenAI nói các tệp đầu vào nên "ngắn và hướng định tuyến," Anthropic nói thông tin kiểm soát agent chạy lâu nên "súc tích và ưu tiên cao." Cả hai đều nói cùng một điều: đừng nhồi nhét mọi thứ vào một tệp. Vali cần được tổ chức, không phải nhồi nhét thô bạo. - -## Ví dụ Thực tế - -Một nhóm SaaS có `AGENTS.md` phình từ 50 dòng lên 600. Nội dung trộn lẫn phiên bản tech stack, tiêu chuẩn mã hóa, ghi chú sửa lỗi lịch sử, hướng dẫn sử dụng API, quy trình triển khai và sở thích cá nhân của các thành viên nhóm — toàn bộ chiếc vali sắp nổ tung. - -Hiệu suất agent bắt đầu giảm rõ rệt: trong khi sửa lỗi đơn giản, agent dành nhiều ngữ cảnh để xử lý các hướng dẫn triển khai không liên quan; ràng buộc bảo mật "tất cả truy vấn cơ sở dữ liệu phải sử dụng truy vấn được tham số hóa" bị chôn ở dòng 300 và thường xuyên bị bỏ qua; ba quy tắc phong cách mã mâu thuẫn gây ra hành vi agent ngẫu nhiên. - -Nhóm đã thực hiện "tái tổ chức vali": -1. `AGENTS.md` được cắt xuống còn 80 dòng: chỉ tổng quan dự án, lệnh chạy và 15 ràng buộc cứng toàn cục -2. Tạo các tài liệu chủ đề: `docs/api-patterns.md` (120 dòng), `docs/database-rules.md` (60 dòng), `docs/testing-standards.md` (80 dòng) -3. Thêm liên kết tài liệu chủ đề trong tệp định tuyến -4. Các ghi chú lịch sử được chuyển đổi thành test case hoặc bị xóa - -Sau tái cấu trúc: tỷ lệ thành công cùng bộ tác vụ tăng từ 45% lên 72%. Tuân thủ ràng buộc bảo mật tăng từ 60% lên 95% — vì nó di chuyển từ giữa tệp lên đầu tệp định tuyến, không còn "lạc giữa đường" nữa. - -## Những Điểm chính cần Nhớ - -- "Thêm quy tắc" là giảm đau ngắn hạn, thuốc độc dài hạn. Trước khi thêm quy tắc, hãy hỏi: quy tắc này có tốt hơn trong một tài liệu chủ đề không? Đừng cứ tiếp tục nhồi nhét vào vali. -- Tệp đầu vào là bộ định tuyến, không phải bách khoa toàn thư. 50-200 dòng chỉ với tổng quan, ràng buộc cứng và liên kết. -- Tận dụng hiệu ứng "lạc giữa đường": thông tin quan trọng đặt ở đầu hoặc cuối; thông tin không quan trọng di chuyển sang tài liệu chủ đề. -- Quản lý phình hướng dẫn như nợ kỹ thuật. Kiểm tra thường xuyên, mỗi hướng dẫn cần nguồn gốc, điều kiện áp dụng và điều kiện hết hạn. -- Sau khi chia, SNR cải thiện và agent dành nhiều ngân sách ngữ cảnh hơn cho các tác vụ thực tế thay vì xử lý các hướng dẫn không liên quan. - -## Đọc thêm - -- [OpenAI: Harness Engineering](https://openai.com/index/harness-engineering/) -- [Anthropic: Effective Harnesses for Long-Running Agents](https://www.anthropic.com/engineering/effective-harnesses-for-long-running-agents) -- [Lost in the Middle: How Language Models Use Long Contexts](https://arxiv.org/abs/2307.03172) -- [HumanLayer: Harness Engineering for Coding Agents](https://humanlayer.dev/articles/harness-engineering-for-coding-agents/) -- [Nielsen Norman Group: Progressive Disclosure](https://www.nngroup.com/articles/progressive-disclosure/) - -## Bài tập - -1. **Kiểm toán SNR**: Lấy tệp hướng dẫn đầu vào hiện tại của bạn và liệt kê tất cả các mục hướng dẫn. Chọn 5 loại tác vụ thông thường khác nhau và đánh dấu liệu mỗi hướng dẫn có liên quan đến tác vụ đó không. Tính SNR cho mỗi loại tác vụ. Các hướng dẫn là nhiễu cho hầu hết các tác vụ nên di chuyển sang tài liệu chủ đề. - -2. **Tái cấu trúc tiết lộ tiến triển**: Nếu bạn có tệp hướng dẫn trên 300 dòng, hãy chia nó thành: (a) tệp định tuyến dưới 100 dòng, (b) 3-5 tài liệu chủ đề. Chạy cùng bộ tác vụ (ít nhất 5) trước và sau, so sánh tỷ lệ thành công. - -3. **Xác minh lạc giữa đường**: Trong một tệp hướng dẫn dài, đặt một ràng buộc quan trọng ở đầu, giữa và cuối lần lượt, chạy cùng bộ tác vụ mỗi lần (ít nhất 5 lần mỗi vị trí). Xem có sự khác biệt về tỷ lệ tuân thủ không. Bạn có thể ngạc nhiên về mức độ mạnh của hiệu ứng vị trí. diff --git a/courses/learn-harness-engineering/docs/vi/lectures/lecture-05-why-long-running-tasks-lose-continuity/code/continuity-checklist.md b/courses/learn-harness-engineering/docs/vi/lectures/lecture-05-why-long-running-tasks-lose-continuity/code/continuity-checklist.md deleted file mode 100644 index 8e82fc4d..00000000 --- a/courses/learn-harness-engineering/docs/vi/lectures/lecture-05-why-long-running-tasks-lose-continuity/code/continuity-checklist.md +++ /dev/null @@ -1,6 +0,0 @@ -# Danh sách Kiểm tra Tính Liên tục - -- Một agent mới có thể xác định công việc gần đây trong vòng dưới năm phút không? -- Đường dẫn khởi động ổn định hiện tại có được ghi lại không? -- Công việc chưa hoàn thành có được xác định rõ ràng không? -- Tác vụ tốt nhất tiếp theo có thể nhìn thấy mà không cần đọc log chat cũ không? diff --git a/courses/learn-harness-engineering/docs/vi/lectures/lecture-05-why-long-running-tasks-lose-continuity/code/index.md b/courses/learn-harness-engineering/docs/vi/lectures/lecture-05-why-long-running-tasks-lose-continuity/code/index.md deleted file mode 100644 index acfa1e23..00000000 --- a/courses/learn-harness-engineering/docs/vi/lectures/lecture-05-why-long-running-tasks-lose-continuity/code/index.md +++ /dev/null @@ -1,7 +0,0 @@ -# Mã cho Bài 05 - -Sử dụng thư mục này cho các ví dụ về: - -- các tác vụ đa phiên bị hỏng -- thiếu artifact tính liên tục -- các mẫu phục hồi tính liên tục diff --git a/courses/learn-harness-engineering/docs/vi/lectures/lecture-05-why-long-running-tasks-lose-continuity/code/session-handoff.md b/courses/learn-harness-engineering/docs/vi/lectures/lecture-05-why-long-running-tasks-lose-continuity/code/session-handoff.md deleted file mode 100644 index 2cc2dfd5..00000000 --- a/courses/learn-harness-engineering/docs/vi/lectures/lecture-05-why-long-running-tasks-lose-continuity/code/session-handoff.md +++ /dev/null @@ -1,17 +0,0 @@ -# Ví dụ Bàn giao Phiên - -## Đã Hoàn thành - -- Thêm hỗ trợ import markdown -- Thêm danh sách tài liệu cơ bản trong renderer - -## Bị Hỏng hoặc Chưa được Xác minh - -- Import thành công với `.md` nhưng thất bại với các tệp `.txt` lớn -- Ứng dụng khởi động, nhưng detail view chưa được kết nối - -## Bước Tốt nhất Tiếp theo - -- Sửa đường dẫn import `.txt` -- Xác minh import end-to-end -- Sau đó thêm panel chi tiết tài liệu diff --git a/courses/learn-harness-engineering/docs/vi/lectures/lecture-05-why-long-running-tasks-lose-continuity/code/session-simulator.ts b/courses/learn-harness-engineering/docs/vi/lectures/lecture-05-why-long-running-tasks-lose-continuity/code/session-simulator.ts deleted file mode 100644 index 69f9c3e2..00000000 --- a/courses/learn-harness-engineering/docs/vi/lectures/lecture-05-why-long-running-tasks-lose-continuity/code/session-simulator.ts +++ /dev/null @@ -1,162 +0,0 @@ -/** - * session-simulator.ts - * - * Simulates two sessions working on a multi-step task. - * Run 1: No handoff file -- Session B starts from scratch and duplicates work. - * Run 2: With handoff file -- Session B picks up where Session A left off. - * - * Run: npx tsx docs/lectures/lecture-05-why-long-running-tasks-lose-continuity/code/session-simulator.ts - */ - -// --------------------------------------------------------------------------- -// Types -// --------------------------------------------------------------------------- - -interface TaskStep { - id: number; - name: string; - durationMs: number; -} - -interface SessionResult { - session: string; - stepsCompleted: number; - totalDurationMs: number; - duplicatedSteps: number; - output: string[]; -} - -// --------------------------------------------------------------------------- -// Task definition -// --------------------------------------------------------------------------- - -const taskSteps: TaskStep[] = [ - { id: 1, name: "Read project structure", durationMs: 50 }, - { id: 2, name: "Understand existing auth module", durationMs: 80 }, - { id: 3, name: "Design new search endpoint", durationMs: 60 }, - { id: 4, name: "Implement search endpoint", durationMs: 100 }, - { id: 5, name: "Write integration tests", durationMs: 70 }, - { id: 6, name: "Update documentation", durationMs: 40 }, -]; - -// --------------------------------------------------------------------------- -// Simulated session executor -// --------------------------------------------------------------------------- - -/** Simulate a session doing work. */ -function runSession( - sessionName: string, - startStep: number, - endStep: number -): SessionResult { - const output: string[] = []; - let totalDuration = 0; - - for (let i = startStep; i <= endStep; i++) { - const step = taskSteps.find((s) => s.id === i)!; - totalDuration += step.durationMs; - output.push(` [${sessionName}] Step ${step.id}: ${step.name} (${step.durationMs}ms)`); - } - - return { - session: sessionName, - stepsCompleted: endStep - startStep + 1, - totalDurationMs: totalDuration, - duplicatedSteps: 0, - output, - }; -} - -// --------------------------------------------------------------------------- -// Run 1: No handoff file -// --------------------------------------------------------------------------- - -function simulateNoHandoff(): { sessionA: SessionResult; sessionB: SessionResult } { - // Session A does steps 1-3 before timing out - const sessionA = runSession("Session A", 1, 3); - - // Session B has no context, starts from scratch - const sessionB = runSession("Session B", 1, 6); - sessionB.duplicatedSteps = 3; // Redoes steps 1-3 that A already did - - return { sessionA, sessionB }; -} - -// --------------------------------------------------------------------------- -// Run 2: With handoff file -// --------------------------------------------------------------------------- - -function simulateWithHandoff(): { sessionA: SessionResult; sessionB: SessionResult } { - // Session A does steps 1-3 and writes a handoff file - const sessionA = runSession("Session A", 1, 3); - - // Session B reads handoff, starts from step 4 - const sessionB = runSession("Session B", 4, 6); - sessionB.duplicatedSteps = 0; - - return { sessionA, sessionB }; -} - -// --------------------------------------------------------------------------- -// Reporting -// --------------------------------------------------------------------------- - -function pad(s: string, len: number): string { - return s.length >= len ? s : s + " ".repeat(len - s.length); -} - -function printRun(title: string, result: { sessionA: SessionResult; sessionB: SessionResult }): void { - console.log("\n" + "=".repeat(80)); - console.log(` ${title}`); - console.log("=".repeat(80)); - - console.log("\n Session A output:"); - for (const line of result.sessionA.output) { - console.log(line); - } - - console.log("\n Session B output:"); - for (const line of result.sessionB.output) { - console.log(line); - } - - console.log("\n Session B notes:"); - if (result.sessionB.duplicatedSteps > 0) { - console.log(` - Redid ${result.sessionB.duplicatedSteps} steps that Session A already completed`); - console.log(` - Wasted ${result.sessionB.totalDurationMs - (taskSteps.slice(3).reduce((s, t) => s + t.durationMs, 0))}ms on duplicate work`); - } else { - console.log(" - Read handoff file, continued from step 4"); - console.log(" - No duplicate work performed"); - } -} - -function printComparison(): void { - const noHandoff = simulateNoHandoff(); - const withHandoff = simulateWithHandoff(); - - printRun("RUN 1: NO HANDOFF FILE", noHandoff); - printRun("RUN 2: WITH HANDOFF FILE", withHandoff); - - // Comparison table - console.log("\n" + "=".repeat(80)); - console.log(" COMPARISON TABLE"); - console.log("=".repeat(80) + "\n"); - - const header = `| ${pad("Metric", 35)}| ${pad("No Handoff", 15)}| ${pad("With Handoff", 15)}|`; - const sep = `|${"-".repeat(37)}|${"-".repeat(17)}|${"-".repeat(17)}|`; - console.log(header); - console.log(sep); - - const noTotal = noHandoff.sessionA.totalDurationMs + noHandoff.sessionB.totalDurationMs; - const withTotal = withHandoff.sessionA.totalDurationMs + withHandoff.sessionB.totalDurationMs; - - console.log(`| ${pad("Session A steps completed", 35)}| ${pad(String(noHandoff.sessionA.stepsCompleted), 15)}| ${pad(String(withHandoff.sessionA.stepsCompleted), 15)}|`); - console.log(`| ${pad("Session B steps completed", 35)}| ${pad(String(noHandoff.sessionB.stepsCompleted), 15)}| ${pad(String(withHandoff.sessionB.stepsCompleted), 15)}|`); - console.log(`| ${pad("Duplicated steps", 35)}| ${pad(String(noHandoff.sessionB.duplicatedSteps), 15)}| ${pad(String(withHandoff.sessionB.duplicatedSteps), 15)}|`); - console.log(`| ${pad("Total work (ms)", 35)}| ${pad(String(noTotal), 15)}| ${pad(String(withTotal), 15)}|`); - console.log(`| ${pad("Time saved by handoff", 35)}| ${pad("-", 15)}| ${pad(String(noTotal - withTotal) + "ms", 15)}|`); - - console.log("\n A handoff file eliminates duplicate work and ensures continuity across sessions.\n"); -} - -printComparison(); diff --git a/courses/learn-harness-engineering/docs/vi/lectures/lecture-05-why-long-running-tasks-lose-continuity/index.md b/courses/learn-harness-engineering/docs/vi/lectures/lecture-05-why-long-running-tasks-lose-continuity/index.md deleted file mode 100644 index 53e5cefb..00000000 --- a/courses/learn-harness-engineering/docs/vi/lectures/lecture-05-why-long-running-tasks-lose-continuity/index.md +++ /dev/null @@ -1,180 +0,0 @@ -[English Version →](../../../en/lectures/lecture-05-why-long-running-tasks-lose-continuity/) | [中文版本 →](../../../zh/lectures/lecture-05-why-long-running-tasks-lose-continuity/) - -> Ví dụ mã nguồn: [code/](https://github.com/walkinglabs/learn-harness-engineering/blob/main/docs/vi/lectures/lecture-05-why-long-running-tasks-lose-continuity/code/) -> Dự án thực hành: [Dự án 03. Tính liên tục đa phiên](./../../projects/project-03-multi-session-continuity/index.md) - -# Bài 05. Duy trì Ngữ cảnh Qua Các Phiên - -Bạn yêu cầu Claude Code triển khai một tính năng hoàn chỉnh. Nó chạy 30 phút, thực hiện hầu hết công việc, nhưng ngữ cảnh đang cạn kiệt. Bạn bắt đầu một phiên mới để tiếp tục — và khám phá rằng nó không nhớ những quyết định nào đã được đưa ra lần trước, tại sao lại chọn phương án A thay vì phương án B, những tệp nào đã được sửa đổi, hay trạng thái của các bài test là gì. Nó dành 15 phút để khám phá lại dự án, và có thể không nhất quán với cách tiếp cận trước. - -Hãy tưởng tượng bạn là một thợ thủ công quên hết mọi thứ mỗi buổi sáng khi thức dậy. Bạn phải làm quen lại với toàn bộ công trường — bức tường nào đang xây dở, tại sao lại chọn gạch đỏ thay vì gạch xanh, đường ống nước đã chạy đến đâu. Tệ hơn nữa, bạn có thể tháo ra một cửa sổ đã được lắp vào ngày hôm qua, đơn giản vì bạn không nhớ nó đã xong. - -Đây chính xác là tình huống khó khăn mà các AI coding agent phải đối mặt trong các tác vụ xuyên phiên. Bài giảng này giải thích tại sao các agent "mất ký ức" trong các tác vụ dài, và cách lưu trữ trạng thái có cấu trúc có thể làm cho chúng giống như một thợ thủ công giữ nhật ký đáng tin cậy hàng ngày — vẫn bị mất ký ức, nhưng nhật ký nhớ mọi thứ. - -## Cửa sổ Ngữ cảnh: Không Vô hạn - -Cửa sổ ngữ cảnh là hữu hạn. Điều này không thể giải quyết bằng nâng cấp mô hình — ngay cả khi kích thước cửa sổ tăng lên 1M token, các tác vụ phức tạp vẫn sẽ tiêu hao chúng. Vì các agent không chỉ tạo ra mã; họ hiểu codebase, theo dõi lịch sử quyết định của chính mình, xử lý kết quả công cụ và duy trì ngữ cảnh hội thoại. Tất cả thông tin này tăng trưởng nhanh hơn sự mở rộng cửa sổ. - -Một vấn đề sâu hơn: thông tin mà agent tạo ra không đồng đều về mức độ quan trọng. Các bước lý luận trung gian chứa "tại sao" của các quyết định — tại sao chọn phương án B thay vì A, tại sao thư viện này thay vì kia, tại sao một tối ưu hóa cụ thể bị bỏ qua. Kết quả cuối cùng chỉ chứa "cái gì" — bản thân mã. Các chiến lược nén thường bảo tồn cái sau nhưng mất cái trước. Phiên tiếp theo thấy mã nhưng không biết tại sao nó được viết như vậy, và có thể "tối ưu hóa" đi một quyết định thiết kế cố ý. - -Anthropic đã phát hiện ra điều hấp dẫn trong nghiên cứu về agent chạy lâu của họ: khi các agent cảm thấy ngữ cảnh đang cạn kiệt, chúng thể hiện hành vi "hội tụ sớm" — vội vàng hoàn thành công việc hiện tại, bỏ qua các bước xác minh, hoặc chọn giải pháp đơn giản thay vì giải pháp tối ưu. Giống như nhận ra thời gian sắp hết trong bài thi và nhanh chóng đoán các câu hỏi trắc nghiệm còn lại. Anthropic gọi đây là "lo lắng ngữ cảnh." - -## Luồng Tính Liên tục Phiên - -Không có các artifact tính liên tục, mọi phiên mới là một thảm họa: - -```mermaid -flowchart LR - S1["Phiên 1
    tính năng đang làm dở"] --> End1["Ngữ cảnh gần đầy
    phiên kết thúc"] - End1 --> S2["Phiên 2 bắt đầu mới"] - S2 --> Guess["Đọc lại thư mục, chạy lại test,
    đoán tại sao mã được viết như vậy"] - Guess --> Drift["Công việc bị lặp lại
    và phục hồi chậm"] -``` - -Với các artifact tính liên tục, các phiên mới có thể tiếp tục nhanh chóng: - -```mermaid -flowchart LR - Work["Công việc Phiên 1"] --> Progress["PROGRESS.md
    xong / đang làm / bước tiếp theo"] - Work --> Decisions["DECISIONS.md
    tại sao cách tiếp cận này được chọn"] - Work --> Verify["Ghi chú xác minh
    test nào vượt qua và thất bại"] - Work --> Commit["Git checkpoint
    trạng thái repo chính xác"] - - Progress --> Rebuild["Tái xây dựng Phiên 2"] - Decisions --> Rebuild - Verify --> Rebuild - Commit --> Rebuild - - Rebuild --> Resume["Phiên mới tiếp tục nhanh chóng"] -``` - -## Các Khái niệm Cốt lõi - -- **Cửa sổ ngữ cảnh là hữu hạn**: Bất kể kích thước cửa sổ nào được tuyên bố (128K, 200K, 1M), các tác vụ dài cuối cùng sẽ tiêu hao chúng. Sau khi tiêu hao, cần phải nén (mất thông tin) hoặc đặt lại (phiên mới). Cả hai đều mất điều gì đó. -- **Artifact tính liên tục (Continuity Artifacts)**: Các tệp trạng thái được lưu trữ cho phép phiên mới tiếp tục không mơ hồ từ nơi phiên cuối kết thúc. Dạng cơ bản: nhật ký tiến độ + bản ghi xác minh + các hành động tiếp theo. Nhật ký của thợ thủ công đó. -- **Chi phí Tái xây dựng (Rebuild Cost)**: Thời gian phiên mới cần để đạt đến trạng thái có thể thực thi. Harness tốt có thể nén chi phí tái xây dựng từ 15 phút xuống còn 3 phút. -- **Trôi dạt (Drift)**: Khoảng cách giữa sự hiểu biết của agent và trạng thái thực tế của kho lưu trữ mã. Mỗi ranh giới phiên tạo ra trôi dạt; nếu không kiểm soát, nó sẽ tích lũy. -- **Lo lắng ngữ cảnh (Context Anxiety)**: Hiện tượng được Anthropic quan sát — các agent thể hiện hành vi hội tụ sớm khi tiếp cận giới hạn ngữ cảnh nhận thức, kết thúc tác vụ sớm để tránh mất thông tin. Đó là sự lo lắng tài nguyên phi lý. -- **Nén vs. Đặt lại (Compaction vs. Reset)**: Nén tóm tắt ngữ cảnh trong cùng phiên (giữ "cái gì," có thể mất "tại sao"); đặt lại mở phiên mới tái xây dựng từ trạng thái được lưu trữ (sạch nhưng phụ thuộc vào tính hoàn chỉnh của artifact). - -## Điều Gì Xảy ra Khi Tính Liên tục Bị Phá vỡ - -Phiên trước đã dành ngân sách ngữ cảnh đáng kể để phân tích ba cách tiếp cận và chọn phương án B. Phiên này của agent không biết về phân tích đó và có thể quyết định lại dựa trên thông tin không đầy đủ — có thể chọn phương án A. Giống như thợ thủ công mất ký ức không nhớ tại sao gạch đỏ được chọn, nhìn vào gạch xanh ngày hôm nay và nghĩ chúng đẹp hơn, và tháo bức tường của ngày hôm qua để xây lại. - -Thậm chí còn tệ hơn là công việc trùng lặp. Agent không chắc chắn liệu một số công việc đã hoàn thành chưa và làm lại. Hoặc tệ hơn — làm một nửa, phát hiện ra xung đột với triển khai hiện có, và phải làm lại. Trên công trường, hai đội không thể xây cùng một bức tường đồng thời — nhưng không có bản ghi tiến độ, đội mới không biết có ai đó đang làm việc đó rồi. - -Qua nhiều phiên, hướng triển khai có thể đã âm thầm trôi xa khỏi yêu cầu ban đầu. Mỗi phiên mới có sự hiểu biết hơi khác nhau về mục tiêu dự án. Giống như trò chơi truyền tin — sau mười người truyền tin, "đón tôi một ly cà phê" có thể trở thành "mua cho tôi máy pha cà phê." - -Cũng có khoảng cách xác minh. Kết quả xác minh của phiên trước (test nào vượt qua, test nào thất bại, tại sao thất bại) không được ghi lại. Phiên mới phải chạy lại tất cả xác minh để hiểu trạng thái hiện tại. Mỗi phiên chẩn đoán lại từ đầu, mỗi lần lãng phí ngữ cảnh quý báu. - -Cả OpenAI và Anthropic đều nhấn mạnh lưu trữ trạng thái có cấu trúc trong tài liệu của họ. Bài viết về harness engineering của OpenAI coi kho lưu trữ là "bản ghi hoạt động" — kết quả của mỗi hoạt động phải để lại bằng chứng có thể truy vết trong repo. Tài liệu về agent chạy lâu của Anthropic đặc biệt khuyến nghị "tệp bàn giao" — các tài liệu có cấu trúc chứa trạng thái hiện tại, các vấn đề đã biết và các hành động tiếp theo. - -## Nhật ký Cho Thợ thủ công Mất Ký ức - -Cách tiếp cận cốt lõi: **Đối xử với agent như một kỹ sư tài giỏi bị mất ký ức.** Trước khi nó "tan ca," nó phải ghi lại thông tin quan trọng để agent "ca tiếp theo" có thể tiếp tục nhanh chóng. - -**Công cụ 1: Tệp tiến độ (PROGRESS.md).** Artifact tính liên tục cơ bản nhất — cốt lõi của nhật ký: - -```markdown -# Tiến độ Dự án - -## Trạng thái Hiện tại -- Commit mới nhất: abc1234 (feat: add user preferences endpoint) -- Trạng thái test: 42/43 vượt qua (test_pagination_edge_case thất bại) -- Lint: vượt qua - -## Đã Hoàn thành -- [x] User model và database migration -- [x] Các endpoint CRUD cơ bản -- [x] Tích hợp auth middleware - -## Đang Thực hiện -- [ ] Tính năng phân trang (90% - edge case test thất bại) - -## Vấn đề Đã biết -- test_pagination_edge_case trả về 500 trên result sets trống -- Cần xác nhận liệu người dùng đã xóa có nên xuất hiện trong danh sách không - -## Các Bước Tiếp theo -1. Sửa lỗi edge case phân trang -2. Thêm tham số truy vấn "include deleted users" -3. Cập nhật tài liệu API -``` - -**Công cụ 2: Nhật ký quyết định (DECISIONS.md).** Ghi lại các quyết định thiết kế quan trọng và lý do. Không cần tài liệu thiết kế chi tiết — chỉ cần "quyết định gì, tại sao, khi nào" — các ghi chú trong nhật ký: - -```markdown -# Các Quyết định Thiết kế - -## 2024-01-15: Sử dụng Redis để cache tùy chọn người dùng -- Lý do: Tần suất đọc cao (mỗi lần gọi API), kích thước dữ liệu nhỏ -- Phương án bị từ chối: PostgreSQL materialized view (tần suất thay đổi cao làm chi phí bảo trì không xứng đáng) -- Ràng buộc: Cache TTL 5 phút, vô hiệu hóa chủ động khi ghi -``` - -**Công cụ 3: Git commit như checkpoint.** Commit sau khi hoàn thành mỗi đơn vị công việc nguyên tử. Commit message phải giải thích những gì đã được thực hiện và tại sao. Đây là các snapshot trạng thái miễn phí, được phiên bản hóa tự động. - -**Công cụ 4: init.sh hoặc luồng khởi tạo harness.** Chỉ định trong `AGENTS.md` các thói quen "bắt đầu ca" và "kết thúc ca": - -```markdown -## Khi bắt đầu phiên (bắt đầu ca) -1. Đọc PROGRESS.md để biết trạng thái hiện tại -2. Đọc DECISIONS.md để biết các quyết định quan trọng -3. Chạy make check để xác nhận repo ở trạng thái nhất quán -4. Tiếp tục từ phần "Các Bước Tiếp theo" của PROGRESS.md - -## Trước khi kết thúc phiên (kết thúc ca) -1. Cập nhật PROGRESS.md -2. Chạy make check để xác nhận trạng thái nhất quán -3. Commit tất cả công việc đã hoàn thành -``` - -**Chiến lược hỗn hợp**: Không phải mọi tác vụ đều cần đặt lại ngữ cảnh. Các tác vụ ngắn (dưới 30 phút) có thể hoàn thành trong một phiên. Các tác vụ dài (trải dài qua các phiên) phải sử dụng tệp tiến độ và nhật ký quyết định để tính liên tục. Tiêu chí quyết định: nếu một tác vụ cần hơn 60% cửa sổ, hãy bắt đầu chuẩn bị bàn giao. - -### Tìm hiểu Sâu hơn về Lo lắng Ngữ cảnh - -Nghiên cứu tháng 3 năm 2026 của Anthropic tiếp tục tiết lộ các biểu hiện cụ thể của lo lắng ngữ cảnh: trên Sonnet 4.5, khi ngữ cảnh tiếp cận giới hạn cửa sổ, agent thể hiện hành vi "hội tụ sớm" mạnh. Giống như nhận ra thời gian gần hết trong bài thi và nhanh chóng điền câu trả lời ngẫu nhiên vào các câu trắc nghiệm. - -Hai chiến lược giải quyết điều này: - -**Nén (Compaction)**: Tóm tắt hội thoại đầu trong cùng phiên. Ưu điểm: duy trì tính liên tục, agent có thể thấy "cái gì." Nhược điểm: "tại sao" thường bị mất trong các bản tóm tắt — tại sao phương án B được chọn thay vì A, tại sao một tối ưu hóa cụ thể bị bỏ qua. Quan trọng hơn, nén không loại bỏ lo lắng ngữ cảnh — agent biết ngữ cảnh từng lớn, và về mặt tâm lý vẫn có xu hướng vội vàng kết thúc. - -**Đặt lại ngữ cảnh (Context Reset)**: Xóa hoàn toàn ngữ cảnh, mở phiên mới, tái xây dựng từ các artifact được lưu trữ. Ưu điểm: trạng thái tâm trí sạch — phiên mới không có lo lắng "tôi sắp hết thời gian." Nhược điểm: phụ thuộc vào tính hoàn chỉnh của artifact bàn giao. Nếu nhật ký thiếu thông tin quan trọng, phiên mới có thể lãng phí thời gian đi theo hướng sai. - -Dữ liệu thực tế của Anthropic: đối với Sonnet 4.5, lo lắng ngữ cảnh đủ nghiêm trọng đến mức nén một mình không đủ — đặt lại ngữ cảnh trở thành thành phần quan trọng của thiết kế harness. Nhưng đối với Opus 4.5, hành vi này giảm đáng kể, và nén có thể quản lý ngữ cảnh mà không cần dựa vào đặt lại. Điều này có nghĩa là: **thiết kế harness cần sự hiểu biết cụ thể về mô hình mục tiêu, không phải một mẫu chung cho tất cả.** - -> Nguồn: [Anthropic: Harness design for long-running application development](https://www.anthropic.com/engineering/harness-design-long-running-apps) - -## Ví dụ Thực tế - -Một agent được giao nhiệm vụ triển khai hệ thống blog với xác thực người dùng — 12 điểm tính năng, ước tính cần 5 phiên. - -**Không có nhật ký**: Phiên 1 triển khai user model và các route cơ bản. Phiên 2 bắt đầu mà agent không nhớ hợp đồng giao diện của auth middleware, dành ~15 phút để suy ra ý định thiết kế trước. Đến phiên 3, trôi dạt tích lũy khiến agent bắt đầu triển khai lại các tính năng đã hoàn thành. Đến phiên 5, repo chứa nhiều mã dư thừa nhưng tính năng auth cốt lõi vẫn chưa vượt qua test end-to-end. Chỉ 7 trong 12 điểm tính năng hoàn thành, 3 có vấn đề tính đúng đắn ẩn. Giống như thợ thủ công không bao giờ ghi nhật ký — đến ngày thứ năm, công trường là hỗn loạn, một số bức tường được xây hai lần, một số lẽ ra phải được xây nhưng chưa bao giờ bắt đầu. - -**Với nhật ký**: Sử dụng tệp tiến độ, nhật ký quyết định, bản ghi xác minh và git checkpoint. Báo cáo trạng thái được cập nhật tự động ở cuối mỗi phiên. Chi phí tái xây dựng của phiên 2 giảm xuống còn ~3 phút. Đến phiên 5, tất cả 12 điểm tính năng hoàn thành và được xác minh. - -So sánh định lượng: thời gian tái xây dựng giảm ~78%, tỷ lệ hoàn thành tính năng từ 58% lên 100%, tỷ lệ lỗi ẩn từ 43% xuống còn 8%. Thợ thủ công vẫn mất ký ức, nhưng với nhật ký, mỗi ngày bắt đầu từ nơi ngày hôm qua dừng lại, không phải từ đầu. - -## Những Điểm chính cần Nhớ - -- Cửa sổ ngữ cảnh là tài nguyên hữu hạn. Các tác vụ dài sẽ trải dài qua nhiều phiên, và các phiên sẽ mất thông tin — giống như thợ thủ công quên mỗi ngày, đây là thực tế khách quan. -- Giải pháp không phải là cửa sổ lớn hơn — mà là lưu trữ trạng thái tốt hơn. Tệp tiến độ + nhật ký quyết định + git checkpoint — đưa cho thợ thủ công mất ký ức một cuốn nhật ký đáng tin cậy. -- Đối xử với agent như kỹ sư bị mất ký ức: trước khi "tan ca," hãy ghi lại những gì đã làm, tại sao, và tiếp theo là gì. -- Chi phí tái xây dựng là chỉ số chính. Harness tốt phải đưa các phiên mới đến trạng thái có thể thực thi trong vòng 3 phút. -- Chiến lược hỗn hợp: các tác vụ ngắn trong phiên, các tác vụ dài với artifact có cấu trúc để tính liên tục. - -## Đọc thêm - -- [Anthropic: Effective Harnesses for Long-Running Agents](https://www.anthropic.com/engineering/effective-harnesses-for-long-running-agents) -- [OpenAI: Harness Engineering](https://openai.com/index/harness-engineering/) -- [Lost in the Middle: How Language Models Use Long Contexts](https://arxiv.org/abs/2307.03172) -- [Claude Code Documentation](https://docs.anthropic.com/en/docs/claude-code) -- [HumanLayer: Harness Engineering for Coding Agents](https://humanlayer.dev/articles/harness-engineering-for-coding-agents/) - -## Bài tập - -1. **Đo lường mất tính liên tục**: Chọn một tác vụ phát triển cần ít nhất 3 phiên. Không cung cấp bất kỳ artifact tính liên tục nào, ghi lại ở mỗi lần bắt đầu phiên xem agent dành bao nhiêu ngữ cảnh để "tìm hiểu chuyện gì đã xảy ra lần trước." Sau mỗi phiên, tạo một tệp tiến độ và để phiên tiếp theo bắt đầu từ đó. So sánh chi phí tái xây dựng có và không có tệp tiến độ. - -2. **Thiết kế mẫu bàn giao**: Thiết kế một mẫu bàn giao tối giản với bốn trường: trạng thái repo (hash commit), trạng thái runtime (tỷ lệ vượt qua test), các chướng ngại vật, các hành động tiếp theo. Để một phiên agent hoàn toàn mới phục hồi trạng thái dự án chỉ sử dụng mẫu này. Ghi lại các điểm mơ hồ gặp phải trong quá trình phục hồi, cải tiến mẫu lặp đi lặp lại. - -3. **Thí nghiệm chiến lược hỗn hợp**: Trong một tác vụ phát triển 5 phiên, so sánh ba chiến lược: (a) luôn bắt đầu phiên mới + tệp tiến độ, (b) làm càng nhiều càng tốt trong một phiên (nén ngữ cảnh), (c) chiến lược hỗn hợp (tác vụ ngắn trong phiên, tác vụ dài xuyên phiên + tệp tiến độ). So sánh thời gian tái xây dựng, tỷ lệ hoàn thành tính năng và tính nhất quán của quyết định. diff --git a/courses/learn-harness-engineering/docs/vi/lectures/lecture-06-why-initialization-needs-its-own-phase/code/index.md b/courses/learn-harness-engineering/docs/vi/lectures/lecture-06-why-initialization-needs-its-own-phase/code/index.md deleted file mode 100644 index 80de5945..00000000 --- a/courses/learn-harness-engineering/docs/vi/lectures/lecture-06-why-initialization-needs-its-own-phase/code/index.md +++ /dev/null @@ -1,8 +0,0 @@ -# Mã cho Bài 06 - -Sử dụng thư mục này cho các ví dụ về: - -- kết quả đầu ra của khởi tạo -- init script -- tệp tiến độ -- scaffolding lần chạy đầu tiên diff --git a/courses/learn-harness-engineering/docs/vi/lectures/lecture-06-why-initialization-needs-its-own-phase/code/init-check.ts b/courses/learn-harness-engineering/docs/vi/lectures/lecture-06-why-initialization-needs-its-own-phase/code/init-check.ts deleted file mode 100644 index 52dcd985..00000000 --- a/courses/learn-harness-engineering/docs/vi/lectures/lecture-06-why-initialization-needs-its-own-phase/code/init-check.ts +++ /dev/null @@ -1,296 +0,0 @@ -/** - * init-check.ts - * - * Programmatically checks initialization prerequisites: - * - Node.js version - * - Dependencies installed (node_modules exists) - * - Data directory exists - * - Config files present - * - * Simulates running with and without an explicit init phase, - * showing how missing prerequisites cause silent failures later. - * - * Run: npx tsx docs/lectures/lecture-06-why-initialization-needs-its-own-phase/code/init-check.ts - */ - -import * as fs from "node:fs"; -import * as path from "node:path"; -import * as childProcess from "node:child_process"; - -// --------------------------------------------------------------------------- -// Types -// --------------------------------------------------------------------------- - -interface CheckItem { - name: string; - category: string; - check: () => { pass: boolean; detail: string }; - impactIfMissing: string; -} - -interface CheckResult { - name: string; - category: string; - passed: boolean; - detail: string; - impactIfMissing: string; -} - -// --------------------------------------------------------------------------- -// Checks -// --------------------------------------------------------------------------- - -function createChecks(targetDir: string): CheckItem[] { - return [ - { - name: "Node.js version >= 18", - category: "Runtime", - check: () => { - try { - const version = process.version; // e.g. "v20.11.0" - const major = parseInt(version.replace("v", "").split(".")[0], 10); - return { - pass: major >= 18, - detail: `Detected: ${version} (need >= v18)`, - }; - } catch { - return { pass: false, detail: "Could not detect Node.js version" }; - } - }, - impactIfMissing: "TypeScript features and built-in APIs unavailable", - }, - { - name: "package.json exists", - category: "Config", - check: () => { - const p = path.join(targetDir, "package.json"); - const exists = fs.existsSync(p); - return { - pass: exists, - detail: exists ? `Found: ${p}` : "Not found in target directory", - }; - }, - impactIfMissing: "Cannot install dependencies or run scripts", - }, - { - name: "Dependencies installed (node_modules)", - category: "Dependencies", - check: () => { - const p = path.join(targetDir, "node_modules"); - const exists = fs.existsSync(p); - return { - pass: exists, - detail: exists ? `Found: ${p}` : "node_modules/ not found -- run npm install", - }; - }, - impactIfMissing: "All imports fail at runtime", - }, - { - name: "TypeScript available", - category: "Toolchain", - check: () => { - try { - const result = childProcess.execSync("npx tsc --version", { - encoding: "utf-8", - timeout: 10000, - stdio: ["pipe", "pipe", "pipe"], - }).trim(); - return { pass: true, detail: `Detected: ${result}` }; - } catch { - return { pass: false, detail: "TypeScript not available via npx" }; - } - }, - impactIfMissing: "Cannot compile TypeScript files", - }, - { - name: "tsconfig.json exists", - category: "Config", - check: () => { - const p = path.join(targetDir, "tsconfig.json"); - const exists = fs.existsSync(p); - return { - pass: exists, - detail: exists ? `Found: ${p}` : "Not found", - }; - }, - impactIfMissing: "TypeScript compiler uses defaults, may not match project needs", - }, - { - name: "Source directory exists", - category: "Structure", - check: () => { - const candidates = ["src", "lib", "app"]; - const found = candidates.filter((d) => { - const p = path.join(targetDir, d); - return fs.existsSync(p) && fs.statSync(p).isDirectory(); - }); - return { - pass: found.length > 0, - detail: found.length > 0 ? `Found: ${found.join(", ")}` : "No src/ lib/ or app/ directory found", - }; - }, - impactIfMissing: "Agent cannot locate source files to modify", - }, - { - name: "Test directory exists", - category: "Structure", - check: () => { - const candidates = ["test", "tests", "__tests__", "spec"]; - const found = candidates.filter((d) => { - const p = path.join(targetDir, d); - return fs.existsSync(p) && fs.statSync(p).isDirectory(); - }); - return { - pass: found.length > 0, - detail: found.length > 0 ? `Found: ${found.join(", ")}` : "No test directory found", - }; - }, - impactIfMissing: "Agent cannot find or run existing tests", - }, - { - name: "Git repository initialized", - category: "Version Control", - check: () => { - const gitDir = path.join(targetDir, ".git"); - const exists = fs.existsSync(gitDir); - return { - pass: exists, - detail: exists ? "Git repository detected" : "No .git directory found", - }; - }, - impactIfMissing: "No rollback capability, no change history", - }, - ]; -} - -// --------------------------------------------------------------------------- -// Simulation: with and without init phase -// --------------------------------------------------------------------------- - -interface SimResult { - scenario: string; - checksRun: boolean; - failuresBeforeWork: number; - workAttempted: boolean; - workSucceeded: boolean; - timeWastedMs: number; -} - -function simulateWithoutInit(): SimResult { - // Agent skips init, goes straight to work. - // Encounters failures one at a time as it hits missing prerequisites. - const checks = createChecks(process.cwd()); - let failures = 0; - let timeWasted = 0; - - for (const check of checks) { - const result = check.check(); - if (!result.pass) { - failures++; - timeWasted += 200; // Agent spends time discovering each missing piece - } - } - - return { - scenario: "NO INIT PHASE", - checksRun: false, - failuresBeforeWork: 0, // Didn't check upfront - workAttempted: true, - workSucceeded: failures === 0, - timeWastedMs: timeWasted, - }; -} - -function simulateWithInit(): SimResult { - // Agent runs init phase first, discovers all issues upfront. - const checks = createChecks(process.cwd()); - let failures = 0; - - for (const check of checks) { - if (!check.check().pass) { - failures++; - } - } - - return { - scenario: "WITH INIT PHASE", - checksRun: true, - failuresBeforeWork: failures, - workAttempted: failures === 0, - workSucceeded: failures === 0, - timeWastedMs: 0, - }; -} - -// --------------------------------------------------------------------------- -// Reporting -// --------------------------------------------------------------------------- - -function pad(s: string, len: number): string { - return s.length >= len ? s : s + " ".repeat(len - s.length); -} - -function run(): void { - const targetDir = process.cwd(); - console.log("\n" + "=".repeat(80)); - console.log(" INITIALIZATION PREREQUISITE CHECK"); - console.log("=".repeat(80)); - console.log(` Target: ${targetDir}\n`); - - const checks = createChecks(targetDir); - const results: CheckResult[] = checks.map((c) => { - const r = c.check(); - return { - name: c.name, - category: c.category, - passed: r.pass, - detail: r.detail, - impactIfMissing: c.impactIfMissing, - }; - }); - - // Detailed report - const header = `| ${pad("Check", 35)}| ${pad("Category", 12)}| ${pad("Status", 6)}| Detail`; - const sep = `|${"-".repeat(37)}|${"-".repeat(14)}|${"-".repeat(8)}|${"-".repeat(40)}`; - console.log(header); - console.log(sep); - - for (const r of results) { - const status = r.passed ? "PASS" : "FAIL"; - console.log(`| ${pad(r.name, 35)}| ${pad(r.category, 12)}| ${pad(status, 6)}| ${r.detail}`); - } - - const passCount = results.filter((r) => r.passed).length; - const failCount = results.filter((r) => !r.passed).length; - - console.log("\n" + "-".repeat(80)); - console.log(` Results: ${passCount} passed, ${failCount} failed out of ${results.length} checks`); - - if (failCount > 0) { - console.log("\n FAILED CHECKS -- Impact if not resolved:"); - for (const r of results.filter((r) => !r.passed)) { - console.log(` - ${r.name}: ${r.impactIfMissing}`); - } - } - - // Comparison: with vs without init - const noInit = simulateWithoutInit(); - const withInit = simulateWithInit(); - - console.log("\n" + "=".repeat(80)); - console.log(" INIT PHASE COMPARISON"); - console.log("=".repeat(80) + "\n"); - - const cHeader = `| ${pad("Metric", 35)}| ${pad("No Init Phase", 18)}| ${pad("With Init Phase", 18)}|`; - const cSep = `|${"-".repeat(37)}|${"-".repeat(20)}|${"-".repeat(20)}|`; - console.log(cHeader); - console.log(cSep); - console.log(`| ${pad("Prerequisites checked upfront", 35)}| ${pad("No", 18)}| ${pad("Yes", 18)}|`); - console.log(`| ${pad("Work attempted despite issues", 35)}| ${pad(String(noInit.workAttempted), 18)}| ${pad(String(withInit.workAttempted), 18)}|`); - console.log(`| ${pad("Time wasted discovering issues late", 35)}| ${pad(noInit.timeWastedMs + "ms", 18)}| ${pad(withInit.timeWastedMs + "ms", 18)}|`); - console.log(`| ${pad("Work succeeded", 35)}| ${pad(String(noInit.workSucceeded), 18)}| ${pad(String(withInit.workSucceeded), 18)}|`); - - console.log("\n An explicit init phase catches problems before they waste time.\n"); -} - -run(); diff --git a/courses/learn-harness-engineering/docs/vi/lectures/lecture-06-why-initialization-needs-its-own-phase/code/init.sh b/courses/learn-harness-engineering/docs/vi/lectures/lecture-06-why-initialization-needs-its-own-phase/code/init.sh deleted file mode 100644 index 2c6e1b0c..00000000 --- a/courses/learn-harness-engineering/docs/vi/lectures/lecture-06-why-initialization-needs-its-own-phase/code/init.sh +++ /dev/null @@ -1,10 +0,0 @@ -#!/usr/bin/env bash -set -euo pipefail - -echo "[init] installing dependencies" -npm install - -echo "[init] starting the docs site is optional" -echo "[init] use npm run docs:dev for course docs" - -echo "[init] project-specific startup would go here" diff --git a/courses/learn-harness-engineering/docs/vi/lectures/lecture-06-why-initialization-needs-its-own-phase/code/initializer-output-checklist.md b/courses/learn-harness-engineering/docs/vi/lectures/lecture-06-why-initialization-needs-its-own-phase/code/initializer-output-checklist.md deleted file mode 100644 index 6c96506b..00000000 --- a/courses/learn-harness-engineering/docs/vi/lectures/lecture-06-why-initialization-needs-its-own-phase/code/initializer-output-checklist.md +++ /dev/null @@ -1,7 +0,0 @@ -# Danh sách Kiểm tra Kết quả Đầu ra của Khởi tạo - -- Có lệnh khởi động chuẩn không? -- Có lệnh xác minh chuẩn không? -- Có artifact tiến độ đầu tiên không? -- Có commit ổn định đầu tiên không? -- Có bề mặt tính năng có thể nhìn thấy cho các lần chạy sau không? diff --git a/courses/learn-harness-engineering/docs/vi/lectures/lecture-06-why-initialization-needs-its-own-phase/index.md b/courses/learn-harness-engineering/docs/vi/lectures/lecture-06-why-initialization-needs-its-own-phase/index.md deleted file mode 100644 index ed680362..00000000 --- a/courses/learn-harness-engineering/docs/vi/lectures/lecture-06-why-initialization-needs-its-own-phase/index.md +++ /dev/null @@ -1,157 +0,0 @@ -[English Version →](../../../en/lectures/lecture-06-why-initialization-needs-its-own-phase/) | [中文版本 →](../../../zh/lectures/lecture-06-why-initialization-needs-its-own-phase/) - -> Ví dụ mã nguồn: [code/](https://github.com/walkinglabs/learn-harness-engineering/blob/main/docs/vi/lectures/lecture-06-why-initialization-needs-its-own-phase/code/) -> Dự án thực hành: [Dự án 03. Tính liên tục đa phiên](./../../projects/project-03-multi-session-continuity/index.md) - -# Bài 06. Khởi tạo Trước Mỗi Phiên Agent - -Bạn bắt đầu một phiên agent mới và nói "thêm tính năng tìm kiếm." Nó nhảy thẳng vào việc lập trình — sự nhiệt tình đáng ngưỡng mộ. Sau 20 phút, nó phát hiện ra khung test chưa được cấu hình đúng, dành thêm 10 phút để sửa cái đó, sau đó định dạng script migration cơ sở dữ liệu sai, loay hoay thêm. Tính năng tìm kiếm cuối cùng được thêm vào, nhưng toàn bộ phiên không hiệu quả — hầu hết thời gian dành cho "tìm hiểu dự án này hoạt động như thế nào" thay vì viết tính năng tìm kiếm. - -Cách tiếp cận tốt hơn: trước khi để agent bắt đầu làm việc, sử dụng một giai đoạn riêng để chuẩn bị môi trường cơ sở, các lệnh xác minh vượt qua, và cấu trúc dự án được hiểu. Giống như xây nhà — bạn không đổ móng và dựng tường cùng một lúc. Nếu bạn làm vậy, tường dựng lên trước khi móng đã đông cứng, và cả tòa nhà phải bị phá đi và bắt đầu lại. Đổ móng trước, để móng đông cứng, rồi xây tường — sạch sẽ và hiệu quả. - -Bài giảng này giải thích tại sao khởi tạo phải là một giai đoạn riêng, không được trộn lẫn với triển khai. - -## Móng và Tường: Hai Công việc Khác Nhau Về Bản chất - -Khởi tạo và triển khai có mục tiêu tối ưu hóa hoàn toàn khác nhau. Giai đoạn triển khai tối ưu hóa cho: tối đa hóa số lượng và chất lượng của các tính năng được xác minh. Giai đoạn khởi tạo tối ưu hóa cho: tối đa hóa độ tin cậy và hiệu quả của tất cả các triển khai tiếp theo. - -Khi bạn trộn khởi tạo và triển khai, agent đối mặt với bài toán tối ưu hóa đa mục tiêu — đồng thời xây dựng cơ sở hạ tầng và viết mã tính năng. Không có cài đặt ưu tiên rõ ràng, agent tự nhiên nghiêng về viết mã (vì đó là kết quả có thể nhìn thấy trực tiếp) trong khi hy sinh cơ sở hạ tầng (vì giá trị của nó chỉ hiện ra trong các phiên sau). Giống như nói với đội xây dựng đồng thời đổ móng và xây tường — họ có thể sẽ vội vàng xây tường vì tường có thể nhìn thấy và trình diễn được. Nhưng một ngôi nhà có móng tệ có vấn đề hệ thống về sau. - -## Vòng đời Khởi tạo - -```mermaid -flowchart TB - subgraph Wrong["Phiên hỗn hợp (sai)"] - W1["Bắt đầu ngay vào công việc tính năng"] --> W2["Phát hiện các khoảng trống env và test giữa tác vụ"] - W2 --> W3["Tích lũy mã chưa được xác minh"] - W3 --> W4["Phiên tiếp theo phải khám phá lại trạng thái dự án"] - end - - subgraph Right["Khởi tạo riêng biệt (đúng)"] - R1["Phiên 1: môi trường có thể chạy"] --> R2["Test mẫu vượt qua"] - R2 --> R3["Bootstrap contract + danh sách tác vụ được viết"] - R3 --> R4["Checkpoint sạch được commit"] - R4 --> R5["Các phiên sau bắt đầu trực tiếp trên các tác vụ đã xác minh"] - end -``` - -## Điều Gì Xảy ra Khi Bạn Trộn Chúng - -Vấn đề trực tiếp nhất: móng không đông cứng đúng cách. Agent dành 80% công sức cho mã tính năng và 20% tùy tiện thiết lập một số cơ sở hạ tầng. Khung test được cấu hình nhưng không bao giờ được xác minh, quy tắc lint được thiết lập nhưng quá lỏng, không có tệp tiến độ được tạo. Những khiếm khuyết này không rõ ràng trong phiên đầu tiên (vì agent vẫn nhớ những gì nó đã làm), nhưng chúng nổi lên trong phiên thứ hai — agent mới không biết cách chạy, test, hoặc mọi thứ đang ở đâu. Móng cẩu thả, tòa nhà lung lay. - -Một chi phí ẩn hơn là "tích lũy chưa xác minh" — mã tính năng được viết trước khi khung test được cấu hình là mã không có xác minh. Khi bạn cuối cùng quay lại thêm test cho mã đó, bạn có thể phát hiện ra thiết kế ngay từ đầu đã sai — nếu biết trước, bạn đã triển khai khác đi. Giống như lát gạch lên bê tông ướt — khi bạn phát hiện ra sàn không phẳng, tất cả gạch phải được cạy lên và làm lại. - -Ngân sách phiên cũng đang bị lãng phí. Công việc khởi tạo (cấu hình môi trường, thiết lập test, hiểu cấu trúc dự án) tiêu thụ ngân sách đáng kể, để lại ít hơn cho việc triển khai tính năng thực tế. Kết quả: phiên đầu chỉ hoàn thành một nửa tính năng, và phiên thứ hai phải bắt đầu lại hiểu dự án. Ngân sách dành cho móng, nhưng móng cũng không vững — không mục tiêu nào đạt được. - -Vấn đề dễ bị bỏ qua nhất là bẫy giả định ẩn. Các quyết định mà agent đưa ra trong quá trình khởi tạo (khung test nào, cách tổ chức thư mục, quản lý phụ thuộc) — nếu không được ghi lại rõ ràng, các phiên sau không thể hiểu những lựa chọn này. Tệ hơn, các phiên sau có thể đưa ra các lựa chọn mâu thuẫn. Đội xây dựng đầu tiên dùng móng bê tông, đội thứ hai không biết và đóng cọc gỗ vào đó — móng nứt. - -Nghiên cứu phát triển ứng dụng chạy lâu của Anthropic đặc biệt khuyến nghị tách khởi tạo khỏi triển khai. Dữ liệu thực nghiệm của họ: các dự án sử dụng giai đoạn khởi tạo riêng biệt cho thấy tỷ lệ hoàn thành tính năng cao hơn 31% trong các kịch bản đa phiên so với các cách tiếp cận hỗn hợp. Hiểu biết chính — thời gian đầu tư vào giai đoạn khởi tạo được thu hồi hoàn toàn trong 3-4 phiên tiếp theo. Móng càng vững, tường xây càng nhanh. - -Hướng dẫn harness engineering Codex của OpenAI cũng nhấn mạnh nguyên tắc "kho lưu trữ là bản ghi hoạt động" — thiết lập cấu trúc hoạt động rõ ràng từ lần chạy đầu tiên, hoặc mọi phiên mới phải suy ra lại các quy ước dự án. - -## Các Khái niệm Cốt lõi - -- **Giai đoạn Khởi tạo**: Giai đoạn đầu tiên trong vòng đời của agent — không triển khai tính năng, chỉ thiết lập các điều kiện tiên quyết cho tất cả các giai đoạn triển khai tiếp theo. Kết quả không phải là mã, mà là cơ sở hạ tầng. -- **Bootstrap Contract**: Các điều kiện mà một dự án có thể được vận hành không mơ hồ bởi một phiên agent mới — có thể bắt đầu, có thể test, có thể thấy tiến độ, có thể chọn bước tiếp theo. Bốn điều kiện, tất cả đều cần thiết. -- **Khởi động Lạnh vs. Khởi động Ấm (Cold Start vs. Warm Start)**: Khởi động lạnh là từ một thư mục trống nơi agent phải đoán cấu trúc dự án; khởi động ấm là từ một mẫu hoặc dự án hiện có nơi cơ sở hạ tầng đã có sẵn. Khởi động ấm vượt trội hơn nhiều so với khởi động lạnh — giống như bắt đầu làm việc trên công trường có nước chạy và điện so với bắt đầu từ vùng đất hoang. -- **Sẵn sàng Bàn giao (Handoff Readiness)**: Dự án ở trạng thái tại bất kỳ thời điểm nào mà một agent mới có thể tiếp quản. Không cần giải thích bằng lời — chỉ cần nội dung repo. -- **Thời gian Đến Xác minh Đầu tiên (Time to First Verification)**: Thời gian từ khi bắt đầu dự án đến khi điểm tính năng đầu tiên vượt qua xác minh. Đây là chỉ số cốt lõi để đo hiệu quả khởi tạo. -- **Khả năng Sử dụng Downstream**: Thước đo tốt nhất về chất lượng khởi tạo — tỷ lệ các phiên tiếp theo có thể thực thi thành công các tác vụ mà không cần dựa vào kiến thức ẩn. - -## Cách Khởi tạo Đúng - -**Coi khởi tạo là một giai đoạn riêng biệt.** Phiên đầu tiên chỉ làm khởi tạo — hoàn toàn không có mã tính năng kinh doanh. Khởi tạo tạo ra: - -**1. Môi trường có thể chạy.** Dự án khởi động, dependencies được cài đặt, không có vấn đề môi trường. Móng được đổ, không có vết nứt. - -**2. Khung test có thể xác minh.** Ít nhất một test mẫu vượt qua. Điều này chứng minh bản thân khung test được cấu hình đúng — giống như đứng một cột trên móng để chứng minh nó có thể chịu được trọng lượng. - -**3. Tài liệu bootstrap contract.** Một tài liệu rõ ràng cho các phiên sau: -```markdown -# Khởi tạo Contract - -## Lệnh Khởi động -- Cài đặt dependencies: `make setup` -- Khởi động dev server: `make dev` -- Chạy test: `make test` -- Xác minh đầy đủ: `make check` - -## Trạng thái Hiện tại -- Tất cả dependencies đã được cài đặt và khóa -- Khung test được cấu hình (Vitest + React Testing Library) -- Test mẫu vượt qua (1/1) -- Quy tắc lint được cấu hình (ESLint + Prettier) - -## Cấu trúc Dự án -- src/ — Mã nguồn -- src/components/ — React components -- src/api/ — API client -- tests/ — Tệp test -``` - -**4. Phân chia tác vụ.** Chia toàn bộ dự án thành danh sách tác vụ có thứ tự, mỗi tác vụ có tiêu chí chấp nhận rõ ràng: -```markdown -# Phân chia Tác vụ - -## Tác vụ 1: Xác thực Người dùng Cơ bản -- Triển khai JWT auth middleware -- Thêm endpoint đăng nhập/đăng ký -- Chấp nhận: pytest tests/test_auth.py tất cả vượt qua - -## Tác vụ 2: Trang Hồ sơ Người dùng -- Triển khai CRUD hồ sơ người dùng -- Thêm form chỉnh sửa hồ sơ -- Chấp nhận: pytest tests/test_profile.py tất cả vượt qua - -## Tác vụ 3: Tính năng Tìm kiếm -- ... -``` - -**5. Git commit như checkpoint.** Sau khi khởi tạo hoàn thành, commit một checkpoint sạch. Tất cả công việc tiếp theo bắt đầu từ checkpoint này. - -**Chiến lược khởi động ấm**: Đừng bắt đầu từ một thư mục trống. Sử dụng mẫu dự án (create-react-app, fastapi-template, v.v.) để đặt trước cấu trúc thư mục chuẩn, cấu hình phụ thuộc và khung test. Nướng các bước khởi tạo thông thường vào mẫu, chỉ để lại công việc khởi tạo cụ thể cho dự án. Giống như bắt đầu làm việc trên công trường có nước chạy và điện — tốt hơn mười nghìn lần so với bắt đầu từ vùng đất hoang. - -**Tiêu chí hoàn thành khởi tạo**: Không phải "bao nhiêu mã đã được viết," mà là liệu bốn điều kiện của bootstrap contract có được đáp ứng không — có thể bắt đầu, có thể test, có thể thấy tiến độ, có thể chọn bước tiếp theo. Sử dụng danh sách kiểm tra này để xác nhận khởi tạo: - -```markdown -## Danh sách Kiểm tra Chấp nhận Khởi tạo -- [ ] `make setup` thành công từ đầu -- [ ] `make test` có ít nhất một test vượt qua -- [ ] Một phiên agent mới có thể trả lời "làm thế nào để chạy" và "làm thế nào để test" chỉ từ nội dung repo -- [ ] Tệp phân chia tác vụ tồn tại với ít nhất 3 tác vụ -- [ ] Mọi thứ đã được commit vào git -``` - -## Ví dụ Thực tế - -Hai cách tiếp cận khởi tạo cho một dự án frontend React: - -**Cách tiếp cận hỗn hợp (đồng thời đổ móng và xây tường)**: Agent đồng thời tạo scaffolding dự án và triển khai tính năng đầu tiên trong phiên 1. Ở cuối phiên, repo có mã có thể chạy nhưng: không có tài liệu lệnh bắt đầu/test rõ ràng, không có tệp theo dõi tiến độ, không có phân chia tác vụ. Phiên 2 dành ~20 phút để suy ra cấu trúc dự án, khung test và quy trình build — giống như đội xây dựng mới đến công trường, không biết móng đã chạy đến đâu hoặc đường ống nước ở đâu, phải đào lỗ từng chỗ để tìm ra. - -**Khởi tạo riêng biệt (móng trước)**: Phiên 1 chỉ làm khởi tạo — tạo cấu trúc thư mục từ mẫu, cấu hình khung test (Vitest + React Testing Library), viết và xác minh một test mẫu, tạo tài liệu bootstrap contract và tệp phân chia tác vụ, commit checkpoint ban đầu. Chi phí tái xây dựng của phiên 2 dưới 3 phút, và nó bắt đầu làm việc trực tiếp từ danh sách tác vụ — đội đến, nhìn vào bản vẽ, và biết chính xác nơi cần tiếp tục. - -So sánh chu kỳ dự án đầy đủ: tổng thời gian tái xây dựng (qua tất cả các phiên) của cách tiếp cận hỗn hợp cao hơn ~60% so với cách tiếp cận khởi tạo riêng biệt. 20 phút thêm dành cho khởi tạo được thu hồi nhiều lần trong các phiên tiếp theo. Giống như móng vững giúp tường xây nhanh hơn — chậm là nhanh. - -## Những Điểm chính cần Nhớ - -- Khởi tạo và triển khai có mục tiêu tối ưu hóa khác nhau — trộn lẫn chúng chỉ kéo cả hai xuống. Đổ móng trước, rồi xây tường. -- Kết quả của khởi tạo không phải là mã, mà là cơ sở hạ tầng: môi trường có thể chạy, test có thể xác minh, bootstrap contract, phân chia tác vụ. -- Xác nhận khởi tạo bằng bốn điều kiện của bootstrap contract: có thể bắt đầu, có thể test, có thể thấy tiến độ, có thể chọn bước tiếp theo. -- Khởi động ấm tốt hơn khởi động lạnh. Sử dụng mẫu dự án để đặt trước cơ sở hạ tầng chuẩn hóa. -- Thời gian đầu tư vào khởi tạo được thu hồi hoàn toàn trong 3-4 phiên tiếp theo. Đây không phải chi phí thêm — đó là đầu tư trước. Móng càng vững, tòa nhà xây càng nhanh. - -## Đọc thêm - -- [Anthropic: Effective Harnesses for Long-Running Agents](https://www.anthropic.com/engineering/effective-harnesses-for-long-running-agents) -- [OpenAI: Harness Engineering](https://openai.com/index/harness-engineering/) -- [HumanLayer: Harness Engineering for Coding Agents](https://humanlayer.dev/articles/harness-engineering-for-coding-agents/) -- [Infrastructure as Code — Martin Fowler](https://martinfowler.com/bliki/InfrastructureAsCode.html) -- [SWE-agent: Agent-Computer Interfaces](https://github.com/princeton-nlp/SWE-agent) - -## Bài tập - -1. **Thiết kế bootstrap contract**: Viết một bootstrap contract hoàn chỉnh cho một dự án bạn đang phát triển. Sau đó mở một phiên agent hoàn toàn mới, chỉ hiển thị nội dung repo (không có ngữ cảnh lời nói), và để nó thử bắt đầu dự án, chạy test và hiểu tiến độ hiện tại. Ghi lại mọi vấn đề gặp phải — mỗi vấn đề tương ứng với một điều khoản còn thiếu trong bootstrap contract của bạn. - -2. **Thí nghiệm so sánh**: Chọn một dự án mới cỡ vừa. Cách A: để agent đồng thời khởi tạo và thực hiện triển khai đầu tiên. Cách B: dành một phiên cho khởi tạo riêng biệt, bắt đầu triển khai trong phiên 2. Sau 4 phiên, so sánh: thời gian đến xác minh đầu tiên, chi phí tái xây dựng, tỷ lệ hoàn thành tính năng. - -3. **Danh sách kiểm tra chấp nhận khởi tạo**: Thiết kế một danh sách kiểm tra chấp nhận khởi tạo cho dự án của bạn. Để một phiên agent mới thực thi từng mục trong danh sách kiểm tra và ghi lại cái nào vượt qua và cái nào thất bại. Các mục thất bại là nơi harness của bạn cần được tăng cường. diff --git a/courses/learn-harness-engineering/docs/vi/lectures/lecture-07-why-agents-overreach-and-under-finish/code/index.md b/courses/learn-harness-engineering/docs/vi/lectures/lecture-07-why-agents-overreach-and-under-finish/code/index.md deleted file mode 100644 index 207904c6..00000000 --- a/courses/learn-harness-engineering/docs/vi/lectures/lecture-07-why-agents-overreach-and-under-finish/code/index.md +++ /dev/null @@ -1,8 +0,0 @@ -# Mã cho Bài 07 - -Sử dụng thư mục này cho các ví dụ về: - -- các thất bại one-shot -- prompt tác vụ quá lớn -- định hình tác vụ tăng dần -- bề mặt tính năng có cấu trúc diff --git a/courses/learn-harness-engineering/docs/vi/lectures/lecture-07-why-agents-overreach-and-under-finish/code/next-task-template.md b/courses/learn-harness-engineering/docs/vi/lectures/lecture-07-why-agents-overreach-and-under-finish/code/next-task-template.md deleted file mode 100644 index 4b880209..00000000 --- a/courses/learn-harness-engineering/docs/vi/lectures/lecture-07-why-agents-overreach-and-under-finish/code/next-task-template.md +++ /dev/null @@ -1,6 +0,0 @@ -# Mẫu Tác vụ Tiếp theo - -- Tính năng có mức ưu tiên cao nhất hiện tại: -- Tại sao tính năng này là tiếp theo: -- Điều gì được tính là vượt qua: -- Điều gì không được thay đổi trong bước này: diff --git a/courses/learn-harness-engineering/docs/vi/lectures/lecture-07-why-agents-overreach-and-under-finish/code/scope-surface-example.md b/courses/learn-harness-engineering/docs/vi/lectures/lecture-07-why-agents-overreach-and-under-finish/code/scope-surface-example.md deleted file mode 100644 index 509405e7..00000000 --- a/courses/learn-harness-engineering/docs/vi/lectures/lecture-07-why-agents-overreach-and-under-finish/code/scope-surface-example.md +++ /dev/null @@ -1,17 +0,0 @@ -# Ví dụ Bề mặt Phạm vi - -Tác vụ: - -- Thêm indexing vào ứng dụng kiến thức Electron - -Hình dạng phạm vi xấu: - -- "Triển khai indexing" - -Hình dạng phạm vi tốt hơn: - -- Parse các tài liệu đã import -- Chia tài liệu thành các chunk -- Lưu trữ chunk metadata -- Hiển thị trạng thái indexing trong UI -- Thêm hành động reindex diff --git a/courses/learn-harness-engineering/docs/vi/lectures/lecture-07-why-agents-overreach-and-under-finish/code/scope-tracker.ts b/courses/learn-harness-engineering/docs/vi/lectures/lecture-07-why-agents-overreach-and-under-finish/code/scope-tracker.ts deleted file mode 100644 index c587a1dc..00000000 --- a/courses/learn-harness-engineering/docs/vi/lectures/lecture-07-why-agents-overreach-and-under-finish/code/scope-tracker.ts +++ /dev/null @@ -1,151 +0,0 @@ -/** - * scope-tracker.ts - * - * Reads a feature list and a change log. Enforces single-active-feature - * policy. Given a log of changes, flags any changes outside the active - * feature scope. Demonstrates how scope drift happens and how the tracker - * catches it. - * - * Run: npx tsx docs/lectures/lecture-07-why-agents-overreach-and-under-finish/code/scope-tracker.ts - */ - -// --------------------------------------------------------------------------- -// Types -// --------------------------------------------------------------------------- - -interface Feature { - id: string; - name: string; - status: "active" | "pending" | "done"; -} - -interface ChangeLogEntry { - step: number; - file: string; - description: string; - featureId: string; // The feature this change claims to belong to -} - -// --------------------------------------------------------------------------- -// Sample data -// --------------------------------------------------------------------------- - -const features: Feature[] = [ - { id: "F-001", name: "Search endpoint", status: "active" }, - { id: "F-002", name: "Delete endpoint", status: "pending" }, - { id: "F-003", name: "Rate limiting", status: "pending" }, - { id: "F-004", name: "User dashboard", status: "pending" }, -]; - -// A realistic change log where the agent gradually drifts from the active feature -const changeLog: ChangeLogEntry[] = [ - { step: 1, file: "src/routes/search.ts", description: "Add search route handler", featureId: "F-001" }, - { step: 2, file: "src/routes/search.ts", description: "Add query parameter validation", featureId: "F-001" }, - { step: 3, file: "src/routes/search.ts", description: "Add search results pagination", featureId: "F-001" }, - { step: 4, file: "src/routes/delete.ts", description: "Add delete route handler", featureId: "F-002" }, // DRIFT - { step: 5, file: "src/middleware/rate-limit.ts", description: "Add rate limiter middleware", featureId: "F-003" }, // DRIFT - { step: 6, file: "src/routes/search.ts", description: "Integrate rate limiter into search", featureId: "F-001" }, - { step: 7, file: "src/dashboard/ui.tsx", description: "Create dashboard layout component", featureId: "F-004" }, // DRIFT - { step: 8, file: "src/routes/search.ts", description: "Add search response formatting", featureId: "F-001" }, - { step: 9, file: "src/routes/delete.ts", description: "Add delete confirmation logic", featureId: "F-002" }, // DRIFT - { step: 10, file: "src/routes/search.ts", description: "Add search tests", featureId: "F-001" }, -]; - -// --------------------------------------------------------------------------- -// Scope tracker -// --------------------------------------------------------------------------- - -interface ScopeCheckResult { - step: number; - file: string; - description: string; - featureId: string; - inScope: boolean; - activeFeature: string; -} - -function trackScope( - featureList: Feature[], - changes: ChangeLogEntry[] -): ScopeCheckResult[] { - const activeFeatures = featureList.filter((f) => f.status === "active"); - const activeIds = new Set(activeFeatures.map((f) => f.id)); - const activeNames = activeFeatures.map((f) => f.name).join(", "); - - return changes.map((change) => ({ - step: change.step, - file: change.file, - description: change.description, - featureId: change.featureId, - inScope: activeIds.has(change.featureId), - activeFeature: activeNames, - })); -} - -// --------------------------------------------------------------------------- -// Reporting -// --------------------------------------------------------------------------- - -function pad(s: string, len: number): string { - return s.length >= len ? s : s + " ".repeat(len - s.length); -} - -function run(): void { - const results = trackScope(features, changeLog); - - console.log("\n" + "=".repeat(100)); - console.log(" SCOPE TRACKER -- Single Active Feature Enforcement"); - console.log("=".repeat(100)); - - console.log("\n Active feature: " + features.filter((f) => f.status === "active").map((f) => `${f.id} (${f.name})`).join(", ")); - console.log(" Pending features: " + features.filter((f) => f.status === "pending").map((f) => `${f.id} (${f.name})`).join(", ")); - - // Detailed change log - console.log("\n" + "-".repeat(100)); - const header = `| ${pad("Step", 5)}| ${pad("File", 35)}| ${pad("Description", 40)}| ${pad("Feature", 8)}| ${pad("In Scope", 10)}|`; - const sep = `|${"-".repeat(7)}|${"-".repeat(37)}|${"-".repeat(42)}|${"-".repeat(10)}|${"-".repeat(12)}|`; - console.log(header); - console.log(sep); - - let inScopeCount = 0; - let driftCount = 0; - - for (const r of results) { - const scopeLabel = r.inScope ? "OK" : "DRIFT"; - if (r.inScope) inScopeCount++; - else driftCount++; - - const marker = r.inScope ? " " : ">>"; - console.log(`${marker}| ${pad(String(r.step), 5)}| ${pad(r.file, 35)}| ${pad(r.description, 40)}| ${pad(r.featureId, 8)}| ${pad(scopeLabel, 10)}|`); - } - - // Summary - console.log("\n" + "=".repeat(100)); - console.log(" SCOPE DRIFT SUMMARY"); - console.log("=".repeat(100) + "\n"); - - const sHeader = `| ${pad("Metric", 40)}| ${pad("Value", 15)}|`; - const sSep = `|${"-".repeat(42)}|${"-".repeat(17)}|`; - console.log(sHeader); - console.log(sSep); - console.log(`| ${pad("Total changes", 40)}| ${pad(String(results.length), 15)}|`); - console.log(`| ${pad("Changes within active scope (F-001)", 40)}| ${pad(String(inScopeCount), 15)}|`); - console.log(`| ${pad("Changes outside active scope (DRIFT)", 40)}| ${pad(String(driftCount), 15)}|`); - console.log(`| ${pad("Features touched (total)", 40)}| ${pad(String(new Set(results.map((r) => r.featureId)).size), 15)}|`); - - // Drift detail - const driftFeatures = [...new Set(results.filter((r) => !r.inScope).map((r) => r.featureId))]; - if (driftFeatures.length > 0) { - console.log("\n DRIFTED FEATURES:"); - for (const fid of driftFeatures) { - const feat = features.find((f) => f.id === fid); - const driftChanges = results.filter((r) => r.featureId === fid); - console.log(` ${fid} (${feat?.name}): ${driftChanges.length} unauthorized changes`); - } - } - - console.log("\n Without a scope tracker, the agent silently worked on " + driftFeatures.length + " unrelated features."); - console.log(" The tracker catches this drift and enforces the single-active-feature policy.\n"); -} - -run(); diff --git a/courses/learn-harness-engineering/docs/vi/lectures/lecture-07-why-agents-overreach-and-under-finish/index.md b/courses/learn-harness-engineering/docs/vi/lectures/lecture-07-why-agents-overreach-and-under-finish/index.md deleted file mode 100644 index e7c0eda4..00000000 --- a/courses/learn-harness-engineering/docs/vi/lectures/lecture-07-why-agents-overreach-and-under-finish/index.md +++ /dev/null @@ -1,134 +0,0 @@ -[English Version →](../../../en/lectures/lecture-07-why-agents-overreach-and-under-finish/) | [中文版本 →](../../../zh/lectures/lecture-07-why-agents-overreach-and-under-finish/) - -> Ví dụ mã nguồn: [code/](https://github.com/walkinglabs/learn-harness-engineering/blob/main/docs/vi/lectures/lecture-07-why-agents-overreach-and-under-finish/code/) -> Dự án thực hành: [Dự án 04. Phản hồi Runtime và Kiểm soát Phạm vi](./../../projects/project-04-incremental-indexing/index.md) - -# Bài 07. Vạch Ranh giới Tác vụ Rõ ràng cho Agent - -Bạn nói với Claude Code "thêm xác thực người dùng vào dự án này," và nó bắt đầu sửa đổi database schema, viết route, thay đổi frontend component, và — nhân tiện — tái cấu trúc error-handling middleware. Hai giờ sau bạn kiểm tra: 12 tệp được sửa đổi, 800 dòng mã mới, và không có một tính năng nào hoạt động end-to-end. - -Bạn không thể ăn vội nuốt tươi — câu nói này áp dụng đặc biệt cho AI agent. Agent vốn có xung lực "làm thêm một chút" — chúng thấy những thứ liên quan và chỉ xử lý chúng luôn, giống như người đi siêu thị mua một chai nước tương mà ra về đẩy cả xe hàng đầy. Vấn đề là, con người mua quá nhiều chỉ lãng phí tiền; agent làm quá nhiều thứ đồng thời có nghĩa là không cái nào được làm đúng. - -Blog kỹ thuật "Effective harnesses for long-running agents" của Anthropic nói rõ ràng: khi các prompt quá rộng, các agent có xu hướng "bắt đầu nhiều thứ cùng một lúc" thay vì "hoàn thành một thứ trước." Các thực hành kỹ thuật Codex của OpenAI cũng thấy điều tương tự — các tác vụ không có kiểm soát phạm vi rõ ràng thấy tỷ lệ hoàn thành giảm mạnh. Đây không phải vấn đề mô hình — đây là vấn đề harness. Bạn chưa vạch ranh giới. - -## Sự Chú ý là Tài nguyên Hữu hạn - -Đây không phải phép ẩn dụ — đây là toán học. Giả sử năng lực ngữ cảnh của agent là C và nó kích hoạt k tác vụ đồng thời. Mỗi tác vụ nhận được trung bình C/k tài nguyên lý luận. Khi C/k giảm xuống dưới ngưỡng tối thiểu cần thiết để hoàn thành một tác vụ duy nhất, không cái nào trong số chúng được hoàn thành. Bụng bạn chỉ có giới hạn — nhét mười cái bánh bao cùng một lúc và bạn sẽ không tiêu hóa hết tất cả, bạn chỉ bị khó tiêu mười lần. - -Hành vi thực tế của Claude Code rất rõ ràng. Yêu cầu nó "thêm đăng ký người dùng" và nó có thể: - -1. Tạo User model -2. Viết route đăng ký -3. Nhận thấy cần xác minh email, vậy thêm mail service -4. Thấy mật khẩu cần được hash, vậy mang vào bcrypt -5. Nhận thấy error handling không nhất quán, vậy tái cấu trúc global error middleware -6. Thấy cấu trúc tệp test lộn xộn, vậy sắp xếp lại thư mục - -Sau sáu bước, mỗi cái đang làm dở. Không có xác minh end-to-end, mã nửa vời phức tạp cộng lẫn nhau, và phiên tiếp theo để dọn dẹp sẽ hoàn toàn bị lạc. Giống như ai đó đang nấu sáu món đồng thời — mỗi món đều đang trong chảo nhưng chưa cái nào được bày ra đĩa. Tất cả đều cháy. - -Dữ liệu thực nghiệm của Anthropic trực tiếp hỗ trợ điều này: các agent sử dụng chiến lược "bước tiếp theo nhỏ" (tương đương WIP=1) cho thấy tỷ lệ hoàn thành tác vụ cao hơn 37% so với các agent sử dụng các prompt rộng. Thú vị hơn, số dòng mã được tạo ra bởi agent tương quan âm yếu với tỷ lệ hoàn thành tính năng thực tế — viết nhiều mã hơn, hoàn thành ít tính năng hơn. Không thể ăn vội nuốt tươi, được chứng minh bằng dữ liệu. - -## Luồng Công việc WIP=1 - -```mermaid -flowchart LR - Queue["Hàng đợi tính năng"] --> Pick["Chọn đúng một tác vụ"] - Pick --> Active["Chỉ một mục đang hoạt động"] - Active --> Verify["Chạy xác minh end-to-end"] - Verify -->|vượt qua| Commit["Commit và mở khóa tác vụ tiếp theo"] - Verify -->|thất bại| Active - Commit --> Queue -``` - -```mermaid -flowchart TB - Budget["Ngân sách lý luận khả dụng = C"] --> One["WIP = 1
    C / 1 mỗi tác vụ"] - Budget --> Many["WIP = 5
    C / 5 mỗi tác vụ"] - - One --> Finish["Một tính năng đạt trạng thái vượt qua"] - Many --> Partial["Năm triển khai một phần"] - Partial --> VCR["Tỷ lệ hoàn thành đã xác minh thấp
    làm lại nhiều trong phiên tiếp theo"] -``` - -## Các Khái niệm Cốt lõi - -- **Vượt phạm vi (Overreach)**: Agent kích hoạt nhiều tác vụ hơn mức tối ưu trong một phiên. Có thể định lượng được — làm 5 tính năng với 0 cái vượt qua end-to-end là vượt phạm vi. -- **Dưới mức hoàn thành (Under-finish)**: Tỷ lệ tác vụ vượt qua xác minh end-to-end, trong tổng số tác vụ được kích hoạt, giảm xuống dưới ngưỡng. Mã được viết nhưng test không vượt qua là dưới mức hoàn thành. -- **Giới hạn WIP (Work-in-Progress Limit)**: Từ phương pháp Kanban. Ý tưởng cốt lõi: giới hạn số lượng tác vụ đang tiến hành cùng một lúc. Đối với agent, WIP=1 là giá trị mặc định an toàn nhất — hoàn thành một cái trước khi bắt đầu cái tiếp theo. Giống như buffet — đừng chồng đĩa, hoàn thành một đĩa rồi quay lại lấy thêm. -- **Bằng chứng Hoàn thành (Completion Evidence)**: Điều kiện có thể xác minh mà một tác vụ phải thỏa mãn để chuyển từ "đang thực hiện" sang "xong." Không có điều này, agent thay thế "mã trông có vẻ ổn" cho "hành vi vượt qua test." -- **Bề mặt Phạm vi (Scope Surface)**: Cấu trúc DAG nơi mỗi nút là một đơn vị công việc và cạnh là các phụ thuộc. Trạng thái giới hạn ở bốn: not_started, active, blocked, passing. -- **Áp lực Hoàn thành (Completion Pressure)**: Lực ràng buộc mà harness tác dụng thông qua giới hạn WIP và yêu cầu bằng chứng hoàn thành, buộc agent hoàn thành tác vụ hiện tại trước khi bắt đầu một cái mới. - -## Vượt Phạm vi và Dưới mức Hoàn thành là Cộng sinh - -Hai vấn đề này không độc lập — chúng khuếch đại lẫn nhau. Vượt phạm vi làm loãng sự chú ý, sự chú ý bị loãng gây dưới mức hoàn thành, và mã nửa vời còn lại tăng độ phức tạp hệ thống, điều này tiếp tục thúc đẩy vượt phạm vi trong tác vụ tiếp theo. Một vòng lặp vicious. - -Theo thuật ngữ Kanban: Định luật Little cho chúng ta biết L = lambda * W. Nếu công việc đang tiến hành L quá cao (làm quá nhiều thứ cùng một lúc), thời gian thực hiện W cho mỗi tác vụ tất yếu tăng lên. Đối với agent, điều này có nghĩa là mỗi tính năng mất nhiều thời gian hơn từ bắt đầu đến hoàn thành đã xác minh, và xác suất thất bại tăng lên. - -Đây cũng là một vấn đề cũ trong thế giới con người — Steve McConnell đã ghi lại trong *Rapid Development* rằng scope creep là nguyên nhân hàng đầu gây thất bại dự án. Nhưng con người ít nhất có trực giác "tôi đã làm đủ rồi." Agent thì không. Tạo ra ý tưởng tiếp theo tốn mô hình gần như không có token thêm — viết "để tôi sửa cái này luôn" hầu như không đáng kể — nhưng mỗi sửa đổi bổ sung làm loãng sự chú ý của agent. Giống như buffet nơi mỗi đĩa thêm có chi phí biên gần bằng không, nhưng bụng bạn chỉ có giới hạn. - -## Cách Làm Đúng - -### 1. Thực thi WIP=1 - -Đây là phương pháp trực tiếp và hiệu quả nhất. Trong harness của bạn, hãy nói rõ ràng với agent: **chỉ một tác vụ được phép ở trạng thái "active" tại bất kỳ thời điểm nào.** Trong CLAUDE.md của Claude Code hoặc AGENTS.md của Codex, hãy viết: - -``` -## Quy tắc Làm việc -- Làm việc trên một tính năng tại một thời điểm -- Chỉ bắt đầu tính năng tiếp theo sau khi tính năng hiện tại vượt qua xác minh end-to-end -- Đừng "cũng tái cấu trúc" tính năng B trong khi triển khai tính năng A -``` - -Giống như ăn buffet — một đĩa mỗi lần, hoàn thành nó trước khi quay lại lấy thêm. - -### 2. Định nghĩa Bằng chứng Hoàn thành Rõ ràng cho Mỗi Tác vụ - -Xong không phải là "mã được viết" — mà là "xác minh hành vi vượt qua." Trong feature list của bạn, mỗi mục cần một lệnh xác minh: - -``` -F01: Đăng ký Người dùng - Xác minh: curl -X POST /api/register -d '{"email":"test@example.com","password":"123456"}' | jq .status == 201 - Trạng thái: passing -``` - -### 3. Ngoại hóa Bề mặt Phạm vi - -Sử dụng tệp có thể đọc bởi máy (JSON hoặc Markdown) để ghi lại tất cả trạng thái tác vụ. Bất kỳ phiên mới nào cũng có thể đọc tệp này và biết ngay: tác vụ nào đang hoạt động? Hành vi nào được coi là xong? Xác minh nào đã vượt qua? - -### 4. Theo dõi Tỷ lệ Hoàn thành Đã xác minh - -Harness nên liên tục theo dõi VCR (Verified Completion Rate) = tác vụ đã xác minh / tác vụ được kích hoạt. Chặn kích hoạt tác vụ mới khi VCR < 1.0. - -## Trường hợp Thực tế - -Một dự án REST API với 8 tính năng, hai chiến lược được so sánh: - -**Chế độ buffet (không có ràng buộc)**: Agent kích hoạt 5 tính năng đồng thời trong phiên 1. Tạo ra ~800 dòng trên 12 tệp. Tỷ lệ vượt qua test end-to-end: 20% — chỉ đăng ký người dùng hoạt động. 4 tính năng còn lại: database schema được tạo nhưng thiếu logic xác thực, route được định nghĩa nhưng trả về định dạng phản hồi sai. Giống như ai đó nấu sáu món cùng một lúc, chỉ có một món tạm ăn được. Đến cuối phiên 3, chỉ 3 trong 8 tính năng hoàn thành. - -**Chế độ một đĩa (WIP=1)**: Agent chỉ làm việc trên đăng ký người dùng trong phiên 1. Tạo ra ~200 dòng trên 4 tệp. Test end-to-end: 100% vượt qua. Commit một triển khai sạch, đã xác minh. Đến cuối phiên 4, 7 trong 8 tính năng hoàn thành (cái thứ 8 bị chặn bởi phụ thuộc bên ngoài). - -Kết quả: tổng mã ít hơn (800 vs 1200 dòng) nhưng mã hiệu quả hơn. Tỷ lệ hoàn thành: 87.5% vs 37.5%. Ăn từng miếng một, và bạn thực sự ăn được nhiều hơn. - -## Những Điểm chính cần Nhớ - -- **WIP=1 là cài đặt an toàn mặc định cho harness agent** — hoàn thành một cái, rồi bắt đầu cái tiếp theo; đừng cố song song hóa. Bạn không thể béo lên trong một bữa ăn. -- **Bằng chứng hoàn thành phải có thể thực thi** — "mã trông có vẻ ổn" không tính; "curl trả về 201" mới tính. -- **Bề mặt phạm vi phải được ngoại hóa như một tệp** — không chỉ đề cập trong hội thoại, mà được ghi lại ở định dạng có thể đọc bởi máy trong repo. -- **Vượt phạm vi và dưới mức hoàn thành là cộng sinh** — giải quyết một cái giải quyết cái kia. -- **"Làm ít nhưng hoàn thành" luôn tốt hơn "làm nhiều nhưng để lại một nửa"** — dòng mã agent và tỷ lệ hoàn thành tính năng tương quan âm. Chất lượng luôn tốt hơn số lượng. - -## Đọc thêm - -- [Effective harnesses for long-running agents - Anthropic](https://www.anthropic.com/engineering/effective-harnesses-for-long-running-agents) — Blog kỹ thuật của Anthropic, thảo luận chi tiết về chiến lược "bước tiếp theo nhỏ" -- [Harness Engineering - OpenAI](https://openai.com/index/harness-engineering/) — Xử lý đầy đủ về harness engineering của OpenAI -- [Kanban: Successful Evolutionary Change - David Anderson](https://www.goodreads.com/book/show/1070822.Kanban) — Nguồn kinh điển về giới hạn WIP -- [Rapid Development - Steve McConnell](https://www.goodreads.com/book/show/125171.Rapid_Development) — Dữ liệu thực nghiệm về scope creep là nguyên nhân hàng đầu gây thất bại dự án - -## Bài tập - -1. **Nguyên tử hóa tác vụ**: Chọn một yêu cầu rộng (ví dụ: "triển khai hệ thống quản lý người dùng") và chia thành ít nhất 5 đơn vị công việc nguyên tử. Cho mỗi đơn vị, chỉ định: (a) mô tả hành vi đơn, (b) lệnh xác minh có thể thực thi, (c) các phụ thuộc. Kiểm tra liệu việc phân chia có thỏa mãn ràng buộc WIP=1 không. - -2. **Thí nghiệm So sánh**: Chạy cùng dự án hai lần — một lần không có ràng buộc, một lần với WIP=1 được thực thi. So sánh: tỷ lệ hoàn thành đã xác minh, tổng số dòng mã, tỷ lệ mã hiệu quả. - -3. **Kiểm toán Bằng chứng Hoàn thành**: Xem xét kết quả của một lần chạy agent gần đây, phân loại từng thay đổi mã là "hành vi đã hoàn thành," "hành vi chưa hoàn thành" hoặc "scaffolding." Thêm các lệnh xác minh còn thiếu cho mỗi hành vi chưa hoàn thành. diff --git a/courses/learn-harness-engineering/docs/vi/lectures/lecture-08-why-feature-lists-are-harness-primitives/code/feature-list-validator.ts b/courses/learn-harness-engineering/docs/vi/lectures/lecture-08-why-feature-lists-are-harness-primitives/code/feature-list-validator.ts deleted file mode 100644 index 52d6a2b9..00000000 --- a/courses/learn-harness-engineering/docs/vi/lectures/lecture-08-why-feature-lists-are-harness-primitives/code/feature-list-validator.ts +++ /dev/null @@ -1,218 +0,0 @@ -/** - * feature-list-validator.ts - * - * Reads a feature_list.json, validates its schema, and checks for features - * marked "pass" without verification evidence. Outputs a structured report. - * Can run against any project directory that has a feature_list.json. - * - * Usage: - * npx tsx docs/lectures/lecture-08.../code/feature-list-validator.ts [path-to-dir] - * (defaults to the directory containing this script) - * - * Run: npx tsx docs/lectures/lecture-08-why-feature-lists-are-harness-primitives/code/feature-list-validator.ts - */ - -import * as fs from "node:fs"; -import * as path from "node:path"; - -// --------------------------------------------------------------------------- -// Types -// --------------------------------------------------------------------------- - -interface FeatureEntry { - id?: string; - category?: string; - description?: string; - verification?: string[]; - passes?: boolean; - // Allow unknown fields for flexibility - [key: string]: unknown; -} - -interface ValidationResult { - featureId: string; - schemaValid: boolean; - schemaErrors: string[]; - hasVerification: boolean; - markedPassWithoutEvidence: boolean; - passes: boolean; - verificationCount: number; -} - -// --------------------------------------------------------------------------- -// Schema validation -// --------------------------------------------------------------------------- - -function validateSchema(entry: FeatureEntry, index: number): string[] { - const errors: string[] = []; - const label = entry.id ?? `entry ${index + 1}`; - - if (!entry.id || typeof entry.id !== "string") { - errors.push(`[${label}] Missing or invalid 'id' field`); - } - if (!entry.category || typeof entry.category !== "string") { - errors.push(`[${label}] Missing or invalid 'category' field`); - } - if (!entry.description || typeof entry.description !== "string") { - errors.push(`[${label}] Missing or invalid 'description' field`); - } - if (entry.verification !== undefined && !Array.isArray(entry.verification)) { - errors.push(`[${label}] 'verification' must be an array if present`); - } - if (entry.passes !== undefined && typeof entry.passes !== "boolean") { - errors.push(`[${label}] 'passes' must be a boolean if present`); - } - - return errors; -} - -// --------------------------------------------------------------------------- -// Evidence validation -// --------------------------------------------------------------------------- - -function checkEvidence(entry: FeatureEntry): { - hasVerification: boolean; - markedPassWithoutEvidence: boolean; -} { - const hasVerification = Array.isArray(entry.verification) && entry.verification.length > 0; - const markedPassWithoutEvidence = entry.passes === true && !hasVerification; - - return { hasVerification, markedPassWithoutEvidence }; -} - -// --------------------------------------------------------------------------- -// Process feature list -// --------------------------------------------------------------------------- - -function processFeatureList(entries: FeatureEntry[]): ValidationResult[] { - return entries.map((entry, index) => { - const schemaErrors = validateSchema(entry, index); - const evidence = checkEvidence(entry); - - return { - featureId: (entry.id as string) ?? `entry-${index + 1}`, - schemaValid: schemaErrors.length === 0, - schemaErrors, - hasVerification: evidence.hasVerification, - markedPassWithoutEvidence: evidence.markedPassWithoutEvidence, - passes: entry.passes === true, - verificationCount: Array.isArray(entry.verification) ? entry.verification.length : 0, - }; - }); -} - -// --------------------------------------------------------------------------- -// Reporting -// --------------------------------------------------------------------------- - -function pad(s: string, len: number): string { - return s.length >= len ? s : s + " ".repeat(len - s.length); -} - -function run(): void { - // Resolve target directory - const scriptDir = path.dirname(new URL(import.meta.url).pathname); - const targetDir = process.argv[2] - ? path.resolve(process.argv[2]) - : scriptDir; - - const filePath = path.join(targetDir, "feature_list.json"); - - console.log("\n" + "=".repeat(90)); - console.log(" FEATURE LIST VALIDATOR"); - console.log("=".repeat(90)); - console.log(` Reading: ${filePath}\n`); - - if (!fs.existsSync(filePath)) { - console.error(` ERROR: feature_list.json not found at ${filePath}`); - console.error(" Usage: npx tsx feature-list-validator.ts [path-to-directory-containing-feature_list.json]\n"); - process.exit(1); - } - - let entries: FeatureEntry[]; - try { - const raw = fs.readFileSync(filePath, "utf-8"); - entries = JSON.parse(raw); - } catch (err) { - console.error(` ERROR: Could not parse feature_list.json: ${err}`); - process.exit(1); - } - - if (!Array.isArray(entries)) { - console.error(" ERROR: feature_list.json must contain a JSON array at the top level."); - process.exit(1); - } - - // For demo purposes, also validate an extended test set - const demoEntries: FeatureEntry[] = [ - ...entries, - { - id: "qna-002", - category: "import", - description: "User can import a PDF document.", - verification: ["Upload a PDF file", "Verify it appears in the document list"], - passes: true, - }, - { - id: "qna-003", - category: "grounded_qa", - description: "System hallucination rate is below 5%.", - verification: [], // Empty -- no evidence - passes: true, // Marked as pass WITHOUT evidence - }, - { - id: "missing-fields", - // Missing 'category' and 'description' - passes: true, - } as FeatureEntry, - ]; - - const results = processFeatureList(demoEntries); - - // Print report - const header = `| ${pad("Feature ID", 14)}| ${pad("Schema", 8)}| ${pad("Passes", 7)}| ${pad("Verifications", 14)}| ${pad("Evidence?", 12)}| Notes`; - const sep = `|${"-".repeat(16)}|${"-".repeat(10)}|${"-".repeat(9)}|${"-".repeat(16)}|${"-".repeat(14)}|${"-".repeat(30)}`; - console.log(header); - console.log(sep); - - for (const r of results) { - const schemaLabel = r.schemaValid ? "OK" : "INVALID"; - const passesLabel = r.passes ? "PASS" : "FAIL"; - const evidenceLabel = r.hasVerification ? "Present" : "MISSING"; - const notes = r.markedPassWithoutEvidence - ? "FLAGGED: passes without evidence!" - : r.schemaErrors.length > 0 - ? r.schemaErrors[0] - : ""; - - const marker = r.markedPassWithoutEvidence ? ">>" : r.schemaValid ? " " : "!!"; - console.log( - `${marker}| ${pad(r.featureId, 14)}| ${pad(schemaLabel, 8)}| ${pad(passesLabel, 7)}| ${pad(String(r.verificationCount), 14)}| ${pad(evidenceLabel, 12)}| ${notes}` - ); - } - - // Summary - const total = results.length; - const schemaOk = results.filter((r) => r.schemaValid).length; - const passing = results.filter((r) => r.passes).length; - const flagged = results.filter((r) => r.markedPassWithoutEvidence).length; - const withEvidence = results.filter((r) => r.hasVerification).length; - - console.log("\n" + "-".repeat(90)); - console.log(" SUMMARY"); - console.log("-".repeat(90)); - console.log(` Total features: ${total}`); - console.log(` Schema valid: ${schemaOk}/${total}`); - console.log(` Marked as passing: ${passing}/${total}`); - console.log(` With verification evidence: ${withEvidence}/${total}`); - console.log(` Flagged (pass without evidence): ${flagged}`); - - if (flagged > 0) { - console.log(`\n WARNING: ${flagged} feature(s) marked as "pass" without any verification evidence.`); - console.log(" These features need verification before they can be trusted.\n"); - } else { - console.log("\n All passing features have verification evidence. Feature list is healthy.\n"); - } -} - -run(); diff --git a/courses/learn-harness-engineering/docs/vi/lectures/lecture-08-why-feature-lists-are-harness-primitives/code/feature_list.json b/courses/learn-harness-engineering/docs/vi/lectures/lecture-08-why-feature-lists-are-harness-primitives/code/feature_list.json deleted file mode 100644 index 8b973e31..00000000 --- a/courses/learn-harness-engineering/docs/vi/lectures/lecture-08-why-feature-lists-are-harness-primitives/code/feature_list.json +++ /dev/null @@ -1,15 +0,0 @@ -[ - { - "id": "qna-001", - "category": "grounded_qa", - "description": "User can ask a question about an imported document and receive an answer with visible citations.", - "verification": [ - "Import a markdown document", - "Open the Q&A panel", - "Ask a question about known content", - "Verify the answer is returned", - "Verify one or more citations are displayed" - ], - "passes": false - } -] diff --git a/courses/learn-harness-engineering/docs/vi/lectures/lecture-08-why-feature-lists-are-harness-primitives/code/index.md b/courses/learn-harness-engineering/docs/vi/lectures/lecture-08-why-feature-lists-are-harness-primitives/code/index.md deleted file mode 100644 index f4e655e7..00000000 --- a/courses/learn-harness-engineering/docs/vi/lectures/lecture-08-why-feature-lists-are-harness-primitives/code/index.md +++ /dev/null @@ -1,8 +0,0 @@ -# Mã cho Bài 08 - -Sử dụng thư mục này cho các ví dụ về: - -- pass-state gating -- xác minh end-to-end -- tiêu chí hoàn thành yếu vs mạnh -- các ví dụ vòng lặp evaluator diff --git a/courses/learn-harness-engineering/docs/vi/lectures/lecture-08-why-feature-lists-are-harness-primitives/code/pass-gate-policy.md b/courses/learn-harness-engineering/docs/vi/lectures/lecture-08-why-feature-lists-are-harness-primitives/code/pass-gate-policy.md deleted file mode 100644 index 87a8d7b7..00000000 --- a/courses/learn-harness-engineering/docs/vi/lectures/lecture-08-why-feature-lists-are-harness-primitives/code/pass-gate-policy.md +++ /dev/null @@ -1,8 +0,0 @@ -# Chính sách Pass Gate - -Một tính năng chỉ có thể chuyển từ `passes: false` sang `passes: true` khi: - -- workflow được kỳ vọng đã được thực thi -- bằng chứng thành công đã được ghi lại -- không có lỗi chặn nào trong đường dẫn đã được test -- triển khai không để lại ứng dụng ở trạng thái bị hỏng hoặc mơ hồ diff --git a/courses/learn-harness-engineering/docs/vi/lectures/lecture-08-why-feature-lists-are-harness-primitives/index.md b/courses/learn-harness-engineering/docs/vi/lectures/lecture-08-why-feature-lists-are-harness-primitives/index.md deleted file mode 100644 index 07913a27..00000000 --- a/courses/learn-harness-engineering/docs/vi/lectures/lecture-08-why-feature-lists-are-harness-primitives/index.md +++ /dev/null @@ -1,137 +0,0 @@ -[English Version →](../../../en/lectures/lecture-08-why-feature-lists-are-harness-primitives/) | [中文版本 →](../../../zh/lectures/lecture-08-why-feature-lists-are-harness-primitives/) - -> Ví dụ mã nguồn: [code/](https://github.com/walkinglabs/learn-harness-engineering/blob/main/docs/vi/lectures/lecture-08-why-feature-lists-are-harness-primitives/code/) -> Dự án thực hành: [Dự án 04. Phản hồi Runtime và Kiểm soát Phạm vi](./../../projects/project-04-incremental-indexing/index.md) - -# Bài 08. Sử dụng Feature List để Ràng buộc những gì Agent Làm - -Bạn yêu cầu một agent xây dựng một trang web thương mại điện tử. Sau khi nó hoàn thành, nó nói với bạn "xong." Bạn nhìn vào mã — xác thực người dùng hoạt động, nhưng nút thanh toán trong giỏ hàng không làm gì cả, và luồng thanh toán hoàn toàn không được kết nối. Vấn đề: bạn không bao giờ nói với nó "xong" có nghĩa là gì, vì vậy nó đã sử dụng tiêu chuẩn của riêng mình — "tôi đã viết nhiều mã và nó trông khá đầy đủ." - -Feature list, theo mắt nhiều người, chỉ là một ghi chú nhắc nhở — viết mọi thứ xuống để không quên, sau đó để sang một bên. Nhưng trong thế giới harness, feature list không phải là ghi chú cho con người — nó là xương sống của toàn bộ harness. Bộ lập lịch phụ thuộc vào nó để chọn tác vụ, bộ xác minh phụ thuộc vào nó để đánh giá hoàn thành, trình báo cáo bàn giao phụ thuộc vào nó để tạo tóm tắt. Phá vỡ xương sống và toàn bộ cơ thể bị tê liệt. - -Cả Anthropic và OpenAI đều nhấn mạnh: **các artifact phải được ngoại hóa.** Trạng thái tính năng phải sống trong một tệp có thể đọc bởi máy trong repo, không phải trong văn bản hội thoại không có cấu trúc. - -## Agent Không Biết "Xong" Có nghĩa là Gì - -Cả Claude Code lẫn Codex đều không tự động biết ý bạn là gì khi nói "xong." Bạn nói "thêm tính năng giỏ hàng," và sự diễn giải của mô hình có thể là "viết một Cart component và một addToCart method." Nhưng ý bạn là "người dùng có thể duyệt sản phẩm, thêm vào giỏ hàng, và hoàn thành thanh toán end-to-end." Khoảng cách hiểu biết này tồn tại mãi mà không có feature list. Agent sử dụng tiêu chuẩn ẩn của riêng mình — thường là "mã không có lỗi cú pháp rõ ràng." Những gì bạn cần là xác minh hành vi end-to-end. Giống như nhờ bạn mua hoa quả — bạn nói "lấy vài thứ hoa quả" và họ về với chanh. Hoa quả của họ và hoa quả của bạn không phải là cùng loại hoa quả. - -Nhìn vào ghi chú tiến độ phổ biến này: - -``` -Đã làm xác thực người dùng, giỏ hàng hầu như xong, vẫn cần thanh toán -``` -Một phiên agent mới có thể trả lời những câu hỏi này từ ghi chú này không? "Hầu như xong" có nghĩa là gì? Test nào giỏ hàng đã vượt qua? Điều gì đang chặn thanh toán? Câu trả lời cho tất cả là "không ai biết." Giống như nói với bác sĩ "bụng tôi đau, gần đây ổn hơn" — họ có thể kê thuốc gì? - -Kết quả: phiên mới dành 20 phút để suy ra trạng thái dự án, và có thể triển khai lại các tính năng đã hoàn thành. Dữ liệu kỹ thuật của Anthropic cho thấy bản ghi tiến độ tốt giảm thời gian chẩn đoán khởi động phiên 60-80%. - -## Máy trạng thái Tính năng - -```mermaid -flowchart LR - Feature["Một hàng tính năng"] --> Behavior["Hành vi
    ví dụ: POST /cart/items trả về 201"] - Feature --> Check["Lệnh xác minh
    kiểm tra chính xác để chạy"] - Feature --> State["Trạng thái
    not_started / active / blocked / passing"] - - Behavior --> Complete["Chỉ với cả ba trường
    hàng tính năng mới sử dụng được"] - Check --> Complete - State --> Complete -``` - -```mermaid -flowchart LR - List["feature_list.json / features.md"] --> Scheduler["Chọn mục not_started tiếp theo"] - Scheduler --> Agent["Agent làm việc trên mục đó"] - Agent --> Verifier["Chạy lệnh xác minh của mục đó"] - Verifier -->|vượt qua| Passing["Đánh dấu là passing
    và ghi bằng chứng"] - Verifier -->|thất bại| Active["Giữ nguyên active"] - Verifier -->|vấn đề phụ thuộc| Blocked["Đánh dấu là blocked"] - Passing --> Handoff["Cập nhật ghi chú bàn giao
    và tiến độ hiện tại"] - Active --> Agent -``` - -## Các Khái niệm Cốt lõi - -- **Feature list là nguyên lý cơ bản của harness**: Không phải "công cụ lập kế hoạch tùy chọn," mà là cấu trúc dữ liệu nền tảng mà tất cả các thành phần harness khác phụ thuộc vào. Giống như cấu trúc bảng cơ sở dữ liệu — bạn không thể nói "hãy bỏ qua khóa chính." -- **Cấu trúc ba yếu tố**: Mỗi mục tính năng là một bộ ba `(mô tả hành vi, lệnh xác minh, trạng thái hiện tại)`. Thiếu bất kỳ yếu tố nào làm cho mục không đầy đủ. -- **Mô hình máy trạng thái**: Mỗi mục tính năng có bốn trạng thái — `not_started`, `active`, `blocked`, `passing`. Chuyển đổi trạng thái được kiểm soát bởi harness, không được thay đổi tự do bởi agent. -- **Gating trạng thái vượt qua (Pass-state gating)**: Cách duy nhất một tính năng chuyển từ `active` sang `passing` là thực thi thành công lệnh xác minh. Điều này không thể đảo ngược — một khi `passing`, nó không thể quay lại. Giống như vượt qua bài thi có nghĩa là bạn đã qua, bạn không thể hồi tố thay đổi điểm. -- **Nguồn sự thật duy nhất**: Tất cả thông tin về "những gì cần làm" phải bắt nguồn từ một feature list. Không có mâu thuẫn giữa feature list và lịch sử hội thoại. -- **Áp lực ngược (Back-pressure)**: Số lượng tính năng chưa vượt qua là áp lực mà harness tác dụng lên agent. Áp lực bằng không = dự án hoàn thành. - -## Tại sao Feature List Phải là "Nguyên lý Cơ bản" - -Tài liệu là để con người đọc; nguyên lý cơ bản là để hệ thống thực thi. Tài liệu có thể bị bỏ qua; nguyên lý cơ bản không thể bị vượt qua. - -Nghĩ về nó như ràng buộc trigger cơ sở dữ liệu vs. kiểm tra lớp ứng dụng: cái trước được thực thi bởi engine cơ sở dữ liệu, không có SQL nào có thể bỏ qua nó; cái sau phụ thuộc vào tính đúng đắn của mã ứng dụng và có thể vô tình bị vượt qua. Feature list như nguyên lý cơ bản harness phục vụ bốn thành phần harness cụ thể: - -1. **Bộ lập lịch (Scheduler)**: Đọc trạng thái, chọn tính năng `not_started` tiếp theo. Giống như hệ thống lập kế hoạch sản xuất nhà máy. -2. **Bộ xác minh (Verifier)**: Thực thi lệnh xác minh, quyết định có cho phép chuyển đổi trạng thái không. Giống như kiểm tra chất lượng. -3. **Trình báo cáo bàn giao (Handoff Reporter)**: Tự động tạo tóm tắt bàn giao phiên từ feature list. Giống như báo cáo ca tự động. -4. **Trình theo dõi tiến độ (Progress Tracker)**: Đếm phân phối trạng thái, cung cấp số liệu sức khỏe dự án. Giống như dashboard. - -## Cách Làm Đúng - -### 1. Định nghĩa Định dạng Feature List Tối giản - -Bạn không cần một hệ thống phức tạp — một tệp Markdown hoặc JSON có cấu trúc là đủ. Điều quan trọng là mỗi mục phải có bộ ba: - -```json -{ - "id": "F03", - "behavior": "POST /cart/items với {product_id, quantity} trả về 201", - "verification": "curl -X POST http://localhost:3000/api/cart/items -H 'Content-Type: application/json' -d '{\"product_id\":1,\"quantity\":2}' | jq .status == 201", - "state": "passing", - "evidence": "commit abc123, test output log" -} -``` - -### 2. Để Harness Kiểm soát Chuyển đổi Trạng thái - -Agent không thể trực tiếp thay đổi trạng thái của tính năng thành `passing`. Nó chỉ có thể gửi yêu cầu xác minh; harness thực thi lệnh xác minh và quyết định có cho phép chuyển đổi không. Đây là "pass-state gating." - -### 3. Viết Quy tắc trong CLAUDE.md - -``` -## Quy tắc Feature List -- Tệp feature list: /docs/features.md -- Chỉ một tính năng active tại một thời điểm -- Lệnh xác minh phải vượt qua trước khi đánh dấu là passing -- Đừng tự mình sửa đổi trạng thái feature list — script xác minh cập nhật chúng tự động -``` - -### 4. Hiệu chỉnh Độ hạt - -Mỗi mục tính năng phải có phạm vi "có thể hoàn thành trong một phiên." Quá rộng thì không xong; quá hẹp thì overhead quản lý tăng lên. "Người dùng có thể thêm mục vào giỏ hàng" là độ hạt tốt. "Triển khai giỏ hàng" là quá rộng. "Tạo trường tên trên Cart model" là quá hẹp. Giống như cắt bít tết — không phải cả miếng, và cũng không phải thịt băm. - -## Trường hợp Thực tế - -Một nền tảng thương mại điện tử với 10 tính năng. Hai cách tiếp cận theo dõi được so sánh: - -**Chế độ ghi chú nhắc nhở**: Agent sử dụng ghi chú không có cấu trúc. Sau 3 phiên, ghi chú trở thành "đã làm xác thực người dùng và danh sách sản phẩm, giỏ hàng hầu như xong nhưng có lỗi, thanh toán chưa bắt đầu." Phiên mới cần 20 phút để suy ra trạng thái, cuối cùng triển khai lại các tính năng đã hoàn thành. Giống như danh sách mua sắm của bạn nói "sữa, bánh mì và cái đó" — ở cửa hàng, bạn vẫn không biết mua gì. - -**Chế độ xương sống**: Mỗi tính năng có trạng thái và lệnh xác minh rõ ràng. Phiên mới đọc feature list và trong 3 phút biết: F01-F05 là `passing`, F06 là `active`, F07-F10 là `not_started`. Tiếp tục từ F06 trực tiếp, không có làm lại nào. - -Kết quả định lượng: các dự án sử dụng feature list có cấu trúc cho thấy tỷ lệ hoàn thành tính năng cao hơn 45% so với theo dõi tự do, với zero triển khai trùng lặp. - -## Những Điểm chính cần Nhớ - -- **Feature list là xương sống của harness**, không phải ghi chú cho con người. Bộ lập lịch, bộ xác minh và trình báo cáo bàn giao đều phụ thuộc vào chúng. -- **Mỗi mục tính năng phải có bộ ba**: mô tả hành vi + lệnh xác minh + trạng thái hiện tại. Thiếu một yếu tố thì không đầy đủ — giống như ghế ba chân thiếu một chân. -- **Chuyển đổi trạng thái được kiểm soát bởi harness** — agent không thể tự mình thay đổi trạng thái. Vượt qua xác minh = con đường nâng cấp duy nhất. -- **Feature list là nguồn sự thật duy nhất của dự án** — tất cả thông tin "phải làm gì" đều bắt nguồn từ một danh sách. -- **Hiệu chỉnh độ hạt thành "có thể hoàn thành trong một phiên."** - -## Đọc thêm - -- [Building Effective Agents - Anthropic](https://www.anthropic.com/research/building-effective-agents) — Xác định rõ ràng feature list là "cấu trúc dữ liệu cốt lõi" để kiểm soát phạm vi agent -- [Harness Engineering - OpenAI](https://openai.com/index/harness-engineering/) — Nhấn mạnh nguyên tắc "ngoại hóa artifact" -- [Design by Contract - Bertrand Meyer](https://www.goodreads.com/book/show/130439.Object_Oriented_Software_Construction) — Nguyên tắc thiết kế hợp đồng, nền tảng lý thuyết của feature list -- [How Google Tests Software](https://www.goodreads.com/book/show/13563030-how-google-tests-software) — Tháp kiểm thử và thực hành kỹ thuật đặc tả hành vi - -## Bài tập - -1. **Thiết kế Feature List**: Định nghĩa một JSON schema feature list tối giản. Bao gồm: id, mô tả hành vi, lệnh xác minh, trạng thái hiện tại, tham chiếu bằng chứng. Sử dụng nó để mô tả một dự án thực tế với 5 tính năng. - -2. **So sánh Độ khắt khe Xác minh**: Chọn 3 tính năng và thiết kế cả xác minh "lỏng" (ví dụ: "mã không có lỗi cú pháp") và xác minh "nghiêm" (ví dụ: "test end-to-end vượt qua"). So sánh tỷ lệ dương tính giả dưới mỗi cách tiếp cận. - -3. **Kiểm toán Nguyên tắc Nguồn duy nhất**: Xem xét một dự án agent hiện có và kiểm tra thông tin phạm vi mâu thuẫn với feature list (yêu cầu ẩn trong hội thoại, chú thích TODO trong mã, v.v.). Thiết kế kế hoạch để hợp nhất tất cả thông tin vào feature list. diff --git a/courses/learn-harness-engineering/docs/vi/lectures/lecture-09-why-agents-declare-victory-too-early/code/clean-state-checklist.md b/courses/learn-harness-engineering/docs/vi/lectures/lecture-09-why-agents-declare-victory-too-early/code/clean-state-checklist.md deleted file mode 100644 index f43a9fe0..00000000 --- a/courses/learn-harness-engineering/docs/vi/lectures/lecture-09-why-agents-declare-victory-too-early/code/clean-state-checklist.md +++ /dev/null @@ -1,6 +0,0 @@ -# Danh sách Kiểm tra Trạng thái Sạch - -- Ứng dụng khởi động mà không cần sửa chữa thủ công -- Tiến độ hiện tại được ghi lại -- Không có bước import hoặc indexing làm dở nào còn chưa được ghi lại -- Agent tiếp theo có thể chạy đường dẫn khởi động và xác minh chuẩn ngay lập tức diff --git a/courses/learn-harness-engineering/docs/vi/lectures/lecture-09-why-agents-declare-victory-too-early/code/index.md b/courses/learn-harness-engineering/docs/vi/lectures/lecture-09-why-agents-declare-victory-too-early/code/index.md deleted file mode 100644 index d980fb70..00000000 --- a/courses/learn-harness-engineering/docs/vi/lectures/lecture-09-why-agents-declare-victory-too-early/code/index.md +++ /dev/null @@ -1,8 +0,0 @@ -# Mã cho Bài 09 - -Sử dụng thư mục này cho các ví dụ về: - -- log như phản hồi -- khả năng hiển thị trạng thái runtime -- kiểm tra trạng thái sạch -- các ví dụ phục hồi diff --git a/courses/learn-harness-engineering/docs/vi/lectures/lecture-09-why-agents-declare-victory-too-early/code/victory-detector.ts b/courses/learn-harness-engineering/docs/vi/lectures/lecture-09-why-agents-declare-victory-too-early/code/victory-detector.ts deleted file mode 100644 index fad1a3ba..00000000 --- a/courses/learn-harness-engineering/docs/vi/lectures/lecture-09-why-agents-declare-victory-too-early/code/victory-detector.ts +++ /dev/null @@ -1,241 +0,0 @@ -/** - * victory-detector.ts - * - * Simulates an agent that claims task completion, then checks the claimed - * state against actual verification. Outputs: claimed vs actual, highlighting - * gaps between what the agent said and what's really true. - * - * Run: npx tsx docs/lectures/lecture-09-why-agents-declare-victory-too-early/code/victory-detector.ts - */ - -// --------------------------------------------------------------------------- -// Types -// --------------------------------------------------------------------------- - -interface Task { - name: string; - claimedComplete: boolean; - checks: VerificationCheck[]; -} - -interface VerificationCheck { - description: string; - claimed: boolean; // What the agent says - actual: boolean; // What is actually true - severity: "critical" | "warning"; -} - -// --------------------------------------------------------------------------- -// Simulated tasks with claimed vs actual states -// --------------------------------------------------------------------------- - -const tasks: Task[] = [ - { - name: "Add search endpoint", - claimedComplete: true, - checks: [ - { - description: "Route handler exists", - claimed: true, - actual: true, - severity: "critical", - }, - { - description: "Authentication middleware applied", - claimed: true, - actual: false, // Agent forgot auth - severity: "critical", - }, - { - description: "Input validation added", - claimed: true, - actual: true, - severity: "critical", - }, - { - description: "Unit tests written", - claimed: true, - actual: false, // Agent skipped tests - severity: "critical", - }, - { - description: "Integration tests pass", - claimed: true, - actual: false, // No tests to run - severity: "critical", - }, - { - description: "Documentation updated", - claimed: true, - actual: false, // Agent skipped docs - severity: "warning", - }, - ], - }, - { - name: "Fix pagination bug", - claimedComplete: true, - checks: [ - { - description: "Bug reproduction case confirmed", - claimed: true, - actual: true, - severity: "critical", - }, - { - description: "Root cause identified", - claimed: true, - actual: true, - severity: "critical", - }, - { - description: "Fix applied", - claimed: true, - actual: true, - severity: "critical", - }, - { - description: "Edge cases tested", - claimed: true, - actual: false, // Only happy path tested - severity: "warning", - }, - { - description: "Regression tests added", - claimed: true, - actual: false, - severity: "critical", - }, - ], - }, - { - name: "Refactor auth module", - claimedComplete: true, - checks: [ - { - description: "Old code removed", - claimed: true, - actual: false, // Old code still present - severity: "warning", - }, - { - description: "All existing tests still pass", - claimed: true, - actual: false, // Two tests broke - severity: "critical", - }, - { - description: "New auth flow works", - claimed: true, - actual: true, - severity: "critical", - }, - { - description: "Migration guide written", - claimed: true, - actual: false, - severity: "warning", - }, - ], - }, -]; - -// --------------------------------------------------------------------------- -// Verification -// --------------------------------------------------------------------------- - -interface TaskVerification { - taskName: string; - claimedComplete: boolean; - actuallyComplete: boolean; - totalChecks: number; - claimedPassing: number; - actualPassing: number; - gaps: { description: string; severity: string }[]; -} - -function verifyTask(task: Task): TaskVerification { - const claimedPassing = task.checks.filter((c) => c.claimed).length; - const actualPassing = task.checks.filter((c) => c.actual).length; - const gaps = task.checks.filter((c) => c.claimed && !c.actual); - - return { - taskName: task.name, - claimedComplete: task.claimedComplete, - actuallyComplete: gaps.length === 0, - totalChecks: task.checks.length, - claimedPassing, - actualPassing, - gaps: gaps.map((g) => ({ description: g.description, severity: g.severity })), - }; -} - -// --------------------------------------------------------------------------- -// Reporting -// --------------------------------------------------------------------------- - -function pad(s: string, len: number): string { - return s.length >= len ? s : s + " ".repeat(len - s.length); -} - -function run(): void { - console.log("\n" + "=".repeat(90)); - console.log(" VICTORY DETECTOR -- Claimed vs Actual Verification"); - console.log("=".repeat(90)); - - const verifications = tasks.map(verifyTask); - - for (const v of verifications) { - console.log("\n Task: " + v.taskName); - console.log(" " + "-".repeat(70)); - console.log(` Agent claimed: ${v.claimedComplete ? "COMPLETE" : "NOT COMPLETE"}`); - console.log(` Actually is: ${v.actuallyComplete ? "COMPLETE" : "INCOMPLETE"}`); - - if (!v.actuallyComplete) { - console.log(` MISMATCH: Agent declared victory but ${v.gaps.length} check(s) failed!`); - } - - // Detailed checks for this task - const task = tasks.find((t) => t.name === v.taskName)!; - console.log("\n Check detail:"); - const header = ` | ${pad("Check", 40)}| ${pad("Claimed", 9)}| ${pad("Actual", 9)}| ${pad("Gap?", 6)}|`; - const sep = ` |${"-".repeat(42)}|${"-".repeat(11)}|${"-".repeat(11)}|${"-".repeat(8)}|`; - console.log(header); - console.log(sep); - - for (const check of task.checks) { - const claimedLabel = check.claimed ? "PASS" : "FAIL"; - const actualLabel = check.actual ? "PASS" : "FAIL"; - const gapLabel = check.claimed && !check.actual ? "GAP" : ""; - const marker = gapLabel ? ">>" : " "; - console.log(`${marker}| ${pad(check.description, 40)}| ${pad(claimedLabel, 9)}| ${pad(actualLabel, 9)}| ${pad(gapLabel, 6)}|`); - } - } - - // Overall summary - console.log("\n" + "=".repeat(90)); - console.log(" OVERALL SUMMARY"); - console.log("=".repeat(90) + "\n"); - - const sHeader = `| ${pad("Task", 28)}| ${pad("Claimed", 10)}| ${pad("Actual", 10)}| ${pad("Gaps", 6)}| ${pad("Accuracy", 10)}|`; - const sSep = `|${"-".repeat(30)}|${"-".repeat(12)}|${"-".repeat(12)}|${"-".repeat(8)}|${"-".repeat(12)}|`; - console.log(sHeader); - console.log(sSep); - - for (const v of verifications) { - const accuracy = Math.round((v.actualPassing / v.totalChecks) * 100); - const cLabel = v.claimedComplete ? "Done" : "Pending"; - const aLabel = v.actuallyComplete ? "Done" : "Incomplete"; - console.log(`| ${pad(v.taskName, 28)}| ${pad(cLabel, 10)}| ${pad(aLabel, 10)}| ${pad(String(v.gaps.length), 6)}| ${pad(accuracy + "%", 10)}|`); - } - - const totalGaps = verifications.reduce((s, v) => s + v.gaps.length, 0); - const falseVictories = verifications.filter((v) => v.claimedComplete && !v.actuallyComplete).length; - - console.log("\n False victories: " + falseVictories + " of " + verifications.length + " tasks"); - console.log(" Total undetected gaps: " + totalGaps); - console.log("\n The agent declared 'done' on every task, but verification reveals " + totalGaps + " unmet criteria."); - console.log(" Without explicit verification, premature victory declarations go unchecked.\n"); -} - -run(); diff --git a/courses/learn-harness-engineering/docs/vi/lectures/lecture-09-why-agents-declare-victory-too-early/index.md b/courses/learn-harness-engineering/docs/vi/lectures/lecture-09-why-agents-declare-victory-too-early/index.md deleted file mode 100644 index a92a6a8a..00000000 --- a/courses/learn-harness-engineering/docs/vi/lectures/lecture-09-why-agents-declare-victory-too-early/index.md +++ /dev/null @@ -1,145 +0,0 @@ -[English Version →](../../../en/lectures/lecture-09-why-agents-declare-victory-too-early/) | [中文版本 →](../../../zh/lectures/lecture-09-why-agents-declare-victory-too-early/) - -> Mã nguồn ví dụ cho bài giảng này: [code/](https://github.com/walkinglabs/learn-harness-engineering/blob/main/docs/vi/lectures/lecture-09-why-agents-declare-victory-too-early/code/) -> Thực hành: [Dự án 05. Để agent tự xác minh công việc của mình](./../../projects/project-05-grounded-qa-verification/index.md) - -# Bài 9. Ngăn chặn Agent Tuyên bố Thành công Quá sớm - -Bạn yêu cầu một agent thực hiện tính năng "đặt lại mật khẩu". Nó sửa đổi cấu trúc cơ sở dữ liệu, viết API endpoint, thêm mẫu email, chạy unit test (tất cả đều qua), và sau đó tự tin nói với bạn rằng "đã xong." Nhưng khi bạn thực sự thử chạy nó—liên kết đặt lại mật khẩu không thể gửi được (thiếu cấu hình dịch vụ email), quá trình di chuyển cơ sở dữ liệu bị lỗi giữa chừng (không nhất quán cấu trúc), và toàn bộ quy trình chưa được thực thi thực tế dù chỉ một lần. - -Cảm giác này không có gì lạ—giống như việc điền kín toàn bộ bài thi, tự tin nộp bài đầu tiên, chỉ để trượt khi có điểm. Chỉ vì bài thi được viết kín không có nghĩa là các câu trả lời đều đúng. - -Đây không phải là một sự cố cá biệt. Bài báo ICML kinh điển năm 2017 của Guo và cộng sự đã chứng minh: **các mạng nơ-ron hiện đại thường quá tự tin một cách có hệ thống**—độ tự tin mà các mô hình báo cáo cao hơn đáng kể so với độ chính xác thực tế của chúng. Điều này cũng đúng với các AI coding agent: chúng "cảm thấy" chúng đã xong, nhưng thực tế, chúng còn lâu mới xong. Harness của bạn phải thay thế "cảm giác" của agent bằng sự xác minh dựa trên thực thi bên ngoài. - -## Con dốc trơn trượt - -Các tuyên bố hoàn thành sớm hầu như luôn theo cùng một khuôn mẫu: mã trông có vẻ ổn—cú pháp đúng, logic có vẻ hợp lý và phân tích tĩnh không cho thấy lỗi rõ ràng. Nhưng harness không bắt buộc xác minh thực thi toàn diện, do đó agent bỏ qua việc thực sự chạy mã hoặc chỉ chạy các bài kiểm thử một phần. Nó chạy unit test nhưng bỏ qua integration test; nó chạy test nhưng không kiểm tra độ phủ (coverage). Cuối cùng, "mã trông có vẻ ổn" được coi là bằng chứng cho "tính năng đã hoàn thành." Và bài thi được nộp. - -Thông tin bị mất đi ở mỗi bước. Từ thông số kỹ thuật tác vụ đến triển khai mã rồi đến hành vi runtime, mọi sự chuyển đổi đều có thể đưa vào sự sai lệch, và mỗi bước xác minh bị bỏ qua đều làm trầm trọng thêm sự bất đối xứng thông tin. - -## Kiểm tra Kết thúc Ba Lớp - -```mermaid -flowchart LR - Claim["Agent nói: đã xong"] --> L1["Chạy lần đầu
    lint / typecheck"] - L1 --> L2["Sau đó chạy
    test và kiểm tra khởi động"] - L2 --> L3["Cuối cùng chạy
    toàn bộ quy trình người dùng"] - L3 --> Done["Vượt qua cả ba mới là xong"] -``` - -```mermaid -flowchart LR - A["Mã đã được viết
    unit test xanh"] --> B["Nhưng ứng dụng chưa thực sự khởi động
    quy trình đầy đủ chưa từng chạy"] - B --> C["Cấu hình, DB, lỗi dịch vụ ngoài
    tất cả vẫn bị ẩn"] - C --> D["Vì vậy agent tuyên bố thành công quá sớm"] -``` - -## Các Khái niệm Cốt lõi - -- **Tuyên bố Hoàn thành Sớm**: Agent khẳng định tác vụ đã hoàn thành, nhưng vẫn tồn tại các thông số kỹ thuật chưa được đáp ứng. Vấn đề cốt lõi: agent đánh giá dựa trên sự tự tin cục bộ ở cấp độ mã nguồn, trong khi tính đúng đắn ở cấp độ hệ thống đòi hỏi xác minh tổng thể. -- **Sai lệch Hiệu chuẩn Độ Tự tin**: Khoảng cách có hệ thống giữa độ tự tin hoàn thành tự báo cáo của agent và chất lượng hoàn thành thực tế. Đối với các tác vụ đa tệp phức tạp, độ lệch này rất dương—agent luôn tự tin hơn so với khả năng thực hiện thực tế. Giống như một học sinh luôn đánh giá quá cao điểm số của mình sau kỳ thi. -- **Tiêu chí Kết thúc**: Một tập hợp các điều kiện đánh giá rõ ràng, có thể thực thi được xác định trong harness. Agent phải thỏa mãn tất cả các điều kiện trước khi tuyên bố hoàn thành. "Đã xong" chuyển từ một đánh giá chủ quan sang một xác định khách quan. -- **Cổng Kép Xác minh-Xác nhận (Verification-Validation)**: Lớp xác minh đầu tiên kiểm tra "mã có triển khai chính xác hành vi được chỉ định không"; lớp xác nhận thứ hai kiểm tra "hành vi cấp hệ thống có đáp ứng các yêu cầu end-to-end không". Cả hai đều phải qua để được coi là hoàn thành. -- **Tín hiệu Phản hồi Runtime**: Nhật ký (logs), trạng thái tiến trình và kiểm tra tình trạng (health checks) từ việc thực thi chương trình. Đây là cơ sở khách quan để harness đánh giá chất lượng hoàn thành. -- **Ràng buộc Ưu tiên Hoàn thành**: Đầu tiên xác minh tính đúng đắn của chức năng, sau đó xử lý hiệu suất, và cuối cùng giải quyết phong cách. Việc tái cấu trúc (refactoring) bị cấm cho đến khi chức năng cốt lõi được xác minh. - -## Vượt qua Unit Test ≠ Tác vụ Hoàn thành - -Đây là cạm bẫy phổ biến nhất và cũng nguy hiểm nhất. Agent đã viết mã, chạy unit test, tất cả đều xanh và nói "đã xong." Nhưng triết lý thiết kế của unit test—cô lập đơn vị được kiểm tra và giả lập (mocking) các phụ thuộc—chính xác là điều làm cho chúng không có khả năng phát hiện các vấn đề xuyên thành phần: - -**Không khớp Giao diện (Interface Mismatch)**: Đường dẫn tệp được chuyển bởi quá trình render cho preload script là đường dẫn tương đối, nhưng preload script mong đợi đường dẫn tuyệt đối. Cả hai bài kiểm tra đơn vị tương ứng của chúng đều sử dụng mock và đã vượt qua. Vấn đề chỉ được phát hiện trong quá trình kiểm tra end-to-end. Giống như mỗi nhạc công trong một ban nhạc thực hành hoàn hảo khi chơi một mình, nhưng lại nhận ra họ đang chơi ở các tông khác nhau khi chơi cùng nhau. - -**Lỗi Truyền Trạng thái (State Propagation Errors)**: Một quá trình migration cơ sở dữ liệu thay đổi cấu trúc bảng, nhưng lớp bộ nhớ cache ORM vẫn giữ các mục cache cho cấu trúc cũ. Unit test cung cấp một môi trường mock mới mỗi lần, điều này sẽ không để lộ sự không nhất quán trạng thái xuyên lớp này. - -**Phụ thuộc Môi trường (Environment Dependency)**: Mã hoạt động chính xác trong môi trường test (nơi mọi thứ được mock) nhưng thất bại trong môi trường thực do sự khác biệt về cấu hình, độ trễ mạng hoặc dịch vụ không khả dụng. Giống như hát hoàn hảo trong phòng tập, nhưng gặp sự cố thiết bị âm thanh trên sân khấu. - -### "Nhân tiện Tái cấu trúc luôn" là Thuốc độc đối với việc Đánh giá Hoàn thành - -Claude Code có một kiểu hành vi phổ biến: nó bắt đầu tái cấu trúc mã, tối ưu hóa hiệu suất và cải thiện phong cách trước khi chức năng cốt lõi vượt qua xác minh. Câu nói của Knuth, "Tối ưu hóa sớm là nguồn gốc của mọi tội lỗi," có một ý nghĩa mới trong kịch bản agent—việc tái cấu trúc làm thay đổi ranh giới giữa mã đã được xác minh và chưa được xác minh, có khả năng phá vỡ các luồng mã trước đó đã ngầm định là đúng. Giống như việc bạn chép lại các câu trả lời trắc nghiệm của mình để có định dạng đẹp hơn trước khi bạn hoàn thành các câu hỏi tự luận toán—không chỉ lãng phí thời gian mà bạn còn có thể chép sai. - -### Sự Sai lệch có Hệ thống trong Tự đánh giá - -Anthropic đã phát hiện ra một mô hình lỗi sâu sắc hơn trong nghiên cứu năm 2026 của họ: **khi một agent được yêu cầu đánh giá công việc của chính nó, nó sẽ đưa ra các đánh giá quá tích cực một cách có hệ thống—ngay cả khi một người quan sát con người sẽ coi chất lượng rõ ràng là dưới tiêu chuẩn.** Điều này giống như yêu cầu một học sinh tự chấm điểm bài thi của mình—họ sẽ luôn đặc biệt khoan dung với câu trả lời của chính họ. - -Vấn đề này đặc biệt nghiêm trọng trong các tác vụ mang tính chủ quan (chẳng hạn như thẩm mỹ thiết kế)—việc liệu một "bố cục có tinh tế hay không" là một cuộc gọi phán đoán, và agent có xu hướng đáng tin cậy về phía tích cực. Ngay cả đối với các tác vụ có thể xác minh kết quả, hiệu suất của agent cũng có thể bị cản trở bởi khả năng phán đoán kém. - -Giải pháp không phải là làm cho agent "khách quan hơn"—cùng một mô hình tạo ra và đánh giá vốn có xu hướng hào phóng với chính nó. **Giải pháp là tách "người làm" khỏi "người kiểm tra".** Giống như một học sinh không nên tự chấm bài thi của mình—bạn cần một người chấm điểm độc lập. - -Một agent đánh giá độc lập, được tinh chỉnh đặc biệt để "kén chọn", hiệu quả hơn nhiều so với việc để agent tạo tự đánh giá. Dữ liệu thực nghiệm từ Anthropic: - -| Kiến trúc | Thời gian chạy | Chi phí | Chức năng Cốt lõi Hoạt động? | -|--------------|---------|------|------------------------| -| Agent Đơn lẻ (chạy trần) | 20 phút | $9 | Không (các thực thể trò chơi không phản hồi với đầu vào) | -| Ba Agent (planner + generator + evaluator) | 6 giờ | $200 | Có (trò chơi có thể chơi hoàn toàn) | - -Đây là cùng một mô hình (Opus 4.5) với cùng một prompt ("xây dựng một trình chỉnh sửa trò chơi retro 2D"). Sự khác biệt duy nhất là harness—từ "chạy trần" đến "planner mở rộng yêu cầu → generator thực hiện từng tính năng → evaluator thực hiện kiểm tra nhấp chuột thực tế bằng Playwright". - -> Nguồn: [Anthropic: Harness design for long-running application development](https://www.anthropic.com/engineering/harness-design-long-running-apps) - -## Cách Ngăn chặn Việc Nộp bài Sớm - -### 1. Ngoại hóa việc Đánh giá Kết thúc - -Việc đánh giá hoàn thành không nên do chính agent thực hiện. Harness phải thực hiện xác nhận kết thúc một cách độc lập, sử dụng các tín hiệu runtime làm đầu vào chứ không phải sự tự tin của agent. Viết rõ điều này trong `CLAUDE.md`: - -``` -## Định nghĩa Hoàn thành (Definition of Done) -- Tính năng hoàn thành = xác minh end-to-end đã qua, không phải "mã đã được viết" -- Các cấp độ xác minh bắt buộc: - 1. Unit tests pass - 2. Integration tests pass - 3. End-to-end flow verification passes -- Không chuyển sang cấp độ 2 nếu cấp độ 1 thất bại -- Không chuyển sang cấp độ 3 nếu cấp độ 2 thất bại -``` - -### 2. Xây dựng Xác nhận Kết thúc Ba Lớp - -- **Lớp 1: Cú pháp và Phân tích Tĩnh**. Chi phí thấp nhất, ít thông tin nhất, nhưng phải vượt qua. Đây là mức kiểm tra tối thiểu—bạn phải đánh vần đúng các từ trước khi chúng ta xem xét bất cứ thứ gì khác. -- **Lớp 2: Xác minh Hành vi Runtime**. Thực thi test, kiểm tra khởi động ứng dụng, xác nhận đường dẫn quan trọng. Đây là bằng chứng cốt lõi của sự hoàn thành. Chỉ viết ra thôi là chưa đủ; nó phải chạy được. -- **Lớp 3: Xác nhận Cấp Hệ thống**. Kiểm thử end-to-end, xác nhận tích hợp, mô phỏng kịch bản người dùng. Tuyến phòng thủ cuối cùng chống lại các tuyên bố sớm. Nó không chỉ phải chạy; nó phải chạy chính xác. - -### 3. Thiết kế "Bút đỏ Đánh dấu" Tốt cho Agents - -OpenAI đã giới thiệu một mẫu đặc biệt hiệu quả trong quá trình thực hành Codex của họ: **các thông báo lỗi cho agent phải bao gồm các hướng dẫn sửa chữa**. Đừng chỉ vẽ một dấu chéo đỏ lớn như một người chấm điểm lười biếng; hãy giống như một giáo viên giỏi và viết "đây là cách bạn nên thay đổi điều này" ở lề. Đừng sử dụng `"Test failed"`, mà hãy sử dụng `"Test failed: POST /api/reset-password returned 500. Check that the email service config exists in environment variables. The template file should be at templates/reset-email.html."` Phản hồi cụ thể, có thể hành động này cho phép agent tự sửa lỗi mà không cần sự can thiệp của con người. - -### 4. Nắm bắt các Tín hiệu Runtime - -Các tín hiệu runtime hiệu quả bao gồm: -- Ứng dụng có khởi động thành công và đạt đến trạng thái sẵn sàng không? -- Các đường dẫn tính năng quan trọng có thực thi thành công trong runtime không? -- Các thao tác ghi cơ sở dữ liệu, hoạt động tệp và các hiệu ứng phụ khác có chính xác không? -- Các tài nguyên tạm thời có được dọn dẹp không? - -## Trường hợp Thực tế - -**Tác vụ**: Triển khai chức năng đặt lại mật khẩu người dùng. Bao gồm hoạt động cơ sở dữ liệu, gửi email và sửa đổi API endpoint. - -**Đường dẫn nộp bài sớm**: Agent sửa đổi cấu trúc cơ sở dữ liệu, viết API endpoint, thêm mẫu email, chạy unit test (qua) và tuyên bố hoàn thành. Bài kiểm tra đã được điền đầy đủ. - -**Các điểm trừ thực tế**: (1) Luồng end-to-end chưa được kiểm tra—việc gửi và xác minh thực tế liên kết đặt lại chưa bao giờ được xác nhận. (2) Quá trình migration cơ sở dữ liệu thất bại sau khi thực thi một phần, gây ra sự không nhất quán cấu trúc. (3) Cấu hình dịch vụ email bị thiếu trong môi trường đích. - -**Sự can thiệp của Harness**: Xác nhận kết thúc bị bắt buộc—(1) Khởi động toàn bộ ứng dụng để xác minh khả năng truy cập endpoint đặt lại; (2) Thực thi toàn bộ quy trình đặt lại; (3) Xác minh tính nhất quán của trạng thái cơ sở dữ liệu. Tất cả các khiếm khuyết đã được tìm thấy trong phiên, tiết kiệm 5-10 lần chi phí của các bản sửa lỗi sau này. Người chấm điểm độc lập đã tìm ra các vấn đề thực sự. - -## Những Điểm chính cần Nhớ - -- **Các Agent thường quá tự tin một cách có hệ thống**—sai lệch hiệu chuẩn độ tự tin là một thực tế khách quan. Điền kín bài kiểm tra không có nghĩa là bạn đã làm đúng. -- **Đánh giá hoàn thành phải được ngoại hóa**—harness tự xác minh một cách độc lập; đừng tin vào "cảm giác" của agent. Học sinh không thể tự chấm bài thi của mình. -- **Cả ba lớp xác nhận đều rất cần thiết**—qua được cú pháp, qua được hành vi, qua được hệ thống, tiến triển từng lớp một. -- **Các thông báo lỗi nên giống như việc dùng bút đỏ đánh dấu của một giáo viên giỏi**—bao gồm các bước sửa chữa cụ thể để agent có thể tự sửa đổi. -- **Không tái cấu trúc (refactoring) cho đến khi chức năng cốt lõi được xác minh**—ràng buộc ưu tiên hoàn thành là chìa khóa để ngăn chặn tối ưu hóa sớm. - -## Đọc thêm - -- [On Calibration of Modern Neural Networks - Guo et al.](https://arxiv.org/abs/1706.04599) — Chứng minh các mạng sâu hiện đại thường quá tự tin một cách có hệ thống -- [Building Effective Agents - Anthropic](https://www.anthropic.com/research/building-effective-agents) — Vai trò quan trọng của bằng chứng runtime trong việc đánh giá hoàn thành -- [Harness Engineering - OpenAI](https://openai.com/index/harness-engineering/) — Tuyên bố hoàn thành sớm là một trong những chế độ lỗi chính của các agent -- [The Art of Software Testing - Myers](https://www.goodreads.com/book/show/137543.The_Art_of_Software_Testing) — Tài liệu tham khảo kinh điển về phân cấp và hiệu quả của phương pháp kiểm thử - -## Bài tập - -1. **Thiết kế Hàm Xác nhận Kết thúc**: Thiết kế một quy trình xác nhận kết thúc hoàn chỉnh cho một tác vụ liên quan đến việc di chuyển cơ sở dữ liệu và sửa đổi API. Liệt kê các tín hiệu runtime cần thiết và các tiêu chí đạt/trượt (pass/fail) cho từng tín hiệu. Chạy nó trên một tác vụ thực tế và ghi lại những vấn đề tiềm ẩn mà nó tìm thấy. - -2. **Đo lường Sai lệch Hiệu chuẩn**: Chọn 10 loại tác vụ lập trình khác nhau và ghi lại độ tự tin hoàn thành tự báo cáo của agent so với chất lượng hoàn thành thực tế. Tính toán giá trị sai lệch và phân tích mối quan hệ của nó với độ phức tạp của tác vụ. - -3. **Thử nghiệm Phòng thủ Đa Lớp**: Chạy ba cấu hình trên cùng một bộ tác vụ—(a) chỉ phân tích tĩnh, (b) thêm unit testing, (c) xác nhận đầy đủ ba lớp. So sánh tỷ lệ các tuyên bố hoàn thành sớm và số lượng các khiếm khuyết không bị phát hiện. \ No newline at end of file diff --git a/courses/learn-harness-engineering/docs/vi/lectures/lecture-10-why-end-to-end-testing-changes-results/code/architecture-rules.md b/courses/learn-harness-engineering/docs/vi/lectures/lecture-10-why-end-to-end-testing-changes-results/code/architecture-rules.md deleted file mode 100644 index 848a0adb..00000000 --- a/courses/learn-harness-engineering/docs/vi/lectures/lecture-10-why-end-to-end-testing-changes-results/code/architecture-rules.md +++ /dev/null @@ -1,6 +0,0 @@ -# Quy tắc Kiến trúc Electron - -- Mã renderer không được truy cập trực tiếp hệ thống tệp. -- Preload là cầu nối duy nhất giữa renderer và Electron main. -- Logic truy xuất và indexing nằm trong các module service, không phải trong UI component. -- Logging nên có cấu trúc và được phát ra từ các ranh giới service. diff --git a/courses/learn-harness-engineering/docs/vi/lectures/lecture-10-why-end-to-end-testing-changes-results/code/e2e-runner.ts b/courses/learn-harness-engineering/docs/vi/lectures/lecture-10-why-end-to-end-testing-changes-results/code/e2e-runner.ts deleted file mode 100644 index 24284165..00000000 --- a/courses/learn-harness-engineering/docs/vi/lectures/lecture-10-why-end-to-end-testing-changes-results/code/e2e-runner.ts +++ /dev/null @@ -1,253 +0,0 @@ -/** - * e2e-runner.ts - * - * A minimal E2E test harness. Defines test cases as user action sequences - * (import doc -> index -> ask question -> verify citation). Simulates - * running them and shows the difference between "unit tests pass" and - * "full pipeline works". - * - * Run: npx tsx docs/lectures/lecture-10-why-end-to-end-testing-changes-results/code/e2e-runner.ts - */ - -// --------------------------------------------------------------------------- -// Types -// --------------------------------------------------------------------------- - -interface PipelineStep { - name: string; - unitTestPasses: boolean; - // Simulated actual behavior in the pipeline - actualBehavior: "works" | "fails" | "partial"; - failureReason?: string; -} - -interface TestCase { - name: string; - steps: PipelineStep[]; -} - -interface TestResult { - testCase: string; - unitTestsPassed: number; - unitTestsTotal: number; - unitTestResult: "PASS" | "FAIL"; - e2eResult: "PASS" | "FAIL"; - e2eFailureStep?: string; - e2eFailureReason?: string; -} - -// --------------------------------------------------------------------------- -// Test cases -- realistic scenarios where unit tests pass but E2E fails -// --------------------------------------------------------------------------- - -const testCases: TestCase[] = [ - { - name: "Import document and ask question", - steps: [ - { - name: "Parse uploaded document", - unitTestPasses: true, - actualBehavior: "works", - }, - { - name: "Store document chunks", - unitTestPasses: true, - actualBehavior: "works", - }, - { - name: "Index chunks for retrieval", - unitTestPasses: true, - actualBehavior: "partial", // Indexes but with wrong embedding dimensions - failureReason: "Embedding dimension mismatch between indexer and retriever", - }, - { - name: "Retrieve relevant chunks", - unitTestPasses: true, // Unit test uses mock data with correct dimensions - actualBehavior: "fails", - failureReason: "Empty results due to dimension mismatch from previous step", - }, - { - name: "Generate answer with citations", - unitTestPasses: true, // Unit test provides pre-retrieved chunks - actualBehavior: "fails", - failureReason: "No chunks retrieved, so answer has no citations", - }, - ], - }, - { - name: "Delete document and verify removal", - steps: [ - { - name: "Find document by ID", - unitTestPasses: true, - actualBehavior: "works", - }, - { - name: "Delete document record", - unitTestPasses: true, - actualBehavior: "works", - }, - { - name: "Remove indexed chunks", - unitTestPasses: true, - actualBehavior: "fails", // Orphaned chunks remain in the index - failureReason: "Index cleanup query timed out, chunks remain orphaned", - }, - { - name: "Verify document not in search results", - unitTestPasses: true, // Unit test mocks the search - actualBehavior: "fails", - failureReason: "Orphaned chunks from previous step still appear in results", - }, - ], - }, - { - name: "Multi-user concurrent access", - steps: [ - { - name: "User A imports document", - unitTestPasses: true, - actualBehavior: "works", - }, - { - name: "User B imports document", - unitTestPasses: true, - actualBehavior: "works", - }, - { - name: "User A queries their document", - unitTestPasses: true, - actualBehavior: "partial", // Cross-contamination of results - failureReason: "No user-scoping on retrieval, returns chunks from User B's doc", - }, - { - name: "Verify only User A's results returned", - unitTestPasses: true, - actualBehavior: "fails", - failureReason: "Results include documents from other users", - }, - ], - }, -]; - -// --------------------------------------------------------------------------- -// Run tests -// --------------------------------------------------------------------------- - -function runUnitTests(tc: TestCase): { passed: number; total: number } { - const passed = tc.steps.filter((s) => s.unitTestPasses).length; - return { passed, total: tc.steps.length }; -} - -function runE2ETest(tc: TestCase): { pass: boolean; failureStep?: string; failureReason?: string } { - // Pipeline: if any step actually fails, the whole E2E fails - for (const step of tc.steps) { - if (step.actualBehavior === "fails") { - return { - pass: false, - failureStep: step.name, - failureReason: step.failureReason ?? "Unknown failure", - }; - } - // "partial" means the step technically completes but creates problems downstream - // We let it continue but track it - } - // Check if any step was "partial" (which may cause downstream issues) - const partialSteps = tc.steps.filter((s) => s.actualBehavior === "partial"); - if (partialSteps.length > 0) { - // The partial steps may or may not cause overall failure - // In our simulation, partial steps always lead to failure downstream - // unless there's an explicit "fails" step that already caught it - // This case means all steps were either "works" or "partial" - return { - pass: false, - failureStep: partialSteps[partialSteps.length - 1].name, - failureReason: partialSteps[partialSteps.length - 1].failureReason ?? "Partial completion", - }; - } - - return { pass: true }; -} - -// --------------------------------------------------------------------------- -// Reporting -// --------------------------------------------------------------------------- - -function pad(s: string, len: number): string { - return s.length >= len ? s : s + " ".repeat(len - s.length); -} - -function run(): void { - console.log("\n" + "=".repeat(95)); - console.log(" E2E TEST RUNNER -- Unit Tests vs Full Pipeline"); - console.log("=".repeat(95)); - - const results: TestResult[] = testCases.map((tc) => { - const unit = runUnitTests(tc); - const e2e = runE2ETest(tc); - - return { - testCase: tc.name, - unitTestsPassed: unit.passed, - unitTestsTotal: unit.total, - unitTestResult: unit.passed === unit.total ? "PASS" : "FAIL", - e2eResult: e2e.pass ? "PASS" : "FAIL", - e2eFailureStep: e2e.failureStep, - e2eFailureReason: e2e.failureReason, - }; - }); - - // Per-test-case detail - for (let i = 0; i < testCases.length; i++) { - const tc = testCases[i]; - const r = results[i]; - - console.log("\n Test Case: " + tc.name); - console.log(" " + "-".repeat(70)); - - const stepHeader = ` | ${pad("Step", 40)}| ${pad("Unit Test", 11)}| ${pad("E2E Actual", 12)}|`; - const stepSep = ` |${"-".repeat(42)}|${"-".repeat(13)}|${"-".repeat(14)}|`; - console.log(stepHeader); - console.log(stepSep); - - for (const step of tc.steps) { - const utLabel = step.unitTestPasses ? "PASS" : "FAIL"; - let e2eLabel: string; - if (step.actualBehavior === "works") e2eLabel = "PASS"; - else if (step.actualBehavior === "partial") e2eLabel = "PARTIAL*"; - else e2eLabel = "FAIL"; - - const marker = step.actualBehavior !== "works" ? ">>" : " "; - console.log(`${marker}| ${pad(step.name, 40)}| ${pad(utLabel, 11)}| ${pad(e2eLabel, 12)}|`); - } - - if (r.e2eFailureStep) { - console.log(`\n E2E Failure at: ${r.e2eFailureStep}`); - console.log(` Reason: ${r.e2eFailureReason}`); - } - } - - // Summary comparison - console.log("\n" + "=".repeat(95)); - console.log(" COMPARISON: Unit Tests vs E2E Tests"); - console.log("=".repeat(95) + "\n"); - - const header = `| ${pad("Test Case", 35)}| ${pad("Unit Tests", 15)}| ${pad("E2E Result", 12)}| Discrepancy`; - const sep = `|${"-".repeat(37)}|${"-".repeat(17)}|${"-".repeat(14)}|${"-".repeat(30)}`; - console.log(header); - console.log(sep); - - for (const r of results) { - const utLabel = `${r.unitTestsPassed}/${r.unitTestsTotal} ${r.unitTestResult}`; - const discrepancy = r.unitTestResult === "PASS" && r.e2eResult === "FAIL"; - const discLabel = discrepancy ? "UNIT PASS BUT E2E FAIL" : "Consistent"; - - console.log(`| ${pad(r.testCase, 35)}| ${pad(utLabel, 15)}| ${pad(r.e2eResult, 12)}| ${discLabel}`); - } - - const falseConfidence = results.filter((r) => r.unitTestResult === "PASS" && r.e2eResult === "FAIL").length; - console.log("\n False confidence count: " + falseConfidence + " of " + results.length); - console.log(" Unit tests pass but E2E reveals integration failures that units cannot catch.\n"); -} - -run(); diff --git a/courses/learn-harness-engineering/docs/vi/lectures/lecture-10-why-end-to-end-testing-changes-results/code/index.md b/courses/learn-harness-engineering/docs/vi/lectures/lecture-10-why-end-to-end-testing-changes-results/code/index.md deleted file mode 100644 index d31d6e71..00000000 --- a/courses/learn-harness-engineering/docs/vi/lectures/lecture-10-why-end-to-end-testing-changes-results/code/index.md +++ /dev/null @@ -1,8 +0,0 @@ -# Mã cho Bài 10 - -Sử dụng thư mục này cho các ví dụ về: - -- ràng buộc kiến trúc -- test có cấu trúc -- bất biến về chất lượng -- thông báo lint hướng đến giải pháp diff --git a/courses/learn-harness-engineering/docs/vi/lectures/lecture-10-why-end-to-end-testing-changes-results/code/review-feedback-to-rule.md b/courses/learn-harness-engineering/docs/vi/lectures/lecture-10-why-end-to-end-testing-changes-results/code/review-feedback-to-rule.md deleted file mode 100644 index bd345a71..00000000 --- a/courses/learn-harness-engineering/docs/vi/lectures/lecture-10-why-end-to-end-testing-changes-results/code/review-feedback-to-rule.md +++ /dev/null @@ -1,10 +0,0 @@ -# Ví dụ: Biến Phản hồi Review thành Quy tắc - -Nhận xét review lặp đi lặp lại: - -> Không gọi filesystem utilities từ renderer. Sử dụng preload bridge. - -Quy tắc harness được đẩy lên: - -- thêm lint hoặc quy tắc import ngăn việc sử dụng `fs` trong mã renderer -- thêm văn bản giải pháp giải thích ranh giới preload diff --git a/courses/learn-harness-engineering/docs/vi/lectures/lecture-10-why-end-to-end-testing-changes-results/index.md b/courses/learn-harness-engineering/docs/vi/lectures/lecture-10-why-end-to-end-testing-changes-results/index.md deleted file mode 100644 index ce3aae81..00000000 --- a/courses/learn-harness-engineering/docs/vi/lectures/lecture-10-why-end-to-end-testing-changes-results/index.md +++ /dev/null @@ -1,158 +0,0 @@ -[English Version →](../../../en/lectures/lecture-10-why-end-to-end-testing-changes-results/) | [中文版本 →](../../../zh/lectures/lecture-10-why-end-to-end-testing-changes-results/) - -> Ví dụ mã nguồn cho bài giảng này: [code/](https://github.com/walkinglabs/learn-harness-engineering/blob/main/docs/vi/lectures/lecture-10-why-end-to-end-testing-changes-results/code/) -> Thực hành: [Dự án 05. Để agent xác minh công việc của chính nó](./../../projects/project-05-grounded-qa-verification/index.md) - -# Bài 10. Chỉ Testing End-to-End mới là Xác minh Thực sự - -Bạn yêu cầu agent thêm tính năng xuất tệp vào ứng dụng Electron. Nó viết component render process, preload script và logic lớp service. Unit test cho từng component vượt qua hoàn hảo. Agent nói, "Xong rồi." Khi bạn thực sự nhấp vào nút xuất — định dạng đường dẫn tệp sai, thanh tiến trình không cập nhật, và xuất tệp lớn gây rò rỉ bộ nhớ. Năm lỗi ranh giới component, và unit test không bắt được một cái nào. - -Giống như buổi tập hợp xướng — từng bè tiếng nghe hoàn hảo khi hát riêng, nhưng khi hát cùng nhau, bè soprano nhanh hơn nửa nhịp so với bè bass, và phần đệm lạc một nửa cung so với giai điệu chính. Từng phần "đúng" riêng lẻ, nhưng tổng thể lại không đồng điệu. - -Tháp kiểm thử của Google cho chúng ta biết: số lượng lớn unit test là nền tảng, nhưng nếu dừng lại ở đó, bạn sẽ bỏ lỡ có hệ thống các vấn đề tương tác component. Đối với AI coding agent, vấn đề này còn nghiêm trọng hơn — các agent có xu hướng chỉ chạy các test nhanh nhất và sau đó tuyên bố hoàn thành. **Chỉ testing end-to-end mới có thể chứng minh rằng các lỗi cấp hệ thống không tồn tại.** - -## Điểm Mù của Unit Testing - -Triết lý thiết kế của unit testing là cô lập — mock các phụ thuộc và chỉ tập trung vào đơn vị đang được test. Triết lý này làm cho unit testing nhanh và chính xác, nhưng nó cũng tạo ra các điểm mù có hệ thống. Giống như từng bè tiếng tập với tai nghe trong buổi tập hợp xướng — nghe ổn với họ, nhưng các vấn đề chỉ xuất hiện khi họ hát cùng nhau: - -**Không khớp giao diện**: Đường dẫn tệp được truyền bởi render process cho preload script là đường dẫn tương đối, nhưng preload script mong đợi đường dẫn tuyệt đối. Unit test tương ứng của chúng đều sử dụng mock và vượt qua. Vấn đề chỉ được phát hiện khi luồng end-to-end được thực thi — giống như hai bè tiếng tập độc lập và cảm thấy ổn, chỉ để nhận ra trong buổi hòa tấu rằng một bè đang hát nhịp 4/4 và cái kia nhịp 3/4. - -**Lỗi Truyền trạng thái**: Một database migration thay đổi schema bảng, nhưng lớp caching ORM vẫn giữ các cache entry cho schema cũ. Unit test cung cấp một môi trường mock hoàn toàn mới mỗi lần, điều này sẽ không làm lộ ra sự không nhất quán trạng thái xuyên lớp này. Giống như thay lời bài hát, nhưng ai đó vẫn đang hát phiên bản cũ. - -**Vấn đề Vòng đời Tài nguyên**: Việc thu thập và giải phóng file handle, database connection và network socket trải dài trên nhiều component. Unit test tạo và hủy tài nguyên độc lập cho mỗi test, không thể làm lộ ra tranh chấp hoặc rò rỉ tài nguyên. Giống như từng bè tiếng lần lượt sử dụng microphone trong buổi tập, nhưng khi tất cả lên sân khấu cùng nhau, không đủ mic cho tất cả. - -**Phụ thuộc Môi trường**: Mã hoạt động đúng trong môi trường test (nơi mọi thứ được mock) nhưng thất bại trong môi trường thực do sự khác biệt cấu hình, độ trễ mạng, hoặc dịch vụ không khả dụng. Giống như hát hoàn hảo trong phòng tập, nhưng gặp phản hồi âm thanh và can thiệp gió ở một lễ hội ngoài trời. - -## Testing End-to-End Không Chỉ Thay đổi Kết quả, Nó Thay đổi Hành vi - -Đây là điều mà nhiều người không nhận ra: khi một agent biết công việc của nó sẽ được đưa qua testing end-to-end, hành vi lập trình của nó thay đổi. - -1. **Cân nhắc Tương tác Component**: Trong khi viết mã, nó sẽ nghĩ về "cách giao diện này kết nối với upstream," thay vì chỉ tập trung vào một hàm duy nhất. Giống như biết cuối cùng bạn sẽ hát cùng nhau, bạn sẽ chú ý đến các bè khác trong khi tập. -2. **Tôn trọng Ranh giới Kiến trúc**: Trong các hệ thống có ràng buộc kiến trúc, testing end-to-end buộc agent phải tuân thủ các quy tắc ranh giới. Giống như bản nhạc được ghi chú "tăng âm lượng ở đây," bạn phải tuân theo. -3. **Xử lý Đường dẫn Lỗi**: Test end-to-end thường bao gồm các kịch bản thất bại, buộc agent phải xem xét xử lý ngoại lệ. Giống như mô phỏng "điều gì sẽ xảy ra nếu mic đột nhiên tắt" trong buổi tập, để bạn biết phải làm gì. - -## Tháp Kiểm thử và Đẩy Phản hồi Review Lên Cấp cao hơn - -```mermaid -flowchart TB - subgraph Unit["Unit test chỉ kiểm tra các phần cô lập"] - U1["Renderer tests"] - U2["Preload tests"] - U3["Service tests"] - end - - subgraph E2E["E2E chạy qua hệ thống thực"] - R["Nhấp nút renderer"] --> P["Preload bridge"] - P --> S["Lớp service"] - S --> F["File System / OS"] - F --> Result["Tệp được xuất thực tế"] - end -``` - -```mermaid -flowchart LR - Review["Phản hồi review:
    renderer không thể import fs trực tiếp"] --> Rule["Thêm kiểm tra import fs trực tiếp"] - Rule --> Message["Nói với agent trong thông báo lỗi
    để chuyển truy cập tệp sang preload"] - Message --> Harness["Thêm kiểm tra này vào harness"] - Harness --> Stronger["Sẽ thất bại ngay lập tức lần sau"] -``` - -Trong các thực hành kỹ thuật Codex, OpenAI nhấn mạnh: **các thông báo lỗi được viết cho agent phải bao gồm hướng dẫn sửa chữa.** Đừng chỉ viết `"Direct filesystem access in renderer"`; viết `"Direct filesystem access in renderer. All file operations must go through the preload bridge. Move this call to preload/file-ops.ts and invoke it via window.api."` Điều này biến các quy tắc kiến trúc thành một vòng lặp tự sửa chữa. Giống như người chỉ huy hợp xướng không chỉ nói "bạn hát sai rồi," mà còn nói "bạn nhanh hơn nửa nhịp ở đây, hãy nghe nhịp của bè alto, và vào ở nhịp 32." - -## Các Khái niệm Cốt lõi - -- **Lỗi Ranh giới Component**: Component A và B đều vượt qua unit test của chúng, nhưng tương tác của chúng tạo ra hành vi không đúng. Đây là loại vấn đề mà testing end-to-end giỏi bắt nhất — giống như các bè hợp xướng đúng riêng lẻ nhưng không đồng điệu cùng nhau. -- **Gradient Đủ kiểm thử (Testing Adequacy Gradient)**: Lỗi được bắt bởi unit test <= lỗi được bắt bởi integration test <= lỗi được bắt bởi test end-to-end. Mỗi lớp lên cao hơn tăng khả năng phát hiện. -- **Quy tắc Thực thi Ranh giới Kiến trúc**: Biến các quy tắc từ tài liệu kiến trúc (như "render process không thể truy cập trực tiếp hệ thống tệp") thành các kiểm tra tự động có thể thực thi. Từ "viết trên giấy" thành "chạy trong CI." -- **Đẩy Phản hồi Review (Review Feedback Promotion)**: Chuyển đổi các nhận xét review mã lặp đi lặp lại thành test tự động. Mỗi lần một vấn đề lặp lại được tìm thấy, thêm một quy tắc, và harness tự động trở nên mạnh hơn. Giống như người chỉ huy biến các lỗi tập phổ biến thành bài tập khởi động — lần sau khi mắc cùng lỗi, bài tập tự nó làm lộ ra mà người chỉ huy không cần nói gì. -- **Thông báo Lỗi Hướng Agent**: Thông báo lỗi không chỉ nên nêu "điều gì đã sai," mà còn nói với agent chính xác cách sửa nó. Điều này biến lỗi test thành vòng phản hồi tự sửa chữa. - -## Cách Thực hiện - -### 0. Định nghĩa Ranh giới Kiến trúc Trước, Sau đó Viết Test E2E - -Điều kiện tiên quyết để testing end-to-end là ranh giới hệ thống rõ ràng. Nếu kiến trúc là một đĩa mì spaghetti, testing end-to-end chỉ chứng minh "đĩa mì spaghetti này chạy được," nó sẽ không nói cho bạn biết ý định thiết kế ở đâu bị vi phạm. Giống như hợp xướng chưa thậm chí chia thành các bè — không có lượng tập nào sẽ làm cho nó nghe hay. - -Kinh nghiệm của OpenAI: **đối với các codebase được tạo bởi agent, ràng buộc kiến trúc phải là điều kiện tiên quyết ban đầu được thiết lập từ ngày đầu tiên, không phải điều gì đó cần xem xét khi nhóm lớn lên.** Lý do rất đơn giản — các agent sẽ sao chép các mẫu hiện có trong kho lưu trữ, ngay cả khi các mẫu đó không đồng đều hoặc tối ưu. Không có ràng buộc kiến trúc, agent sẽ giới thiệu thêm sai lệch trong mỗi phiên. - -OpenAI đã áp dụng "Kiến trúc Miền Phân lớp" — mỗi domain kinh doanh được chia thành các lớp cố định: Types → Config → Repo → Service → Runtime → UI. Các phụ thuộc chảy nghiêm ngặt theo hướng tiến, và các mối quan tâm xuyên domain đi vào qua các giao diện Provider rõ ràng. Bất kỳ phụ thuộc nào khác đều bị cấm và được thực thi cơ học qua linting tùy chỉnh. - -Nguyên tắc quan trọng: **Thực thi các bất biến, không vi quản lý triển khai.** Ví dụ, yêu cầu "dữ liệu được parse tại ranh giới," nhưng không chỉ định nên sử dụng thư viện nào. Thông báo lỗi phải bao gồm hướng dẫn sửa chữa — không chỉ nói "vi phạm," mà nói với agent chính xác cách thay đổi. - -> Nguồn: [OpenAI: Harness engineering: leveraging Codex in an agent-first world](https://openai.com/index/harness-engineering/) - -### 1. Harness Phải Bao gồm Một Lớp End-to-End - -Làm rõ ràng trong luồng xác minh của bạn: đối với các tác vụ liên quan đến thay đổi xuyên component, vượt qua test end-to-end là điều kiện tiên quyết cho hoàn thành: - -``` -## Thứ bậc Xác minh -- Mức 1: Unit test (Bắt buộc vượt qua) -- Mức 2: Integration test (Bắt buộc vượt qua) -- Mức 3: Test End-to-end (Bắt buộc vượt qua khi có thay đổi xuyên component) -- Bỏ qua bất kỳ mức nào bắt buộc = Chưa Hoàn thành -``` - -### 2. Biến Quy tắc Kiến trúc Thành Kiểm tra Có thể Thực thi - -Mỗi ràng buộc kiến trúc nên có test hoặc quy tắc lint tương ứng: - -```bash -# Kiểm tra xem render process có gọi trực tiếp Node.js API không -grep -r "require('fs')" src/renderer/ && exit 1 || echo "OK: no direct fs access in renderer" -``` - -### 3. Thiết kế Thông báo Lỗi Hướng Agent - -Thông báo lỗi nên chứa ba yếu tố: điều gì đã sai, tại sao, và cách sửa: - -``` -LỖI: Tìm thấy import trực tiếp 'fs' trong src/renderer/App.tsx:12 -TẠI SAO: Render process không có quyền truy cập Node.js API vì lý do bảo mật -CÁCH SỬA: Di chuyển các hoạt động tệp sang src/preload/file-ops.ts và gọi qua window.api.readFile() -``` - -### 4. Thiết lập Quy trình Đẩy Phản hồi Review - -Mỗi khi tìm thấy một loại lỗi agent mới trong code review, biến nó thành kiểm tra tự động. Một tháng sau, harness của bạn sẽ mạnh hơn đáng kể so với lúc đầu tháng. Giống như ghi chú buổi tập cho hợp xướng — ghi lại các vấn đề được tìm thấy trong mỗi buổi tập để có thể kiểm tra trước buổi tiếp theo. Theo thời gian, các lỗi phổ biến giảm đi, và âm nhạc trở nên hài hòa hơn. - -## Trường hợp Thực tế - -**Tác vụ**: Triển khai tính năng xuất tệp trong ứng dụng Electron. Liên quan đến UI render process, proxy hệ thống tệp preload script, và chuyển đổi dữ liệu lớp service. - -**Hát từng bè riêng (Unit test vượt qua)**: Test component Render (vượt qua, file operations được mock), test preload script (vượt qua, filesystem được mock), test lớp service (vượt qua, data source được mock). Agent tuyên bố hoàn thành. - -**Hát cùng nhau (Lỗi được Test End-to-End tiết lộ)**: - -| Lỗi | Mô tả | Unit Test | E2E | -|-----|-------|-----------|-----| -| Không khớp giao diện | Định dạng đường dẫn tệp không nhất quán | Bỏ qua | Bắt được | -| Truyền trạng thái | Tiến trình xuất không được gửi trở lại UI qua IPC | Bỏ qua | Bắt được | -| Rò rỉ tài nguyên | File handle xuất tệp lớn không được giải phóng | Bỏ qua | Bắt được | -| Vấn đề quyền | Quyền khác nhau trong môi trường đóng gói | Bỏ qua | Bắt được | -| Truyền lỗi | Ngoại lệ lớp service không đến được lớp UI | Bỏ qua | Bắt được | - -Tất cả 5 lỗi đều được test end-to-end bắt được, trong khi unit test không bắt được cái nào. Chi phí là tăng thời gian test từ 2 giây lên 15 giây — hoàn toàn chấp nhận được trong quy trình agent. Dù từng bè hát tốt đến đâu, không thể so với một buổi tập hòa tấu đầy đủ. - -## Những Điểm chính cần Nhớ - -- **Unit test mù có hệ thống với các lỗi ranh giới component** — thiết kế cô lập của chúng chính xác là điều ngăn chúng phát hiện các vấn đề tương tác. Mọi người đều hát đúng không có nghĩa là hợp xướng không lạc nhịp. -- **Testing end-to-end không chỉ phát hiện lỗi, nó thay đổi hành vi lập trình của agent** — làm cho nó tập trung hơn vào tích hợp và ranh giới. -- **Các quy tắc kiến trúc phải có thể thực thi** — không viết trong tài liệu để được đọc, mà được kiểm tra tự động trên mỗi commit. -- **Thông báo lỗi phải được thiết kế cho agent** — bao gồm các bước cụ thể về "cách sửa nó" để tạo thành vòng lặp tự sửa chữa. -- **Đẩy phản hồi review làm cho harness tự động mạnh hơn** — mỗi danh mục lỗi được bắt trở thành một tuyến phòng thủ vĩnh viễn. - -## Đọc thêm - -- [How Google Tests Software - Whittaker et al.](https://www.goodreads.com/book/show/13563030-how-google-tests-software) — Nguồn kinh điển của mô hình Tháp Kiểm thử -- [Harness Engineering - OpenAI](https://openai.com/index/harness-engineering/) — Thực hành kỹ thuật để thực thi tự động các ràng buộc kiến trúc -- [Chaos Engineering - Netflix (Basiri et al.)](https://ieeexplore.ieee.org/document/7466237) — Chủ động đưa vào lỗi để xác minh độ bền hệ thống -- [QuickCheck - Claessen & Hughes](https://www.cs.tufts.edu/~nr/cs257/archive/john-hughes/quick.pdf) — Phương pháp property testing, nằm giữa example testing và kiểm chứng hình thức - -## Bài tập - -1. **Phát hiện Lỗi Xuyên Component**: Chọn một tác vụ sửa đổi liên quan đến ít nhất ba component. Đầu tiên, chỉ chạy unit test và ghi lại kết quả, sau đó chạy test end-to-end. Phân tích mỗi lỗi được phát hiện thêm thuộc loại vấn đề tương tác xuyên lớp nào. - -2. **Tự động hóa Quy tắc Kiến trúc**: Chọn một ràng buộc kiến trúc từ dự án của bạn và biến nó thành kiểm tra có thể thực thi (với thông báo lỗi hướng agent). Tích hợp nó vào harness và xác minh hiệu quả của nó với một tác vụ baseline. - -3. **Đẩy Phản hồi Review**: Tìm một loại nhận xét lặp lại từ lịch sử code review của bạn và chuyển đổi nó thành kiểm tra tự động bằng quy trình năm bước. So sánh tần suất của vấn đề trước và sau khi đẩy. \ No newline at end of file diff --git a/courses/learn-harness-engineering/docs/vi/lectures/lecture-11-why-observability-belongs-inside-the-harness/code/evaluator-rubric.md b/courses/learn-harness-engineering/docs/vi/lectures/lecture-11-why-observability-belongs-inside-the-harness/code/evaluator-rubric.md deleted file mode 100644 index 0b3aa95b..00000000 --- a/courses/learn-harness-engineering/docs/vi/lectures/lecture-11-why-observability-belongs-inside-the-harness/code/evaluator-rubric.md +++ /dev/null @@ -1,8 +0,0 @@ -# Ví dụ Rubric Evaluator - -Sử dụng tính điểm 1-5 cho mỗi chiều: - -- Grounding: câu trả lời có được gắn rõ ràng với các nguồn đã import không? -- Chất lượng trích dẫn: các tham chiếu nguồn có hiển thị và cụ thể không? -- Chức năng: người dùng có thể hoàn thành luồng hỏi-đáp không? -- Sự nhất quán sản phẩm: workflow có cảm giác tích hợp không? diff --git a/courses/learn-harness-engineering/docs/vi/lectures/lecture-11-why-observability-belongs-inside-the-harness/code/index.md b/courses/learn-harness-engineering/docs/vi/lectures/lecture-11-why-observability-belongs-inside-the-harness/code/index.md deleted file mode 100644 index af88959f..00000000 --- a/courses/learn-harness-engineering/docs/vi/lectures/lecture-11-why-observability-belongs-inside-the-harness/code/index.md +++ /dev/null @@ -1,8 +0,0 @@ -# Mã cho Bài 11 - -Sử dụng thư mục này cho các ví dụ về: - -- kết quả đầu ra của planner -- rubric của evaluator -- các vòng lặp generator/evaluator -- so sánh single-agent vs multi-role diff --git a/courses/learn-harness-engineering/docs/vi/lectures/lecture-11-why-observability-belongs-inside-the-harness/code/runtime-logger.ts b/courses/learn-harness-engineering/docs/vi/lectures/lecture-11-why-observability-belongs-inside-the-harness/code/runtime-logger.ts deleted file mode 100644 index 213dc198..00000000 --- a/courses/learn-harness-engineering/docs/vi/lectures/lecture-11-why-observability-belongs-inside-the-harness/code/runtime-logger.ts +++ /dev/null @@ -1,260 +0,0 @@ -/** - * runtime-logger.ts - * - * A structured logging module demo. Shows ad-hoc console.log output vs - * structured JSON log output when diagnosing a failure. Includes a seeded - * failure scenario and demonstrates how structured logs pinpoint the issue - * faster. - * - * Run: npx tsx docs/lectures/lecture-11-why-observability-belongs-inside-the-harness/code/runtime-logger.ts - */ - -// --------------------------------------------------------------------------- -// Types -// --------------------------------------------------------------------------- - -interface StructuredLogEntry { - timestamp: string; - level: "info" | "warn" | "error" | "debug"; - component: string; - action: string; - durationMs?: number; - input?: unknown; - output?: unknown; - error?: string; - correlationId: string; -} - -// --------------------------------------------------------------------------- -// Simulated pipeline with a seeded failure -// --------------------------------------------------------------------------- - -const CORRELATION_ID = "req-" + Math.random().toString(36).slice(2, 8); - -// Simulated stages of a document Q&A pipeline -interface PipelineStage { - component: string; - action: string; - durationMs: number; - success: boolean; - errorMessage?: string; - input?: unknown; - output?: unknown; -} - -function runPipeline(): PipelineStage[] { - return [ - { - component: "DocumentLoader", - action: "parse_upload", - durationMs: 45, - success: true, - input: { filename: "report.pdf", size: "2.3MB" }, - output: { chunks: 47 }, - }, - { - component: "ChunkIndexer", - action: "embed_and_store", - durationMs: 230, - success: true, - input: { chunks: 47 }, - output: { indexed: 47, embeddingDim: 1536 }, - }, - { - component: "QueryRouter", - action: "route_query", - durationMs: 12, - success: true, - input: { query: "What is the revenue target?" }, - output: { routedTo: "RetrievalEngine" }, - }, - { - // SEEDED FAILURE: Retrieval returns 0 results due to dimension mismatch - component: "RetrievalEngine", - action: "semantic_search", - durationMs: 180, - success: false, - errorMessage: "Vector dimension mismatch: query embedding dim=768, index embedding dim=1536", - input: { query: "What is the revenue target?", topK: 5 }, - output: { results: 0 }, - }, - { - component: "AnswerGenerator", - action: "generate_with_citations", - durationMs: 1500, - success: true, // Doesn't crash, but produces a bad answer - input: { context: [], query: "What is the revenue target?" }, - output: { answer: "I could not find relevant information.", citations: 0 }, - }, - ]; -} - -// --------------------------------------------------------------------------- -// Ad-hoc logging (console.log style) -// --------------------------------------------------------------------------- - -function printAdHocLog(stages: PipelineStage[]): void { - console.log("Starting document Q&A pipeline..."); - console.log("User uploaded report.pdf"); - - for (const stage of stages) { - if (stage.success) { - console.log(`${stage.component}: ${stage.action} done (${stage.durationMs}ms)`); - } else { - console.log(`${stage.component}: something went wrong`); - } - } - - console.log("Pipeline finished. Answer: I could not find relevant information."); -} - -// --------------------------------------------------------------------------- -// Structured logging (JSON) -// --------------------------------------------------------------------------- - -function printStructuredLog(stages: PipelineStage[]): StructuredLogEntry[] { - const entries: StructuredLogEntry[] = []; - - for (const stage of stages) { - entries.push({ - timestamp: new Date().toISOString(), - level: stage.success ? "info" : "error", - component: stage.component, - action: stage.action, - durationMs: stage.durationMs, - input: stage.input, - output: stage.output, - error: stage.errorMessage, - correlationId: CORRELATION_ID, - }); - } - - return entries; -} - -// --------------------------------------------------------------------------- -// Diagnose failure from structured logs -// --------------------------------------------------------------------------- - -function diagnoseFromStructured(entries: StructuredLogEntry[]): string[] { - const diagnosis: string[] = []; - - // Find errors - const errors = entries.filter((e) => e.level === "error"); - if (errors.length > 0) { - diagnosis.push("ERRORS FOUND:"); - for (const err of errors) { - diagnosis.push(` - ${err.component}.${err.action}: ${err.error}`); - } - } - - // Check for latency spikes - const slowStages = entries.filter((e) => (e.durationMs ?? 0) > 1000); - if (slowStages.length > 0) { - diagnosis.push("LATENCY SPIKES:"); - for (const s of slowStages) { - diagnosis.push(` - ${s.component}.${s.action}: ${s.durationMs}ms`); - } - } - - // Check for cascading failures - const emptyOutputs = entries.filter((e) => { - const out = e.output as Record | undefined; - return out && typeof out === "object" && "results" in out && (out.results as number) === 0; - }); - if (emptyOutputs.length > 0) { - diagnosis.push("EMPTY OUTPUTS (possible cascade):"); - for (const e of emptyOutputs) { - diagnosis.push(` - ${e.component}.${e.action}: returned 0 results`); - } - } - - return diagnosis; -} - -// --------------------------------------------------------------------------- -// Reporting -// --------------------------------------------------------------------------- - -function pad(s: string, len: number): string { - return s.length >= len ? s : s + " ".repeat(len - s.length); -} - -function run(): void { - const pipeline = runPipeline(); - - console.log("\n" + "=".repeat(90)); - console.log(" OBSERVABILITY DEMO -- Ad-hoc vs Structured Logging"); - console.log("=".repeat(90)); - - // --- Ad-hoc output --- - console.log("\n" + "-".repeat(90)); - console.log(" SCENARIO A: Ad-hoc console.log output"); - console.log("-".repeat(90) + "\n"); - printAdHocLog(pipeline); - console.log("\n Diagnosis from ad-hoc logs: ??? Hard to tell what went wrong."); - console.log(" The failure message is vague: 'something went wrong'."); - console.log(" No dimensions, no input/output data, no correlation ID."); - - // --- Structured output --- - console.log("\n" + "-".repeat(90)); - console.log(" SCENARIO B: Structured JSON log output"); - console.log("-".repeat(90) + "\n"); - - const structuredEntries = printStructuredLog(pipeline); - - const header = `| ${pad("Timestamp", 26)}| ${pad("Level", 6)}| ${pad("Component", 20)}| ${pad("Action", 25)}| ${pad("Duration", 9)}| Error?`; - const sep = `|${"-".repeat(28)}|${"-".repeat(8)}|${"-".repeat(22)}|${"-".repeat(27)}|${"-".repeat(11)}|${"-".repeat(30)}`; - console.log(header); - console.log(sep); - - for (const entry of structuredEntries) { - const hasError = entry.error ? entry.error.slice(0, 30) : ""; - const marker = entry.level === "error" ? ">>" : " "; - console.log( - `${marker}| ${pad(entry.timestamp, 26)}| ${pad(entry.level, 6)}| ${pad(entry.component, 20)}| ${pad(entry.action, 25)}| ${pad(String(entry.durationMs) + "ms", 9)}| ${hasError}` - ); - } - - // --- Diagnosis --- - console.log("\n" + "-".repeat(90)); - console.log(" AUTOMATED DIAGNOSIS FROM STRUCTURED LOGS"); - console.log("-".repeat(90) + "\n"); - - const errors = structuredEntries.filter((e) => e.level === "error"); - for (const err of errors) { - console.log(` ROOT CAUSE: ${err.component}.${err.action}`); - console.log(` Error: ${err.error}`); - console.log(` Input: ${JSON.stringify(err.input)}`); - console.log(` Output: ${JSON.stringify(err.output)}`); - console.log(` Correlation ID: ${err.correlationId}`); - } - - // Downstream impact - console.log("\n DOWNSTREAM IMPACT:"); - const answerGen = structuredEntries.find((e) => e.component === "AnswerGenerator"); - if (answerGen) { - const out = answerGen.output as Record; - console.log(` AnswerGenerator received empty context (${JSON.stringify(answerGen.input)})`); - console.log(` Produced answer: "${out.answer}" with ${out.citations} citations`); - } - - // Comparison summary - console.log("\n" + "=".repeat(90)); - console.log(" COMPARISON"); - console.log("=".repeat(90) + "\n"); - - const cHeader = `| ${pad("Metric", 35)}| ${pad("Ad-hoc Logs", 18)}| ${pad("Structured Logs", 18)}|`; - const cSep = `|${"-".repeat(37)}|${"-".repeat(20)}|${"-".repeat(20)}|`; - console.log(cHeader); - console.log(cSep); - console.log(`| ${pad("Root cause identifiable", 35)}| ${pad("No", 18)}| ${pad("Yes", 18)}|`); - console.log(`| ${pad("Input/output traceable", 35)}| ${pad("No", 18)}| ${pad("Yes", 18)}|`); - console.log(`| ${pad("Correlation across steps", 35)}| ${pad("No", 18)}| ${pad("Yes", 18)}|`); - console.log(`| ${pad("Machine-parseable", 35)}| ${pad("No", 18)}| ${pad("Yes", 18)}|`); - console.log(`| ${pad("Time to diagnose", 35)}| ${pad("Minutes (manual)", 18)}| ${pad("Seconds (auto)", 18)}|`); - - console.log("\n Structured logging transforms debugging from guesswork into a deterministic lookup.\n"); -} - -run(); diff --git a/courses/learn-harness-engineering/docs/vi/lectures/lecture-11-why-observability-belongs-inside-the-harness/code/sprint-contract.md b/courses/learn-harness-engineering/docs/vi/lectures/lecture-11-why-observability-belongs-inside-the-harness/code/sprint-contract.md deleted file mode 100644 index 35291518..00000000 --- a/courses/learn-harness-engineering/docs/vi/lectures/lecture-11-why-observability-belongs-inside-the-harness/code/sprint-contract.md +++ /dev/null @@ -1,12 +0,0 @@ -# Ví dụ Sprint Contract - -Mục tiêu sprint: - -- Thêm trích dẫn có thể nhìn thấy vào các kết quả Q&A có grounding - -Xong có nghĩa là: - -- Người dùng đặt câu hỏi -- Ứng dụng trả về câu trả lời -- Ít nhất một trích dẫn được hiển thị -- Nhấp vào trích dẫn mở vị trí nguồn trong document view diff --git a/courses/learn-harness-engineering/docs/vi/lectures/lecture-11-why-observability-belongs-inside-the-harness/index.md b/courses/learn-harness-engineering/docs/vi/lectures/lecture-11-why-observability-belongs-inside-the-harness/index.md deleted file mode 100644 index 200dc004..00000000 --- a/courses/learn-harness-engineering/docs/vi/lectures/lecture-11-why-observability-belongs-inside-the-harness/index.md +++ /dev/null @@ -1,154 +0,0 @@ -[English Version →](../../../en/lectures/lecture-11-why-observability-belongs-inside-the-harness/) | [中文版本 →](../../../zh/lectures/lecture-11-why-observability-belongs-inside-the-harness/) - -> Ví dụ mã nguồn: [code/](https://github.com/walkinglabs/learn-harness-engineering/blob/main/docs/vi/lectures/lecture-11-why-observability-belongs-inside-the-harness/code/) -> Dự án thực hành: [Dự án 06. Harness Đầy đủ (Capstone)](./../../projects/project-06-runtime-observability-and-debugging/index.md) - -# Bài 11. Làm Cho Runtime của Agent Có thể Quan sát Được - -## Bài giảng này Giải quyết Vấn đề Gì? - -Bạn yêu cầu một agent triển khai một tính năng. Nó chạy 20 phút, sửa đổi một loạt tệp, sau đó nói với bạn "xong, nhưng hai test đang thất bại." Bạn hỏi tại sao chúng thất bại — "không chắc, có thể là vấn đề timing." Bạn hỏi nó đã thay đổi những đường dẫn quan trọng nào — "để tôi nhìn vào mã..." - -Đây không phải về việc agent thiếu năng lực. Đó là về việc harness của bạn không cung cấp đủ khả năng quan sát. **Không có khả năng quan sát, agent đưa ra quyết định trong sự không chắc chắn, đánh giá trở thành phán xét chủ quan, và thử lại trở thành lang thang mù quáng.** Cả OpenAI và Anthropic đều định nghĩa độ tin cậy là một vấn đề bằng chứng — harness phải phơi bày hành vi runtime và các tín hiệu đánh giá ở dạng có thể hướng dẫn quyết định tiếp theo. - -## Các Khái niệm Cốt lõi - -- **Quan sát runtime**: Các tín hiệu cấp hệ thống — log, trace, sự kiện process, health check. Trả lời "hệ thống đã làm gì." -- **Quan sát quá trình**: Khả năng hiển thị vào các artifact quyết định harness — kế hoạch, rubric tính điểm, tiêu chí chấp nhận. Trả lời "tại sao thay đổi này nên được chấp nhận." -- **Task trace**: Bản ghi đường dẫn quyết định hoàn chỉnh từ khi bắt đầu tác vụ đến khi hoàn thành, tương tự như request tracing trong hệ thống phân tán. Mỗi bước agent thực hiện, với ngữ cảnh, được ghi lại. -- **Sprint contract**: Thỏa thuận ngắn hạn được thương lượng trước khi bắt đầu lập trình — chỉ định phạm vi tác vụ, tiêu chuẩn xác minh và các ngoại lệ. Công cụ cốt lõi cho quan sát quá trình. -- **Evaluator rubric**: Biến đánh giá chất lượng từ phán xét chủ quan thành tính điểm có cấu trúc dựa trên bằng chứng. Làm cho các evaluator khác nhau tạo ra kết quả tương tự cho cùng một kết quả đầu ra. -- **Quan sát phân lớp**: Quan sát lớp hệ thống và lớp quá trình được thiết kế đồng thời và củng cố lẫn nhau. Tín hiệu runtime giải thích hành vi; artifact quá trình giải thích ý định. - -## Quan sát Phân lớp - -```mermaid -flowchart LR - Contract["Ghi xuống tác vụ trước
    thay đổi gì / không thay đổi gì / tiêu chí vượt qua"] --> Generator["Generator"] - Generator --> Signals["Thu thập app log, trace,
    và health check trong khi chạy"] - Contract --> Review["Kiểm tra kết quả từng mục
    hành vi / test / ranh giới"] - Signals --> Review - Review --> Verdict["Chỉ ra kiểm tra thất bại
    và nơi cần sửa"] - Verdict --> Generator -``` - -## Tại sao Điều này Xảy ra - -### Chi phí Thực sự của Thiếu Quan sát - -Khi một harness thiếu khả năng quan sát, bốn loại vấn đề xuất hiện có hệ thống: - -**Không thể phân biệt "đúng" với "trông có vẻ đúng"**: Một hàm trông hoàn hảo trong code review — cú pháp đúng, logic hợp lý. Nhưng ở runtime, một lỗi xử lý edge case tạo ra kết quả không đúng với các đầu vào cụ thể. Chỉ có runtime trace mới có thể tiết lộ rằng đường dẫn thực thi thực tế lệch khỏi kỳ vọng. - -**Đánh giá trở thành huyền bí**: Không có rubric tính điểm và tiêu chí chấp nhận, các evaluator (con người hoặc agent) dựa vào các giả định ẩn. Cùng một kết quả đầu ra có thể nhận được đánh giá hoàn toàn khác nhau từ các người đánh giá khác nhau. Đánh giá chất lượng trở thành không thể tái tạo. - -**Thử lại trở thành đoán mù**: Khi agent không biết tại sao thứ gì đó thất bại, hướng thử lại là ngẫu nhiên. Nó có thể thử lặp đi lặp lại theo hướng sai — sửa các đường dẫn mã không liên quan trong khi bỏ qua nguyên nhân thất bại thực sự. Mỗi lần thử lại mù quáng tốn token và thời gian. - -**Vách đá thông tin bàn giao phiên**: Khi công việc chưa hoàn thành được bàn giao cho phiên tiếp theo, thiếu khả năng quan sát có nghĩa là phiên mới phải chẩn đoán trạng thái hệ thống từ đầu. Quan sát của Anthropic về agent chạy lâu cho thấy việc chẩn đoán thừa này có thể tiêu thụ 30-50% tổng thời gian phiên. - -### Kịch bản Claude Code Thực tế - -Hãy tưởng tượng một harness sử dụng luồng công việc ba vai "planner-generator-evaluator," thực thi tác vụ "thêm dark mode vào ứng dụng." - -**Không có khả năng quan sát**: Planner đưa ra mô tả mơ hồ. Generator triển khai dark mode dựa trên sự mơ hồ đó, nhưng nó không khớp với kỳ vọng ẩn của planner. Evaluator từ chối dựa trên tiêu chuẩn ẩn của riêng họ nhưng không thể nói cụ thể điều gì sai. Generator thử lại mù quáng dựa trên lý do từ chối mơ hồ. Chu kỳ lặp lại 3-4 lần, mất khoảng 45 phút, tạo ra kết quả vừa tạm chấp nhận. - -**Với đầy đủ khả năng quan sát**: Planner đưa ra sprint contract — liệt kê các component cần sửa đổi, tiêu chuẩn xác minh cho mỗi cái, và các ngoại lệ (không xử lý print styles). Generator triển khai theo contract. Quan sát runtime ghi lại quá trình tải và áp dụng style của mỗi component. Evaluator sử dụng rubric tính điểm để đánh giá từng chiều một, với các trích dẫn bằng chứng cụ thể. Một lần lặp tạo ra kết quả chất lượng cao, trong khoảng 15 phút. - -Khác biệt hiệu quả 3x. Thay đổi duy nhất là khả năng quan sát. - -### Tại sao Agent Không thể Tự Giải quyết Điều này - -Bạn có thể đang nghĩ: "Agent không thể tự in log của nó sao?" Các vấn đề là: - -1. Agent không biết những gì nó không biết — nó sẽ không chủ động ghi lại các tín hiệu mà nó không nhận ra là cần thiết. -2. Các định dạng log không nhất quán — các phiên khác nhau sử dụng các định dạng log khác nhau, làm cho phân tích có hệ thống không thể thực hiện. -3. Quan sát quá trình không thể được giải quyết bằng log — sprint contract và rubric tính điểm là artifact có cấu trúc cần hỗ trợ cấp harness. - -## Cách Làm Đúng - -### 1. Xây dựng Thu thập Tín hiệu Runtime vào Harness - -Đừng dựa vào agent để tự in log của nó. Harness nên tự động thu thập các tín hiệu này: - -- **Vòng đời ứng dụng**: Các trạng thái giai đoạn Startup, ready, running, shutdown -- **Thực thi đường dẫn tính năng**: Bản ghi thực thi đường dẫn quan trọng, bao gồm điểm vào, điểm kiểm tra và điểm thoát -- **Luồng dữ liệu**: Bản ghi dữ liệu chảy giữa các component -- **Sử dụng tài nguyên**: Các mẫu sử dụng tài nguyên bất thường (ví dụ: bộ nhớ liên tục tăng) -- **Lỗi và ngoại lệ**: Đầy đủ ngữ cảnh lỗi, không chỉ thông báo lỗi - -### 2. Triển khai Sprint Contract - -Trước khi mỗi tác vụ bắt đầu, generator và evaluator (có thể là các lần gọi khác nhau của cùng một agent) thương lượng một contract: - -```markdown -# Sprint Contract: Hỗ trợ Dark Mode - -## Phạm vi -- Sửa đổi component chuyển đổi theme -- Cập nhật biến CSS toàn cục -- Thêm test dark mode - -## Tiêu chuẩn Xác minh -- Test hồi quy trực quan vượt qua cho mỗi component -- Test end-to-end luồng chính vượt qua -- Không có flash of unstyled content (FOUC) - -## Ngoại lệ -- Không xử lý print styles -- Không xử lý dark mode cho component bên thứ ba -``` - -### 3. Thiết lập Evaluator Rubric - -Biến "tốt hay không" thành tính điểm có thể định lượng: - -```markdown -# Rubric Tính điểm - -| Chiều | A | B | C | D | -|-------|---|---|---|---| -| Tính đúng đắn mã | Tất cả test vượt qua | Luồng chính vượt qua | Vượt qua một phần | Build thất bại | -| Tuân thủ kiến trúc | Hoàn toàn tuân thủ | Sai lệch nhỏ | Sai lệch rõ ràng | Vi phạm nghiêm trọng | -| Phạm vi test | Luồng chính + edge case | Chỉ luồng chính | Chỉ skeleton | Không có test | -``` - -### 4. Chuẩn hóa với OpenTelemetry - -Tạo một trace cho mỗi phiên harness, một span cho mỗi tác vụ, và sub-span cho mỗi bước xác minh. Sử dụng các thuộc tính chuẩn để chú thích thông tin quan trọng. Theo cách này, dữ liệu quan sát tích hợp với các công cụ chuẩn (Jaeger, Zipkin). - -## Trường hợp Thực tế - -Một harness sử dụng luồng công việc planner-generator-evaluator, thực thi "thêm hỗ trợ dark mode": - -**Phiên bản không quan sát được**: 3-4 vòng thử lại mù quáng, 45 phút, kết quả vừa tạm chấp nhận. Evaluator nói "nó không cảm thấy đúng" nhưng không thể nói cụ thể điều gì. Generator lãng phí nhiều thời gian theo hướng sai. - -**Phiên bản quan sát đầy đủ**: -- Sprint contract làm rõ phạm vi, tiêu chuẩn và ngoại lệ -- Runtime trace ghi lại quá trình tải style của mỗi component -- Rubric tính điểm cung cấp đánh giá có cấu trúc từng chiều một -- Một lần lặp tạo ra kết quả chất lượng cao, 15 phút - -Cải thiện hiệu quả 3x, chất lượng ổn định hơn, đánh giá có thể tái tạo. - -## Những Điểm chính cần Nhớ - -- **Khả năng quan sát là thuộc tính kiến trúc harness** — không phải tính năng được thêm vào sau, mà là khả năng cốt lõi phải được xem xét trong quá trình thiết kế. -- **Cả hai lớp quan sát đều cần thiết** — tín hiệu runtime giải thích "điều gì đã xảy ra," artifact quá trình giải thích "tại sao nó được thực hiện theo cách đó." -- **Sprint contract front-load alignment** — ngăn "generator xây dựng thứ gì đó mà evaluator ngay lập tức từ chối vì lý do có thể dự đoán trước." -- **Rubric tính điểm làm cho đánh giá có thể tái tạo** — các evaluator khác nhau tạo ra điểm tương tự cho cùng một kết quả đầu ra. -- **Thiếu khả năng quan sát lãng phí 30-50% thời gian phiên vào chẩn đoán thừa.** - -## Đọc thêm - -- [Observability Engineering - Charity Majors](https://www.honeycomb.io/blog/observability-engineering-book) — Khung lý thuyết và thực hành cho kỹ thuật quan sát hiện đại -- [Dapper - Google (Sigelman et al.)](https://research.google/pubs/pub36356/) — Thực hành đột phá trong distributed tracing quy mô lớn -- [Harness Design - Anthropic](https://www.anthropic.com/engineering/harness-design-long-running-apps) — Giới thiệu sprint contract và evaluator rubric -- [Site Reliability Engineering - Google](https://sre.google/sre-book/table-of-contents/) — Ứng dụng có hệ thống của khả năng quan sát trong hệ thống production - -## Bài tập - -1. **Phân tích Khoảng cách Quan sát**: Kiểm toán harness hiện tại của bạn để tìm quan sát lớp hệ thống và lớp quá trình. Tìm các trạng thái hệ thống không thể phân biệt từ các tín hiệu hiện có, và đề xuất bổ sung. - -2. **Thực hành Sprint Contract**: Viết một sprint contract cho một tác vụ thực tế. Để agent thực thi theo contract, và so sánh hiệu quả và chất lượng có và không có contract. - -3. **Xây dựng Task Trace**: Ghi lại mỗi bước trong các hoạt động của agent trong một tác vụ lập trình hoàn chỉnh. Chú thích với các quy ước ngữ nghĩa OpenTelemetry. Phân tích các điểm nghẽn thông tin trong trace — bước nào thiếu hỗ trợ tín hiệu đủ cho các quyết định. \ No newline at end of file diff --git a/courses/learn-harness-engineering/docs/vi/lectures/lecture-12-why-every-session-must-leave-a-clean-state/code/benchmark-comparison-template.md b/courses/learn-harness-engineering/docs/vi/lectures/lecture-12-why-every-session-must-leave-a-clean-state/code/benchmark-comparison-template.md deleted file mode 100644 index 36ce1bb6..00000000 --- a/courses/learn-harness-engineering/docs/vi/lectures/lecture-12-why-every-session-must-leave-a-clean-state/code/benchmark-comparison-template.md +++ /dev/null @@ -1,18 +0,0 @@ -# Mẫu So sánh Benchmark - -Harness A: - -- tỷ lệ hoàn thành -- số lần thử lại trung bình -- lỗi được bắt trước khi review của con người - -Harness B: - -- tỷ lệ hoàn thành -- số lần thử lại trung bình -- lỗi được bắt trước khi review của con người - -Diễn giải: - -- Harness nào thay đổi kết quả? -- Harness nào thay đổi chi phí để đạt được kết quả? diff --git a/courses/learn-harness-engineering/docs/vi/lectures/lecture-12-why-every-session-must-leave-a-clean-state/code/benchmark-runner.ts b/courses/learn-harness-engineering/docs/vi/lectures/lecture-12-why-every-session-must-leave-a-clean-state/code/benchmark-runner.ts deleted file mode 100644 index 0388cad7..00000000 --- a/courses/learn-harness-engineering/docs/vi/lectures/lecture-12-why-every-session-must-leave-a-clean-state/code/benchmark-runner.ts +++ /dev/null @@ -1,216 +0,0 @@ -/** - * benchmark-runner.ts - * - * Reads a benchmark task definition (JSON array of tasks with pass criteria), - * "executes" each task, records timing and pass/fail, outputs a comparison - * report showing which tasks pass and which fail. - * - * Run: npx tsx docs/lectures/lecture-12-why-every-session-must-leave-a-clean-state/code/benchmark-runner.ts - */ - -// --------------------------------------------------------------------------- -// Types -// --------------------------------------------------------------------------- - -interface BenchmarkTask { - id: string; - name: string; - category: string; - passCriteria: string[]; - expectedDurationMs: number; - // Simulated actual results - actualDurationMs: number; - actualPass: boolean; - failureReason?: string; -} - -interface BenchmarkResult { - id: string; - name: string; - category: string; - criteriaTotal: number; - criteriaPassed: number; - pass: boolean; - expectedMs: number; - actualMs: number; - durationDelta: number; - failureReason?: string; -} - -// --------------------------------------------------------------------------- -// Benchmark task definitions -// --------------------------------------------------------------------------- - -const benchmarkTasks: BenchmarkTask[] = [ - { - id: "bench-001", - name: "Import markdown document", - category: "Document Pipeline", - passCriteria: ["File accepted", "Chunks created", "Metadata stored"], - expectedDurationMs: 100, - actualDurationMs: 95, - actualPass: true, - }, - { - id: "bench-002", - name: "Import PDF document", - category: "Document Pipeline", - passCriteria: ["File accepted", "Text extracted", "Chunks created", "Metadata stored"], - expectedDurationMs: 200, - actualDurationMs: 310, - actualPass: false, - failureReason: "PDF text extraction failed on page 3 -- encoding issue", - }, - { - id: "bench-003", - name: "Ask grounded question", - category: "Q&A Pipeline", - passCriteria: ["Query received", "Relevant chunks retrieved", "Answer generated", "Citations present"], - expectedDurationMs: 500, - actualDurationMs: 480, - actualPass: true, - }, - { - id: "bench-004", - name: "Ask question with no relevant docs", - category: "Q&A Pipeline", - passCriteria: ["Query received", "Graceful 'no results' message", "No hallucinated citations"], - expectedDurationMs: 400, - actualDurationMs: 420, - actualPass: false, - failureReason: "Model hallucinated a citation instead of saying 'no results'", - }, - { - id: "bench-005", - name: "Delete imported document", - category: "Document Pipeline", - passCriteria: ["Document removed from list", "Chunks removed from index", "No orphan data"], - expectedDurationMs: 150, - actualDurationMs: 145, - actualPass: true, - }, - { - id: "bench-006", - name: "Concurrent user access", - category: "Security", - passCriteria: ["Users isolated", "No cross-user data leak", "Performance within SLA"], - expectedDurationMs: 300, - actualDurationMs: 850, - actualPass: false, - failureReason: "Cross-user data leak detected + response time exceeded SLA (850ms > 500ms)", - }, - { - id: "bench-007", - name: "Session continuity after restart", - category: "Reliability", - passCriteria: ["State persisted", "Session recovers", "No data loss"], - expectedDurationMs: 200, - actualDurationMs: 180, - actualPass: true, - }, - { - id: "bench-008", - name: "API rate limiting", - category: "Security", - passCriteria: ["Rate limit enforced", "429 response after limit", "Legitimate traffic unaffected"], - expectedDurationMs: 100, - actualDurationMs: 100, - actualPass: true, - }, -]; - -// --------------------------------------------------------------------------- -// Execution simulation -// --------------------------------------------------------------------------- - -function executeBenchmark(tasks: BenchmarkTask[]): BenchmarkResult[] { - return tasks.map((task) => ({ - id: task.id, - name: task.name, - category: task.category, - criteriaTotal: task.passCriteria.length, - criteriaPassed: task.actualPass ? task.passCriteria.length : Math.floor(task.passCriteria.length * 0.5), - pass: task.actualPass, - expectedMs: task.expectedDurationMs, - actualMs: task.actualDurationMs, - durationDelta: task.actualDurationMs - task.expectedDurationMs, - failureReason: task.failureReason, - })); -} - -// --------------------------------------------------------------------------- -// Reporting -// --------------------------------------------------------------------------- - -function pad(s: string, len: number): string { - return s.length >= len ? s : s + " ".repeat(len - s.length); -} - -function run(): void { - console.log("\n" + "=".repeat(100)); - console.log(" BENCHMARK RUNNER -- Task Execution Report"); - console.log("=".repeat(100)); - - const results = executeBenchmark(benchmarkTasks); - - // Detailed results - const header = `| ${pad("ID", 10)}| ${pad("Task", 35)}| ${pad("Category", 18)}| ${pad("Pass?", 6)}| ${pad("Criteria", 10)}| ${pad("Expected", 10)}| ${pad("Actual", 10)}| ${pad("Delta", 8)}|`; - const sep = `|${"-".repeat(12)}|${"-".repeat(37)}|${"-".repeat(20)}|${"-".repeat(8)}|${"-".repeat(12)}|${"-".repeat(12)}|${"-".repeat(12)}|${"-".repeat(10)}|`; - console.log("\n" + header); - console.log(sep); - - for (const r of results) { - const passLabel = r.pass ? "PASS" : "FAIL"; - const criteriaLabel = `${r.criteriaPassed}/${r.criteriaTotal}`; - const deltaLabel = r.durationDelta >= 0 ? `+${r.durationDelta}ms` : `${r.durationDelta}ms`; - const marker = r.pass ? " " : ">>"; - - console.log( - `${marker}| ${pad(r.id, 10)}| ${pad(r.name, 35)}| ${pad(r.category, 18)}| ${pad(passLabel, 6)}| ${pad(criteriaLabel, 10)}| ${pad(r.expectedMs + "ms", 10)}| ${pad(r.actualMs + "ms", 10)}| ${pad(deltaLabel, 8)}|` - ); - } - - // Failure details - const failures = results.filter((r) => !r.pass); - if (failures.length > 0) { - console.log("\n" + "-".repeat(100)); - console.log(" FAILURE DETAILS"); - console.log("-".repeat(100)); - for (const f of failures) { - console.log(`\n [${f.id}] ${f.name}`); - console.log(` Category: ${f.category}`); - console.log(` Criteria: ${f.criteriaPassed}/${f.criteriaTotal} passed`); - console.log(` Reason: ${f.failureReason ?? "Unknown"}`); - console.log(` Timing: Expected ${f.expectedMs}ms, actual ${f.actualMs}ms (delta: ${f.durationDelta >= 0 ? "+" : ""}${f.durationDelta}ms)`); - } - } - - // Summary - console.log("\n" + "=".repeat(100)); - console.log(" SUMMARY BY CATEGORY"); - console.log("=".repeat(100) + "\n"); - - const categories = [...new Set(results.map((r) => r.category))]; - const catHeader = `| ${pad("Category", 20)}| ${pad("Total", 8)}| ${pad("Passed", 8)}| ${pad("Failed", 8)}| ${pad("Pass Rate", 12)}|`; - const catSep = `|${"-".repeat(22)}|${"-".repeat(10)}|${"-".repeat(10)}|${"-".repeat(10)}|${"-".repeat(14)}|`; - console.log(catHeader); - console.log(catSep); - - for (const cat of categories) { - const catResults = results.filter((r) => r.category === cat); - const catPassed = catResults.filter((r) => r.pass).length; - const catFailed = catResults.filter((r) => !r.pass).length; - const rate = Math.round((catPassed / catResults.length) * 100); - - console.log(`| ${pad(cat, 20)}| ${pad(String(catResults.length), 8)}| ${pad(String(catPassed), 8)}| ${pad(String(catFailed), 8)}| ${pad(rate + "%", 12)}|`); - } - - // Overall - const totalPassed = results.filter((r) => r.pass).length; - const totalFailed = results.filter((r) => !r.pass).length; - console.log("\n" + "-".repeat(100)); - console.log(` OVERALL: ${totalPassed}/${results.length} passed (${Math.round((totalPassed / results.length) * 100)}%), ${totalFailed} failures`); - console.log("=".repeat(100) + "\n"); -} - -run(); diff --git a/courses/learn-harness-engineering/docs/vi/lectures/lecture-12-why-every-session-must-leave-a-clean-state/code/cleanup-loop.md b/courses/learn-harness-engineering/docs/vi/lectures/lecture-12-why-every-session-must-leave-a-clean-state/code/cleanup-loop.md deleted file mode 100644 index 9a59e306..00000000 --- a/courses/learn-harness-engineering/docs/vi/lectures/lecture-12-why-every-session-must-leave-a-clean-state/code/cleanup-loop.md +++ /dev/null @@ -1,9 +0,0 @@ -# Ví dụ Vòng lặp Dọn dẹp - -Các tác vụ dọn dẹp định kỳ: - -- quét tài liệu lỗi thời -- quét các vi phạm cấu trúc -- cập nhật điểm chất lượng -- mở các PR dọn dẹp có mục tiêu -- chạy lại một slice benchmark cố định sau khi dọn dẹp diff --git a/courses/learn-harness-engineering/docs/vi/lectures/lecture-12-why-every-session-must-leave-a-clean-state/code/cleanup-scanner.ts b/courses/learn-harness-engineering/docs/vi/lectures/lecture-12-why-every-session-must-leave-a-clean-state/code/cleanup-scanner.ts deleted file mode 100644 index 6b35905c..00000000 --- a/courses/learn-harness-engineering/docs/vi/lectures/lecture-12-why-every-session-must-leave-a-clean-state/code/cleanup-scanner.ts +++ /dev/null @@ -1,336 +0,0 @@ -/** - * cleanup-scanner.ts - * - * Scans a project directory for stale artifacts, dead code, structural - * violations, and outputs a cleanup report. Helps enforce the "clean state - * at end of every session" principle. - * - * Usage: - * npx tsx docs/lectures/lecture-12.../code/cleanup-scanner.ts [path] - * (defaults to current working directory) - * - * Run: npx tsx docs/lectures/lecture-12-why-every-session-must-leave-a-clean-state/code/cleanup-scanner.ts - */ - -import * as fs from "node:fs"; -import * as path from "node:path"; - -// --------------------------------------------------------------------------- -// Types -// --------------------------------------------------------------------------- - -interface ScanResult { - category: string; - check: string; - severity: "critical" | "warning" | "info"; - found: string[]; - description: string; -} - -// --------------------------------------------------------------------------- -// Scanner checks -// --------------------------------------------------------------------------- - -interface ScannerCheck { - category: string; - name: string; - severity: "critical" | "warning" | "info"; - description: string; - scan: (dir: string) => string[]; -} - -function createChecks(): ScannerCheck[] { - return [ - { - category: "Stale Artifacts", - name: "Temporary files (*.tmp, *.bak, *.swp)", - severity: "warning", - description: "Leftover temp files from editing or processing", - scan: (dir) => findFiles(dir, [".tmp", ".bak", ".swp", "~"]), - }, - { - category: "Stale Artifacts", - name: "Debug/log files in source", - severity: "warning", - description: "Log files that should not be in the source tree", - scan: (dir) => { - const found: string[] = []; - const logPatterns = [".log"]; - for (const p of findFiles(dir, logPatterns)) { - // Only flag log files in source directories - if (p.includes("/src/") || p.includes("/lib/") || p.includes("/app/")) { - found.push(p); - } - } - return found; - }, - }, - { - category: "Dead Code", - name: "Unused imports (placeholder detection)", - severity: "info", - description: "Files that may contain unused code (heuristic scan)", - scan: (dir) => { - const found: string[] = []; - const srcDirs = ["src", "lib", "app"]; - for (const srcDir of srcDirs) { - const srcPath = path.join(dir, srcDir); - if (fs.existsSync(srcPath)) { - scanForPatterns(srcPath, ["TODO:", "FIXME:", "HACK:", "XXX:"], found, dir); - } - } - return found; - }, - }, - { - category: "Structural Violations", - name: "Missing .gitignore", - severity: "warning", - description: "No .gitignore file found -- risk of committing build artifacts", - scan: (dir) => fs.existsSync(path.join(dir, ".gitignore")) ? [] : [".gitignore (MISSING)"], - }, - { - category: "Structural Violations", - name: "Build artifacts in source", - severity: "critical", - description: "Compiled output (dist/, build/) mixed with source", - scan: (dir) => { - const found: string[] = []; - // Check if dist/ or build/ exists inside src/ - const dirs = ["src/dist", "src/build", "lib/dist", "lib/build"]; - for (const d of dirs) { - if (fs.existsSync(path.join(dir, d))) { - found.push(d + "/"); - } - } - return found; - }, - }, - { - category: "Structural Violations", - name: "node_modules in source tree", - severity: "critical", - description: "node_modules directory found outside root (nested dependency)", - scan: (dir) => { - const found: string[] = []; - const srcDirs = ["src", "lib", "app", "test", "tests"]; - for (const srcDir of srcDirs) { - const nmPath = path.join(dir, srcDir, "node_modules"); - if (fs.existsSync(nmPath)) { - found.push(srcDir + "/node_modules/"); - } - } - return found; - }, - }, - { - category: "Session Cleanliness", - name: "Uncommitted changes indicator", - severity: "info", - description: "Checks for common artifacts of incomplete sessions", - scan: (dir) => { - const found: string[] = []; - const indicators = [ - "WIP.md", "IN_PROGRESS.md", "scratch.ts", "scratch.js", - "debug.ts", "test-manual.ts", "temp.ts", - ]; - for (const f of indicators) { - if (fs.existsSync(path.join(dir, f))) { - found.push(f); - } - } - return found; - }, - }, - { - category: "Session Cleanliness", - name: "Empty directories", - severity: "info", - description: "Empty directories that may be leftover from incomplete work", - scan: (dir) => { - const found: string[] = []; - const commonDirs = ["src", "lib", "app", "test", "tests", "docs"]; - for (const d of commonDirs) { - const dp = path.join(dir, d); - if (fs.existsSync(dp) && fs.statSync(dp).isDirectory()) { - try { - const contents = fs.readdirSync(dp); - if (contents.length === 0) { - found.push(d + "/"); - } - } catch { - // Skip directories we can't read - } - } - } - return found; - }, - }, - { - category: "Configuration", - name: "Environment files in source", - severity: "critical", - description: ".env files that may contain secrets", - scan: (dir) => { - const found: string[] = []; - const envFiles = [".env", ".env.local", ".env.production", ".env.staging"]; - for (const f of envFiles) { - if (fs.existsSync(path.join(dir, f))) { - found.push(f); - } - } - return found; - }, - }, - ]; -} - -// --------------------------------------------------------------------------- -// Helper functions -// --------------------------------------------------------------------------- - -function findFiles(dir: string, extensions: string[]): string[] { - const found: string[] = []; - - function walk(current: string, depth: number): void { - if (depth > 4) return; // Limit recursion depth - try { - const entries = fs.readdirSync(current, { withFileTypes: true }); - for (const entry of entries) { - // Skip common non-source directories - if (entry.isDirectory() && ["node_modules", ".git", "dist", "build", ".next", "coverage"].includes(entry.name)) { - continue; - } - - const fullPath = path.join(current, entry.name); - const relativePath = path.relative(dir, fullPath); - - if (entry.isDirectory()) { - walk(fullPath, depth + 1); - } else if (entry.isFile()) { - if (extensions.some((ext) => entry.name.endsWith(ext))) { - found.push(relativePath); - } - } - } - } catch { - // Skip directories we can't read - } - } - - walk(dir, 0); - return found; -} - -function scanForPatterns(dir: string, patterns: string[], results: string[], baseDir: string): void { - try { - const entries = fs.readdirSync(dir, { withFileTypes: true }); - for (const entry of entries) { - if (entry.isDirectory()) { - if (!["node_modules", ".git", "dist", "build"].includes(entry.name)) { - scanForPatterns(path.join(dir, entry.name), patterns, results, baseDir); - } - continue; - } - - const fullPath = path.join(dir, entry.name); - if (!entry.name.endsWith(".ts") && !entry.name.endsWith(".tsx") && !entry.name.endsWith(".js")) { - continue; - } - - try { - const content = fs.readFileSync(fullPath, "utf-8"); - const lines = content.split("\n"); - for (let i = 0; i < Math.min(lines.length, 50); i++) { - for (const pattern of patterns) { - if (lines[i].includes(pattern)) { - const relative = path.relative(baseDir, fullPath); - results.push(`${relative}:${i + 1} contains ${pattern}`); - } - } - } - } catch { - // Skip files we can't read - } - } - } catch { - // Skip directories we can't read - } -} - -// --------------------------------------------------------------------------- -// Reporting -// --------------------------------------------------------------------------- - -function pad(s: string, len: number): string { - return s.length >= len ? s : s + " ".repeat(len - s.length); -} - -function run(): void { - const targetDir = process.argv[2] ? path.resolve(process.argv[2]) : process.cwd(); - - console.log("\n" + "=".repeat(90)); - console.log(" CLEANUP SCANNER -- Stale Artifact & Violation Report"); - console.log("=".repeat(90)); - console.log(` Scanning: ${targetDir}\n`); - - const checks = createChecks(); - const results: ScanResult[] = []; - - for (const check of checks) { - const found = check.scan(targetDir); - results.push({ - category: check.category, - check: check.name, - severity: check.severity, - found, - description: check.description, - }); - } - - // Report - const header = `| ${pad("Category", 20)}| ${pad("Check", 40)}| ${pad("Severity", 10)}| ${pad("Count", 6)}| Details`; - const sep = `|${"-".repeat(22)}|${"-".repeat(42)}|${"-".repeat(12)}|${"-".repeat(8)}|${"-".repeat(30)}`; - console.log(header); - console.log(sep); - - for (const r of results) { - const count = r.found.length; - const detail = count > 0 ? r.found.slice(0, 2).join(", ") : "(clean)"; - const marker = count > 0 && r.severity === "critical" ? ">>" : count > 0 ? " * " : " "; - - console.log(`${marker}| ${pad(r.category, 20)}| ${pad(r.check, 40)}| ${pad(r.severity, 10)}| ${pad(String(count), 6)}| ${detail}`); - } - - // Summary - const criticals = results.filter((r) => r.severity === "critical" && r.found.length > 0); - const warnings = results.filter((r) => r.severity === "warning" && r.found.length > 0); - const infos = results.filter((r) => r.severity === "info" && r.found.length > 0); - const clean = results.filter((r) => r.found.length === 0); - - console.log("\n" + "=".repeat(90)); - console.log(" CLEANUP SUMMARY"); - console.log("=".repeat(90) + "\n"); - - console.log(` Critical issues: ${criticals.length}`); - console.log(` Warnings: ${warnings.length}`); - console.log(` Info items: ${infos.length}`); - console.log(` Clean checks: ${clean.length}/${results.length}`); - - if (criticals.length > 0) { - console.log("\n ACTION REQUIRED (Critical):"); - for (const c of criticals) { - console.log(` - ${c.check}: ${c.found.length} item(s) found`); - console.log(` ${c.description}`); - } - } - - const totalIssues = results.reduce((s, r) => s + r.found.length, 0); - if (totalIssues === 0) { - console.log("\n Project is in a clean state. No issues found.\n"); - } else { - console.log(`\n Total issues found: ${totalIssues}. Run this scanner at the end of every session to maintain clean state.\n`); - } -} - -run(); diff --git a/courses/learn-harness-engineering/docs/vi/lectures/lecture-12-why-every-session-must-leave-a-clean-state/code/index.md b/courses/learn-harness-engineering/docs/vi/lectures/lecture-12-why-every-session-must-leave-a-clean-state/code/index.md deleted file mode 100644 index d9e39360..00000000 --- a/courses/learn-harness-engineering/docs/vi/lectures/lecture-12-why-every-session-must-leave-a-clean-state/code/index.md +++ /dev/null @@ -1,8 +0,0 @@ -# Mã cho Bài 12 - -Sử dụng thư mục này cho các ví dụ về: - -- các slice benchmark -- tác vụ dọn dẹp -- các ví dụ giảm entropy -- các lần chạy harness có thể lặp lại diff --git a/courses/learn-harness-engineering/docs/vi/lectures/lecture-12-why-every-session-must-leave-a-clean-state/index.md b/courses/learn-harness-engineering/docs/vi/lectures/lecture-12-why-every-session-must-leave-a-clean-state/index.md deleted file mode 100644 index 508b3d22..00000000 --- a/courses/learn-harness-engineering/docs/vi/lectures/lecture-12-why-every-session-must-leave-a-clean-state/index.md +++ /dev/null @@ -1,186 +0,0 @@ -[English Version →](../../../en/lectures/lecture-12-why-every-session-must-leave-a-clean-state/) | [中文版本 →](../../../zh/lectures/lecture-12-why-every-session-must-leave-a-clean-state/) - -> Ví dụ mã nguồn: [code/](https://github.com/walkinglabs/learn-harness-engineering/blob/main/docs/vi/lectures/lecture-12-why-every-session-must-leave-a-clean-state/code/) -> Dự án thực hành: [Dự án 06. Harness Đầy đủ (Capstone)](./../../projects/project-06-runtime-observability-and-debugging/index.md) - -# Bài 12. Bàn giao Sạch Sẽ ở Cuối Mỗi Phiên - -## Bài giảng này Giải quyết Vấn đề Gì? - -Agent của bạn chạy cả buổi chiều, sửa đổi 20 tệp, commit mã, phiên kết thúc. Phiên agent tiếp theo bắt đầu và ngay lập tức phát hiện: build bị hỏng, test đang đỏ, các tệp debug tạm thời ở khắp nơi, feature list không được cập nhật, và tiến độ hoàn toàn không rõ ràng. Phiên mới dành 30 phút đầu tiên chỉ để tìm hiểu "phiên cuối thực sự đã làm gì." - -Cả OpenAI và Anthropic đều nói rõ ràng: **độ tin cậy dài hạn phụ thuộc vào kỷ luật vận hành, không chỉ là thành công một lần chạy.** Chất lượng trạng thái khi thoát phiên trực tiếp xác định hiệu quả của phiên tiếp theo. Hãy nghĩ về nó giống như các thực hành tốt nhất của Git — mỗi commit nên là một thay đổi nguyên tử, có thể biên dịch, không phải một đống mã làm dở. - -## Các Khái niệm Cốt lõi - -- **Trạng thái sạch (Clean state)**: Hệ thống thỏa mãn năm điều kiện khi thoát phiên — build vượt qua, test vượt qua, tiến độ được ghi lại, không có artifact lỗi thời, đường dẫn khởi động khả dụng. Thiếu bất kỳ cái nào có nghĩa là phiên không "xong." -- **Tính toàn vẹn phiên (Session Integrity)**: Tương tự như giao dịch cơ sở dữ liệu — hoặc commit đầy đủ và để lại trạng thái sạch, hoặc rollback về trạng thái nhất quán cuối cùng. Không có lựa chọn trung gian. -- **Tài liệu chất lượng (Quality Document)**: Một artifact đang hoạt động liên tục ghi lại xếp loại chất lượng cho mỗi module. Không phải đánh giá một lần, mà là tracker cho thấy liệu codebase có đang trở nên mạnh hơn hay yếu hơn theo thời gian. -- **Vòng lặp dọn dẹp (Cleanup Loop)**: Một phiên bảo trì thường xuyên nhằm giảm entropy có hệ thống trong codebase. Không phải sửa chữa khẩn cấp, mà là vận hành thường xuyên. -- **Đơn giản hóa harness (Harness Simplification)**: Khi năng lực mô hình cải thiện, định kỳ xóa các thành phần harness không còn cần thiết. Một ràng buộc cần thiết ngày hôm nay có thể là overhead không cần thiết ba tháng sau. -- **Dọn dẹp Idempotent**: Các hoạt động dọn dẹp tạo ra cùng kết quả bất kể chúng chạy bao nhiêu lần. Đảm bảo dọn dẹp vẫn an toàn ngay cả trong các kịch bản thất bại-thử lại. - -## Năm Chiều của Trạng thái Sạch - -```mermaid -flowchart LR - Work["Công việc tính năng hoàn thành"] --> Build{"Build vượt qua?"} - Build -->|có| Test{"Test vượt qua?"} - Build -->|không| Fix["Sửa trước khi thoát"] - Test -->|có| Record["Cập nhật feature list + tiến độ"] - Test -->|không| Fix - Record --> Cleanup["Xóa artifact tạm thời / mã debug"] - Cleanup --> Startup{"Đường dẫn khởi động chuẩn hoạt động?"} - Startup -->|có| Clean["Bàn giao sạch sẽ"] - Startup -->|không| Fix - Fix --> Build -``` - -```mermaid -flowchart LR - Dirty["Phiên kết thúc với
    test đỏ / tệp tạm / không cập nhật tiến độ"] --> Diagnose["Phiên tiếp theo đầu tiên phải
    tìm hiểu chuyện gì đã xảy ra"] - Diagnose --> Fragile["Công việc mới bắt đầu trên repo lộn xộn"] - Fragile --> More["Thêm tệp debug, thêm kiểm tra hỏng,
    thêm tiến độ không rõ ràng"] - More --> Dirty - - Clean["Phiên kết thúc với
    test xanh / tiến độ cập nhật / tệp tạm đã xóa"] --> Fast["Phiên tiếp theo có thể bắt đầu lập trình ngay"] - Fast --> Stable["Không cần cứu repo trước"] - Stable --> Clean -``` - -## Tại sao Điều này Xảy ra - -### Tăng trưởng Entropy là Trạng thái Mặc định - -Các quy luật tiến hóa phần mềm của Lehman cho chúng ta biết: các hệ thống trải qua thay đổi liên tục sẽ không thể tránh khỏi tăng độ phức tạp trừ khi được quản lý chủ động. Điều này đặc biệt đúng với AI coding agent — mỗi phiên giới thiệu các thay đổi, và nếu không dọn dẹp khi thoát, nợ kỹ thuật tích lũy theo cấp số nhân. - -Dữ liệu thực tế rất rõ ràng. Một dự án được phát triển với agent trong 12 tuần, không có chiến lược dọn dẹp: - -- Tuần 1: Tỷ lệ vượt qua build 100%, tỷ lệ vượt qua test 100%, khởi động phiên mới 5 phút -- Tuần 4: Build 95%, test 92%, khởi động 15 phút -- Tuần 8: Build 82%, test 78%, khởi động 35 phút -- Tuần 12: Build 68%, test 61%, khởi động 60+ phút - -Cùng dự án với chiến lược dọn dẹp: - -- Tuần 1: 100%, 100%, 5 phút -- Tuần 12: 97%, 95%, 9 phút - -Sau 12 tuần: tỷ lệ vượt qua build khác nhau 29 điểm phần trăm, thời gian khởi động phiên mới khác nhau 85%. Đây không phải lý thuyết — đây là sự khác biệt được quan sát. - -### Năm Chiều của Trạng thái Sạch - -Trạng thái sạch không chỉ là "mã được biên dịch." Đó là năm chiều được đánh giá cùng nhau: - -**Chiều Build**: Mã có build không có lỗi không? Đây là cơ bản nhất — phiên tiếp theo không nên phải sửa lỗi build trước tiên. - -**Chiều Test**: Tất cả test có vượt qua không? Bao gồm cả test tồn tại trước phiên — phiên có trách nhiệm không làm hỏng chức năng hiện có. Và nó phải được xác minh trong CI, không chỉ "hoạt động trên máy của tôi." - -**Chiều Tiến độ**: Tiến độ hiện tại có được ghi lại trong một artifact có thể đọc bởi máy không? Các subtask đã hoàn thành với tiêu chí vượt qua của chúng, các subtask đang tiến hành nhưng chưa hoàn thành với trạng thái hiện tại, các subtask chưa bắt đầu. Bản ghi tiến độ tốt giảm 60-80% thời gian chẩn đoán khởi động phiên. - -**Chiều Artifact**: Có các artifact tạm thời hoặc mơ hồ lỗi thời không? Debug log, tệp tạm, mã bị comment out, marker TODO — tất cả những thứ này tăng tải nhận thức cho phiên tiếp theo. - -**Chiều Khởi động**: Đường dẫn khởi động chuẩn có khả dụng không? Phiên tiếp theo có thể bắt đầu làm việc mà không cần can thiệp thủ công không? Khởi tạo môi trường, tải codebase, thu thập ngữ cảnh, lựa chọn tác vụ — các đường dẫn này không được bị hỏng. - -### "Dọn dẹp sau" Có nghĩa là Không bao giờ Dọn dẹp - -Cái bẫy tâm lý phổ biến nhất là "không có thời gian dọn dẹp trong phiên này, tôi sẽ làm lần sau." Nhưng phiên agent tiếp theo không biết bạn đã để lại gì — nó thấy một mớ mã và trạng thái không chắc chắn. Nó sẽ dành nhiều thời gian để suy ra "phần nào của mã này là có chủ ý và phần nào là tạm thời." - -Tệ hơn, mỗi phiên có mục tiêu tác vụ riêng của mình. Phiên mới ở đó để làm công việc mới, không phải dọn dẹp mớ bòng bong của phiên trước. Nó sẽ bỏ qua sự hỗn loạn và bắt đầu công việc mới trên đó, giới thiệu thêm hỗn loạn trên đỉnh hỗn loạn. Đây là vòng phản hồi tích cực của entropy. - -## Cách Làm Đúng - -### 1. Trạng thái Sạch như Yêu cầu Hoàn thành - -Định nghĩa rõ ràng trong harness: **hoàn thành phiên = tác vụ vượt qua xác minh VÀ kiểm tra trạng thái sạch vượt qua.** Thiếu bất kỳ cái nào có nghĩa là phiên không hoàn thành. Viết trong CLAUDE.md: - -``` -## Danh sách Kiểm tra Thoát Phiên -- [ ] Build vượt qua (npm run build) -- [ ] Tất cả test vượt qua (npm test) -- [ ] Feature list đã được cập nhật -- [ ] Không có mã debug còn lại (console.log, debugger, TODO) -- [ ] Đường dẫn khởi động chuẩn khả dụng (npm run dev) -``` - -### 2. Chiến lược Dọn dẹp Hai Chế độ - -Kết hợp hai chế độ dọn dẹp: - -**Dọn dẹp tức thì (ở cuối mỗi phiên)**: Dọn dẹp các artifact tạm thời được tạo trong phiên, cập nhật trạng thái feature list, đảm bảo build và test vượt qua. Đây là dọn dẹp "tính tham chiếu." - -**Dọn dẹp định kỳ (hàng tuần)**: Quét toàn hệ thống — xử lý các vấn đề cấu trúc tích lũy, cập nhật tài liệu chất lượng, chạy test benchmark để phát hiện trôi dạt. Đây là dọn dẹp "tracing." - -### 3. Duy trì Tài liệu Chất lượng - -Tài liệu chất lượng là một artifact đang hoạt động liên tục tính điểm mỗi module: - -```markdown -# Tài liệu Chất lượng - -## Module Xác thực Người dùng (Chất lượng: A) -- Xác minh vượt qua: Có -- Agent có thể hiểu: Có -- Độ ổn định test: Ổn định -- Ranh giới kiến trúc: Tuân thủ -- Quy ước mã: Được tuân theo - -## Module Thanh toán (Chất lượng: C) -- Xác minh vượt qua: Một phần (payment callback chưa được test) -- Agent có thể hiểu: Khó (logic trải rộng trên 3 tệp) -- Độ ổn định test: Không ổn định (2 test flaky) -- Ranh giới kiến trúc: Có vi phạm -- Quy ước mã: Được tuân theo một phần -``` - -Các phiên mới đọc tài liệu này và biết ngay nơi ưu tiên. Sửa module có điểm thấp nhất trước. - -### 4. Định kỳ Đơn giản hóa Harness - -Một hiểu biết quan trọng từ Anthropic: **mỗi thành phần harness tồn tại vì mô hình không thể thực hiện điều gì đó một cách đáng tin cậy. Nhưng khi các mô hình cải thiện, các giả định này trở nên lỗi thời.** Một ràng buộc cần thiết ba tháng trước có thể là overhead không cần thiết ngày hôm nay. - -Thực hành được khuyến nghị: Mỗi tháng, chọn một thành phần harness, tạm thời vô hiệu hóa nó, và chạy các tác vụ benchmark. Nếu kết quả không giảm sút, hãy xóa vĩnh viễn. Nếu có, hãy khôi phục hoặc thay thế bằng một thay thế nhẹ hơn. - -### 5. Các Hoạt động Dọn dẹp Phải là Idempotent - -Script dọn dẹp phải an toàn để chạy lặp đi lặp lại: - -```bash -# Các hoạt động dọn dẹp idempotent -rm -f /tmp/debug-*.log # -f đảm bảo không có lỗi khi tệp không tồn tại -git checkout -- .env.local # Khôi phục về trạng thái đã biết -npm run test # Xác minh dọn dẹp không làm hỏng gì -``` - -## Trường hợp Thực tế - -Một ứng dụng Electron được phát triển với agent trong 12 tuần, so sánh hai cách tiếp cận: - -**Không có chiến lược dọn dẹp** (nhóm kiểm soát): Tuần 12, tỷ lệ vượt qua build 68%, tỷ lệ vượt qua test 61%, khởi động phiên mới 60+ phút, artifact lỗi thời 103. - -**Với chiến lược dọn dẹp** (nhóm thực nghiệm): Kiểm tra trạng thái sạch đầy đủ ở cuối mỗi phiên + vòng lặp dọn dẹp hàng tuần. Tuần 12, tỷ lệ vượt qua build 97%, tỷ lệ vượt qua test 95%, khởi động phiên mới 9 phút, artifact lỗi thời 11. - -Đến tuần 12, tỷ lệ vượt qua build của nhóm thực nghiệm cao hơn 29 điểm phần trăm, tỷ lệ vượt qua test cao hơn 34 điểm, và thời gian khởi động phiên mới thấp hơn 85%. - -## Những Điểm chính cần Nhớ - -- **Trạng thái sạch là điều kiện cần thiết cho hoàn thành phiên** — không phải dọn dẹp tùy chọn, mà là một phần của "định nghĩa hoàn thành." -- **Tất cả năm chiều đều bắt buộc** — build, test, tiến độ, artifact, khởi động — mỗi cái phải được kiểm tra rõ ràng. -- **Tài liệu chất lượng làm cho sức khỏe codebase có thể theo dõi** — bạn chỉ có thể sửa những gì bạn biết đang suy giảm. -- **Định kỳ đơn giản hóa harness** — khi năng lực mô hình cải thiện, hãy xóa các ràng buộc không còn cần thiết. -- **"Dọn dẹp sau" bằng với không bao giờ dọn dẹp** — tăng trưởng entropy là mặc định; chỉ có dọn dẹp chủ động mới chống lại nó. - -## Đọc thêm - -- [Clean Code - Robert C. Martin](https://www.goodreads.com/book/show/3735293-clean-code) — Các nguyên tắc có hệ thống về sự sạch sẽ của mã -- [Harness Engineering - OpenAI](https://openai.com/index/harness-engineering/) — Khả năng tái tạo như một yêu cầu thiết kế harness cốt lõi -- [Effective Harnesses - Anthropic](https://www.anthropic.com/engineering/effective-harnesses-for-long-running-agents) — Vai trò quan trọng của thoát phiên sạch cho độ tin cậy dài hạn -- [Programs, Life Cycles, and Laws of Software Evolution - Lehman](https://ieeexplore.ieee.org/document/1702314) — Các quy luật tiến hóa phần mềm chứng minh độ phức tạp hệ thống tất yếu tăng mà không có bảo trì chủ động - -## Bài tập - -1. **Danh sách Kiểm tra Trạng thái Sạch**: Thiết kế một danh sách kiểm tra thoát phiên cho codebase của bạn bao phủ tất cả năm chiều. Áp dụng nó qua 5 phiên liên tiếp và ghi lại vi phạm theo từng chiều. - -2. **So sánh Benchmark**: Sử dụng một bộ tác vụ cố định với hai biến thể harness (có/không có yêu cầu trạng thái sạch). So sánh tỷ lệ hoàn thành, số lần thử lại và tỷ lệ thoát lỗi. - -3. **Thực hành Đơn giản hóa Harness**: Chọn một thành phần harness, tạm thời vô hiệu hóa nó, và chạy các tác vụ benchmark. So sánh kết quả có và không có nó. Quyết định có giữ, xóa hay thay thế. \ No newline at end of file diff --git a/courses/learn-harness-engineering/docs/vi/projects/index.md b/courses/learn-harness-engineering/docs/vi/projects/index.md deleted file mode 100644 index adde11e3..00000000 --- a/courses/learn-harness-engineering/docs/vi/projects/index.md +++ /dev/null @@ -1,23 +0,0 @@ -# Chào mừng đến với Dự án - -Đây là phần thực hành của Learn Harness Engineering. Chỉ đọc các bài giảng là không đủ—bạn cần phải tự mình xây dựng các môi trường và quan sát cách Codex, Claude Code hoặc các AI agent khác hoạt động dưới các quy tắc khác nhau. - -## Tổng quan về Dự án - -Khóa học này bao gồm 6 dự án thực hành tiến triển dần, dạy bạn cách xây dựng một môi trường làm việc agent đáng tin cậy từ đầu: - -1. **Chỉ Prompt vs. Ưu tiên Quy tắc**: So sánh cách agent hoạt động chỉ với prompt so với một harness cơ bản. -2. **Không gian làm việc Agent đọc được**: Tìm hiểu cách cấu trúc kho lưu trữ của bạn để thân thiện với AI và thiết lập cơ chế bàn giao. -3. **Tính liên tục đa phiên**: Thiết kế các tệp trạng thái và kịch bản khởi tạo để agent của bạn có thể tiếp tục công việc một cách liền mạch qua nhiều phiên. -4. **Phản hồi Runtime và Kiểm soát Phạm vi**: Giới thiệu các công cụ cho phép agent tự kiểm tra mã của mình và sửa lỗi trong quá trình thực thi. -5. **Tự xác minh và Phân tách Vai trò**: Xây dựng một cơ chế đánh giá độc lập để ngăn chặn ảo giác (hallucinations) và việc tuyên bố thành công sớm. -6. **Harness Hoàn chỉnh (Đồ án)**: Lắp ráp một môi trường làm việc agent hoàn chỉnh, có thể quan sát được từ đầu đến cuối. - -## Cách Thực hiện - -Mỗi thư mục dự án thường chứa: -- `starter/`: Không gian làm việc bắt đầu của bạn. -- `solution/`: Cách triển khai tham khảo (nếu bạn bị mắc kẹt). -- Các hướng dẫn tác vụ nêu chi tiết bối cảnh của bạn và các mục tiêu cụ thể. - -Sử dụng AI Coding Agent ưa thích của bạn (ví dụ: Claude Code, Cursor, Trae) để hoàn thành các tác vụ bên trong thư mục `starter/`. diff --git a/courses/learn-harness-engineering/docs/vi/projects/project-01-baseline-vs-minimal-harness/index.md b/courses/learn-harness-engineering/docs/vi/projects/project-01-baseline-vs-minimal-harness/index.md deleted file mode 100644 index d7abd3b4..00000000 --- a/courses/learn-harness-engineering/docs/vi/projects/project-01-baseline-vs-minimal-harness/index.md +++ /dev/null @@ -1,25 +0,0 @@ -[English Version →](../../../en/projects/project-01-baseline-vs-minimal-harness/) | [中文版本 →](../../../zh/projects/project-01-baseline-vs-minimal-harness/) - -> Bài giảng liên quan: [Bài 01. Mô hình mạnh không có nghĩa là thực thi đáng tin cậy](./../../lectures/lecture-01-why-capable-agents-still-fail/index.md) · [Bài 02. Harness thực sự có nghĩa là gì](./../../lectures/lecture-02-what-a-harness-actually-is/index.md) -> Tệp mẫu: [templates/](https://github.com/walkinglabs/learn-harness-engineering/blob/main/docs/vi/resources/templates/) - -# Dự án 01. Chỉ Prompt vs. Ưu tiên Quy tắc: Sự Khác biệt Lớn thế Nào - -## Bạn Làm Gì - -Xây dựng một ứng dụng Electron knowledge-base shell tối giản — một cửa sổ với danh sách tài liệu bên trái, panel Q&A bên phải, và thư mục dữ liệu cục bộ. Bản thân tác vụ không phức tạp. Điều phức tạp là cách bạn để agent hoàn thành nó. - -Bạn chạy hai lần. Lần đầu: chỉ một prompt, không chuẩn bị gì. Lần hai: `AGENTS.md`, `init.sh`, `feature_list.json` được đặt sẵn trong repo. Sau đó so sánh. - -Cốt lõi của dự án này không phải là viết mã — mà là tìm hiểu khoảng cách lớn thế nào giữa "dành 15 phút chuẩn bị quy tắc trước" và "cứ để agent tự làm." - -## Công cụ - -- Claude Code hoặc Codex (chọn một, sử dụng cho cả hai lần chạy) -- Git (quản lý branch và so sánh) -- Node.js + Electron (tech stack dự án) -- Đồng hồ đo thời gian (ghi lại thời gian mỗi lần chạy) - -## Cơ chế Harness - -Harness tối giản: `AGENTS.md` + `init.sh` + `feature_list.json` diff --git a/courses/learn-harness-engineering/docs/vi/projects/project-02-agent-readable-workspace/index.md b/courses/learn-harness-engineering/docs/vi/projects/project-02-agent-readable-workspace/index.md deleted file mode 100644 index 68a4fa94..00000000 --- a/courses/learn-harness-engineering/docs/vi/projects/project-02-agent-readable-workspace/index.md +++ /dev/null @@ -1,22 +0,0 @@ -[English Version →](../../../en/projects/project-02-agent-readable-workspace/) | [中文版本 →](../../../zh/projects/project-02-agent-readable-workspace/) - -> Bài giảng liên quan: [Bài 03. Biến kho lưu trữ thành nguồn sự thật duy nhất](./../../lectures/lecture-03-why-the-repository-must-become-the-system-of-record/index.md) · [Bài 04. Chia hướng dẫn ra thành nhiều tệp](./../../lectures/lecture-04-why-one-giant-instruction-file-fails/index.md) -> Tệp mẫu: [templates/](https://github.com/walkinglabs/learn-harness-engineering/blob/main/docs/vi/resources/templates/) - -# Dự án 02. Làm cho Dự án Có thể Đọc Được và Tiếp tục từ Nơi Đã Dừng - -## Bạn Làm Gì - -Thêm "khả năng đọc được" vào repo để một agent mới có thể nhanh chóng hiểu cấu trúc dự án, biết tiến độ hiện tại, và tiếp tục công việc. Cụ thể: triển khai import tài liệu, document detail view, và local persistence, hoàn thành qua hai phiên. - -Bạn chạy hai lần: lần đầu không có sự trợ giúp nào, lần hai với `ARCHITECTURE.md`, `PRODUCT.md`, và `session-handoff.md` được đặt sẵn trong repo. - -## Công cụ - -- Claude Code hoặc Codex -- Git -- Node.js + Electron - -## Cơ chế Harness - -Không gian làm việc agent có thể đọc được + tệp trạng thái liên tục diff --git a/courses/learn-harness-engineering/docs/vi/projects/project-03-multi-session-continuity/index.md b/courses/learn-harness-engineering/docs/vi/projects/project-03-multi-session-continuity/index.md deleted file mode 100644 index a0ac963c..00000000 --- a/courses/learn-harness-engineering/docs/vi/projects/project-03-multi-session-continuity/index.md +++ /dev/null @@ -1,22 +0,0 @@ -[English Version →](../../../en/projects/project-03-multi-session-continuity/) | [中文版本 →](../../../zh/projects/project-03-multi-session-continuity/) - -> Bài giảng liên quan: [Bài 05. Duy trì ngữ cảnh qua các phiên](./../../lectures/lecture-05-why-long-running-tasks-lose-continuity/index.md) · [Bài 06. Khởi tạo trước mỗi phiên agent](./../../lectures/lecture-06-why-initialization-needs-its-own-phase/index.md) -> Tệp mẫu: [templates/](https://github.com/walkinglabs/learn-harness-engineering/blob/main/docs/vi/resources/templates/) - -# Dự án 03. Giữ cho Agent Tiếp tục Làm việc Qua Các Lần Khởi động Lại Phiên - -## Bạn Làm Gì - -Thêm kiểm soát phạm vi và cổng xác minh vào agent. Triển khai document chunking, metadata extraction, hiển thị tiến độ indexing, và luồng Q&A dựa trên trích dẫn. Sử dụng `feature_list.json` để theo dõi trạng thái tính năng — mỗi lần một tính năng, không đánh dấu là "pass" mà không có bằng chứng xác minh. - -Bạn chạy hai lần: lần đầu không có ràng buộc, lần hai với thực thi nghiêm ngặt. - -## Công cụ - -- Claude Code hoặc Codex -- Git -- Node.js + Electron - -## Cơ chế Harness - -Nhật ký tiến độ + bàn giao phiên + tính liên tục đa phiên diff --git a/courses/learn-harness-engineering/docs/vi/projects/project-04-incremental-indexing/index.md b/courses/learn-harness-engineering/docs/vi/projects/project-04-incremental-indexing/index.md deleted file mode 100644 index fdaba738..00000000 --- a/courses/learn-harness-engineering/docs/vi/projects/project-04-incremental-indexing/index.md +++ /dev/null @@ -1,22 +0,0 @@ -[English Version →](../../../en/projects/project-04-incremental-indexing/) | [中文版本 →](../../../zh/projects/project-04-incremental-indexing/) - -> Bài giảng liên quan: [Bài 07. Vạch ranh giới tác vụ rõ ràng cho agent](./../../lectures/lecture-07-why-agents-overreach-and-under-finish/index.md) · [Bài 08. Sử dụng feature list để ràng buộc những gì agent làm](./../../lectures/lecture-08-why-feature-lists-are-harness-primitives/index.md) -> Tệp mẫu: [templates/](https://github.com/walkinglabs/learn-harness-engineering/blob/main/docs/vi/resources/templates/) - -# Dự án 04. Sử dụng Phản hồi Runtime để Điều chỉnh Hành vi Agent - -## Bạn Làm Gì - -Thêm observability runtime (log khởi động, log import/indexing, trạng thái lỗi) và các ràng buộc kiến trúc để ngăn chặn vi phạm xuyên lớp. Cài một lỗi runtime để agent sửa. - -Bạn chạy hai lần: lần đầu không có log hoặc ràng buộc, lần hai với các công cụ và quy tắc phù hợp. - -## Công cụ - -- Claude Code hoặc Codex -- Git -- Node.js + Electron - -## Cơ chế Harness - -Phản hồi runtime + kiểm soát phạm vi + indexing tăng dần diff --git a/courses/learn-harness-engineering/docs/vi/projects/project-05-grounded-qa-verification/index.md b/courses/learn-harness-engineering/docs/vi/projects/project-05-grounded-qa-verification/index.md deleted file mode 100644 index 3c1302d2..00000000 --- a/courses/learn-harness-engineering/docs/vi/projects/project-05-grounded-qa-verification/index.md +++ /dev/null @@ -1,22 +0,0 @@ -[English Version →](../../../en/projects/project-05-grounded-qa-verification/) | [中文版本 →](../../../zh/projects/project-05-grounded-qa-verification/) - -> Bài giảng liên quan: [Bài 09. Ngăn agent tuyên bố hoàn thành quá sớm](./../../lectures/lecture-09-why-agents-declare-victory-too-early/index.md) · [Bài 10. Chỉ testing end-to-end mới là xác minh thực sự](./../../lectures/lecture-10-why-end-to-end-testing-changes-results/index.md) -> Tệp mẫu: [templates/](https://github.com/walkinglabs/learn-harness-engineering/blob/main/docs/vi/resources/templates/) - -# Dự án 05. Để Agent Xác minh Công việc của Chính nó - -## Bạn Làm Gì - -Triển khai phân tách vai trò — một generator thực hiện, một evaluator review, và tùy chọn một planner. Chạy ba lần để đo lường tác động của mỗi vai trò được thêm vào. - -Chọn một tính năng nâng cấp thực chất (hội thoại đa lượt, thiết kế lại citation panel, hoặc lọc tài liệu) và giữ nó nhất quán qua tất cả các lần chạy. - -## Công cụ - -- Claude Code hoặc Codex -- Git -- Node.js + Electron - -## Cơ chế Harness - -Tự xác minh + Q&A có grounding + hoàn thành dựa trên bằng chứng diff --git a/courses/learn-harness-engineering/docs/vi/projects/project-06-runtime-observability-and-debugging/index.md b/courses/learn-harness-engineering/docs/vi/projects/project-06-runtime-observability-and-debugging/index.md deleted file mode 100644 index f36607d4..00000000 --- a/courses/learn-harness-engineering/docs/vi/projects/project-06-runtime-observability-and-debugging/index.md +++ /dev/null @@ -1,25 +0,0 @@ -[English Version →](../../../en/projects/project-06-runtime-observability-and-debugging/) | [中文版本 →](../../../zh/projects/project-06-runtime-observability-and-debugging/) - -> Bài giảng liên quan: [Bài 11. Làm cho runtime của agent có thể quan sát được](./../../lectures/lecture-11-why-observability-belongs-inside-the-harness/index.md) · [Bài 12. Bàn giao sạch sẽ ở cuối mỗi phiên](./../../lectures/lecture-12-why-every-session-must-leave-a-clean-state/index.md) -> Tệp mẫu: [templates/](https://github.com/walkinglabs/learn-harness-engineering/blob/main/docs/vi/resources/templates/) - -# Dự án 06. Xây dựng Harness Agent Đầy đủ (Capstone) - -## Bạn Làm Gì - -Đây là dự án capstone. Tập hợp tất cả những gì đã học trong năm dự án đầu tiên, chạy một benchmark đầy đủ, sau đó thực hiện một lần dọn dẹp để xác minh chất lượng có thể duy trì được. - -Sử dụng một bộ tác vụ đa tính năng cố định bao phủ toàn bộ product slice: import tài liệu, indexing, Q&A dựa trên trích dẫn, observability runtime, và trạng thái repo có thể đọc và khởi động lại. Lần đầu chạy với baseline harness yếu, sau đó với harness mạnh nhất của bạn, sau đó dọn dẹp và chạy lại. Cuối cùng, thực hiện thí nghiệm ablation harness — xóa từng thành phần một và xem cái nào thực sự quan trọng. - -## Công cụ - -- Claude Code hoặc Codex -- Git -- Node.js + Electron -- Mẫu tài liệu chất lượng -- Rubric evaluator -- Tất cả các thành phần harness tích lũy từ năm dự án đầu tiên - -## Cơ chế Harness - -Harness đầy đủ: tất cả các cơ chế + observability + nghiên cứu ablation diff --git a/courses/learn-harness-engineering/docs/vi/resources/index.md b/courses/learn-harness-engineering/docs/vi/resources/index.md deleted file mode 100644 index 9b1b17bf..00000000 --- a/courses/learn-harness-engineering/docs/vi/resources/index.md +++ /dev/null @@ -1,52 +0,0 @@ -# Thư viện Tài nguyên - -Thư mục này chuyển đổi các phương pháp của khóa học thành các mẫu sao chép ngay và tài liệu tham khảo ngắn gọn mà bạn có thể sử dụng trong một kho lưu trữ thực tế. - -## Khi nào nên sử dụng - -Bắt đầu từ đây khi bạn muốn Codex, Claude Code, hoặc một coding agent khác hoạt động qua nhiều phiên làm việc mà không cần liên tục thiết lập lại cài đặt, trạng thái và phạm vi. - -Nó đặc biệt hữu ích khi: - -- công việc kéo dài qua nhiều phiên -- có nhiều tính năng và dễ bị bỏ dở giữa chừng -- các agent có xu hướng tuyên bố thành công quá sớm -- các bước khởi động bị tìm lại từ đầu mỗi lần - -## Bắt đầu từ đây - -Đối với thiết lập tối giản, hãy bắt đầu với: - -- hướng dẫn gốc: [`templates/AGENTS.md`](./templates/AGENTS.md) hoặc [`templates/CLAUDE.md`](./templates/CLAUDE.md) -- trạng thái tính năng: [`templates/feature_list.json`](./templates/feature_list.json) -- nhật ký tiến độ: [`templates/claude-progress.md`](./templates/claude-progress.md) -- tham chiếu script khởi động: `docs/vi/resources/templates/init.sh` - -Sau đó thêm: - -- bàn giao phiên: [`templates/session-handoff.md`](./templates/session-handoff.md) -- danh sách kiểm tra thoát sạch: [`templates/clean-state-checklist.md`](./templates/clean-state-checklist.md) -- tiêu chí đánh giá: [`templates/evaluator-rubric.md`](./templates/evaluator-rubric.md) - -Nếu bạn muốn cấu trúc kho lưu trữ kiểu OpenAI đầy đủ hơn từ bài viết -"Harness engineering", hãy sử dụng gói nâng cao: - -- [`openai-advanced/index.md`](./openai-advanced/index.md) - -## Cấu trúc Thư viện - -- [`templates/`](./templates/index.md): các mẫu để sao chép vào một kho lưu trữ thực tế -- [`reference/`](./reference/index.md): ghi chú phương pháp, luồng khởi động và sơ đồ chế độ lỗi -- [`openai-advanced/`](./openai-advanced/index.md): khung kho lưu trữ nâng cao, tài liệu nguồn sự thật, và các mẫu quản trị ưu tiên agent - -## Gói Tối giản Khuyên dùng - -- `AGENTS.md` hoặc `CLAUDE.md` -- `feature_list.json` -- `claude-progress.md` -- `init.sh` - -Bốn tệp này là đủ để làm cho hầu hết các quy trình làm việc của agent ổn định hơn rõ rệt. - -Khi kho lưu trữ phát triển thành một hệ thống hoạt động lâu dài hơn với nhiều domain, kế hoạch hoạt động, chấm điểm chất lượng và chính sách độ tin cậy, hãy chuyển sang gói -[`openai-advanced/`](./openai-advanced/index.md) thay vì cố mở rộng gói tối giản quá xa. diff --git a/courses/learn-harness-engineering/docs/vi/resources/openai-advanced/index.md b/courses/learn-harness-engineering/docs/vi/resources/openai-advanced/index.md deleted file mode 100644 index 85854722..00000000 --- a/courses/learn-harness-engineering/docs/vi/resources/openai-advanced/index.md +++ /dev/null @@ -1,74 +0,0 @@ -[English Version →](../../../en/resources/openai-advanced/) | [中文版本 →](../../../zh/resources/openai-advanced/) - -# Gói Harness Nâng cao OpenAI - -Gói này tập hợp thiết kế harness được mô tả trong bài viết "Harness Engineering" của OpenAI thành một bộ tệp bắt đầu có thể áp dụng và cấu trúc SOP đi kèm. - -## Tại sao Nó Tồn tại - -Bài viết harness engineering mô tả các nguyên tắc cấp cao: kho lưu trữ là hệ thống ghi chép, bộ nhớ ngoại hóa, kiểm tra cơ học thay vì ký ức, và các vòng phản hồi phục hồi. Gói này biến các nguyên tắc đó thành: - -- bộ tài liệu cấu trúc rõ ràng cho một repo thực tế -- tính điểm chất lượng theo domain sản phẩm và lớp kiến trúc -- thư mục tài liệu tham khảo thân thiện với model -- các quy trình vận hành chuẩn cho kiến trúc, thu thập kiến thức, và xác minh runtime - -## Bố cục Bắt đầu Có sẵn - -Gói bắt đầu trong [`repo-template/`](./repo-template/index.md) phản ánh cấu trúc dưới đây: - -```text -AGENTS.md -ARCHITECTURE.md -docs/ -├── design-docs/ -│ ├── index.md -│ └── core-beliefs.md -├── exec-plans/ -│ ├── active/ -│ ├── completed/ -│ └── tech-debt-tracker.md -├── generated/ -│ └── db-schema.md -├── product-specs/ -│ ├── index.md -│ └── new-user-onboarding.md -├── references/ -│ ├── design-system-reference-llms.txt -│ ├── nixpacks-llms.txt -│ └── uv-llms.txt -├── DESIGN.md -├── FRONTEND.md -├── PLANS.md -├── PRODUCT_SENSE.md -├── QUALITY_SCORE.md -├── RELIABILITY.md -└── SECURITY.md -``` - -## Cách Áp dụng Nó - -1. Bắt đầu từ gói tối giản nếu repo của bạn vẫn còn nhỏ. -2. Sao chép các tệp trong [`repo-template/`](./repo-template/index.md) vào kho lưu trữ của bạn khi bạn cần cấu trúc mạnh hơn. -3. Giữ `AGENTS.md` ngắn. Coi nó như bộ định tuyến vào các tài liệu sâu hơn, không phải như bách khoa toàn thư. -4. Cập nhật các tài liệu chất lượng, độ tin cậy và kế hoạch như một phần của công việc thông thường, không phải như một ngày dọn dẹp riêng biệt. -5. Giữ các artifact được tạo ra và tài liệu tham khảo bên ngoài rõ ràng để agent có thể tìm thấy chúng mà không cần dựa vào lịch sử chat. - -## Thư viện SOP - -Thư mục [`sops/`](./sops/index.md) biến các sơ đồ của bài viết thành các quy trình vận hành từng bước: - -- thiết lập kiến trúc domain phân lớp -- mã hóa kiến thức ẩn vào kho lưu trữ -- stack observability cục bộ và workflow vòng phản hồi -- vòng lặp xác minh Chrome DevTools cho công việc UI - -## Nguyên tắc Thiết kế - -- Điểm đầu vào ngắn, tài liệu liên kết sâu hơn -- Kho lưu trữ là hệ thống ghi chép -- Kiểm tra cơ học tốt hơn các quy tắc được nhớ -- Kế hoạch và lịch sử chất lượng nằm bên cạnh mã -- Dọn dẹp và đơn giản hóa là trách nhiệm hạng nhất - -Gói này có chủ ý theo quan điểm, nhưng nó vẫn nên được điều chỉnh cho dự án của bạn thay vì sao chép mù quáng. diff --git a/courses/learn-harness-engineering/docs/vi/resources/openai-advanced/repo-template/AGENTS.md b/courses/learn-harness-engineering/docs/vi/resources/openai-advanced/repo-template/AGENTS.md deleted file mode 100644 index adad44be..00000000 --- a/courses/learn-harness-engineering/docs/vi/resources/openai-advanced/repo-template/AGENTS.md +++ /dev/null @@ -1,55 +0,0 @@ -# AGENTS.md - -Kho lưu trữ này được tối ưu hóa cho công việc coding-agent chạy lâu. Giữ tệp này ngắn. Sử dụng nó như lớp định tuyến vào các tài liệu hệ thống ghi chép, không phải như một đống hướng dẫn khổng lồ. - -## Quy trình Khởi động - -Trước khi thay đổi mã: - -1. Xác nhận thư mục gốc repo bằng `pwd`. -2. Đọc `ARCHITECTURE.md` để biết bản đồ hệ thống hiện tại và các quy tắc phụ thuộc cứng. -3. Đọc `docs/QUALITY_SCORE.md` để xem domain hoặc lớp nào yếu nhất. -4. Đọc `docs/PLANS.md`, sau đó mở kế hoạch active bạn đang làm việc từ đó. -5. Đọc spec sản phẩm liên quan trong `docs/product-specs/`. -6. Chạy đường dẫn bootstrap và xác minh chuẩn cho repo này. -7. Nếu xác minh baseline đang thất bại, hãy sửa baseline trước khi thêm phạm vi. - -## Bản đồ Định tuyến - -- `ARCHITECTURE.md`: bản đồ domain, mô hình lớp, quy tắc phụ thuộc -- `docs/design-docs/index.md`: các quyết định thiết kế và niềm tin cốt lõi -- `docs/product-specs/index.md`: các hành vi sản phẩm hiện tại và mục tiêu chấp nhận -- `docs/PLANS.md`: vòng đời kế hoạch và chính sách kế hoạch thực thi -- `docs/QUALITY_SCORE.md`: sức khỏe domain sản phẩm và lớp -- `docs/RELIABILITY.md`: tín hiệu runtime, benchmark và kỳ vọng khởi động lại -- `docs/SECURITY.md`: bí mật, sandbox, dữ liệu và quy tắc hành động bên ngoài -- `docs/FRONTEND.md`: ràng buộc UI, quy tắc design system, kiểm tra accessibility - -## Hợp đồng Làm việc - -- Làm việc từ một kế hoạch có ranh giới hoặc slice tính năng tại một thời điểm. -- Không đánh dấu công việc xong chỉ từ kiểm tra mã; cần bằng chứng có thể chạy được. -- Nếu bạn thay đổi hành vi, hãy cập nhật tài liệu sản phẩm, kế hoạch hoặc độ tin cậy phù hợp trong cùng phiên. -- Nếu bạn thấy phản hồi review lặp đi lặp lại, hãy thúc đẩy nó thành quy tắc cơ học, kiểm tra hoặc linter thay vì giải thích lại trong chat. -- Giữ tài liệu được tạo ra trong `docs/generated/` và tài liệu tham khảo nguồn trong `docs/references/`. -- Ưu tiên thêm tài liệu nhỏ, hiện tại hơn là phát triển tệp này. - -## Định nghĩa Hoàn thành - -Một thay đổi chỉ xong khi tất cả những điều sau đây là đúng: - -- hành vi mục tiêu đã được triển khai -- xác minh cần thiết đã thực sự chạy -- bằng chứng được liên kết từ kế hoạch hoặc tài liệu chất lượng liên quan -- các tài liệu bị ảnh hưởng vẫn là hiện tại -- kho lưu trữ có thể khởi động lại sạch sẽ từ đường dẫn khởi động chuẩn - -## Cuối Phiên - -Trước khi kết thúc phiên: - -1. Cập nhật kế hoạch thực thi active. -2. Cập nhật `docs/QUALITY_SCORE.md` nếu bất kỳ domain hoặc lớp nào thay đổi có ý nghĩa. -3. Ghi lại nợ mới trong `docs/exec-plans/tech-debt-tracker.md` nếu bạn đã hoãn nó. -4. Di chuyển các kế hoạch đã hoàn thành sang `docs/exec-plans/completed/` khi phù hợp. -5. Để repo ở trạng thái có thể khởi động lại với hành động tiếp theo rõ ràng. diff --git a/courses/learn-harness-engineering/docs/vi/resources/openai-advanced/repo-template/ARCHITECTURE.md b/courses/learn-harness-engineering/docs/vi/resources/openai-advanced/repo-template/ARCHITECTURE.md deleted file mode 100644 index 96221863..00000000 --- a/courses/learn-harness-engineering/docs/vi/resources/openai-advanced/repo-template/ARCHITECTURE.md +++ /dev/null @@ -1,55 +0,0 @@ -# ARCHITECTURE.md - -Tệp này là bản đồ cấp cao nhất của hệ thống. Nó nên ngắn gọn và trỏ đến các tài liệu sâu hơn khi cần. - -## Hình dạng Hệ thống - -- Sản phẩm: `[thay thế bằng tên sản phẩm]` -- Workflow người dùng chính: `[thay thế bằng workflow chính]` -- Bề mặt runtime: `[desktop / web / cli / services / workers]` -- Nguồn sự thật cho hành vi sản phẩm: `docs/product-specs/` - -## Bản đồ Domain - -| Domain | Mục đích | Điểm đầu vào chính | Spec liên quan | -|--------|---------|----------------------|----------------| -| `[domain-a]` | `[những gì nó sở hữu]` | `[modules / routes / commands]` | `[đường dẫn spec]` | -| `[domain-b]` | `[những gì nó sở hữu]` | `[modules / routes / commands]` | `[đường dẫn spec]` | - -## Mô hình Lớp - -Sử dụng mô hình định hướng cố định để agent không tự phát minh ra kiến trúc ad hoc: - -`Types -> Config -> Repo -> Service -> Runtime -> UI` - -Các mối quan tâm xuyên suốt nên đi vào qua các ranh giới provider hoặc adapter rõ ràng thay vì tiếp cận trực tiếp qua các lớp. - -## Quy tắc Phụ thuộc Cứng - -- Các lớp thấp hơn không được phụ thuộc vào các lớp cao hơn. -- UI không được bỏ qua các hợp đồng runtime hoặc service. -- Truy cập dữ liệu phải đi qua các repository hoặc adapter tương đương. -- Các tiện ích dùng chung phải là chung chung và không được tích lũy logic domain. -- Các phụ thuộc mới nên được chứng minh trong kế hoạch hoặc tài liệu thiết kế phù hợp. - -## Giao diện Xuyên suốt - -| Mối quan tâm | Ranh giới được phê duyệt | Ghi chú | -|--------|-------------------|---------| -| Logging và tracing | `[đường dẫn provider / utility]` | `[chỉ có cấu trúc, không sử dụng console ad hoc]` | -| Auth | `[đường dẫn provider]` | `[quy tắc token/session]` | -| External API | `[đường dẫn client hoặc provider]` | `[hướng dẫn rate limit / retry]` | -| Feature flags | `[ranh giới flag]` | `[quyền sở hữu]` | - -## Điểm Nóng Hiện tại - -- `[khu vực khó thay đổi an toàn nhất cho agent]` -- `[khu vực có ranh giới yếu hoặc test dễ vỡ]` - -## Danh sách Kiểm tra Thay đổi - -Khi bạn chạm vào mã liên quan đến kiến trúc: - -1. Cập nhật tệp này nếu bản đồ domain hoặc ranh giới được phép thay đổi. -2. Cập nhật tài liệu thiết kế liên quan trong `docs/design-docs/` nếu lý luận thay đổi. -3. Thêm hoặc cập nhật kiểm tra có thể thực thi nếu quy tắc nên được thực thi cơ học. diff --git a/courses/learn-harness-engineering/docs/vi/resources/openai-advanced/repo-template/docs/DESIGN.md b/courses/learn-harness-engineering/docs/vi/resources/openai-advanced/repo-template/docs/DESIGN.md deleted file mode 100644 index c7159d91..00000000 --- a/courses/learn-harness-engineering/docs/vi/resources/openai-advanced/repo-template/docs/DESIGN.md +++ /dev/null @@ -1,25 +0,0 @@ -# DESIGN.md - -Tệp này là điểm đầu vào thiết kế. Giữ nó ngắn gọn và sử dụng nó để định tuyến vào các tệp chi tiết hơn trong `docs/design-docs/`. - -## Mục đích - -Ghi lại các quyết định thiết kế sản phẩm và hệ thống lâu bền nên tồn tại vượt ra ngoài một chat, sprint, hoặc bộ nhớ reviewer đơn lẻ. - -## Đọc Điều này Khi - -- bạn cần triết lý thiết kế hiện tại -- bạn sắp giới thiệu một mẫu mới -- bạn cần biết quyết định thiết kế nào đã được giải quyết so với vẫn đang mở - -## Tài liệu Thiết kế Chuẩn - -- `docs/design-docs/index.md`: chỉ mục các tài liệu đã được chấp nhận, đề xuất và không dùng nữa -- `docs/design-docs/core-beliefs.md`: niềm tin agent-first toàn dự án - -## Quy tắc Thiết kế - -- Giữ các tài liệu thiết kế nhỏ và hiện tại. -- Ưu tiên một tài liệu cho mỗi khu vực quyết định. -- Liên kết tài liệu thiết kế từ các kế hoạch và spec khi một thay đổi phụ thuộc vào chúng. -- Nếu một quy tắc thiết kế trở nên quan trọng về mặt vận hành, hãy thúc đẩy nó thành một kiểm tra tự động hoặc cập nhật `ARCHITECTURE.md`. diff --git a/courses/learn-harness-engineering/docs/vi/resources/openai-advanced/repo-template/docs/FRONTEND.md b/courses/learn-harness-engineering/docs/vi/resources/openai-advanced/repo-template/docs/FRONTEND.md deleted file mode 100644 index a18c4bba..00000000 --- a/courses/learn-harness-engineering/docs/vi/resources/openai-advanced/repo-template/docs/FRONTEND.md +++ /dev/null @@ -1,23 +0,0 @@ -# FRONTEND.md - -Tệp này định nghĩa các kỳ vọng frontend ổn định để agent không phát minh ra các mẫu UI một cách không thể đoán trước. - -## Nguyên tắc UI - -- Tối ưu hóa cho sự rõ ràng trước sự mới lạ. -- Giữ các luồng tương tác có thể khám phá và khởi động lại được. -- Ưu tiên một số ít component tái sử dụng thay vì các biến thể một lần. -- Kiểm tra accessibility là một phần của xác minh thông thường, không phải công việc đánh bóng. - -## Guardrail - -- Ghi lại design system hoặc thư viện component trong `docs/references/`. -- Ghi lại các trạng thái quan trọng dành cho người dùng: trống, đang tải, thành công, lỗi, thử lại. -- Giữ copy, hành vi bàn phím và hệ thống phân cấp trực quan nhất quán qua các luồng. -- Khi một lỗi UI được sửa, hãy thêm hoặc cập nhật bước xác minh phù hợp. - -## Kỳ vọng Xác minh - -- Ghi lại bằng chứng cho các user journey quan trọng. -- Ghi lại các bước xác minh browser hoặc runtime trong kế hoạch liên quan. -- Nếu các hồi quy trực quan phổ biến, hãy chuẩn hóa kiểm tra screenshot hoặc DOM. diff --git a/courses/learn-harness-engineering/docs/vi/resources/openai-advanced/repo-template/docs/PLANS.md b/courses/learn-harness-engineering/docs/vi/resources/openai-advanced/repo-template/docs/PLANS.md deleted file mode 100644 index 95a79226..00000000 --- a/courses/learn-harness-engineering/docs/vi/resources/openai-advanced/repo-template/docs/PLANS.md +++ /dev/null @@ -1,34 +0,0 @@ -# PLANS.md - -Tệp này định nghĩa cách các kế hoạch thực thi được tạo, cập nhật, hoàn thành và lưu trữ. - -## Khi Cần Một Kế hoạch - -Tạo một kế hoạch thực thi khi công việc: - -- trải dài hơn một phiên -- thay đổi nhiều hơn một hệ thống con -- có rủi ro xác minh hoặc triển khai không tầm thường -- phụ thuộc vào các quyết định mở nên được ghi lại - -## Vị trí Kế hoạch - -- `docs/exec-plans/active/`: các kế hoạch hiện đang thúc đẩy công việc -- `docs/exec-plans/completed/`: các kế hoạch đã hoàn thành được giữ lại để cung cấp ngữ cảnh cho agent trong tương lai -- `docs/exec-plans/tech-debt-tracker.md`: công việc đã hoãn và các follow-up - -## Các Phần Kế hoạch Tối thiểu - -- mục tiêu -- phạm vi và ngoài phạm vi -- đường dẫn xác minh -- rủi ro và sự cố chặn -- nhật ký tiến độ -- quyết định mở - -## Quy tắc Vận hành - -- Một kế hoạch active nên có một bước hiện tại được sở hữu rõ ràng. -- Cập nhật kế hoạch khi công việc tiến triển; đừng coi nó như văn xuôi tĩnh. -- Nếu một quyết định thay đổi hướng triển khai, hãy ghi lại nó trong kế hoạch. -- Di chuyển các kế hoạch đã hoàn thành sang `completed/` để agent vẫn có thể khám phá ngữ cảnh trước đó. diff --git a/courses/learn-harness-engineering/docs/vi/resources/openai-advanced/repo-template/docs/PRODUCT_SENSE.md b/courses/learn-harness-engineering/docs/vi/resources/openai-advanced/repo-template/docs/PRODUCT_SENSE.md deleted file mode 100644 index 56923877..00000000 --- a/courses/learn-harness-engineering/docs/vi/resources/openai-advanced/repo-template/docs/PRODUCT_SENSE.md +++ /dev/null @@ -1,24 +0,0 @@ -# PRODUCT_SENSE.md - -Tệp này ghi lại phán xét sản phẩm lâu bền mà agent không thể suy ra đáng tin cậy chỉ từ mã. - -## Cốt lõi Sản phẩm - -- Người dùng chính: `[thay thế]` -- Công việc cần hoàn thành: `[thay thế]` -- Sự thất vọng chính cần loại bỏ: `[thay thế]` -- Tiêu chuẩn chất lượng để chấp nhận: `[thay thế]` - -## Quy tắc Sản phẩm - -- Ưu tiên độ tin cậy có thể nhìn thấy của người dùng hơn số lượng tính năng. -- Coi hành vi mơ hồ là khoảng trống spec, không phải sự cho phép để đoán. -- Nếu việc triển khai thay đổi những gì người dùng nhìn thấy hoặc tin tưởng, hãy cập nhật spec phù hợp. -- Sử dụng product spec cho các luồng cụ thể, và sử dụng tệp này cho các ưu tiên sản phẩm xuyên suốt. - -## Mẫu Không được phép - -- Các hành động phá hủy ẩn -- Thất bại âm thầm mà không có phản hồi cho người dùng -- Nguồn sự thật không rõ ràng cho trạng thái có thể nhìn thấy -- Các tính năng không thể giải thích trong một câu diff --git a/courses/learn-harness-engineering/docs/vi/resources/openai-advanced/repo-template/docs/QUALITY_SCORE.md b/courses/learn-harness-engineering/docs/vi/resources/openai-advanced/repo-template/docs/QUALITY_SCORE.md deleted file mode 100644 index c8d1b772..00000000 --- a/courses/learn-harness-engineering/docs/vi/resources/openai-advanced/repo-template/docs/QUALITY_SCORE.md +++ /dev/null @@ -1,39 +0,0 @@ -# QUALITY_SCORE.md - -Tài liệu này theo dõi liệu kho lưu trữ có đang trở nên mạnh hơn hay yếu hơn theo thời gian. - -## Thang điểm - -- `A`: đã xác minh, có thể đọc được, ổn định, ranh giới được thực thi -- `B`: hoạt động với các khoảng trống nhỏ -- `C`: hoạt động một phần, nhầm lẫn hoặc không ổn định đáng kể -- `D`: bị hỏng, không an toàn, hoặc cấu trúc không rõ ràng - -## Domain Sản phẩm - -| Domain | Điểm | Xác minh | Khả năng đọc của Agent | Độ ổn định Test | Khoảng trống chính | Cập nhật lần cuối | -|--------|-------|-------------|-----------------|---------------|----------|-------------| -| `[domain-a]` | - | - | - | - | - | - | -| `[domain-b]` | - | - | - | - | - | - | -| `[domain-c]` | - | - | - | - | - | - | - -## Lớp Kiến trúc - -| Lớp | Điểm | Thực thi Ranh giới | Khả năng đọc của Agent | Khoảng trống chính | Cập nhật lần cuối | -|-------|-------|---------------------|-----------------|----------|-------------| -| Types | - | - | - | - | - | -| Services | - | - | - | - | - | -| Runtime | - | - | - | - | - | -| UI | - | - | - | - | - | - -## Snapshot Benchmark - -| Ngày | Biến thể Harness | Tỷ lệ Hoàn thành | Thử lại | Lỗi trước Review | Ghi chú | -|------|-----------------|----------------|--------|-----------------------|---------| -| YYYY-MM-DD | `[baseline / improved / simplified]` | - | - | - | - | - -## Nhật ký Đơn giản hóa - -| Ngày | Thành phần Đã xóa | Kết quả | Quyết định | -|------|-------------------|---------|------------| -| YYYY-MM-DD | `[thành phần]` | `[giảm sút / không thay đổi]` | `[khôi phục / giữ đã xóa]` | diff --git a/courses/learn-harness-engineering/docs/vi/resources/openai-advanced/repo-template/docs/RELIABILITY.md b/courses/learn-harness-engineering/docs/vi/resources/openai-advanced/repo-template/docs/RELIABILITY.md deleted file mode 100644 index 3b61954c..00000000 --- a/courses/learn-harness-engineering/docs/vi/resources/openai-advanced/repo-template/docs/RELIABILITY.md +++ /dev/null @@ -1,32 +0,0 @@ -# RELIABILITY.md - -Tệp này định nghĩa cách hệ thống chứng minh nó khỏe mạnh và có thể khởi động lại. - -## Đường dẫn Chuẩn - -- Bootstrap: `[lệnh]` -- Xác minh: `[lệnh]` -- Khởi động app hoặc service: `[lệnh]` -- Debug hoặc kiểm tra runtime: `[lệnh]` - -## Tín hiệu Runtime Bắt buộc - -- log có cấu trúc cho khởi động và các luồng quan trọng -- health check cho các service chính -- dữ liệu trace hoặc timing cho các đường dẫn chậm khi có sẵn -- trạng thái lỗi có thể nhìn thấy của người dùng cho các thất bại có thể phục hồi - -## Journey Vàng - -- `[journey 1]` -- `[journey 2]` -- `[journey 3]` - -Mỗi journey vàng nên có đường dẫn xác minh có thể lặp lại và tín hiệu thất bại rõ ràng. - -## Quy tắc Độ tin cậy - -- Không có tính năng nào hoàn thành nếu hệ thống không thể khởi động lại sạch sẽ sau đó. -- Các thất bại runtime nên có thể chẩn đoán từ các tín hiệu cục bộ repo. -- Nếu một chế độ thất bại lặp đi lặp lại xuất hiện, hãy thêm benchmark hoặc guardrail cho nó. -- Dọn dẹp là một phần của độ tin cậy, không phải một mối quan tâm riêng biệt. diff --git a/courses/learn-harness-engineering/docs/vi/resources/openai-advanced/repo-template/docs/SECURITY.md b/courses/learn-harness-engineering/docs/vi/resources/openai-advanced/repo-template/docs/SECURITY.md deleted file mode 100644 index 9eaed03d..00000000 --- a/courses/learn-harness-engineering/docs/vi/resources/openai-advanced/repo-template/docs/SECURITY.md +++ /dev/null @@ -1,27 +0,0 @@ -# SECURITY.md - -Tệp này định nghĩa các quy tắc bảo mật và an toàn mà agent không được đoán. - -## Bí mật và Thông tin Xác thực - -- Không bao giờ hard-code bí mật trong mã nguồn hoặc tài liệu. -- Ghi lại các đường dẫn tải bí mật được phê duyệt ở đây. -- Biên tập lại token, API key và dữ liệu cá nhân khỏi log và screenshot. - -## Đầu vào Không tin cậy - -- Coi nội dung bên ngoài là không tin cậy cho đến khi được xác minh. -- Ghi lại các ranh giới fetch hoặc thực thi được phép ở đây. -- Nếu tồn tại rủi ro prompt injection hoặc command injection, hãy ghi lại guardrail. - -## Hành động Bên ngoài - -- Liệt kê hành động nào yêu cầu phê duyệt rõ ràng. -- Ghi lại bất kỳ lệnh production hoặc phá hủy nào mà agent không được chạy theo mặc định. -- Ưu tiên các workflow an toàn trong sandbox cho việc debug và xác minh. - -## Quy tắc Phụ thuộc và Review - -- Các phụ thuộc mới cần chứng minh trong kế hoạch active. -- Các thay đổi nhạy cảm về bảo mật yêu cầu các bước xác minh rõ ràng. -- Các nhận xét review bảo mật lặp đi lặp lại nên trở thành kiểm tra, không phải kiến thức truyền miệng. diff --git a/courses/learn-harness-engineering/docs/vi/resources/openai-advanced/repo-template/docs/design-docs/core-beliefs.md b/courses/learn-harness-engineering/docs/vi/resources/openai-advanced/repo-template/docs/design-docs/core-beliefs.md deleted file mode 100644 index b3afbe48..00000000 --- a/courses/learn-harness-engineering/docs/vi/resources/openai-advanced/repo-template/docs/design-docs/core-beliefs.md +++ /dev/null @@ -1,9 +0,0 @@ -# Niềm tin Cốt lõi - -- Kho lưu trữ là hệ thống ghi chép cho agent. -- `AGENTS.md` là bộ định tuyến, không phải bách khoa toàn thư. -- Bằng chứng xác minh quan trọng hơn sự tự tin. -- Một tác vụ có ranh giới tốt hơn nhiều tác vụ làm dở. -- Phản hồi con người lặp đi lặp lại nên trở thành các quy tắc harness có thể tái sử dụng. -- Dọn dẹp và đơn giản hóa là một phần của việc phát hành, không phải là suy nghĩ sau. -- Nếu agent không thể khám phá một sự thật trong repo, hãy coi sự thật đó là không khả dụng về mặt vận hành. diff --git a/courses/learn-harness-engineering/docs/vi/resources/openai-advanced/repo-template/docs/design-docs/index.md b/courses/learn-harness-engineering/docs/vi/resources/openai-advanced/repo-template/docs/design-docs/index.md deleted file mode 100644 index d9c7ef18..00000000 --- a/courses/learn-harness-engineering/docs/vi/resources/openai-advanced/repo-template/docs/design-docs/index.md +++ /dev/null @@ -1,21 +0,0 @@ -# Chỉ mục Tài liệu Thiết kế - -Sử dụng chỉ mục này như bản đồ có thể khám phá của lịch sử thiết kế. - -## Đã Chấp nhận - -- `core-beliefs.md`: niềm tin vận hành agent-first và chuẩn mực dự án lâu bền - -## Đề xuất - -- `[thêm đường dẫn tài liệu thiết kế mới ở đây]` - -## Không còn Dùng - -- `[di chuyển các tài liệu thiết kế cũ hoặc đã bị thay thế ở đây với các liên kết thay thế]` - -## Quy tắc Bảo trì - -- Mọi tài liệu thiết kế nên có người sở hữu hoặc kích hoạt cập nhật. -- Xóa các tài liệu lỗi thời hoặc đánh dấu chúng không còn dùng thay vì để chúng trôi dạt. -- Liên kết các kế hoạch thực thi active với các tài liệu thiết kế mà chúng phụ thuộc vào. diff --git a/courses/learn-harness-engineering/docs/vi/resources/openai-advanced/repo-template/docs/exec-plans/active/index.md b/courses/learn-harness-engineering/docs/vi/resources/openai-advanced/repo-template/docs/exec-plans/active/index.md deleted file mode 100644 index 986e628e..00000000 --- a/courses/learn-harness-engineering/docs/vi/resources/openai-advanced/repo-template/docs/exec-plans/active/index.md +++ /dev/null @@ -1,9 +0,0 @@ -# Kế hoạch Active - -Giữ một tệp markdown cho mỗi kế hoạch thực thi active trong thư mục này. - -Mẫu tên tệp đề xuất: - -- `YYYY-MM-DD-chu-de-ngan.md` - -Mỗi kế hoạch active nên đủ hiện tại để một phiên agent mới có thể tiếp tục công việc chỉ từ kho lưu trữ. diff --git a/courses/learn-harness-engineering/docs/vi/resources/openai-advanced/repo-template/docs/exec-plans/completed/index.md b/courses/learn-harness-engineering/docs/vi/resources/openai-advanced/repo-template/docs/exec-plans/completed/index.md deleted file mode 100644 index e2b675da..00000000 --- a/courses/learn-harness-engineering/docs/vi/resources/openai-advanced/repo-template/docs/exec-plans/completed/index.md +++ /dev/null @@ -1,3 +0,0 @@ -# Kế hoạch Đã hoàn thành - -Di chuyển các kế hoạch đã hoàn thành ở đây thay vì xóa chúng. Các kế hoạch đã hoàn thành là một phần của bề mặt bộ nhớ kho lưu trữ và giúp các lần chạy agent sau hiểu tại sao mã trông như vậy. diff --git a/courses/learn-harness-engineering/docs/vi/resources/openai-advanced/repo-template/docs/exec-plans/tech-debt-tracker.md b/courses/learn-harness-engineering/docs/vi/resources/openai-advanced/repo-template/docs/exec-plans/tech-debt-tracker.md deleted file mode 100644 index 4957b3b0..00000000 --- a/courses/learn-harness-engineering/docs/vi/resources/openai-advanced/repo-template/docs/exec-plans/tech-debt-tracker.md +++ /dev/null @@ -1,7 +0,0 @@ -# Tracker Nợ Kỹ thuật - -Sử dụng tệp này cho nợ thực, đã được thừa nhận và có chủ ý bị hoãn. - -| Ngày | Khu vực | Nợ | Lý do Hoãn | Rủi ro | Kích hoạt Tiếp theo | -|------|------|------|--------------|------|--------------| -| YYYY-MM-DD | `[khu vực]` | `[nợ]` | `[lý do]` | `[rủi ro]` | `[khi nào xem xét lại]` | diff --git a/courses/learn-harness-engineering/docs/vi/resources/openai-advanced/repo-template/docs/generated/db-schema.md b/courses/learn-harness-engineering/docs/vi/resources/openai-advanced/repo-template/docs/generated/db-schema.md deleted file mode 100644 index c37f2689..00000000 --- a/courses/learn-harness-engineering/docs/vi/resources/openai-advanced/repo-template/docs/generated/db-schema.md +++ /dev/null @@ -1,13 +0,0 @@ -# Database Schema - -Sử dụng thư mục này cho các artifact được tạo ra hoặc dẫn xuất mà agent có thể kiểm tra mà không cần reverse-engineer chúng từ mã. - -## Nguồn - -- Được tạo từ: `[lệnh hoặc đường dẫn nguồn]` -- Làm mới lần cuối: `YYYY-MM-DD` - -## Ghi chú - -- Không chỉnh sửa thủ công các phần được tạo ra. -- Tạo lại tệp này khi schema bên dưới thay đổi. diff --git a/courses/learn-harness-engineering/docs/vi/resources/openai-advanced/repo-template/docs/product-specs/index.md b/courses/learn-harness-engineering/docs/vi/resources/openai-advanced/repo-template/docs/product-specs/index.md deleted file mode 100644 index 1fb824e6..00000000 --- a/courses/learn-harness-engineering/docs/vi/resources/openai-advanced/repo-template/docs/product-specs/index.md +++ /dev/null @@ -1,13 +0,0 @@ -# Chỉ mục Product Spec - -Sử dụng thư mục này cho các spec hành vi dành cho người dùng hiện tại. - -## Spec Active - -- `new-user-onboarding.md` - -## Quy tắc - -- Spec nên mô tả hành vi có thể nhìn thấy của người dùng và tiêu chí chấp nhận. -- Nếu triển khai khác với spec, hãy cập nhật một trong số chúng trong cùng phiên. -- Giữ chỉ mục này hiện tại để agent mới có thể nhanh chóng khám phá phạm vi sản phẩm. diff --git a/courses/learn-harness-engineering/docs/vi/resources/openai-advanced/repo-template/docs/product-specs/new-user-onboarding.md b/courses/learn-harness-engineering/docs/vi/resources/openai-advanced/repo-template/docs/product-specs/new-user-onboarding.md deleted file mode 100644 index 5ef99731..00000000 --- a/courses/learn-harness-engineering/docs/vi/resources/openai-advanced/repo-template/docs/product-specs/new-user-onboarding.md +++ /dev/null @@ -1,26 +0,0 @@ -# Onboarding Người dùng Mới - -## Mục tiêu - -Mô tả trải nghiệm lần chạy đầu tiên mà người dùng mới nên có. - -## Điều kiện Đầu vào - -- `[trạng thái trước khi luồng bắt đầu]` - -## Luồng Người dùng - -1. `[bước một]` -2. `[bước hai]` -3. `[bước ba]` - -## Tiêu chí Chấp nhận - -- `[kết quả có thể quan sát]` -- `[kết quả có thể quan sát]` -- `[kết quả có thể quan sát]` - -## Trạng thái Lỗi - -- `[lỗi có thể phục hồi và phản hồi người dùng]` -- `[trạng thái bị chặn và phương án dự phòng]` diff --git a/courses/learn-harness-engineering/docs/vi/resources/openai-advanced/repo-template/docs/references/design-system-reference-llms.txt b/courses/learn-harness-engineering/docs/vi/resources/openai-advanced/repo-template/docs/references/design-system-reference-llms.txt deleted file mode 100644 index 6f1c612c..00000000 --- a/courses/learn-harness-engineering/docs/vi/resources/openai-advanced/repo-template/docs/references/design-system-reference-llms.txt +++ /dev/null @@ -1,9 +0,0 @@ -Purpose: store model-friendly reference material for the design system. - -Suggested contents: -- component naming rules -- spacing and typography tokens -- state variants -- accessibility expectations - -Keep this file concise and refresh it when the upstream design system changes. diff --git a/courses/learn-harness-engineering/docs/vi/resources/openai-advanced/repo-template/docs/references/nixpacks-llms.txt b/courses/learn-harness-engineering/docs/vi/resources/openai-advanced/repo-template/docs/references/nixpacks-llms.txt deleted file mode 100644 index c30cc9c2..00000000 --- a/courses/learn-harness-engineering/docs/vi/resources/openai-advanced/repo-template/docs/references/nixpacks-llms.txt +++ /dev/null @@ -1,8 +0,0 @@ -Purpose: store a clean agent-readable extract of the deployment or packaging -rules your repository depends on. - -Suggested contents: -- build entrypoints -- runtime assumptions -- environment variable expectations -- common failure signatures diff --git a/courses/learn-harness-engineering/docs/vi/resources/openai-advanced/repo-template/docs/references/uv-llms.txt b/courses/learn-harness-engineering/docs/vi/resources/openai-advanced/repo-template/docs/references/uv-llms.txt deleted file mode 100644 index f2ceba9a..00000000 --- a/courses/learn-harness-engineering/docs/vi/resources/openai-advanced/repo-template/docs/references/uv-llms.txt +++ /dev/null @@ -1,8 +0,0 @@ -Purpose: store a compact reference for your Python package and environment -workflow when `uv` or similar tooling matters to the repo. - -Suggested contents: -- install and sync commands -- lockfile policy -- virtualenv expectations -- verification commands diff --git a/courses/learn-harness-engineering/docs/vi/resources/openai-advanced/repo-template/index.md b/courses/learn-harness-engineering/docs/vi/resources/openai-advanced/repo-template/index.md deleted file mode 100644 index b7779f8a..00000000 --- a/courses/learn-harness-engineering/docs/vi/resources/openai-advanced/repo-template/index.md +++ /dev/null @@ -1,21 +0,0 @@ -# Mẫu Repo Nâng cao - -Sao chép starter này vào một kho lưu trữ thực khi bạn muốn một bề mặt tài liệu agent-first theo phong cách OpenAI thay vì chỉ một harness tối giản. - -## Thứ tự Sao chép - -1. Sao chép `AGENTS.md` và `ARCHITECTURE.md` vào thư mục gốc repo. -2. Sao chép toàn bộ cây `docs/`. -3. Điền vào `docs/PRODUCT_SENSE.md`, `docs/QUALITY_SCORE.md`, và `docs/RELIABILITY.md` trước tiên. -4. Thêm kế hoạch active đầu tiên của bạn trong `docs/exec-plans/active/`. -5. Giữ các tệp đầu vào ngắn và định tuyến chi tiết vào các tài liệu được liên kết. - -## Mẫu Này Tối ưu hóa Cho - -- ngữ cảnh cục bộ repo lâu bền -- tiết lộ tiến triển thay vì một tệp hướng dẫn khổng lồ -- vòng đời kế hoạch rõ ràng -- theo dõi chất lượng theo thời gian -- ranh giới có thể đọc được cho agent và con người - -Coi mỗi tệp ở đây là một starter. Thay thế các placeholder, ví dụ và lệnh mẫu bằng các đặc thù dự án thực của bạn trước khi dựa vào nó. diff --git a/courses/learn-harness-engineering/docs/vi/resources/openai-advanced/sops/chrome-devtools-validation-loop.md b/courses/learn-harness-engineering/docs/vi/resources/openai-advanced/sops/chrome-devtools-validation-loop.md deleted file mode 100644 index 7b65baf8..00000000 --- a/courses/learn-harness-engineering/docs/vi/resources/openai-advanced/sops/chrome-devtools-validation-loop.md +++ /dev/null @@ -1,48 +0,0 @@ -# SOP: Vòng lặp Xác minh Chrome DevTools - -Sử dụng SOP này khi công việc UI phụ thuộc vào tương tác runtime thực tế và screenshot, trạng thái DOM và đầu ra console quan trọng hơn chỉ kiểm tra mã. - -## Mục tiêu - -Biến xác minh UI thành một vòng lặp tương tác có thể lặp lại mà agent có thể chạy cho đến khi journey sạch sẽ. - -## Vòng lặp Cốt lõi - -1. Chọn trang mục tiêu hoặc phiên bản ứng dụng. -2. Xóa noise console lỗi thời. -3. Chụp trạng thái TRƯỚC. -4. Kích hoạt đường dẫn UI. -5. Quan sát các sự kiện runtime trong khi tương tác. -6. Chụp trạng thái SAU. -7. Áp dụng sửa chữa và khởi động lại ứng dụng nếu cần. -8. Chạy lại xác minh cho đến khi journey sạch sẽ. - -## Đầu vào Bắt buộc - -- một lệnh khởi động ổn định -- một UI journey có thể tái tạo -- một cách để snapshot DOM, console hoặc screenshot -- một quy tắc cho những gì được tính là "sạch sẽ" - -## SOP Thực thi - -1. Viết journey mục tiêu trong kế hoạch active. -2. Định nghĩa thành công theo các thuật ngữ có thể quan sát: văn bản hiện diện, nút được bật, lỗi biến mất, console sạch, yêu cầu thành công. -3. Snapshot trạng thái ban đầu trước khi tương tác. -4. Kích hoạt chính xác một đường dẫn mỗi lần. -5. Ghi lại các sự kiện runtime, thay đổi DOM và đầu ra có thể nhìn thấy. -6. Nếu journey thất bại, hãy sửa lớp chịu trách nhiệm nhỏ nhất và khởi động lại. -7. Chạy lại cùng đường dẫn và so sánh bằng chứng TRƯỚC/SAU. - -## Tiêu chí Sạch sẽ - -- trạng thái có thể nhìn thấy dự định là hiện diện -- các lỗi không mong đợi vắng mặt -- noise console được hiểu hoặc đã xóa -- chạy lại cùng đường dẫn cho cùng kết quả - -## Artifact Repo Cần Cập nhật - -- kế hoạch thực thi active -- `docs/RELIABILITY.md` nếu journey trở thành một golden path -- product spec nếu hành vi có thể nhìn thấy thay đổi diff --git a/courses/learn-harness-engineering/docs/vi/resources/openai-advanced/sops/encode-knowledge-into-repo.md b/courses/learn-harness-engineering/docs/vi/resources/openai-advanced/sops/encode-knowledge-into-repo.md deleted file mode 100644 index bf60b464..00000000 --- a/courses/learn-harness-engineering/docs/vi/resources/openai-advanced/sops/encode-knowledge-into-repo.md +++ /dev/null @@ -1,42 +0,0 @@ -# SOP: Mã hóa Kiến thức Ẩn vào Repo - -Sử dụng SOP này khi ngữ cảnh quan trọng vẫn còn trong Google Docs, luồng chat, ticket hoặc trong đầu của mọi người. - -## Mục tiêu - -Làm cho kiến thức ẩn với agent có thể khám phá được trong codebase để một phiên mới có thể hành động dựa trên nó mà không cần dựa vào hội thoại trước. - -## Tín hiệu Kích hoạt - -- Agent tiếp tục hỏi cách hệ thống hoạt động. -- Con người nói "chúng tôi đã quyết định điều này trong Slack" hoặc "làm theo những gì X nói tuần trước." -- Các review tham chiếu đến các quy tắc sản phẩm hoặc bảo mật không được viết trong repo. -- Các phiên mới lặp lại công việc khám phá đáng lẽ đã được giải quyết. - -## SOP Thực thi - -1. Liệt kê các nguồn kiến thức ẩn: tài liệu, chat, quy tắc team mặc nhiên, quyết định bằng miệng. -2. Cho mỗi nguồn, hỏi: đây là kiến trúc, hành vi sản phẩm, chính sách bảo mật, kỳ vọng độ tin cậy, ngữ cảnh kế hoạch hay tài liệu tham khảo? -3. Mã hóa nó vào artifact repo phù hợp: - - kiến trúc -> `ARCHITECTURE.md` - - hành vi sản phẩm -> `docs/product-specs/` - - lý luận thiết kế -> `docs/design-docs/` - - trạng thái thực thi -> `docs/exec-plans/` - - tài liệu tham khảo bên ngoài lặp đi lặp lại -> `docs/references/` - - kỳ vọng chất lượng hoặc độ tin cậy -> `docs/QUALITY_SCORE.md` hoặc `docs/RELIABILITY.md` -4. Thay thế các phát biểu mơ hồ bằng cách diễn đạt hữu ích về mặt vận hành. -5. Xóa hoặc không dùng các bản sao lỗi thời để repo giữ một sự thật có thể khám phá. - -## Quy tắc Mã hóa Tốt - -- Viết cho khả năng khám phá, không phải cho sự hoàn chỉnh về văn học. -- Ưu tiên các tài liệu ngắn với tên tệp rõ ràng. -- Liên kết các artifact liên quan với nhau. -- Lưu trữ các quy tắc lâu bền, không phải bản ghi cuộc họp. -- Cập nhật repo trong cùng phiên mà quyết định được đưa ra. - -## Định nghĩa Hoàn thành - -- Một agent mới có thể khám phá quy tắc liên quan mà không cần hỏi con người. -- Cùng một sự thật không bị phân tán qua nhiều tệp mâu thuẫn. -- Artifact mới nằm gần mã hoặc workflow mà nó điều chỉnh. diff --git a/courses/learn-harness-engineering/docs/vi/resources/openai-advanced/sops/index.md b/courses/learn-harness-engineering/docs/vi/resources/openai-advanced/sops/index.md deleted file mode 100644 index 7939a30f..00000000 --- a/courses/learn-harness-engineering/docs/vi/resources/openai-advanced/sops/index.md +++ /dev/null @@ -1,25 +0,0 @@ -[English Version →](../../../../en/resources/openai-advanced/sops/) | [中文版本 →](../../../../zh/resources/openai-advanced/sops/) - -# Thư viện SOP - -Các quy trình vận hành chuẩn từng bước để thiết lập và vận hành harness. - -## SOP Có sẵn - -- [`layered-domain-architecture.md`](./layered-domain-architecture.md): - thiết lập kiến trúc domain phân lớp để agent không vi phạm ranh giới -- [`encode-knowledge-into-repo.md`](./encode-knowledge-into-repo.md): - di chuyển kiến thức ẩn từ chat, tài liệu và bộ nhớ vào các tệp cục bộ repo -- [`observability-feedback-loop.md`](./observability-feedback-loop.md): - cung cấp cho agent log, metrics, trace và vòng lặp debug có thể lặp lại -- [`chrome-devtools-validation-loop.md`](./chrome-devtools-validation-loop.md): - sử dụng tự động hóa browser và snapshot để xác minh hành vi UI cho đến khi sạch - -## Cách Sử dụng Chúng - -1. Chọn SOP phù hợp với điểm nghẽn hiện tại của bạn. -2. Sử dụng danh sách kiểm tra để thiết lập các artifact hoặc công cụ còn thiếu. -3. Mã hóa các quy tắc kết quả vào các tài liệu `repo-template/` đã sao chép của bạn. -4. Chuyển đổi các nhận xét review lặp đi lặp lại thành kiểm tra, script hoặc guardrail. - -Những điều này không phải để được tuân theo mù quáng. Chúng được thiết kế để làm cho harness có thể đọc được, có thể thực thi và có thể lặp lại hơn. diff --git a/courses/learn-harness-engineering/docs/vi/resources/openai-advanced/sops/layered-domain-architecture.md b/courses/learn-harness-engineering/docs/vi/resources/openai-advanced/sops/layered-domain-architecture.md deleted file mode 100644 index 13bf838f..00000000 --- a/courses/learn-harness-engineering/docs/vi/resources/openai-advanced/sops/layered-domain-architecture.md +++ /dev/null @@ -1,47 +0,0 @@ -# SOP: Kiến trúc Domain Phân lớp - -Sử dụng SOP này khi agent tiếp tục vi phạm ranh giới, sao chép logic qua các lớp, hoặc tạo ra mã khó review sau một vài phiên. - -## Mục tiêu - -Làm cho ranh giới domain đủ rõ ràng để agent có thể di chuyển nhanh mà không âm thầm làm xuống cấp cấu trúc. - -## Mô hình Mục tiêu - -Trong một domain kinh doanh, ưu tiên luồng định hướng này: - -`Types -> Config -> Repo -> Service -> Runtime -> UI` - -Các mối quan tâm xuyên suốt nên đi vào qua các provider hoặc adapter rõ ràng. Các utils dùng chung nằm bên ngoài domain và không nên tích lũy logic domain. - -## Danh sách Kiểm tra Thiết lập - -- Định nghĩa các domain hiện tại trong `ARCHITECTURE.md`. -- Viết các hướng phụ thuộc được phép trong `ARCHITECTURE.md`. -- Ghi lại các giao diện xuyên suốt như auth, telemetry và external API. -- Thêm một ghi chú ngắn cho vi phạm ranh giới khó nhất hiện tại. -- Quyết định những gì nên được thực thi cơ học bởi lint, test hoặc script. - -## SOP Thực thi - -1. Ánh xạ codebase thành các domain trước khi chạm vào phong cách triển khai. -2. Cho mỗi domain, xác định chuỗi lớp được phép. -3. Xác định tất cả các mối quan tâm xuyên suốt và định tuyến chúng qua các provider hoặc adapter. -4. Di chuyển logic dùng chung mơ hồ sang domain sở hữu hoặc sang utils thực sự chung chung. -5. Ghi lại các quy tắc trong `ARCHITECTURE.md`. -6. Thêm một guardrail có thể thực thi cho vi phạm có chi phí cao nhất. -7. Cập nhật điểm chất lượng sau khi thay đổi. - -## Định nghĩa Hoàn thành - -- Một agent mới có thể biết lớp nào sở hữu một thay đổi. -- Mã UI không còn tiếp cận vào repo hoặc side effect bên ngoài trực tiếp. -- Các mối quan tâm xuyên suốt có các điểm đầu vào được đặt tên. -- Ít nhất một ranh giới quan trọng được thực thi cơ học. - -## Artifact Repo Cần Cập nhật - -- `ARCHITECTURE.md` -- `docs/QUALITY_SCORE.md` -- `docs/design-docs/` khi lý luận thay đổi -- `docs/PLANS.md` hoặc kế hoạch thực thi active diff --git a/courses/learn-harness-engineering/docs/vi/resources/openai-advanced/sops/observability-feedback-loop.md b/courses/learn-harness-engineering/docs/vi/resources/openai-advanced/sops/observability-feedback-loop.md deleted file mode 100644 index 9933119a..00000000 --- a/courses/learn-harness-engineering/docs/vi/resources/openai-advanced/sops/observability-feedback-loop.md +++ /dev/null @@ -1,41 +0,0 @@ -# SOP: Vòng lặp Phản hồi Observability - -Sử dụng SOP này khi debug chậm, agent tiếp tục tuyên bố thành công mà không có bằng chứng, hoặc hành vi runtime khó kiểm tra hơn bản thân mã. - -## Mục tiêu - -Cung cấp cho agent một vòng lặp phản hồi cục bộ qua log, metrics, trace và workload có thể chạy để nó có thể lý luận từ thực thi, không chỉ từ kiểm tra mã. - -## Stack Tối thiểu - -- ứng dụng phát ra các log có cấu trúc -- ứng dụng phát ra metrics và trace khi khả thi -- lớp fan-out hoặc thu thập cục bộ -- giao diện truy vấn cho log, metrics và trace -- workload hoặc user journey có thể lặp lại để chạy lại sau mỗi thay đổi - -## SOP Thực thi - -1. Định nghĩa các journey runtime vàng quan trọng nhất. -2. Thêm các log có cấu trúc vào khởi động và đường dẫn quan trọng. -3. Thêm metrics cho độ trễ, số lần thất bại hoặc độ sâu hàng đợi khi hữu ích. -4. Thêm trace hoặc marker timing cho các luồng chậm hoặc nhiều bước. -5. Làm cho các tín hiệu có thể truy vấn từ môi trường dev cục bộ. -6. Cung cấp cho agent một workload hoặc kịch bản có thể lặp lại để chạy lại. -7. Yêu cầu vòng lặp: truy vấn -> tương quan -> lý luận -> triển khai -> khởi động lại -> chạy lại -> xác minh. - -## Danh sách Kiểm tra Phiên Debug - -- Điều gì đã thất bại? -- Tín hiệu nào chứng minh sự thất bại? -- Lớp nào sở hữu sự thất bại? -- Điều gì thay đổi sau khi sửa? -- Ứng dụng có khởi động lại sạch sẽ không? -- Cùng workload có vượt qua sau khi chạy lại không? - -## Định nghĩa Hoàn thành - -- Agent có thể giải thích một chế độ thất bại từ bằng chứng runtime. -- Cùng workload có thể được chạy lại sau mỗi thay đổi. -- Khởi động lại và chạy lại là một phần của vòng lặp tác vụ bình thường. -- Các tín hiệu độ tin cậy được ghi lại trong `docs/RELIABILITY.md`. diff --git a/courses/learn-harness-engineering/docs/vi/resources/reference/coding-agent-startup-flow.md b/courses/learn-harness-engineering/docs/vi/resources/reference/coding-agent-startup-flow.md deleted file mode 100644 index bfc15ae8..00000000 --- a/courses/learn-harness-engineering/docs/vi/resources/reference/coding-agent-startup-flow.md +++ /dev/null @@ -1,33 +0,0 @@ -# Luồng Khởi động Coding Agent - -Sử dụng cái này ở đầu mỗi phiên sau khi khởi tạo hoàn thành. - -## Mẫu Khởi động Cố định - -1. Chạy `pwd` và xác nhận thư mục gốc kho lưu trữ. -2. Đọc `claude-progress.md`. -3. Đọc `feature_list.json`. -4. Xem lại các commit gần đây bằng `git log --oneline -5`. -5. Chạy `./init.sh`. -6. Chạy một đường dẫn smoke hoặc end-to-end baseline. -7. Nếu baseline bị hỏng, hãy sửa điều đó trước. -8. Chọn tính năng chưa hoàn thành có mức ưu tiên cao nhất. -9. Chỉ làm việc trên tính năng đó cho đến khi nó được xác minh hoặc bị chặn rõ ràng. - -## Tại sao Thứ tự này Quan trọng - -- `pwd` ngăn chặn việc vô tình làm việc trong thư mục sai. -- các tệp tiến độ và tính năng khôi phục trạng thái lâu bền trước khi bắt đầu chỉnh sửa mới. -- các commit gần đây giải thích những gì đã thay đổi gần đây nhất. -- `init.sh` chuẩn hóa khởi động thay vì dựa vào bộ nhớ. -- xác minh baseline bắt các trạng thái khởi đầu bị hỏng trước khi công việc mới che giấu chúng. - -## Gương Cuối Phiên - -Cùng một phiên nên kết thúc bằng cách: - -1. ghi lại tiến độ -2. cập nhật trạng thái tính năng -3. viết bàn giao nếu cần -4. commit công việc an toàn -5. để lại đường dẫn khởi động lại sạch sẽ diff --git a/courses/learn-harness-engineering/docs/vi/resources/reference/index.md b/courses/learn-harness-engineering/docs/vi/resources/reference/index.md deleted file mode 100644 index c8c54236..00000000 --- a/courses/learn-harness-engineering/docs/vi/resources/reference/index.md +++ /dev/null @@ -1,19 +0,0 @@ -[English Version →](../../../en/resources/reference/) | [中文版本 →](../../../zh/resources/reference/) - -# Tài liệu Tham khảo Tiếng Việt - -Các ghi chú này giải thích cách sử dụng các mẫu như một harness làm việc thực thụ thay vì một đống tệp rời rạc. - -## Ghi chú Tham khảo - -- [`method-map.md`](./method-map.md): ánh xạ các chế độ lỗi chạy lâu phổ biến sang artifact hoặc chính sách giải quyết chúng đầu tiên -- [`initializer-agent-playbook.md`](./initializer-agent-playbook.md): những gì initializer nên để lại trước khi công việc tính năng bắt đầu -- [`coding-agent-startup-flow.md`](./coding-agent-startup-flow.md): luồng bắt đầu phiên cố định cho các lần chạy coding sau -- [`prompt-calibration.md`](./prompt-calibration.md): cách giữ cho hướng dẫn gốc sắc nét mà không làm chúng phình to và dễ vỡ - -## Thứ tự Đọc Đề nghị - -1. `method-map.md` -2. `initializer-agent-playbook.md` -3. `coding-agent-startup-flow.md` -4. `prompt-calibration.md` diff --git a/courses/learn-harness-engineering/docs/vi/resources/reference/initializer-agent-playbook.md b/courses/learn-harness-engineering/docs/vi/resources/reference/initializer-agent-playbook.md deleted file mode 100644 index 7932c4ec..00000000 --- a/courses/learn-harness-engineering/docs/vi/resources/reference/initializer-agent-playbook.md +++ /dev/null @@ -1,35 +0,0 @@ -# Playbook Initializer Agent - -Sử dụng playbook này cho phiên nghiêm túc đầu tiên trong một kho lưu trữ, trước khi công việc tính năng tăng dần bắt đầu. - -## Mục tiêu - -Tạo ra một bề mặt vận hành ổn định để các phiên sau có thể triển khai hành vi mà không cần phải suy ra lại các lệnh khởi động, trạng thái hiện tại, hoặc ranh giới tác vụ. - -## Kết quả Đầu ra Bắt buộc - -Initializer nên để lại ít nhất các artifact này: - -- một tệp hướng dẫn gốc như `AGENTS.md` hoặc `CLAUDE.md` -- một bề mặt tính năng có thể đọc bởi máy như `feature_list.json` -- một artifact tiến độ lâu bền như `claude-progress.md` -- một helper khởi động chuẩn như `init.sh` -- một commit cơ sở an toàn ban đầu ghi lại scaffold cơ sở - -## Danh sách Kiểm tra - -1. Định nghĩa đường dẫn khởi động chuẩn. -2. Định nghĩa đường dẫn xác minh chuẩn. -3. Tạo nhật ký tiến độ và ghi lại trạng thái khởi đầu. -4. Phân chia công việc thành các tính năng rõ ràng với trạng thái. -5. Tạo commit baseline sạch đầu tiên. - -## Bài kiểm tra Thành công - -Một phiên mới không có ngữ cảnh chat trước đó nên có thể trả lời: - -- kho lưu trữ này làm gì -- cách khởi động nó -- cách xác minh nó -- những gì chưa hoàn thành -- bước tốt nhất tiếp theo là gì diff --git a/courses/learn-harness-engineering/docs/vi/resources/reference/method-map.md b/courses/learn-harness-engineering/docs/vi/resources/reference/method-map.md deleted file mode 100644 index f19b01ad..00000000 --- a/courses/learn-harness-engineering/docs/vi/resources/reference/method-map.md +++ /dev/null @@ -1,16 +0,0 @@ -# Bản đồ Phương pháp - -Bảng này ánh xạ các chế độ lỗi coding-agent chạy lâu phổ biến nhất sang artifact hoặc quy tắc vận hành thường sửa chúng đầu tiên. - -| Chế độ lỗi | Trông như thế nào trong thực tế | Sửa chữa chính | Artifact hỗ trợ | -| --- | --- | --- | --- | -| Nhầm lẫn cold-start | Một phiên mới dành hầu hết thời gian để khám phá lại cài đặt và trạng thái | Biến kho lưu trữ thành hệ thống ghi chép | `claude-progress.md` | -| Phình phạm vi | Agent bắt đầu nhiều tính năng và không hoàn thành sạch cái nào | Giới hạn phạm vi active | `feature_list.json` | -| Hoàn thành sớm | Agent tuyên bố xong sau khi chỉnh sửa mã nhưng trước khi có bằng chứng có thể chạy | Ràng buộc hoàn thành với bằng chứng | `clean-state-checklist.md` | -| Khởi động dễ vỡ | Mỗi phiên học lại cách khởi động dự án | Chuẩn hóa cài đặt và xác minh | `init.sh` | -| Bàn giao yếu | Phiên tiếp theo không thể biết những gì đã được xác minh, bị hỏng, hoặc là tiếp theo | Kết thúc với bàn giao rõ ràng | `session-handoff.md` | -| Review chủ quan | Chất lượng review phụ thuộc vào sở thích hoặc trí nhớ | Tính điểm đầu ra bằng các hạng mục cố định | `evaluator-rubric.md` | - -## Nguyên tắc Vận hành - -Thêm artifact nhỏ nhất giải quyết trực tiếp chế độ lỗi đã quan sát. Tránh giải quyết mọi vấn đề độ tin cậy bằng cách đổ thêm văn bản vào một tệp hướng dẫn toàn cục. diff --git a/courses/learn-harness-engineering/docs/vi/resources/reference/prompt-calibration.md b/courses/learn-harness-engineering/docs/vi/resources/reference/prompt-calibration.md deleted file mode 100644 index 9369ca29..00000000 --- a/courses/learn-harness-engineering/docs/vi/resources/reference/prompt-calibration.md +++ /dev/null @@ -1,23 +0,0 @@ -# Hiệu chỉnh Prompt - -Hướng dẫn gốc nên định nghĩa khung vận hành, không phải mọi nước đi có thể. - -## Giữ trong Tệp Gốc - -- mục đích và phạm vi kho lưu trữ -- đường dẫn khởi động -- đường dẫn xác minh -- các ràng buộc không thể thương lượng -- các artifact trạng thái bắt buộc -- quy tắc cuối phiên - -## Chuyển Ra Khỏi Tệp Gốc - -- các edge case lịch sử dài -- chi tiết triển khai cụ thể theo chủ đề -- ghi chú kiến trúc cục bộ thuộc về gần mã -- các ví dụ chỉ áp dụng cho một hệ thống con - -## Quy tắc Làm việc - -Tệp gốc nên giúp một phiên mới định hướng nhanh chóng. Nếu tệp đang trở thành bãi rác cho mọi lỗi trong quá khứ, hãy chia chi tiết thành các tài liệu nhỏ hơn và liên kết đến chúng thay thế. diff --git a/courses/learn-harness-engineering/docs/vi/resources/templates/AGENTS.md b/courses/learn-harness-engineering/docs/vi/resources/templates/AGENTS.md deleted file mode 100644 index a7216bec..00000000 --- a/courses/learn-harness-engineering/docs/vi/resources/templates/AGENTS.md +++ /dev/null @@ -1,50 +0,0 @@ -# AGENTS.md - -Kho lưu trữ này được thiết kế cho công việc coding-agent chạy lâu. Mục tiêu không phải là tối đa hóa đầu ra mã thô. Mục tiêu là để lại repo ở trạng thái mà phiên tiếp theo có thể tiếp tục mà không cần đoán. - -## Quy trình Khởi động - -Trước khi viết mã: - -1. Xác nhận thư mục làm việc bằng `pwd`. -2. Đọc `claude-progress.md` để biết trạng thái đã xác minh mới nhất và bước tiếp theo. -3. Đọc `feature_list.json` và chọn tính năng chưa hoàn thành có mức ưu tiên cao nhất. -4. Xem lại các commit gần đây bằng `git log --oneline -5`. -5. Chạy `./init.sh`. -6. Chạy xác minh smoke hoặc end-to-end cần thiết trước khi bắt đầu công việc mới. - -Nếu xác minh baseline đã thất bại, hãy sửa điều đó trước. Không chồng công việc tính năng mới lên trên trạng thái khởi đầu bị hỏng. - -## Quy tắc Làm việc - -- Làm việc trên một tính năng tại một thời điểm. -- Không đánh dấu tính năng hoàn thành chỉ vì mã đã được thêm vào. -- Giữ các thay đổi trong phạm vi tính năng đã chọn trừ khi có sự cố chặn cần sửa hỗ trợ hẹp. -- Không thay đổi ngầm các quy tắc xác minh trong khi triển khai. -- Ưu tiên các artifact repo lâu bền hơn tóm tắt chat. - -## Artifact Bắt buộc - -- `feature_list.json`: nguồn sự thật cho trạng thái tính năng -- `claude-progress.md`: nhật ký phiên và trạng thái đã xác minh hiện tại -- `init.sh`: đường dẫn khởi động và xác minh chuẩn -- `session-handoff.md`: bàn giao ngắn gọn tùy chọn cho các phiên lớn hơn - -## Định nghĩa Hoàn thành - -Một tính năng chỉ xong khi tất cả những điều sau đây là đúng: - -- hành vi mục tiêu đã được triển khai -- xác minh cần thiết đã thực sự chạy -- bằng chứng được ghi lại trong `feature_list.json` hoặc `claude-progress.md` -- kho lưu trữ vẫn có thể khởi động lại từ đường dẫn khởi động chuẩn - -## Cuối Phiên - -Trước khi kết thúc phiên: - -1. Cập nhật `claude-progress.md`. -2. Cập nhật `feature_list.json`. -3. Ghi lại bất kỳ rủi ro hoặc sự cố chặn chưa được giải quyết nào. -4. Commit với thông điệp mô tả khi công việc ở trạng thái an toàn. -5. Để repo đủ sạch để phiên tiếp theo có thể chạy `./init.sh` ngay lập tức. diff --git a/courses/learn-harness-engineering/docs/vi/resources/templates/CLAUDE.md b/courses/learn-harness-engineering/docs/vi/resources/templates/CLAUDE.md deleted file mode 100644 index f78501f5..00000000 --- a/courses/learn-harness-engineering/docs/vi/resources/templates/CLAUDE.md +++ /dev/null @@ -1,43 +0,0 @@ -# CLAUDE.md - -Bạn đang làm việc trong một kho lưu trữ được thiết kế cho công việc triển khai chạy lâu. Ưu tiên hoàn thành đáng tin cậy, tính liên tục qua các phiên, và xác minh rõ ràng hơn tốc độ. - -## Vòng lặp Vận hành - -Ở đầu mỗi phiên: - -1. Chạy `pwd` và xác nhận bạn đang ở trong thư mục gốc kho lưu trữ dự kiến. -2. Đọc `claude-progress.md`. -3. Đọc `feature_list.json`. -4. Xem lại các commit gần đây bằng `git log --oneline -5`. -5. Chạy `./init.sh`. -6. Kiểm tra xem đường dẫn smoke hoặc end-to-end baseline có đã bị hỏng chưa. - -Sau đó chọn chính xác một tính năng chưa hoàn thành và chỉ làm việc trên tính năng đó cho đến khi bạn xác minh nó hoặc ghi lại lý do tại sao nó bị chặn. - -## Quy tắc - -- Một tính năng active tại một thời điểm. -- Không tuyên bố hoàn thành mà không có bằng chứng có thể chạy được. -- Không viết lại feature list để ẩn công việc chưa hoàn thành. -- Không xóa hoặc làm yếu các test chỉ để tác vụ có vẻ hoàn thành. -- Sử dụng các artifact kho lưu trữ như hệ thống ghi chép. - -## Tệp Bắt buộc - -- `feature_list.json` -- `claude-progress.md` -- `init.sh` -- `session-handoff.md` khi bàn giao ngắn gọn hữu ích - -## Cổng Hoàn thành - -Một tính năng chỉ có thể chuyển sang `passing` sau khi xác minh cần thiết thành công và kết quả được ghi lại. - -## Trước khi Bạn Dừng - -1. Cập nhật nhật ký tiến độ. -2. Cập nhật trạng thái tính năng. -3. Ghi lại những gì vẫn bị hỏng hoặc chưa được xác minh. -4. Commit khi kho lưu trữ an toàn để tiếp tục. -5. Để lại đường dẫn khởi động lại sạch sẽ cho phiên tiếp theo. diff --git a/courses/learn-harness-engineering/docs/vi/resources/templates/claude-progress.md b/courses/learn-harness-engineering/docs/vi/resources/templates/claude-progress.md deleted file mode 100644 index 1a2bb46a..00000000 --- a/courses/learn-harness-engineering/docs/vi/resources/templates/claude-progress.md +++ /dev/null @@ -1,35 +0,0 @@ -# Nhật ký Tiến độ - -## Trạng thái Đã xác minh Hiện tại - -- Thư mục gốc kho lưu trữ: -- Đường dẫn khởi động chuẩn: -- Đường dẫn xác minh chuẩn: -- Tính năng chưa hoàn thành có mức ưu tiên cao nhất hiện tại: -- Sự cố chặn hiện tại: - -## Nhật ký Phiên - -### Phiên 001 - -- Ngày: -- Mục tiêu: -- Đã hoàn thành: -- Xác minh đã chạy: -- Bằng chứng đã ghi lại: -- Commit: -- Tệp hoặc artifact đã cập nhật: -- Rủi ro đã biết hoặc vấn đề chưa được giải quyết: -- Bước tốt nhất tiếp theo: - -### Phiên 002 - -- Ngày: -- Mục tiêu: -- Đã hoàn thành: -- Xác minh đã chạy: -- Bằng chứng đã ghi lại: -- Commit: -- Tệp hoặc artifact đã cập nhật: -- Rủi ro đã biết hoặc vấn đề chưa được giải quyết: -- Bước tốt nhất tiếp theo: diff --git a/courses/learn-harness-engineering/docs/vi/resources/templates/clean-state-checklist.md b/courses/learn-harness-engineering/docs/vi/resources/templates/clean-state-checklist.md deleted file mode 100644 index c44e332d..00000000 --- a/courses/learn-harness-engineering/docs/vi/resources/templates/clean-state-checklist.md +++ /dev/null @@ -1,8 +0,0 @@ -# Danh sách Kiểm tra Trạng thái Sạch - -- [ ] Đường dẫn khởi động chuẩn vẫn hoạt động. -- [ ] Đường dẫn xác minh chuẩn vẫn chạy được. -- [ ] Tiến độ hiện tại được ghi lại trong nhật ký tiến độ. -- [ ] Trạng thái tính năng phản ánh những gì thực sự đang vượt qua so với chưa được xác minh. -- [ ] Không có bước làm dở nào bị bỏ lại mà không được ghi lại. -- [ ] Phiên tiếp theo có thể tiếp tục mà không cần sửa chữa thủ công. diff --git a/courses/learn-harness-engineering/docs/vi/resources/templates/evaluator-rubric.md b/courses/learn-harness-engineering/docs/vi/resources/templates/evaluator-rubric.md deleted file mode 100644 index f048f57f..00000000 --- a/courses/learn-harness-engineering/docs/vi/resources/templates/evaluator-rubric.md +++ /dev/null @@ -1,24 +0,0 @@ -# Rubric Evaluator - -Sử dụng rubric này sau khi triển khai và trước khi chấp nhận cuối cùng. - -| Hạng mục | Câu hỏi | Điểm (0-2) | Ghi chú | -| --- | --- | --- | --- | -| Tính đúng đắn | Hành vi đã triển khai có khớp với tính năng được yêu cầu không? | | | -| Xác minh | Các kiểm tra bắt buộc có thực sự chạy, với bằng chứng không? | | | -| Kỷ luật phạm vi | Phiên có ở trong phạm vi tính năng đã chọn không? | | | -| Độ tin cậy | Kết quả có tồn tại qua khởi động lại hoặc chạy lại mà không cần sửa chữa không? | | | -| Khả năng bảo trì | Mã và tài liệu có đủ rõ ràng cho phiên tiếp theo không? | | | -| Sẵn sàng bàn giao | Một phiên mới có thể tiếp tục công việc chỉ từ các artifact repo không? | | | - -## Kết luận - -- Chấp nhận -- Sửa đổi -- Chặn - -## Hành động Tiếp theo Bắt buộc - -- Bằng chứng còn thiếu: -- Sửa chữa bắt buộc: -- Kích hoạt review tiếp theo: diff --git a/courses/learn-harness-engineering/docs/vi/resources/templates/feature_list.json b/courses/learn-harness-engineering/docs/vi/resources/templates/feature_list.json deleted file mode 100644 index 138adc6a..00000000 --- a/courses/learn-harness-engineering/docs/vi/resources/templates/feature_list.json +++ /dev/null @@ -1,64 +0,0 @@ -{ - "project": "replace-with-project-name", - "last_updated": "YYYY-MM-DD", - "rules": { - "single_active_feature": true, - "passing_requires_evidence": true, - "do_not_skip_verification": true - }, - "status_legend": { - "not_started": "Work has not begun.", - "in_progress": "The feature is the current active task.", - "blocked": "Work cannot continue until a documented blocker is resolved.", - "passing": "Required verification has passed and evidence is recorded." - }, - "features": [ - { - "id": "chat-001", - "priority": 1, - "area": "chat", - "title": "Create a new conversation", - "user_visible_behavior": "A user can click New Chat and see a fresh empty conversation.", - "status": "not_started", - "verification": [ - "Open the app.", - "Click New Chat.", - "Verify a new conversation appears in the sidebar.", - "Verify the main panel shows an empty conversation state." - ], - "evidence": [], - "notes": "" - }, - { - "id": "chat-002", - "priority": 2, - "area": "chat", - "title": "Send a message in the current conversation", - "user_visible_behavior": "A user can submit a message and see it appear in the active thread.", - "status": "not_started", - "verification": [ - "Open an existing conversation.", - "Type a message into the input.", - "Submit the message.", - "Verify the new message appears in the thread." - ], - "evidence": [], - "notes": "" - }, - { - "id": "chat-003", - "priority": 3, - "area": "chat", - "title": "Persist the active conversation list", - "user_visible_behavior": "A user sees previously created conversations after restarting the app.", - "status": "not_started", - "verification": [ - "Create two conversations.", - "Restart the app.", - "Verify both conversations still appear in the sidebar." - ], - "evidence": [], - "notes": "" - } - ] -} diff --git a/courses/learn-harness-engineering/docs/vi/resources/templates/index.md b/courses/learn-harness-engineering/docs/vi/resources/templates/index.md deleted file mode 100644 index 31cdd3b6..00000000 --- a/courses/learn-harness-engineering/docs/vi/resources/templates/index.md +++ /dev/null @@ -1,15 +0,0 @@ -[English Version →](../../../en/resources/templates/) | [中文版本 →](../../../zh/resources/templates/) - -# Thư viện Mẫu - -Các mẫu harness sẵn sàng sử dụng cho các dự án agent. - -## Mẫu Có sẵn - -- [AGENTS.md](./AGENTS.md) — Tệp hướng dẫn agent chuẩn -- [CLAUDE.md](./CLAUDE.md) — Biến thể dành cho Claude Code -- [claude-progress.md](./claude-progress.md) — Nhật ký tiến độ phiên -- [clean-state-checklist.md](./clean-state-checklist.md) — Danh sách kiểm tra trạng thái sạch khi thoát phiên -- [evaluator-rubric.md](./evaluator-rubric.md) — Rubric tính điểm evaluator -- [quality-document.md](./quality-document.md) — Mẫu tài liệu chất lượng -- [session-handoff.md](./session-handoff.md) — Mẫu bàn giao phiên ngắn gọn diff --git a/courses/learn-harness-engineering/docs/vi/resources/templates/init.sh b/courses/learn-harness-engineering/docs/vi/resources/templates/init.sh deleted file mode 100755 index d7522d66..00000000 --- a/courses/learn-harness-engineering/docs/vi/resources/templates/init.sh +++ /dev/null @@ -1,29 +0,0 @@ -#!/usr/bin/env bash - -set -euo pipefail - -ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" -cd "$ROOT_DIR" - -# Replace these commands with the correct commands for your repository. -INSTALL_CMD=(npm install) -VERIFY_CMD=(npm test) -START_CMD=(npm run dev) - -echo "==> Working directory: $PWD" -echo "==> Syncing dependencies" -"${INSTALL_CMD[@]}" - -echo "==> Running baseline verification" -"${VERIFY_CMD[@]}" - -echo "==> Startup command" -printf ' %q' "${START_CMD[@]}" -printf '\n' - -if [ "${RUN_START_COMMAND:-0}" = "1" ]; then - echo "==> Starting the app" - exec "${START_CMD[@]}" -fi - -echo "Set RUN_START_COMMAND=1 if you want init.sh to launch the app directly." diff --git a/courses/learn-harness-engineering/docs/vi/resources/templates/quality-document.md b/courses/learn-harness-engineering/docs/vi/resources/templates/quality-document.md deleted file mode 100644 index 16fab2c5..00000000 --- a/courses/learn-harness-engineering/docs/vi/resources/templates/quality-document.md +++ /dev/null @@ -1,43 +0,0 @@ -# Tài liệu Chất lượng - -Một snapshot chất lượng cho mỗi domain sản phẩm và lớp kiến trúc. Cả agent và con người đều có thể sử dụng tài liệu này để nhanh chóng hiểu nơi nào codebase mạnh và nơi nào cần cải thiện. - -**Tần suất cập nhật:** Sau mỗi phiên quan trọng, hoặc trước khi bắt đầu giai đoạn công việc mới. - -**Thang điểm:** - -- **A**: Tất cả xác minh đang vượt qua, kiến trúc sạch, agent có thể đọc được, test ổn định -- **B**: Xác minh đang vượt qua, hầu hết sạch, thiếu nhỏ về khả năng đọc hoặc độ phủ test -- **C**: Hoạt động một phần, có khoảng trống đã biết, một số khu vực mã khó cho agent hiểu -- **D**: Không hoạt động, hoặc có vấn đề cấu trúc lớn - ---- - -## Domain Sản phẩm - -| Domain | Điểm | Xác minh | Khả năng đọc của Agent | Độ ổn định Test | Khoảng trống chính | Cập nhật lần cuối | -|--------|-------|-------------|-----------------|---------------|----------|-------------| -| Import Tài liệu | - | - | - | - | - | - | -| Quản lý Tài liệu | - | - | - | - | - | - | -| Indexing Tài liệu | - | - | - | - | - | - | -| Luồng Q&A | - | - | - | - | - | - | -| Câu trả lời có Grounding | - | - | - | - | - | - | - -## Lớp Kiến trúc - -| Lớp | Điểm | Thực thi Ranh giới | Khả năng đọc của Agent | Khoảng trống chính | Cập nhật lần cuối | -|-------|-------|---------------------|-----------------|----------|-------------| -| Main Process | - | - | - | - | - | -| Preload | - | - | - | - | - | -| Renderer | - | - | - | - | - | -| Services | - | - | - | - | - | - -## Lịch sử Thay đổi - -### YYYY-MM-DD - -- Thay đổi: -- Domain được nâng cấp: -- Domain bị hạ cấp: -- Khoảng trống mới được xác định: -- Khoảng trống đã đóng: diff --git a/courses/learn-harness-engineering/docs/vi/resources/templates/session-handoff.md b/courses/learn-harness-engineering/docs/vi/resources/templates/session-handoff.md deleted file mode 100644 index f2d0538b..00000000 --- a/courses/learn-harness-engineering/docs/vi/resources/templates/session-handoff.md +++ /dev/null @@ -1,30 +0,0 @@ -# Bàn giao Phiên - -## Đang Hoạt động Hiện tại - -- Những gì đang hoạt động: -- Xác minh nào thực sự đã chạy: - -## Thay đổi Trong Phiên này - -- Mã hoặc hành vi đã thêm: -- Thay đổi cơ sở hạ tầng hoặc harness: - -## Bị Hỏng hoặc Chưa được Xác minh - -- Lỗi đã biết: -- Đường dẫn chưa được xác minh: -- Rủi ro cho phiên tiếp theo: - -## Bước Tốt nhất Tiếp theo - -- Tính năng chưa hoàn thành có mức ưu tiên cao nhất: -- Tại sao đây là tính năng tiếp theo: -- Điều gì được tính là vượt qua: -- Điều gì không được thay đổi trong bước đó: - -## Lệnh - -- Khởi động: -- Xác minh: -- Lệnh debug tập trung: diff --git a/courses/learn-harness-engineering/docs/vi/skills/index.md b/courses/learn-harness-engineering/docs/vi/skills/index.md deleted file mode 100644 index 4d9ad40e..00000000 --- a/courses/learn-harness-engineering/docs/vi/skills/index.md +++ /dev/null @@ -1,49 +0,0 @@ -# Kỹ năng (Skills) - -Thư mục này chứa các bộ kỹ năng (skills) đi kèm với khóa học này. Các kỹ năng là các mẫu prompt độc lập có thể được nạp bởi các AI coding agent (Claude Code, Codex, Cursor, Windsurf, v.v.) để thực hiện các tác vụ chuyên môn. - -## harness-creator - -Một kỹ năng xây dựng harness cấp độ thực tế (production-grade) dành cho AI coding agent. Nó giúp bạn tạo ra, đánh giá và cải thiện năm hệ thống cốt lõi của harness: hướng dẫn (instructions), trạng thái (state), xác minh (verification), phạm vi (scope) và vòng đời phiên làm việc (session lifecycle). - -### Chức năng - -- **Tạo harness từ đầu** — AGENTS.md, danh sách tính năng, luồng công việc xác minh -- **Cải thiện các harness hiện có** — Đánh giá năm hệ thống phụ với các cải tiến được ưu tiên -- **Thiết kế tính liên tục của phiên** — Lưu trữ bộ nhớ, theo dõi tiến độ, thủ tục bàn giao -- **Áp dụng các mẫu production** — Bộ nhớ, kỹ thuật ngữ cảnh, an toàn công cụ, điều phối đa agent - -### Bắt đầu Nhanh - -Các tệp kỹ năng nằm trong kho lưu trữ tại [`skills/harness-creator/`](https://github.com/walkinglabs/learn-harness-engineering/tree/main/skills/harness-creator). - -Để sử dụng nó với Claude Code, hãy sao chép thư mục `harness-creator/` vào đường dẫn kỹ năng trong dự án của bạn, hoặc chỉ định tệp SKILL.md cho agent của bạn. - -### Các Mẫu Tham Khảo (Reference Patterns) - -Kỹ năng này bao gồm 6 tài liệu tham khảo chuyên sâu: - -| Mẫu (Pattern) | Khi nào sử dụng | -|---------|-------------| -| Lưu trữ bộ nhớ | Agent quên giữa các phiên | -| Kỹ thuật ngữ cảnh | Quản lý ngân sách ngữ cảnh, nạp tức thời (JIT) | -| Đăng ký công cụ | An toàn công cụ, kiểm soát đồng thời | -| Điều phối Đa Agent | Xử lý song song, quy trình làm việc chuyên môn hóa | -| Vòng đời & Khởi động | Hooks, tác vụ nền, khởi tạo | -| Cạm bẫy (Gotchas) | 15 chế độ lỗi không rõ ràng kèm theo cách sửa | - -### Các Mẫu (Templates) - -Kỹ năng này đi kèm với các mẫu sẵn sàng sử dụng: - -- `agents.md` — Khung AGENTS.md với các quy tắc hoạt động -- `feature-list.json` — JSON Schema + danh sách tính năng mẫu -- `init.sh` — Kịch bản khởi tạo tiêu chuẩn -- `progress.md` — Mẫu nhật ký tiến độ phiên làm việc - -### Kỹ năng này được Xây dựng như thế nào - -`harness-creator` được phát triển bằng phương pháp **skill-creator** — kỹ năng meta (meta-skill) chính thức của Anthropic để tạo, thử nghiệm và cải thiện lặp đi lặp lại các kỹ năng của agent. skill-creator cung cấp một luồng công việc có cấu trúc (bản nháp → thử nghiệm → đánh giá → lặp lại) được tích hợp sẵn các công cụ chạy đánh giá (eval runners), máy chấm điểm (graders) và trình xem điểm chuẩn (benchmark viewer). - -- **Mã nguồn skill-creator**: [anthropics/skills — skill-creator](https://github.com/anthropics/skills/tree/main/skills/skill-creator) -- **Tài liệu kỹ năng Claude Code**: [anthropics/claude-code — plugin-dev/skills](https://github.com/anthropics/claude-code/tree/main/plugins/plugin-dev/skills) diff --git a/courses/learn-harness-engineering/docs/zh-TW/index.md b/courses/learn-harness-engineering/docs/zh-TW/index.md deleted file mode 100644 index 5f1a5485..00000000 --- a/courses/learn-harness-engineering/docs/zh-TW/index.md +++ /dev/null @@ -1,73 +0,0 @@ -# 歡迎來到 Learn Harness Engineering - -Learn Harness Engineering 是一門專注於 AI 程式設計智慧體工程化落地的課程。本課程深入研究並總結了業界最前沿的 Harness Engineering(工具馬具/腳手架工程)理論與實踐,參考資料包括: -- [OpenAI: Harness engineering: leveraging Codex in an agent-first world](https://openai.com/index/harness-engineering/) -- [Anthropic: Effective harnesses for long-running agents](https://www.anthropic.com/engineering/effective-harnesses-for-long-running-agents) -- [Anthropic: Harness design for long-running application development](https://www.anthropic.com/engineering/harness-design-long-running-apps) -- [Awesome Harness Engineering](https://github.com/walkinglabs/awesome-harness-engineering) - -透過系統性的環境設計、狀態管理、驗證與控制機制,本課程旨在幫助你讓 Codex 和 Claude Code 等 AI Agent 能夠真正可靠地完成真實工程任務。它透過明確的規則和邊界約束你的 AI 程式設計助手,幫助你更可靠地建構功能、修復 Bug 並自動化開發任務。 - -## 開始學習 - -選擇適合你的學習路徑。本課程分為理論講義、實戰專案和開箱即用的資源庫。 - - - -## Harness 的核心機制 - -Harness 的本質不是「讓模型變聰明」,而是給模型建立一套閉環的**工作系統**。你可以透過下面的簡單圖示理解它的核心運作流: - -```mermaid -graph TD - A["明確目標
    AGENTS.md"] --> B("初始化檢查
    init.sh") - B --> C{"執行任務
    AI Agent"} - C -->|遇到障礙| D["執行回饋
    CLI / Logs"] - D -->|自動修復| C - C -->|程式碼完成| E{"驗證與評審
    Test & QA"} - E -->|未通過| D - E -->|通過| F["清理與交接
    claude-progress.md"] - - classDef primary fill:#D95C41,stroke:#C14E36,color:#fff,font-weight:bold; - classDef process fill:#F4F3EE,stroke:#D1D1D1,color:#1A1A1A; - classDef check fill:#EAE8E1,stroke:#B3B3B3,color:#1A1A1A; - - class A,F primary; - class B,D process; - class C,E check; -``` - -## 你將學到什麼 - -你將在本課程中掌握以下核心概念: - -
      -
    • 用明確的規則和邊界約束 Agent 的行為
    • -
    • 在跨會話的長時任務中保持上下文連續性
    • -
    • 防止 Agent 提前宣告任務完成。
    • -
    • 讓 Agent 學會透過完整的流水線測試來驗證自己的工作
    • -
    • 讓 Agent 的執行過程可觀測、可除錯
    • -
    - -## 下一步 - -了解核心概念後,可以透過以下內容深入學習: - - diff --git a/courses/learn-harness-engineering/docs/zh-TW/lectures/lecture-01-why-capable-agents-still-fail/code/failure-pattern-demo.ts b/courses/learn-harness-engineering/docs/zh-TW/lectures/lecture-01-why-capable-agents-still-fail/code/failure-pattern-demo.ts deleted file mode 100644 index 311e665b..00000000 --- a/courses/learn-harness-engineering/docs/zh-TW/lectures/lecture-01-why-capable-agents-still-fail/code/failure-pattern-demo.ts +++ /dev/null @@ -1,170 +0,0 @@ -/** - * failure-pattern-demo.ts - * - * Simulates the 4-step failure pattern that capable agents fall into: - * 1. Incomplete context - * 2. Locally reasonable changes - * 3. No global verification - * 4. Premature completion - * - * Run: npx tsx docs/lectures/lecture-01-why-capable-agents-still-fail/code/failure-pattern-demo.ts - */ - -// --------------------------------------------------------------------------- -// Types -// --------------------------------------------------------------------------- - -interface StepState { - step: number; - name: string; - contextAvailable: string[]; - contextMissing: string[]; - actionTaken: string; - localOutcome: string; - globalImpact: string; - completed: boolean; -} - -// --------------------------------------------------------------------------- -// Simulated "model" -- a simple decision function that bases its output -// solely on whatever context it has been given. -// --------------------------------------------------------------------------- - -function modelDecide(context: string[], task: string): string { - const has = (s: string) => context.some((c) => c.includes(s)); - - // The task is to "add a search endpoint to the API". - // Correct answer requires knowing about auth middleware and rate limiting. - - if (!has("auth")) { - return "Created new route handler /search without authentication checks"; - } - if (!has("rate-limit")) { - return "Added search route with auth but forgot rate limiting"; - } - if (!has("test-standards")) { - return "Implemented search with auth and rate-limit, but no tests"; - } - return "Fully implemented search endpoint with auth, rate-limit, and tests"; -} - -// --------------------------------------------------------------------------- -// Failure simulation -// --------------------------------------------------------------------------- - -function simulateFailurePattern(): StepState[] { - const steps: StepState[] = []; - - // ---- Step 1: Incomplete Context ---- - const step1Context = ["project structure", "route definitions"]; - const step1Missing = ["auth middleware", "rate-limiting policy", "test standards"]; - const step1Decision = modelDecide(step1Context, "add search endpoint"); - - steps.push({ - step: 1, - name: "Incomplete Context", - contextAvailable: step1Context, - contextMissing: step1Missing, - actionTaken: step1Decision, - localOutcome: "Looks good -- route compiles, returns data", - globalImpact: "Missing auth means unauthenticated access to search", - completed: false, - }); - - // ---- Step 2: Locally Reasonable Changes ---- - // The agent adds auth after a hint, but still lacks other context. - const step2Context = [...step1Context, "auth middleware"]; - const step2Missing = ["rate-limiting policy", "test standards"]; - const step2Decision = modelDecide(step2Context, "add search endpoint"); - - steps.push({ - step: 2, - name: "Locally Reasonable Changes", - contextAvailable: step2Context, - contextMissing: step2Missing, - actionTaken: step2Decision, - localOutcome: "Route has auth -- looks complete locally", - globalImpact: "No rate limiting means the endpoint can be abused", - completed: false, - }); - - // ---- Step 3: No Global Verification ---- - const step3Context = [...step2Context, "rate-limiting policy"]; - const step3Missing = ["test standards"]; - const step3Decision = modelDecide(step3Context, "add search endpoint"); - - steps.push({ - step: 3, - name: "No Global Verification", - contextAvailable: step3Context, - contextMissing: step3Missing, - actionTaken: step3Decision, - localOutcome: "Feature appears fully implemented", - globalImpact: "No tests -- regression risk, violates project standards", - completed: false, - }); - - // ---- Step 4: Premature Completion ---- - steps.push({ - step: 4, - name: "Premature Completion", - contextAvailable: step3Context, - contextMissing: step3Missing, - actionTaken: 'Agent outputs: "Done. Added search endpoint."', - localOutcome: "Agent is satisfied, task marked complete", - globalImpact: "Task is incomplete -- missing tests, no E2E verification", - completed: true, - }); - - return steps; -} - -// --------------------------------------------------------------------------- -// Comparison table -// --------------------------------------------------------------------------- - -function printComparisonTable(steps: StepState[]): void { - console.log("\n" + "=".repeat(90)); - console.log(" FAILURE PATTERN DEMO -- 4 Steps to a Broken Deliverable"); - console.log("=".repeat(90)); - - for (const s of steps) { - console.log(`\n Step ${s.step}: ${s.name}`); - console.log(" " + "-".repeat(60)); - console.log(` Context available : ${s.contextAvailable.join(", ")}`); - console.log(` Context missing : ${s.contextMissing.join(", ") || "(none)"}`); - console.log(` Action taken : ${s.actionTaken}`); - console.log(` Local outcome : ${s.localOutcome}`); - console.log(` Global impact : ${s.globalImpact}`); - console.log(` Marked complete? : ${s.completed ? "YES (premature)" : "No"}`); - } - - // Summary comparison - console.log("\n" + "=".repeat(90)); - console.log(" COMPARISON: What the agent saw vs. what was actually needed"); - console.log("=".repeat(90)); - - const totalRequired = ["project structure", "route definitions", "auth middleware", "rate-limiting policy", "test standards"]; - const finalAvailable = steps[steps.length - 1].contextAvailable; - - const header = "| Criterion | Available | Missing | Status |"; - const sep = "|------------------------|-----------|---------|----------|"; - console.log("\n" + header); - console.log(sep); - - for (const item of totalRequired) { - const avail = finalAvailable.includes(item); - const row = `| ${item.padEnd(23)}| ${(avail ? "Yes" : "No").padEnd(10)}| ${(!avail ? "Yes" : "").padEnd(8)}| ${(avail ? "OK" : "GAP").padEnd(9)}|`; - console.log(row); - } - - const gapCount = totalRequired.filter((i) => !finalAvailable.includes(i)).length; - console.log("\n Result: Agent completed with " + gapCount + " of " + totalRequired.length + " context items missing."); - console.log(" This is the core failure pattern: each step looked reasonable in isolation.\n"); -} - -// --------------------------------------------------------------------------- -// Run -// --------------------------------------------------------------------------- - -printComparisonTable(simulateFailurePattern()); diff --git a/courses/learn-harness-engineering/docs/zh-TW/lectures/lecture-01-why-capable-agents-still-fail/code/failure-signals-checklist.md b/courses/learn-harness-engineering/docs/zh-TW/lectures/lecture-01-why-capable-agents-still-fail/code/failure-signals-checklist.md deleted file mode 100644 index 821dc199..00000000 --- a/courses/learn-harness-engineering/docs/zh-TW/lectures/lecture-01-why-capable-agents-still-fail/code/failure-signals-checklist.md +++ /dev/null @@ -1,9 +0,0 @@ -# 失敗訊號檢查清單 - -在審查一次較弱的 harness 執行結果時,請使用此清單。 - -- Agent 是否曾錯誤詢問或錯誤推斷應用程式的啟動方式? -- 它是否建立了與預期產品不符的目錄或抽象? -- 它是否在製作出可見的 UI 外殼後就停止了,而沒有完整的工作流程? -- 它是否留下了有助於後續執行繼續的筆記或產物? -- 一個全新的 session 能否在五分鐘內理解發生了什麼事? diff --git a/courses/learn-harness-engineering/docs/zh-TW/lectures/lecture-01-why-capable-agents-still-fail/code/index.md b/courses/learn-harness-engineering/docs/zh-TW/lectures/lecture-01-why-capable-agents-still-fail/code/index.md deleted file mode 100644 index 9b09c1ef..00000000 --- a/courses/learn-harness-engineering/docs/zh-TW/lectures/lecture-01-why-capable-agents-still-fail/code/index.md +++ /dev/null @@ -1,7 +0,0 @@ -# 第 01 講的程式碼 - -此資料夾用於展示以下小型範例: - -- 強大的模型在薄弱的環境中失敗 -- 規格不足的 repo 設定 -- 缺少反饋迴圈 diff --git a/courses/learn-harness-engineering/docs/zh-TW/lectures/lecture-01-why-capable-agents-still-fail/code/underspecified-task.md b/courses/learn-harness-engineering/docs/zh-TW/lectures/lecture-01-why-capable-agents-still-fail/code/underspecified-task.md deleted file mode 100644 index b488fc80..00000000 --- a/courses/learn-harness-engineering/docs/zh-TW/lectures/lecture-01-why-capable-agents-still-fail/code/underspecified-task.md +++ /dev/null @@ -1,18 +0,0 @@ -# 規格不足的任務範例 - -建構一個具備 AI 問答功能的桌面知識庫應用程式。 - -限制條件: - -- 未指定任何限制 -- 未提供啟動指令 -- 無資料夾結構指引 -- 未定義資料模型 -- 無明確的完成標準 - -此類提示的典型結果: - -- Agent 會臨時編造架構 -- 應用程式可能可以編譯,但無法穩定啟動 -- UI 可能會在任何可用的匯入/查詢路徑出現之前就顯示 -- Agent 通常會在外觀成功後停止 diff --git a/courses/learn-harness-engineering/docs/zh-TW/lectures/lecture-01-why-capable-agents-still-fail/index.md b/courses/learn-harness-engineering/docs/zh-TW/lectures/lecture-01-why-capable-agents-still-fail/index.md deleted file mode 100644 index 32e8cc68..00000000 --- a/courses/learn-harness-engineering/docs/zh-TW/lectures/lecture-01-why-capable-agents-still-fail/index.md +++ /dev/null @@ -1,117 +0,0 @@ -[English Version →](../../../en/lectures/lecture-01-why-capable-agents-still-fail/) - -> 本篇代碼示例:[code/](https://github.com/walkinglabs/learn-harness-engineering/blob/main/docs/zh-TW/lectures/lecture-01-why-capable-agents-still-fail/code/) -> 實戰練習:[Project 01. 只寫提示詞讓 agent 做,和定好規則再讓它做,差多少](./../../projects/project-01-baseline-vs-minimal-harness/index.md) - -# 第一講. 模型能力強,不等於執行可靠 - -你說你也算見過世面的人了——Claude Pro 訂著,GPT-4o 的 API key 也有,SWE-bench 排行榜上的數字你比誰都清楚。有一天你終於想讓 AI agent 幫你改一個真實的項目,信心滿滿地交代下去。結果呢?加了功能但測試掛了,改了 bug 但引入了新 bug,跑了 20 分鐘然後自信滿滿地說"完成了"——你一看代碼,根本不是你要的東西。 - -這時候你的第一反應是什麼?"這模型不行,得換一個更貴的。"且慢。先別急著掏錢包。問題可能根本不在模型身上。 - -讓我們先看一組數據。截至 2025 年底,最強的 coding agent 在 SWE-bench Verified 上的通過率大約在 50-60%。這還是精心挑選過的、有明確 issue 描述和測試用例的任務。換到你日常開發的場景——需求模糊、沒有現成測試、隱含的業務規則散落在各處——這個數字只會更低。 - -但這組數據背後藏著一個反直覺的事實。 - -## 同一匹馬,兩種命運 - -Anthropic 做過一個對照實驗。同一個 prompt("做一個 2D 復古遊戲編輯器"),同一個模型(Opus 4.5)。第一次讓它裸跑——20 分鐘,花了 $9,遊戲核心功能根本跑不起來。第二次給它配上完整的 harness(planner + generator + evaluator 三 agent 架構)——6 小時,花了 $200,遊戲可以正常遊玩。 - -模型沒換。Opus 4.5 還是那個 Opus 4.5。換的是馬具。 - -OpenAI 在 2025 年發佈的 harness engineering 文章裡說得更直白:Codex 在一個 harness 搭得好的倉庫裡,表現能從"不可靠"變成"可靠"。注意他們的用詞——不是"好了一點",是質變。就像一匹千裡馬,沒馬具你也能騎,但控不穩、走不遠、摔下來也不稀奇。harness 就是那整套馬具——**模型權重之外的一切工程基礎設施**。 - -## agent 到底卡在哪 - -那具體是什麼在出問題? - -最常見的:你根本沒把任務說清楚。你說"加個搜索功能",agent 理解的跟你完全不一樣——搜什麼?全文本還是結構化?要不要分頁?要不要高亮?你沒說清楚,agent 就自己猜。猜對了是運氣,猜錯了你再改,改的成本比一開始說清楚還高。這就像你去飯館跟師傅說"來個魚",端上來是紅燒還是清蒸還是酸菜——全看運氣。 - -就算你說清了,項目裡隱含的架構約定 agent 也不知道。你們團隊統一用 SQLAlchemy 2.0 的新語法,但 agent 默認寫了 1.x 的代碼。所有 API 端點必須走 OAuth 2.0 認證,但這個規則只存在於你腦子裡和一個三個月前的 Slack 消息裡。Agent 看不到這些——它不是不想遵守,是壓根不知道有這回事。 - -環境也是個坑。開發環境配置不完整、依賴缺了、工具版本不對。Agent 把寶貴的上下文窗口花在了 `pip install` 失敗、Node 版本不對這些事上,而不是解決你的核心任務。就好比你讓一個木匠來幹活,結果錘子沒帶、釘子找不到、工作臺還是歪的——手藝再好也使不上勁。 - -更常見的:壓根沒有驗證手段。沒有測試、沒有 lint、或者驗證命令根本沒告訴 agent。Agent 寫完代碼,自己看了看覺得沒問題,就說完成了。這就像讓學生交作業但不給答案檢查——他自己覺得自己做對了,但批改的時候一堆錯誤。Anthropic 還觀察到一個有意思的現象:當 agent 感覺上下文快滿了,它們會匆忙結束當前工作,跳過驗證步驟,選一個簡單的方案而不是最優方案。他們把這叫"上下文焦慮"——跟你考試時發現時間快到了趕緊隨便選幾個選擇題是一回事。 - -長任務跨會話就更慘了——上次會話的發現全丟了,每個新會話都得重新探索項目結構、理解代碼組織。缺乏持久化狀態的 agent 在超過 30 分鐘的任務中失敗率急劇上升。 - -## 關鍵名詞解釋 - -理解了上面的場景,這些概念就不再是一堆術語了: - -- **能力鴻溝(Capability Gap)**:模型在基準測試上的表現和真實任務上的表現之間的巨大落差。SWE-bench Verified 上 50-60% 的通過率意味著近一半的真實 issue 解不了。 -- **Harness**:模型之外的一切——指令、工具、環境、狀態管理、驗證反饋。不是模型權重的部分,全是 harness。也就是我們說的"馬具"。 -- **Harness 誘導失敗**:模型本身能力足夠,但因為執行環境有結構性缺陷而失敗。Anthropic 的對照實驗已經證明了這一點。 -- **驗證缺口**:agent 對自己輸出的信心評估和實際正確性之間的偏差。agent 說"我做完了"但實際沒做完——這是最常見的失敗模式。 -- **診斷循環**:執行 → 觀察失敗 → 定位到 harness 的哪一層出了問題 → 修補那一層 → 重新執行。這是 harness 工程的核心方法論。 -- **完成定義(Definition of Done)**:一組可以用命令驗證的條件——測試通過、lint 沒報錯、類型檢查通過。沒有顯式的完成定義,agent 就會自己編一個。 - -## 遇到失敗,先修 harness - -核心原則:**遇到失敗,先別換模型,先檢查 harness。** 如果同一個模型在類似的結構良好的任務中能成功,那優先假設是 harness 的問題。這就像汽車拋錨——你不會第一時間懷疑是發動機壞了,你會先看看是不是沒油了。 - -具體怎麼做: - -**每次失敗都歸因到具體層。** 不要籠統地說"模型不行",而是問:是任務沒說清楚?是上下文不夠?是沒有驗證手段?把每次失敗歸到五層防禦:任務規範、上下文供給、執行環境、驗證反饋、狀態管理。這裡說的是實操診斷用的五層防禦,不是上面那組六個術語。養成這個習慣,你會發現"模型不行"這個結論在你的日誌裡出現得越來越少。 - -**給每個任務寫顯式的完成定義。** 不要說"加個搜索功能",要說: -``` -完成標準: -- 新增 GET /api/search?q=xxx 端点 -- 支持分页,默認 20 条 -- 返回結果包含高亮片段 -- 所有新程式碼通過 pytest -- 類型檢查通過(mypy --strict) -``` - -**創建 AGENTS.md 檔案。** 在倉庫根目錄放一個檔案,告訴 agent 這個項目的技術棧、架構約定、驗證命令。這是 harness 工程的第一步,也是投入產出比最高的一步。一個 `AGENTS.md` 檔案可能比你換一個更貴的模型更有效——我不是在開玩笑。 - -**建立診斷循環。** 不要把失敗當作"模型又犯傻了",而是當作"harness 又暴露了一個缺陷"的信號。每次失敗 → 定位層 → 修補 → 下次不再犯。幾輪下來,你的 harness 會越來越強,agent 的表現會穩定提升。就像修路——每填一個坑,下一段路就更平坦。 - -**量化改進。** 記個簡單的日誌:每個任務成功了沒有,失敗了是哪一層的問題。跑幾輪之後你就能看出來哪個層是瓶頸,集中火力修那個層。 - -## 一百萬人行代碼的實驗 - -OpenAI 在 2025 年做了一個激進的實驗:用 Codex 從一個空的 git 倉庫起步,構建一個完整的內部產品。五個月後,這個倉庫有大約 100 萬行代碼——應用邏輯、基礎設施、工具、文檔、內部開發工具——全部由 agent 生成。三個工程師驅動 Codex,開了大約 1,500 個 PR 併合並。平均每人每天 3.5 個 PR。 - -這個實驗的關鍵約束是:**人類永遠不直接寫代碼。** 這不是噱頭,而是為了逼團隊搞清楚——當工程師的主要工作不再是寫代碼,而是設計環境、表達意圖、構建反饋迴路時,到底什麼變了? - -早期進展比預期慢。不是 Codex 不行,而是環境不夠完整——agent 缺少必要的工具、抽象和內部結構來推進高層次目標。工程師的工作變成了:把大目標拆成小積木(設計、編碼、審查、測試),讓 agent 去搭建,然後用這些積木解鎖更復雜的任務。當某件事失敗了,修復幾乎從來不是"更努力",而是"agent 缺什麼能力,怎麼讓它既可理解又可執行"。 - -這個實驗直接證明了本講的核心論點:**同一個模型,在空白環境裡和在有完整 harness 的環境裡,產出有本質差異。** 模型沒變,變的是環境。 - -> 來源:[OpenAI: Harness engineering: leveraging Codex in an agent-first world](https://openai.com/index/harness-engineering/) - -## 一個更接地氣的例子 - -一個團隊用 Claude Sonnet 給一箇中等規模的 Python Web 應用(FastAPI + PostgreSQL + Redis,約 15,000 行代碼)添加新的 API 端點。 - -起初他們只給了一句話:"在 `/api/v2/users` 下添加用戶偏好設置端點"。結果呢?agent 花了 40% 的上下文窗口探索倉庫結構,產出了看似合理的代碼但沒遵循項目的錯誤處理模式,用了舊版 SQLAlchemy 語法,宣稱完成但端點實際有運行時錯誤。下一個會話還得重新做發現工作。 - -後來他們加了 `AGENTS.md`(描述項目架構和技術棧版本)、顯式的驗證命令(`pytest tests/api/v2/ && python -m mypy src/`)、和架構決策記錄。同一模型在三次獨立運行中全部成功,上下文使用效率提高了約 60%。 - -模型沒變。變的還是 harness。 - -## 帶走什麼 - -- 模型能力和執行可靠性是兩回事。千裡馬也得配上好馬具。 -- 失敗的時候先看 harness,再看模型。換模型是成本最高的選擇——而且很多時候根本不是模型的問題。 -- 每次失敗都是一個信號:你的 harness 有結構性缺陷。把它找出來、修掉。 -- 五層防禦:任務規範、上下文供給、執行環境、驗證反饋、狀態管理。逐層排查,就像醫生看病先檢查最常見的病因。 -- 一個 `AGENTS.md` 檔案可能比你換一個更貴的模型更有效。真的。 - -## 延伸閱讀 - -- [OpenAI: Harness Engineering — Leveraging Codex in an Agent-First World](https://openai.com/index/harness-engineering/) -- [Anthropic: Effective Harnesses for Long-Running Agents](https://www.anthropic.com/engineering/effective-harnesses-for-long-running-agents) -- [HumanLayer: Skill Issue — Harness Engineering for Coding Agents](https://humanlayer.dev/articles/harness-engineering-for-coding-agents/) -- [SWE-bench Leaderboard](https://www.swebench.com/) -- [Thoughtworks Technology Radar: Harness Engineering](https://www.thoughtworks.com/radar) - -## 練習 - -1. **對比實驗**:選一個你熟悉的代碼倉庫和一項非平凡的修改任務。先不給任何 harness 支持,讓 agent 跑一次,記錄失敗。然後加一個 `AGENTS.md` 和顯式的驗證命令,讓同一個 agent 再跑一次。對比兩次的結果,把失敗歸因到五層防禦中的某一層。 - -2. **驗證缺口測量**:選 5 個編碼任務,在每個任務完成後記錄 agent 是否聲稱完成,然後用獨立測試驗證實際正確性。計算 agent 在實際沒完成時聲稱完成的比例——這就是你的驗證缺口。然後想想:加什麼驗證命令能把這個比例降下來? - -3. **診斷循環實踐**:找一個 agent 在你的項目中反覆失敗的任務。跑一次,記錄失敗。歸因到五層中的某一層。修那一層。再跑。重複三到五輪,記錄每一輪的改善。 diff --git a/courses/learn-harness-engineering/docs/zh-TW/lectures/lecture-02-what-a-harness-actually-is/code/harness-components.md b/courses/learn-harness-engineering/docs/zh-TW/lectures/lecture-02-what-a-harness-actually-is/code/harness-components.md deleted file mode 100644 index a23437d5..00000000 --- a/courses/learn-harness-engineering/docs/zh-TW/lectures/lecture-02-what-a-harness-actually-is/code/harness-components.md +++ /dev/null @@ -1,21 +0,0 @@ -# Harness 元件範例 - -對於在本機 repo 中工作的程式碼 Agent: - -- 模型: - LLM 本身 - -- Harness: - - system prompt - - AGENTS.md - - bash 工具 - - 檔案讀取/寫入工具 - - git 存取 - - 本機檔案系統 - - 啟動腳本 - - 測試指令 - - 停止鉤子 - - lint 檢查 - - 評估迴圈 - -如果你更改上述任何一個 harness 元件,你就改變了有效的 Agent。 diff --git a/courses/learn-harness-engineering/docs/zh-TW/lectures/lecture-02-what-a-harness-actually-is/code/harness-vs-no-harness.ts b/courses/learn-harness-engineering/docs/zh-TW/lectures/lecture-02-what-a-harness-actually-is/code/harness-vs-no-harness.ts deleted file mode 100644 index d856ba36..00000000 --- a/courses/learn-harness-engineering/docs/zh-TW/lectures/lecture-02-what-a-harness-actually-is/code/harness-vs-no-harness.ts +++ /dev/null @@ -1,180 +0,0 @@ -/** - * harness-vs-no-harness.ts - * - * Side-by-side comparison of the same task executor running with and without - * a harness. The harness version adds explicit rules, verification steps, - * and stop conditions. - * - * Run: npx tsx docs/lectures/lecture-02-what-a-harness-actually-is/code/harness-vs-no-harness.ts - */ - -// --------------------------------------------------------------------------- -// Types & helpers -// --------------------------------------------------------------------------- - -interface TaskResult { - name: string; - passed: boolean; - durationMs: number; - issues: string[]; -} - -function pad(s: string, len: number): string { - return s.length >= len ? s : s + " ".repeat(len - s.length); -} - -// --------------------------------------------------------------------------- -// Simulated task executor -// --------------------------------------------------------------------------- - -/** A simple task that can succeed or fail depending on rules. */ -type Task = { - name: string; - requiresAuth: boolean; - hasTests: boolean; - withinScope: boolean; -}; - -const tasks: Task[] = [ - { name: "Add search endpoint", requiresAuth: true, hasTests: false, withinScope: true }, - { name: "Add delete endpoint", requiresAuth: true, hasTests: true, withinScope: true }, - { name: "Refactor auth middleware", requiresAuth: true, hasTests: true, withinScope: false }, - { name: "Add health check", requiresAuth: false, hasTests: true, withinScope: true }, - { name: "Add rate limiter", requiresAuth: true, hasTests: false, withinScope: true }, -]; - -// --------------------------------------------------------------------------- -// Run WITHOUT harness -- just execute every task, no checks -// --------------------------------------------------------------------------- - -function runWithoutHarness(taskList: Task[]): TaskResult[] { - const results: TaskResult[] = []; - - for (const t of taskList) { - const start = performance.now(); - const issues: string[] = []; - - // The "agent" does the work and calls it done. - let passed = true; - - // Problems that go undetected without a harness - if (t.requiresAuth && !t.hasTests) { - issues.push("No tests for auth-protected endpoint"); - // Agent doesn't notice -- marks as pass anyway - } - if (!t.withinScope) { - issues.push("Task is outside current scope"); - // Agent doesn't notice - } - - const duration = performance.now() - start; - results.push({ name: t.name, passed, durationMs: duration, issues }); - } - - return results; -} - -// --------------------------------------------------------------------------- -// Run WITH harness -- rules, verification, stop conditions -// --------------------------------------------------------------------------- - -function runWithHarness(taskList: Task[]): TaskResult[] { - const rules = { - requireTestsForAuth: true, - enforceScope: true, - }; - - const results: TaskResult[] = []; - - for (const t of taskList) { - const start = performance.now(); - const issues: string[] = []; - let passed = true; - - // Rule: auth endpoints must have tests - if (rules.requireTestsForAuth && t.requiresAuth && !t.hasTests) { - issues.push("BLOCKED: Auth-protected endpoint missing tests"); - passed = false; - } - - // Rule: stay in scope - if (rules.enforceScope && !t.withinScope) { - issues.push("BLOCKED: Task outside active scope -- skipped"); - passed = false; - } - - // Verification: re-check after execution - if (passed && !t.hasTests) { - issues.push("WARNING: No tests exist for this change"); - } - - const duration = performance.now() - start; - results.push({ name: t.name, passed, durationMs: duration, issues }); - } - - return results; -} - -// --------------------------------------------------------------------------- -// Reporting -// --------------------------------------------------------------------------- - -function printComparison( - noHarness: TaskResult[], - withHarness: TaskResult[] -): void { - console.log("\n" + "=".repeat(80)); - console.log(" HARNESS vs NO-HARNESS COMPARISON"); - console.log("=".repeat(80)); - - const header = `| ${pad("Task", 28)}| ${pad("No-Harness", 12)}| ${pad("Issues(NH)", 35)}| ${pad("Harness", 12)}| ${pad("Issues(H)", 35)}|`; - const sep = `|${"-".repeat(30)}|${"-".repeat(14)}|${"-".repeat(37)}|${"-".repeat(14)}|${"-".repeat(37)}|`; - - console.log("\n" + header); - console.log(sep); - - for (let i = 0; i < noHarness.length; i++) { - const nh = noHarness[i]; - const wh = withHarness[i]; - const nhPass = nh.passed ? "PASS" : "FAIL"; - const whPass = wh.passed ? "PASS" : "FAIL"; - const nhIssue = nh.issues[0] ?? "(none)"; - const whIssue = wh.issues[0] ?? "(none)"; - - console.log( - `| ${pad(nh.name, 28)}| ${pad(nhPass, 12)}| ${pad(nhIssue, 35)}| ${pad(whPass, 12)}| ${pad(whIssue, 35)}|` - ); - } - - // Metrics - const nhPassed = noHarness.filter((r) => r.passed).length; - const whPassed = withHarness.filter((r) => r.passed).length; - const nhIssues = noHarness.reduce((sum, r) => sum + r.issues.length, 0); - const whIssues = withHarness.reduce((sum, r) => sum + r.issues.length, 0); - - console.log("\n" + "=".repeat(80)); - console.log(" SUMMARY METRICS"); - console.log("=".repeat(80)); - - const metricsHeader = `| ${pad("Metric", 30)}| ${pad("No Harness", 15)}| ${pad("With Harness", 15)}|`; - const metricsSep = `|${"-".repeat(32)}|${"-".repeat(17)}|${"-".repeat(17)}|`; - console.log("\n" + metricsHeader); - console.log(metricsSep); - console.log(`| ${pad("Tasks passed", 30)}| ${pad(String(nhPassed) + "/" + noHarness.length, 15)}| ${pad(String(whPassed) + "/" + withHarness.length, 15)}|`); - console.log(`| ${pad("Issues detected", 30)}| ${pad(String(nhIssues), 15)}| ${pad(String(whIssues), 15)}|`); - console.log(`| ${pad("False positives (passed but flawed)", 30)}| ${pad(String(nhPassed - truePassCount(noHarness)), 15)}| ${pad(String(whPassed - truePassCount(withHarness)), 15)}|`); - - console.log("\n The harness catches problems the no-harness run silently ignores."); - console.log(" Without a harness, every task 'passes' even when it shouldn't.\n"); -} - -/** Count tasks that actually pass (have tests and are in scope). */ -function truePassCount(results: TaskResult[]): number { - return results.filter((r) => r.passed && r.issues.length === 0).length; -} - -// --------------------------------------------------------------------------- -// Run -// --------------------------------------------------------------------------- - -printComparison(runWithoutHarness(tasks), runWithHarness(tasks)); diff --git a/courses/learn-harness-engineering/docs/zh-TW/lectures/lecture-02-what-a-harness-actually-is/code/index.md b/courses/learn-harness-engineering/docs/zh-TW/lectures/lecture-02-what-a-harness-actually-is/code/index.md deleted file mode 100644 index b9bcdeb2..00000000 --- a/courses/learn-harness-engineering/docs/zh-TW/lectures/lecture-02-what-a-harness-actually-is/code/index.md +++ /dev/null @@ -1,8 +0,0 @@ -# 第 02 講的程式碼 - -此資料夾用於區分以下項目的小型範例: - -- 模型行為 -- harness 行為 -- 僅基於 prompt 的設定 -- 由環境支援的 Agent 設定 diff --git a/courses/learn-harness-engineering/docs/zh-TW/lectures/lecture-02-what-a-harness-actually-is/code/minimal-harness-loop.ts b/courses/learn-harness-engineering/docs/zh-TW/lectures/lecture-02-what-a-harness-actually-is/code/minimal-harness-loop.ts deleted file mode 100644 index e56cddd8..00000000 --- a/courses/learn-harness-engineering/docs/zh-TW/lectures/lecture-02-what-a-harness-actually-is/code/minimal-harness-loop.ts +++ /dev/null @@ -1,36 +0,0 @@ -type Message = { - role: "user" | "assistant"; - content: string; -}; - -type ToolResult = { - ok: boolean; - output: string; -}; - -function runTool(name: string, input: string): ToolResult { - if (name === "read_file") { - return { ok: true, output: `contents of ${input}` }; - } - - return { ok: false, output: `unknown tool: ${name}` }; -} - -export function minimalHarness(messages: Message[]) { - const nextAction = { - tool: "read_file", - input: "README.md" - }; - - const result = runTool(nextAction.tool, nextAction.input); - - return { - messages: [ - ...messages, - { - role: "assistant", - content: `Tool ${nextAction.tool} returned: ${result.output}` - } - ] - }; -} diff --git a/courses/learn-harness-engineering/docs/zh-TW/lectures/lecture-02-what-a-harness-actually-is/index.md b/courses/learn-harness-engineering/docs/zh-TW/lectures/lecture-02-what-a-harness-actually-is/index.md deleted file mode 100644 index 0d6c8bcb..00000000 --- a/courses/learn-harness-engineering/docs/zh-TW/lectures/lecture-02-what-a-harness-actually-is/index.md +++ /dev/null @@ -1,109 +0,0 @@ -[English Version →](../../../en/lectures/lecture-02-what-a-harness-actually-is/) - -> 本篇代碼示例:[code/](https://github.com/walkinglabs/learn-harness-engineering/blob/main/docs/zh-TW/lectures/lecture-02-what-a-harness-actually-is/code/) -> 實戰練習:[Project 01. 只寫提示詞讓 agent 做,和定好規則再讓它做,差多少](./../../projects/project-01-baseline-vs-minimal-harness/index.md) - -# 第二講. Harness 到底是什麼 - -"harness"這個詞在 AI coding agent 的圈子裡被用得越來越多了,但說實話,大部分人說的 harness 其實就只是"一個 prompt 檔案"。這不是 harness。就像你開了一家餐廳,只有食材——沒有灶臺、沒有刀具、沒有菜譜、沒有出菜流程——那不叫餐廳,那叫冰箱。 - -這節課要給你一個精確的、可操作的 harness 定義。不是學術論文裡的抽象概念,而是你今天就能拿去用的框架:harness 由五個子系統組成,就像一個完整廚房裡的五個功能區——菜譜架、刀具架、灶臺、備菜臺、和出菜檢查口。每個子系統都有明確的職責和評判標準。 - -## 先講一個類比 - -想象你是一個剛入職的工程師,被丟進一個沒有任何文檔的項目裡。沒有 README,代碼裡沒有註釋,沒有人告訴你怎麼跑測試,CI 配置檔案藏在某個角落裡。你能寫出好代碼嗎?也許能——如果你足夠聰明又足夠有耐心。但你會花大量時間在"搞清楚這個項目是怎麼回事"上,而不是在"解決問題"上。 - -AI agent 面對的困境一模一樣。而且更糟——你至少可以問同事,agent 只能看到你放在它面前的檔案和它能執行的命令。它不能拍拍同事的肩膀問"哎,這個項目的 ORM 用的是哪個版本?" - -OpenAI 在他們的 harness engineering 文章裡把 harness 的核心原則表述為"倉庫即規範"——所有必要的上下文都應該在倉庫裡,通過結構化的指令檔案、明確的驗證命令和清晰的目錄組織來呈現。Anthropic 的 long-running agents 文檔則更側重狀態持久化、顯式恢復路徑和結構化的進度跟蹤。兩家公司的側重點不同,但都在說同一件事:**模型之外的一切工程基礎設施,決定了模型能力能被髮揮多少。** - -看看幾個你熟悉的工具: - -**Claude Code** 的設計就體現了 harness 思想。它會讀你倉庫裡的 `CLAUDE.md`(菜譜架),能用 shell 跑命令(刀具架),在你的本地環境裡執行(灶臺),有會話歷史(備菜臺),能跑測試看結果(出菜檢查口)。但如果你不告訴它怎麼跑測試,出菜檢查口就是斷的——菜做沒做熟誰也不知道。 - -**Cursor** 也是類似的邏輯。它的 `.cursorrules` 檔案是菜譜架,終端是刀具架,它能讀你的項目結構和 lint 配置是灶臺。但 Cursor 的狀態管理相對弱——你關掉 IDE 再打開,上次的上下文就沒了。 - -**Codex**(OpenAI 的 coding agent)用 git worktree 隔離每個任務的運行環境,配合本地的可觀測性棧(日誌、指標、追蹤),讓每個變更都在獨立的環境中驗證。它在有 `AGENTS.md` 和清晰驗證命令的倉庫裡,表現遠超在"裸"倉庫裡。 - -**AutoGPT** 則是反面教材——缺乏結構化的狀態管理導致長任務中上下文不斷累積,缺乏精確的反饋機制導致 agent 陷入循環。很多人說 AutoGPT "不行",但其實是它的 harness 不行——你給它一個破灶臺,再好的食材也做不出菜來。 - -## 核心概念 - -- **什麼是 harness**:模型權重之外的一切工程基礎設施。OpenAI 把工程師的核心工作概括為三件事:設計環境、表達意圖、構建反饋循環。Anthropic 直接把 Claude Agent SDK 稱為"通用 agent harness"。 -- **倉庫是唯一事實來源**:agent 看不到的東西,對它來說就不存在。OpenAI 把倉庫當作"記錄系統"——所有必要的上下文都必須在倉庫裡,通過結構化的檔案和清晰的目錄組織來呈現。 -- **給地圖,不給說明書**:OpenAI 的經驗——`AGENTS.md` 應該是目錄頁,不是百科全書。100 行左右就夠了,放不下就拆分到 `docs/` 目錄裡,讓 agent 按需去讀。 -- **約束而非微操**:好的 harness 用可執行的規則來約束 agent,而不是在指令裡逐條叮囑。OpenAI 說"執行不變量,不要微管實現";Anthropic 發現 agent 會自信地誇讚自己的工作,解決方案是把"幹活的人"和"檢查的人"分開。 -- **逐個組件拆除法**:想量化 harness 各組件的邊際貢獻,就逐個移除,看哪個移除後性能下降最多。它能告訴你哪些組件當前最有價值,也能暴露哪些組件暫時貢獻不明顯。Anthropic 用這個方法發現:隨著模型變強,某些組件不再關鍵,但總會有新的關鍵組件出現。 - -## Harness 五子系統模型 - -回到廚房的類比。一個完整的廚房有五個功能區,harness 也有五個子系統: - -```mermaid -flowchart LR - Rules["專案规则
    AGENTS.md / CLAUDE.md"] --> Agent["AI Agent"] - State["進度和 git
    PROGRESS.md / commits"] --> Agent - Agent --> Tools["工具
    shell / 檔案 / 測試"] - Tools --> Env["執行环境
    依赖 / 服務 / 版本"] - Env --> Checks["檢查結果
    test / lint / build"] - Checks --> Agent -``` - -**指令子系統(菜譜架)**:創建 `AGENTS.md`(或 `CLAUDE.md`),內容包括項目概覽和目的(一句話說清楚這是什麼)、技術棧和版本(Python 3.11、FastAPI 0.100+、PostgreSQL 15)、首次運行命令(`make setup`、`make test`)、不可違反的硬約束("所有 API 必須走 OAuth 2.0")、指向更詳細文檔的鏈接。 - -**工具子系統(刀具架)**:確保 agent 有足夠的工具訪問權限。不要因為"安全考慮"把 shell 給禁了——agent 連 `pip install` 都跑不了,還怎麼幹活?但也別什麼都開放,按最小權限原則來。 - -**環境子系統(灶臺)**:讓環境狀態自描述。用 `pyproject.toml` 或 `package.json` 鎖定依賴,用 `.nvmrc` 或 `.python-version` 指定運行時版本,用 Docker 或 devcontainer 讓環境可重現。 - -**狀態子系統(備菜臺)**:長任務必須有進度跟蹤。用一個簡單的 `PROGRESS.md` 檔案記錄:哪些做完了,哪些在做,哪些被阻塞。每個會話結束前更新,下一個會話開始時讀取。 - -**反饋子系統(出菜檢查口)**:這是投入產出比最高的子系統。在 `AGENTS.md` 裡顯式列出驗證命令: -``` -驗證命令: -- 測試:pytest tests/ -x -- 類型檢查:mypy src/ --strict -- Lint:ruff check src/ -- 完整驗證:make check(包含以上全部) -``` - -五個子系統缺一個,就像廚房裡少了一個功能區——菜還是能做,但總是彆扭。 - -**量化 harness 組件價值的方法**:用"等模型對照實驗"。保持模型不變,逐個移除五個子系統,看哪個子系統缺失時性能下降最多。下降最多的組件,說明它在當前任務裡邊際貢獻最大,應該優先保護和加強;幾乎沒有影響的組件,則可能是冗餘、設計失效,或還沒有被任務充分觸發。它不能單獨證明"瓶頸在哪裡"。真正定位瓶頸,還要結合失敗記錄和歸因:任務沒說清楚、上下文不足、環境不可復現、驗證反饋缺失,還是狀態管理斷裂。 - -## 一個團隊的真實經歷 - -一個團隊用 GPT-4o 開發一個 TypeScript + React 前端應用(約 20,000 行代碼)。他們經歷了四個階段——其實就是在一件一件地添置廚具: - -**階段 1——空廚房**:只有 README 裡的基本項目描述。5 次運行成功 1 次(20%)。主要失敗:選錯了包管理器(npm vs yarn)、沒遵循組件命名約定、跑不了測試。 - -**階段 2——裝上菜譜架**:添加 `AGENTS.md`,寫明技術棧版本、命名約定、關鍵架構決策。成功率升到 60%。剩餘失敗主要來自環境問題和驗證缺失。 - -**階段 3——開起出菜檢查口**:在 `AGENTS.md` 裡列出驗證命令 `yarn test && yarn lint && yarn build`。成功率升到 80%。 - -**階段 4——備菜臺就位**:引入進度檔案範本,agent 在每次運行中記錄完成和未完成的工作。成功率穩定在 80-100%。 - -四次迭代,模型一個字沒改,成功率從 20% 到接近 100%。這就是 harness 工程的力量。你沒有換更貴的食材,你只是把廚房收拾利索了。 - -## 關鍵要點 - -- Harness = 指令 + 工具 + 環境 + 狀態 + 反饋。五個子系統,就像廚房的五個功能區,缺一不可。 -- 不是模型權重的部分全是 harness。你的 harness 決定了模型能力能被髮揮多少。 -- 五個子系統中,反饋子系統通常是投入最少、回報最高的。先把驗證命令寫清楚——出菜檢查口是最值得先裝的。 -- 用"等模型對照實驗"量化各子系統的邊際貢獻,別憑感覺;用失敗記錄和歸因定位真正的瓶頸。 -- Harness 和代碼一樣會腐化。定期審計,像還技術債一樣還 harness 債。 - -## 延伸閱讀 - -- [OpenAI: Harness Engineering](https://openai.com/index/harness-engineering/) -- [Anthropic: Effective Harnesses for Long-Running Agents](https://www.anthropic.com/engineering/effective-harnesses-for-long-running-agents) -- [HumanLayer: Harness Engineering for Coding Agents](https://humanlayer.dev/articles/harness-engineering-for-coding-agents/) -- [SWE-agent: Agent-Computer Interfaces](https://github.com/princeton-nlp/SWE-agent) -- [Thoughtworks: Harness Engineering on Technology Radar](https://www.thoughtworks.com/radar) - -## 練習 - -1. **Harness 五元組審計**:拿你正在用 AI agent 的項目,按五元組框架做一個完整審計。每個子系統打 1-5 分。找出最低分的那個子系統,花 30 分鐘改進它,然後觀察 agent 的表現變化。 - -2. **等模型對照實驗**:選一個模型和一個有挑戰性的任務。依次移除指令(刪掉 AGENTS.md)、移除反饋(不給驗證命令)、移除狀態(不提供進度檔案),每次只移除一個,測量性能下降幅度。基於結果,排出各子系統對你項目的重要性排名。 - -3. **可供性分析**:找一個 agent 在你的項目中"想做但做不了"的場景(比如知道要用參數化查詢但不知道你項目的 ORM 怎麼寫)。分析這是執行鴻溝(不知道怎麼操作)還是評估鴻溝(不知道做得對不對),然後設計 harness 改進來彌補。 diff --git a/courses/learn-harness-engineering/docs/zh-TW/lectures/lecture-03-why-the-repository-must-become-the-system-of-record/code/index.md b/courses/learn-harness-engineering/docs/zh-TW/lectures/lecture-03-why-the-repository-must-become-the-system-of-record/code/index.md deleted file mode 100644 index 75c4cbc2..00000000 --- a/courses/learn-harness-engineering/docs/zh-TW/lectures/lecture-03-why-the-repository-must-become-the-system-of-record/code/index.md +++ /dev/null @@ -1,7 +0,0 @@ -# 第 03 講的程式碼 - -此資料夾用於以下範例: - -- Agent 可讀的 repo 結構 -- 以文件作為系統記錄 -- 不良與良好的知識放置方式 diff --git a/courses/learn-harness-engineering/docs/zh-TW/lectures/lecture-03-why-the-repository-must-become-the-system-of-record/code/repo-knowledge-layout.txt b/courses/learn-harness-engineering/docs/zh-TW/lectures/lecture-03-why-the-repository-must-become-the-system-of-record/code/repo-knowledge-layout.txt deleted file mode 100644 index 45315dac..00000000 --- a/courses/learn-harness-engineering/docs/zh-TW/lectures/lecture-03-why-the-repository-must-become-the-system-of-record/code/repo-knowledge-layout.txt +++ /dev/null @@ -1,13 +0,0 @@ -AGENTS.md -docs/ - ARCHITECTURE.md - PRODUCT.md - RELIABILITY.md - references/ - electron.md - sqlite.md - plans/ - active/ - completed/ -src/ -scripts/ diff --git a/courses/learn-harness-engineering/docs/zh-TW/lectures/lecture-03-why-the-repository-must-become-the-system-of-record/code/repo-reader.ts b/courses/learn-harness-engineering/docs/zh-TW/lectures/lecture-03-why-the-repository-must-become-the-system-of-record/code/repo-reader.ts deleted file mode 100644 index 46f6a62a..00000000 --- a/courses/learn-harness-engineering/docs/zh-TW/lectures/lecture-03-why-the-repository-must-become-the-system-of-record/code/repo-reader.ts +++ /dev/null @@ -1,189 +0,0 @@ -/** - * repo-reader.ts - * - * Reads a directory structure and scores it on discoverability. - * Checks for: AGENTS.md, docs/, architecture docs, feature tracking, - * handoff files, and other signals of a repository that serves as the - * system of record. - * - * Usage: - * npx tsx docs/lectures/lecture-03.../code/repo-reader.ts [path] - * (defaults to current working directory if no path given) - * - * Run: npx tsx docs/lectures/lecture-03-why-the-repository-must-become-the-system-of-record/code/repo-reader.ts - */ - -import * as fs from "node:fs"; -import * as path from "node:path"; - -// --------------------------------------------------------------------------- -// Scoring criteria -// --------------------------------------------------------------------------- - -interface Check { - name: string; - description: string; - maxPoints: number; - check: (dir: string) => { points: number; found: string[]; missing: string[] }; -} - -const checks: Check[] = [ - { - name: "AGENTS.md / CLAUDE.md", - description: "Agent-readable instruction file at repo root", - maxPoints: 15, - check: (dir) => { - const candidates = ["AGENTS.md", "CLAUDE.md", ".claude/CLAUDE.md"]; - const found = candidates.filter((f) => fs.existsSync(path.join(dir, f))); - return { points: found.length > 0 ? 15 : 0, found, missing: found.length > 0 ? [] : candidates }; - }, - }, - { - name: "Documentation directory", - description: "Dedicated docs/ or documentation/ directory", - maxPoints: 10, - check: (dir) => { - const candidates = ["docs", "documentation", "doc"]; - const found = candidates.filter((f) => { - const p = path.join(dir, f); - return fs.existsSync(p) && fs.statSync(p).isDirectory(); - }); - return { points: found.length > 0 ? 10 : 0, found, missing: found.length > 0 ? [] : ["docs/"] }; - }, - }, - { - name: "Architecture documentation", - description: "Files describing system architecture", - maxPoints: 15, - check: (dir) => { - const patterns = [ - "architecture.md", "ARCHITECTURE.md", "docs/architecture.md", - "docs/architecture/", "design.md", "DESIGN.md", - ]; - const found = patterns.filter((f) => fs.existsSync(path.join(dir, f))); - return { points: found.length > 0 ? 15 : 0, found, missing: found.length > 0 ? [] : ["architecture.md or docs/architecture/"] }; - }, - }, - { - name: "Feature tracking", - description: "Feature list or task tracking file", - maxPoints: 15, - check: (dir) => { - const patterns = [ - "feature_list.json", "features.md", "FEATURES.md", - "docs/features.md", "tasks.json", "TODO.md", - ]; - const found = patterns.filter((f) => fs.existsSync(path.join(dir, f))); - return { points: found.length > 0 ? 15 : 0, found, missing: found.length > 0 ? [] : ["feature_list.json or features.md"] }; - }, - }, - { - name: "Handoff / session continuity", - description: "Files for multi-session continuity", - maxPoints: 15, - check: (dir) => { - const patterns = [ - "HANDOFF.md", "handoff.md", "SESSION_NOTES.md", - "docs/handoff.md", ".handoff", "PROGRESS.md", - ]; - const found = patterns.filter((f) => fs.existsSync(path.join(dir, f))); - return { points: found.length > 0 ? 15 : 0, found, missing: found.length > 0 ? [] : ["HANDOFF.md or PROGRESS.md"] }; - }, - }, - { - name: "Testing structure", - description: "Test directory or test configuration", - maxPoints: 10, - check: (dir) => { - const patterns = ["test", "tests", "__tests__", "spec"]; - const found = patterns.filter((f) => { - const p = path.join(dir, f); - return fs.existsSync(p) && fs.statSync(p).isDirectory(); - }); - return { points: found.length > 0 ? 10 : 0, found, missing: found.length > 0 ? [] : ["test/ or tests/"] }; - }, - }, - { - name: "Configuration files", - description: "Project config (package.json, tsconfig, etc.)", - maxPoints: 10, - check: (dir) => { - const patterns = ["package.json", "tsconfig.json", "pyproject.toml", "Cargo.toml", "go.mod"]; - const found = patterns.filter((f) => fs.existsSync(path.join(dir, f))); - return { points: found.length > 0 ? 10 : 0, found, missing: found.length > 0 ? [] : ["package.json or equivalent"] }; - }, - }, - { - name: "README", - description: "Root README file", - maxPoints: 10, - check: (dir) => { - const patterns = ["README.md", "README.rst", "README.txt", "README"]; - const found = patterns.filter((f) => fs.existsSync(path.join(dir, f))); - return { points: found.length > 0 ? 10 : 0, found, missing: found.length > 0 ? [] : ["README.md"] }; - }, - }, -]; - -// --------------------------------------------------------------------------- -// Report generation -// --------------------------------------------------------------------------- - -function pad(s: string, len: number): string { - return s.length >= len ? s : s + " ".repeat(len - s.length); -} - -function scoreRepo(targetDir: string): void { - const resolved = path.resolve(targetDir); - - if (!fs.existsSync(resolved)) { - console.error(` Error: Directory not found: ${resolved}`); - process.exit(1); - } - - console.log("\n" + "=".repeat(80)); - console.log(" REPOSITORY DISCOVERABILITY SCORE"); - console.log("=".repeat(80)); - console.log(` Target: ${resolved}\n`); - - const header = `| ${pad("Criterion", 30)}| ${pad("Points", 8)}| ${pad("Status", 10)}| Details`; - const sep = `|${"-".repeat(32)}|${"-".repeat(10)}|${"-".repeat(12)}|${"-".repeat(30)}`; - console.log(header); - console.log(sep); - - let totalScore = 0; - let maxScore = 0; - - for (const check of checks) { - const result = check.check(resolved); - totalScore += result.points; - maxScore += check.maxPoints; - const status = result.points > 0 ? "PASS" : "FAIL"; - const detail = result.found.length > 0 ? result.found.join(", ") : result.missing[0]; - - console.log(`| ${pad(check.name, 30)}| ${pad(result.points + "/" + check.maxPoints, 8)}| ${pad(status, 10)}| ${detail}`); - } - - // Final score - console.log("\n" + "-".repeat(80)); - console.log(` TOTAL SCORE: ${totalScore} / ${maxScore} (${Math.round((totalScore / maxScore) * 100)}%)`); - - // Grade - const pct = totalScore / maxScore; - let grade: string; - if (pct >= 0.9) grade = "A -- Repository is a strong system of record"; - else if (pct >= 0.7) grade = "B -- Good foundation, minor gaps"; - else if (pct >= 0.5) grade = "C -- Partial structure, significant gaps"; - else if (pct >= 0.3) grade = "D -- Minimal structure, hard for agents to navigate"; - else grade = "F -- Repository lacks discoverability signals"; - - console.log(` GRADE: ${grade}`); - console.log("=".repeat(80) + "\n"); -} - -// --------------------------------------------------------------------------- -// Main -// --------------------------------------------------------------------------- - -const target = process.argv[2] || process.cwd(); -scoreRepo(target); diff --git a/courses/learn-harness-engineering/docs/zh-TW/lectures/lecture-03-why-the-repository-must-become-the-system-of-record/code/system-of-record-checklist.md b/courses/learn-harness-engineering/docs/zh-TW/lectures/lecture-03-why-the-repository-must-become-the-system-of-record/code/system-of-record-checklist.md deleted file mode 100644 index b87b2eb3..00000000 --- a/courses/learn-harness-engineering/docs/zh-TW/lectures/lecture-03-why-the-repository-must-become-the-system-of-record/code/system-of-record-checklist.md +++ /dev/null @@ -1,11 +0,0 @@ -# 系統記錄檢查清單 - -一個全新的 Agent 能否僅從 repo 中發現以下資訊? - -- 正在建構什麼產品? -- 應用程式應該為使用者做什麼? -- 程式碼庫如何組織? -- 應用程式如何啟動? -- 如何檢查健康狀態? -- 目前正在進行什麼工作? -- 哪些品質標準很重要? diff --git a/courses/learn-harness-engineering/docs/zh-TW/lectures/lecture-03-why-the-repository-must-become-the-system-of-record/index.md b/courses/learn-harness-engineering/docs/zh-TW/lectures/lecture-03-why-the-repository-must-become-the-system-of-record/index.md deleted file mode 100644 index 7592e7f4..00000000 --- a/courses/learn-harness-engineering/docs/zh-TW/lectures/lecture-03-why-the-repository-must-become-the-system-of-record/index.md +++ /dev/null @@ -1,137 +0,0 @@ -[English Version →](../../../en/lectures/lecture-03-why-the-repository-must-become-the-system-of-record/) - -> 本篇代碼示例:[code/](https://github.com/walkinglabs/learn-harness-engineering/blob/main/docs/zh-TW/lectures/lecture-03-why-the-repository-must-become-the-system-of-record/code/) -> 實戰練習:[Project 02. 讓 agent 看懂項目、接住上次的工作](./../../projects/project-02-agent-readable-workspace/index.md) - -# 第三講. 讓代碼倉庫成為唯一的事實來源 - -你團隊的架構決策散落在 Confluence、Slack、Jira、和幾個資深工程師的腦子裡。對人類來說這勉強夠用——你可以問同事、搜聊天記錄、翻文檔。實在不行還能去茶水間堵人。但對 AI agent 來說,不在倉庫裡的信息等於不存在。 - -這不是誇張。想想 agent 的輸入都有什麼:系統提示和任務描述、倉庫裡的檔案內容、以及工具執行的輸出。就這三樣。你的 Slack 歷史、Jira 工單、Confluence 頁面、和週五下午跟同事在茶水間聊的架構決定——agent 全都看不到。它不能"去問一下",也不能"搜一下聊天記錄"。它就是一個被關在倉庫裡的工程師——倉庫外面的事,它一概不知。 - -所以問題變成了:你給不給這個工程師一張好地圖? - -## 地圖上該畫什麼 - -OpenAI 在他們的 harness engineering 文章裡把這個問題說得非常直白:**倉庫裡不存在的信息,對 agent 來說等於不存在。** 他們把這稱為"倉庫即規範"原則——倉庫本身就是最高權威的規範文檔。 - -Anthropic 的 long-running agents 文檔也強調了類似的觀點:持久化狀態是長任務連續性的必要條件。跨會話的知識可恢復性直接決定了任務成功率。而這些狀態必須存在於倉庫中——因為那是 agent 唯一穩定可訪問的存儲。 - -你可能會想:"我們團隊人少,知識都在大家腦子裡,這不也工作得好好的嗎?"沒錯,對人類來說確實可以。但你要用 agent,就得接受一個事實:agent 不能問人。所有它需要知道的東西,都必須寫下來,放在它能找到的地方。 - -這不是"寫更多文檔"的問題。這是"把決策信息放到正確的位置"的問題。一份在 `src/api/` 目錄下、50 行的 `ARCHITECTURE.md`,比一份在 Confluence 裡、500 頁但沒人維護的設計文檔有用一萬倍。就好比一張貼在你工位上的、手畫的辦公室走法圖,比一份精美的、放在檔案室裡的建築藍圖實用得多——因為前者在你需要的時候就在手邊。 - -## 知識可見性 - -```mermaid -flowchart LR - Slack["Slack 裡的规则"] --> Write["寫进倉庫檔案
    AGENTS.md / ARCHITECTURE.md / PROGRESS.md"] - Confluence["Confluence 裡的规则"] --> Write - Heads["工程师脑子裡的规则"] --> Write - Jira["Jira 裡的规则"] --> Write - Write --> Repo["倉庫檔案"] - Repo --> Agent["新的 agent 會話
    直接讀倉庫"] - Warning["规则不寫进倉庫
    agent 就看不见"] --> Agent -``` - -怎麼檢驗你的地圖畫得夠不夠好?做一個"冷啟動測試":開一個全新的 agent 會話,只看倉庫內容,看它能不能回答五個基本問題: - -```mermaid -flowchart TB - Q1["這是什么系統?"] --> A1["AGENTS.md / README"] - Q2["怎么组织的?"] --> A2["ARCHITECTURE.md / 模块文檔"] - Q3["怎么跑?"] --> A3["Makefile / init.sh / package scripts"] - Q4["怎么驗證?"] --> A4["測試、lint、check 命令"] - Q5["现在做到哪了?"] --> A5["PROGRESS.md / 功能清單 / git 历史"] - - A1 --> Ready["新的會話不用问人
    就能開始工作"] - A2 --> Ready - A3 --> Ready - A4 --> Ready - A5 --> Ready -``` - -如果它答不上來,說明地圖上有空白。空白的地方,agent 就得自己猜——猜錯了就是 bug,猜多了就浪費上下文。每個新會話都要猜一遍,猜的成本遠高於一開始就把地圖畫好的成本。 - -## 核心概念 - -- **知識可見性缺口**:項目總知識中不在倉庫裡的比例。缺口越大,agent 失敗的概率越高。你腦子裡有多少關於這個項目的隱性知識?把它們全算上,再看有多少寫進了倉庫——兩者的差距就是你的可見性缺口。 -- **系統記錄(System of Record)**:代碼倉庫作為項目決策、架構約束、執行狀態和驗證標準的權威信息源。倉庫說了算,別的地方說了不算。就像地圖上標註了"此路不通",你就不會再往那條路走——但如果這個信息只在老張的腦子裡,你每次都得問老張。 -- **冷啟動測試**:上一節說的五個問題。能回答幾個,你的地圖就畫了幾分。 -- **發現成本**:agent 為了在倉庫裡找到一條關鍵信息需要消耗多少上下文。信息放得越隱蔽,發現成本越高,留給實際任務的預算越少。把關鍵信息藏在十層目錄深處的 README 裡,就像把滅火器鎖在地下室的保險櫃裡——不是沒有,是用的時候找不到。 -- **知識衰減率**:倉庫中單位時間內變得過時的知識條目比例。文檔和代碼脫節是最大的敵人——比沒有文檔更危險的是過時的文檔。 -- **ACID 類比**:把數據庫的事務管理原則(原子性、一致性、隔離性、持久性)用到 agent 的狀態管理上。後面會展開講。 - -## 怎麼畫好這張地圖 - -**原則 1:知識靠近代碼。** 一條關於 API 端點認證的規則,應該放在 API 代碼旁邊,而不是藏在一個巨大的全局文檔裡。每個模塊目錄下放一個簡短的文檔,說清楚這個模塊的職責、接口和特殊約束。就像圖書館的書架標籤——你想找歷史類書籍,直接去標著"歷史"的書架,不用把整個圖書館翻一遍。 - -**原則 2:用標準化的入口檔案。** `AGENTS.md`(或 `CLAUDE.md`)是 agent 的"著陸頁"。它不需要包含所有信息,但必須能讓 agent 快速回答"這是什麼項目"、"怎麼跑"、"怎麼驗證"這三個問題。50-100 行就夠了。 - -**原則 3:最小但完備。** 每條知識都應該有明確的使用場景。如果你刪掉某條規則不影響 agent 的決策質量,那這條規則就不應該存在。但冷啟動測試中的每個問題都必須有答案。這是一個精妙的平衡——不多不少,剛好夠用。 - -**原則 4:和代碼一起更新。** 把知識更新跟代碼變更綁定在一起。最簡單的方法:把架構文檔放在對應的模塊目錄裡。改代碼的時候自然會看到文檔,改代碼之後 CI 提醒你檢查文檔是否需要更新。 - -**具體的倉庫結構**: - -``` -project/ -├── AGENTS.md # 入口:專案概覽、執行命令、硬约束 -├── src/ -│ ├── api/ -│ │ ├── ARCHITECTURE.md # API 层的架构决策 -│ │ └── ... -│ ├── db/ -│ │ ├── CONSTRAINTS.md # 資料庫操作的硬约束 -│ │ └── ... -│ └── ... -├── PROGRESS.md # 当前進度:做了什么、在做什么、被什么阻塞 -└── Makefile # 標準化的操作命令:setup、test、lint、check -``` - -## 用 ACID 原則管理 agent 狀態 - -這個類比來自數據庫的事務管理——你可能覺得這是在把簡單的事情搞複雜,但實際上它給了你一個非常實用的框架: - -- **原子性**:每次"邏輯操作"(比如"添加新端點並更新測試")用一個 git commit 原子化。中途掛了就 `git stash` 回滾。要麼全做,要麼不做,沒有"做了一半"。 -- **一致性**:定義"一致狀態"的驗證謂詞——所有測試通過、lint 無報錯。Agent 每次操作後跑驗證,不一致的中間狀態不要 commit。就像銀行轉賬——你不能只扣款不入賬。 -- **隔離性**:多個 agent 併發工作時,狀態檔案要避免競爭條件。簡單方案:每個 agent 用獨立的進度檔案,或者用 git 分支隔離。兩個廚師不能同時往同一口鍋裡放鹽——放重了誰負責? -- **持久性**:關鍵的項目知識用 git 跟蹤的檔案持久化。臨時狀態可以只在會話內存裡,但跨會話必須的知識必須寫到檔案裡。腦子裡的不算,寫在紙上的才算。 - -## 一個真實的改造故事 - -一個團隊維護一個包含約 30 個微服務的電商平臺。架構決策(服務間通信協議、數據一致性策略、API 版本化規則)散落在:Confluence(部分過時)、Slack(難以搜索)、幾個資深工程師的腦子裡(不可擴展)、以及零星的代碼註釋(不繫統)。 - -引入 AI agent 後,70% 的任務需要人工干預。幾乎每次失敗都涉及 agent 違反了某個"所有人都知道但從未寫入倉庫"的隱性約束。這就像一個新來的員工,沒人告訴他"中午點外賣要在群裡接龍"——他自己猜,猜錯了被罵,但罵完了還是沒人告訴他規則。 - -團隊執行了改造: -1. 倉庫根目錄創建 `AGENTS.md`,寫明項目概覽、技術棧版本、全局硬約束 -2. 每個微服務目錄下添加 `ARCHITECTURE.md`,描述該服務的職責、接口和依賴 -3. 創建集中的 `CONSTRAINTS.md`,用"禁止/必須"的明確語言記錄硬約束 -4. 每個服務目錄添加 `PROGRESS.md`,記錄當前工作狀態 - -改造後:同一 agent 能在冷啟動時回答所有關鍵項目問題,任務完成質量顯著提升。 - -## 關鍵要點 - -- 不在倉庫裡的知識對 agent 來說等於不存在。把關鍵決策信息放進倉庫是最基本的 harness 投資——畫好地圖,才不會迷路。 -- 用"冷啟動測試"檢驗倉庫質量:全新會話能不能只看倉庫回答五個基本問題。 -- 知識要靠近代碼、最小但完備、跟代碼一起更新。不是寫更多文檔,是把信息放到正確的位置。 -- 用 ACID 原則管理 agent 狀態:原子提交、一致性驗證、隔離併發、持久化關鍵知識。 -- 知識衰減是最大敵人。過時的文檔比沒有文檔更危險——它會讓 agent 走錯方向還以為自己是對的。 - -## 延伸閱讀 - -- [OpenAI: Harness Engineering](https://openai.com/index/harness-engineering/) -- [Anthropic: Effective Harnesses for Long-Running Agents](https://www.anthropic.com/engineering/effective-harnesses-for-long-running-agents) -- [Infrastructure as Code — Martin Fowler](https://martinfowler.com/bliki/InfrastructureAsCode.html) -- [ADR: Architecture Decision Records](https://adr.github.io/) -- [The Twelve-Factor App](https://12factor.net/) - -## 練習 - -1. **冷啟動測試**:在你的項目裡開一個全新的 agent 會話(不提供任何口頭上下文),只讓它看倉庫內容,然後問它五個問題:這是什麼系統?怎麼組織的?怎麼運行?怎麼驗證?現在進度如何?記錄它答不上來的問題,然後改進倉庫讓它能答上來。 - -2. **知識外置化量化**:列出你的項目中所有對開發工作重要的決策和約束。標註每個條目是在倉庫內還是倉庫外。算一下你的知識可見性缺口有多大(不在倉庫裡的佔總數的比例)。制定計劃把缺口降到 10% 以下。 - -3. **ACID 準則評估**:用本講的 ACID 類比評估你的項目狀態管理。原子性——agent 的操作能不能幹淨地回滾?一致性——倉庫有沒有"一致狀態"的驗證?隔離性——多 agent 併發時會不會互相踩腳?持久性——跨會話的知識是不是都持久化了? diff --git a/courses/learn-harness-engineering/docs/zh-TW/lectures/lecture-04-why-one-giant-instruction-file-fails/code/AGENTS-short.md b/courses/learn-harness-engineering/docs/zh-TW/lectures/lecture-04-why-one-giant-instruction-file-fails/code/AGENTS-short.md deleted file mode 100644 index 65a0de89..00000000 --- a/courses/learn-harness-engineering/docs/zh-TW/lectures/lecture-04-why-one-giant-instruction-file-fails/code/AGENTS-short.md +++ /dev/null @@ -1,14 +0,0 @@ -# AGENTS.md - -## 從這裡開始 - -- 閱讀 `docs/ARCHITECTURE.md` -- 閱讀 `docs/PRODUCT.md` -- 使用 `npm run dev` 啟動應用程式 -- 在標記工作完成前使用 `npm run check` - -## 硬性規則 - -- 在未閱讀 `docs/ARCHITECTURE.md` 的情況下,不得更改 Electron main/preload/renderer 邊界 -- 未經驗證不得將功能標記為完成 -- 為下一個 session 留下乾淨的狀態 diff --git a/courses/learn-harness-engineering/docs/zh-TW/lectures/lecture-04-why-one-giant-instruction-file-fails/code/anti-patterns.md b/courses/learn-harness-engineering/docs/zh-TW/lectures/lecture-04-why-one-giant-instruction-file-fails/code/anti-patterns.md deleted file mode 100644 index 10def73a..00000000 --- a/courses/learn-harness-engineering/docs/zh-TW/lectures/lecture-04-why-one-giant-instruction-file-fails/code/anti-patterns.md +++ /dev/null @@ -1,7 +0,0 @@ -# 指令檔案反模式 - -- 將所有 repo 知識放入單一檔案 -- 在多個地方重複相同的規則 -- 編寫無人審核的過時規則 -- 撰寫過於具體而鮮少適用的條件式指令 -- 在啟動上下文中嵌入冗長的工具手冊 diff --git a/courses/learn-harness-engineering/docs/zh-TW/lectures/lecture-04-why-one-giant-instruction-file-fails/code/index.md b/courses/learn-harness-engineering/docs/zh-TW/lectures/lecture-04-why-one-giant-instruction-file-fails/code/index.md deleted file mode 100644 index 3ecb4b9f..00000000 --- a/courses/learn-harness-engineering/docs/zh-TW/lectures/lecture-04-why-one-giant-instruction-file-fails/code/index.md +++ /dev/null @@ -1,7 +0,0 @@ -# 第 04 講的程式碼 - -此資料夾用於以下範例: - -- 單體式指令檔案 -- 簡短入口點 -- 漸進式揭露模式 diff --git a/courses/learn-harness-engineering/docs/zh-TW/lectures/lecture-04-why-one-giant-instruction-file-fails/code/split-vs-monolithic.ts b/courses/learn-harness-engineering/docs/zh-TW/lectures/lecture-04-why-one-giant-instruction-file-fails/code/split-vs-monolithic.ts deleted file mode 100644 index 59a96328..00000000 --- a/courses/learn-harness-engineering/docs/zh-TW/lectures/lecture-04-why-one-giant-instruction-file-fails/code/split-vs-monolithic.ts +++ /dev/null @@ -1,209 +0,0 @@ -/** - * split-vs-monolithic.ts - * - * Creates a monolithic instruction file (~200 lines) and then shows how - * splitting into 4 focused files dramatically reduces the context needed - * for any single query. Simulates an "agent" searching for a specific rule - * and measures how many lines it must read in each approach. - * - * Run: npx tsx docs/lectures/lecture-04-why-one-giant-instruction-file-fails/code/split-vs-monolithic.ts - */ - -// --------------------------------------------------------------------------- -// Simulated monolithic instruction file (200 lines of rules) -// --------------------------------------------------------------------------- - -const monolithicInstructions: { lineNumber: number; section: string; content: string }[] = []; - -// Section 1: Project Overview (lines 1-50) -for (let i = 1; i <= 50; i++) { - monolithicInstructions.push({ - lineNumber: i, - section: "Project Overview", - content: i === 25 ? "This project uses React 18 with TypeScript strict mode." : `Overview detail line ${i}`, - }); -} - -// Section 2: Code Style Rules (lines 51-100) -for (let i = 51; i <= 100; i++) { - monolithicInstructions.push({ - lineNumber: i, - section: "Code Style", - content: - i === 72 - ? "RULE: Always use explicit return types on exported functions." - : i === 78 - ? "RULE: Use const assertions for immutable arrays." - : `Style rule detail line ${i}`, - }); -} - -// Section 3: Testing Standards (lines 101-150) -for (let i = 101; i <= 150; i++) { - monolithicInstructions.push({ - lineNumber: i, - section: "Testing", - content: - i === 120 - ? "RULE: Every new endpoint must have integration tests." - : i === 135 - ? "RULE: Test files must mirror the source file structure." - : `Testing detail line ${i}`, - }); -} - -// Section 4: Deployment Rules (lines 151-200) -for (let i = 151; i <= 200; i++) { - monolithicInstructions.push({ - lineNumber: i, - section: "Deployment", - content: - i === 175 - ? "RULE: Never deploy on Fridays. Deploy window is Tue-Thu 10am-3pm." - : `Deployment detail line ${i}`, - }); -} - -// --------------------------------------------------------------------------- -// Split instruction files (4 focused files) -// --------------------------------------------------------------------------- - -const splitInstructions: Record = { - "01-project-overview.md": monolithicInstructions.filter((l) => l.section === "Project Overview"), - "02-code-style.md": monolithicInstructions.filter((l) => l.section === "Code Style"), - "03-testing.md": monolithicInstructions.filter((l) => l.section === "Testing"), - "04-deployment.md": monolithicInstructions.filter((l) => l.section === "Deployment"), -}; - -// --------------------------------------------------------------------------- -// Simulated queries -- the agent needs to find specific rules -// --------------------------------------------------------------------------- - -interface Query { - description: string; - targetRule: string; - relevantSection: string; -} - -const queries: Query[] = [ - { - description: "Find the rule about return types", - targetRule: "explicit return types", - relevantSection: "Code Style", - }, - { - description: "Find the deployment window rule", - targetRule: "deploy on Fridays", - relevantSection: "Deployment", - }, - { - description: "Find the integration test rule", - targetRule: "integration tests", - relevantSection: "Testing", - }, - { - description: "Find the test file structure rule", - targetRule: "mirror the source file", - relevantSection: "Testing", - }, -]; - -// --------------------------------------------------------------------------- -// Search simulation -// --------------------------------------------------------------------------- - -function searchMonolithic(query: Query): { linesRead: number; found: boolean } { - // Agent must scan from the top, reading each line until it finds the rule. - // In the worst case it reads all lines. - let linesRead = 0; - let found = false; - - for (const line of monolithicInstructions) { - linesRead++; - if (line.content.toLowerCase().includes(query.targetRule.toLowerCase())) { - found = true; - break; - } - } - - return { linesRead, found }; -} - -function searchSplit(query: Query): { linesRead: number; found: boolean; fileAccessed: string } { - // Agent knows which file to look in based on the section. - // It only reads lines from that one file. - const fileMap: Record = { - "Project Overview": "01-project-overview.md", - "Code Style": "02-code-style.md", - Testing: "03-testing.md", - Deployment: "04-deployment.md", - }; - - const targetFile = fileMap[query.relevantSection]; - const lines = splitInstructions[targetFile]; - let linesRead = 0; - let found = false; - - for (const line of lines) { - linesRead++; - if (line.content.toLowerCase().includes(query.targetRule.toLowerCase())) { - found = true; - break; - } - } - - return { linesRead, found, fileAccessed: targetFile }; -} - -// --------------------------------------------------------------------------- -// Reporting -// --------------------------------------------------------------------------- - -function pad(s: string, len: number): string { - return s.length >= len ? s : s + " ".repeat(len - s.length); -} - -function run(): void { - console.log("\n" + "=".repeat(90)); - console.log(" MONOLITHIC vs SPLIT INSTRUCTION FILES"); - console.log("=".repeat(90)); - - console.log("\n Monolithic file: 1 file, " + monolithicInstructions.length + " lines total"); - console.log(" Split files: 4 files, ~" + Math.round(monolithicInstructions.length / 4) + " lines each\n"); - - const header = `| ${pad("Query", 42)}| ${pad("Monolithic (lines)", 20)}| ${pad("Split (lines)", 15)}| ${pad("File Accessed", 22)}| Savings`; - const sep = `|${"-".repeat(44)}|${"-".repeat(22)}|${"-".repeat(17)}|${"-".repeat(24)}|${"-".repeat(10)}`; - console.log(header); - console.log(sep); - - let totalMono = 0; - let totalSplit = 0; - - for (const q of queries) { - const mono = searchMonolithic(q); - const split = searchSplit(q); - totalMono += mono.linesRead; - totalSplit += split.linesRead; - - const savings = Math.round(((mono.linesRead - split.linesRead) / mono.linesRead) * 100); - console.log( - `| ${pad(q.description, 42)}| ${pad(String(mono.linesRead), 20)}| ${pad(String(split.linesRead), 15)}| ${pad(split.fileAccessed, 22)}| ${savings}%` - ); - } - - console.log(sep); - const avgMono = Math.round(totalMono / queries.length); - const avgSplit = Math.round(totalSplit / queries.length); - console.log( - `| ${pad("AVERAGE", 42)}| ${pad(String(avgMono), 20)}| ${pad(String(avgSplit), 15)}| ${pad("(targeted file)", 22)}| ${Math.round(((avgMono - avgSplit) / avgMono) * 100)}%` - ); - - console.log("\n" + "=".repeat(90)); - console.log(" KEY INSIGHT"); - console.log("=".repeat(90)); - console.log(" With a monolithic file, the agent must scan up to " + monolithicInstructions.length + " lines for every query."); - console.log(" With split files, it reads only the relevant " + Math.round(monolithicInstructions.length / 4) + "-line file."); - console.log(" This means less context window usage, fewer hallucinations, and faster execution.\n"); -} - -run(); diff --git a/courses/learn-harness-engineering/docs/zh-TW/lectures/lecture-04-why-one-giant-instruction-file-fails/index.md b/courses/learn-harness-engineering/docs/zh-TW/lectures/lecture-04-why-one-giant-instruction-file-fails/index.md deleted file mode 100644 index 8ff9acf5..00000000 --- a/courses/learn-harness-engineering/docs/zh-TW/lectures/lecture-04-why-one-giant-instruction-file-fails/index.md +++ /dev/null @@ -1,132 +0,0 @@ -[English Version →](../../../en/lectures/lecture-04-why-one-giant-instruction-file-fails/) - -> 本篇代碼示例:[code/](https://github.com/walkinglabs/learn-harness-engineering/blob/main/docs/zh-TW/lectures/lecture-04-why-one-giant-instruction-file-fails/code/) -> 實戰練習:[Project 02. 讓 agent 看懂項目、接住上次的工作](./../../projects/project-02-agent-readable-workspace/index.md) - -# 第四講. 把指令拆分到不同檔案裡 - -你開始認真對待 harness 了——好事。你建了個 `AGENTS.md`,把你能想到的所有規則、約束、歷史教訓都塞了進去。一個月後這個檔案膨脹到了 300 行,兩個月 450 行,三個月 600 行。然後你發現 agent 的表現反而變差了——改一個小 bug,agent 花大量上下文處理無關的部署指令;關鍵的安全約束埋在第 300 行,被直接忽略了;檔案裡有三條互相矛盾的代碼風格規則,agent 每次隨機選一條。 - -這就是"巨型指令檔案"陷阱。就像你往行李箱裡塞東西——覺得什麼都有用,什麼都往裡裝,結果拉鍊快崩了,想找換洗內衣得把整個箱子翻一遍。帶了一箱子東西,但真正用上的可能只有三分之一。 - -## 問題的根源:一個惡性循環 - -最常見的惡性循環是這樣的:agent 犯了個錯 → 你說"加條規則防止這個" → 加到 AGENTS.md → 暫時管用 → agent 又犯了另一個錯 → 再加一條 → 重複 → 檔案膨脹到不可控。 - -這不是你的錯。這是一個非常自然的反應——每次出問題就"加條規則"感覺很合理,就像每次出門忘帶東西就往包裡多塞一樣。但累積效應是災難性的。讓我們看看具體出了什麼問題。 - -**上下文預算被吃掉了。** Agent 的上下文窗口是有限的。假設你的 agent 有 200K tokens 的窗口(Claude 的標準),一個膨脹的指令檔案可能佔掉 10-20K。看起來還有不少餘量?但一個複雜的任務可能需要讀幾十個源檔案、工具執行的輸出也占上下文、對話歷史也在累積。到真正需要理解代碼的時候,預算已經不夠了——就像你的行李箱塞滿了"萬一用得著"的東西,結果放不下真正需要帶的電腦了。 - -**中間迷失。** "Lost in the Middle"這篇論文(Liu et al., 2023)清楚地證明了:LLM 對長文本中間部分的信息利用效率顯著低於兩端。你的 AGENTS.md 有 600 行,第 300 行寫的是"所有數據庫查詢必須用參數化查詢"——這是安全硬約束。但它被埋在中間,agent 幾乎一定會忽略它。就像你的行李箱裡那瓶防曬霜——明明在最底層,但你翻三遍也找不到,最後又去買了一瓶。 - -**優先級衝突。** 檔案裡混合了不可違反的硬約束("不得使用 eval()")、重要的設計指導("優先使用函數式風格")、和某個特定場景的歷史教訓("上週修了一個 WebSocket 內存洩漏,注意類似的模式")。這三條規則的重要性完全不同,但在檔案裡看起來一模一樣。Agent 沒有可靠的信號來區分——就像行李箱裡護照和充電線混在一起,看不出哪個更緊急。 - -**維護衰減。** 大檔案天生難維護。指令過時了沒人刪——因為刪除的後果不確定("也許別的地方依賴這條規則?"),但加新指令是無成本的。結果檔案只增不減,信噪比持續下降。這和軟件裡的技術債務積累一模一樣。 - -**矛盾累積。** 不同時期加的指令之間開始出現矛盾——一條說"用 TypeScript 嚴格模式",另一條說"某些遺留檔案允許用 any"。Agent 每次隨機選一條遵循。就像你媽說"穿厚點",你爸說"別穿太多",你站在門口不知道聽誰的。 - -## 核心概念 - -- **指令膨脹**:當指令檔案佔用了超過上下文窗口 10-15% 的時候,它就開始擠佔代碼閱讀和任務推理的預算。600 行的 `AGENTS.md` 可能佔用 10,000-20,000 tokens——對 128K 的窗口來說,這就吃掉了 8-15%。 -- **中間迷失效應**:Liu 等人 2023 年的研究證明,LLM 對長文本中間部分的信息利用效率顯著低於兩端。埋在 600 行檔案中間第 300 行的關鍵約束,被有效忽略的概率非常高。 -- **指令信噪比(SNR)**:檔案中與當前任務相關的指令佔總指令的比例。做 bug 修復時被要求讀 50 行部署指令——SNR 很低。 -- **路由檔案**:短小的入口檔案,核心功能是引導 agent 去找更詳細的文檔,而不是自己包含所有內容。50-200 行就夠了。 -- **漸進式披露**:先給概要信息,需要的時候再給詳細信息。好的 harness 設計和好的 UI 設計一樣,不把所有選項一次性砸到用戶臉上。 -- **優先級模糊度**:當所有指令以相同格式和位置呈現時,agent 分不清哪些是不可違反的硬約束,哪些是建議性的軟約束。 - -## 指令檔案架構 - -```mermaid -flowchart LR - Mono["一個超长 AGENTS.md"] --> MonoLoad["改一個小 bug
    也得把部署說明和历史备注全讀一遍"] - MonoLoad --> MonoRisk["關键规则埋在中间
    很容易漏掉"] - - Router["短 AGENTS.md"] --> Topics["按任務去讀 API / 資料庫 / 測試文檔"] - Topics --> RoutedResult["把更多上下文留给程式碼阅讀
    和驗證"] -``` - -```mermaid -flowchart TB - File["600 行指令檔案"] --> Top["顶部
    快速開始 + 硬约束"] - File --> Mid["中部
    第 300 行的安全规则"] - File --> Bot["底部
    明确的結束檢查清單"] - Top --> Seen["高概率被记住"] - Bot --> Seen - Mid --> Missed["高概率被稀释或忽略"] -``` - -## 拆分思路 - -核心原則:常用信息放手邊,偶爾用的收起來,用不上的別帶。 - -入口檔案 `AGENTS.md` 控制在 50-200 行,只放最常用的東西——項目概覽(一兩句話說清楚這是什麼)、首次運行命令(`make setup && make test`)、全局硬約束(不超過 15 條不可違反的規則)、指向專題文檔的鏈接(一行描述 + 適用條件)。 - -```markdown -# AGENTS.md - -## 專案概覽 -Python 3.11 FastAPI 後端,PostgreSQL 15 資料庫。 - -## 快速開始 -- 安装:`make setup` -- 測試:`make test` -- 完整驗證:`make check` - -## 硬约束 -- 所有 API 必須走 OAuth 2.0 認證 -- 所有資料庫查询必須用 SQLAlchemy 2.0 语法 -- 所有 PR 必須通過 pytest + mypy --strict + ruff check - -## 专题文檔 -- [API 設計規範](docs/api-patterns.md) — 添加新端点時必讀 -- [資料庫操作约束](docs/database-rules.md) — 涉及資料庫修改時必讀 -- [測試標準](docs/testing-standards.md) — 编寫測試時参考 -``` - -每個專題文檔 50-150 行,按主題放在 `docs/` 目錄下或對應模塊目錄旁。Agent 只在需要時才去讀。就像行李箱裡的收納袋——內衣一個袋,洗漱一個袋,充電器一個袋。找東西不用翻整個箱子。 - -還有些信息直接放在代碼裡更合適——類型定義、接口註釋、配置檔案裡的說明。Agent 讀代碼的時候自然能看到,不用再在指令裡重複一遍。 - -每條指令都應該標明來源("為什麼加這條規則?")、適用條件("這條規則在什麼時候需要?")、過期條件("什麼情況下可以刪掉這條規則?")。定期審計,刪掉過時的、冗餘的、矛盾的條目。像管理代碼依賴一樣管理你的指令——用不上的依賴就該刪掉,不然它們只會拖慢系統。 - -如果某條指令必須在入口檔案裡,放頂部或底部——不要放中間。"中間迷失"效應告訴我們,LLM 對長文本中間部分的信息利用效率顯著低於兩端。但更好的做法是把指令放到專題文檔裡,讓 agent 按需加載。 - -OpenAI 和 Anthropic 都隱性支持拆分的做法。OpenAI 說入口檔案應"短小且以路由為導向",Anthropic 說長運行 agent 的控制信息應"簡潔且高優先級"。兩家都在說同一件事:別把什麼都塞進一個檔案裡。行李箱得整理,不能只靠蠻力塞。 - -## 實際案例 - -一個 SaaS 團隊的 `AGENTS.md` 從最初的 50 行膨脹到 600 行。內容混合了技術棧版本、編碼規範、歷史 bug 修復筆記、API 使用說明、部署流程、和團隊成員的個人偏好——整個行李箱塞得滿滿當當,拉鍊都快崩了。 - -Agent 表現開始明顯下降:簡單 bug 修復任務中 agent 花大量上下文處理無關的部署指令;安全約束"所有數據庫查詢必須用參數化查詢"埋在第 300 行,經常被忽略;三條矛盾的代碼風格規則導致 agent 隨機選擇。 - -團隊執行了"行李箱整理": -1. `AGENTS.md` 裁剪到 80 行:只保留項目概覽、運行命令、15 條全局硬約束 -2. 創建專題文檔:`docs/api-patterns.md`(120 行)、`docs/database-rules.md`(60 行)、`docs/testing-standards.md`(80 行) -3. 路由檔案添加指向專題文檔的鏈接 -4. 歷史筆記要麼轉成測試用例,要麼刪除 - -重構後:同一任務集的成功率從 45% 提升到 72%。安全約束遵循率從 60% 提升到 95%——因為從檔案中間移到了路由檔案頂部,不再被"中間迷失"了。 - -## 關鍵要點 - -- "加條規則"是短期的止痛藥,長期的毒藥。每次加規則前想想:這條規則放專題文檔是不是更合適?——別什麼都往行李箱裡塞。 -- 入口檔案是路由器,不是百科全書。50-200 行,只放概覽、硬約束、和鏈接。 -- 利用"中間迷失"效應:重要信息放檔案頂部或底部,不重要的移到專題文檔。 -- 像管理技術債一樣管理指令膨脹。定期審計,每條指令要有來源、適用條件、和過期條件。 -- 拆分之後信噪比提升,agent 把更多上下文預算花在實際任務上,而不是處理無關指令。 - -## 延伸閱讀 - -- [OpenAI: Harness Engineering](https://openai.com/index/harness-engineering/) -- [Anthropic: Effective Harnesses for Long-Running Agents](https://www.anthropic.com/engineering/effective-harnesses-for-long-running-agents) -- [Lost in the Middle: How Language Models Use Long Contexts](https://arxiv.org/abs/2307.03172) -- [HumanLayer: Harness Engineering for Coding Agents](https://humanlayer.dev/articles/harness-engineering-for-coding-agents/) -- [Nielsen Norman Group: Progressive Disclosure](https://www.nngroup.com/articles/progressive-disclosure/) - -## 練習 - -1. **信噪比審計**:拿你當前的入口指令檔案,列出所有指令條目。選 5 個不同的常見任務類型,標註每條指令是否跟該任務相關。計算每個任務類型的 SNR。那些對大多數任務都是噪聲的指令,移到專題文檔裡。 - -2. **漸進式披露重構**:如果你有一個超過 300 行的指令檔案,把它拆成:(a) 不超過 100 行的路由檔案,(b) 3-5 個專題文檔。重構前後各跑同一組任務(至少 5 個),對比成功率。 - -3. **中間迷失驗證**:在一個長指令檔案裡,把一條關鍵約束分別放在頂部、中間、底部各跑一組任務(每組至少 5 次),看遵循率有沒有差別。你可能會驚訝於位置效應有多大。 diff --git a/courses/learn-harness-engineering/docs/zh-TW/lectures/lecture-05-why-long-running-tasks-lose-continuity/code/continuity-checklist.md b/courses/learn-harness-engineering/docs/zh-TW/lectures/lecture-05-why-long-running-tasks-lose-continuity/code/continuity-checklist.md deleted file mode 100644 index 87705f68..00000000 --- a/courses/learn-harness-engineering/docs/zh-TW/lectures/lecture-05-why-long-running-tasks-lose-continuity/code/continuity-checklist.md +++ /dev/null @@ -1,6 +0,0 @@ -# 連續性檢查清單 - -- 一個全新的 Agent 能否在五分鐘內識別近期的工作? -- 目前穩定的啟動路徑是否有記錄? -- 未完成的工作是否已被清楚標示? -- 是否可以在不閱讀舊的聊天記錄的情況下看到下一個最佳任務? diff --git a/courses/learn-harness-engineering/docs/zh-TW/lectures/lecture-05-why-long-running-tasks-lose-continuity/code/index.md b/courses/learn-harness-engineering/docs/zh-TW/lectures/lecture-05-why-long-running-tasks-lose-continuity/code/index.md deleted file mode 100644 index a4afec83..00000000 --- a/courses/learn-harness-engineering/docs/zh-TW/lectures/lecture-05-why-long-running-tasks-lose-continuity/code/index.md +++ /dev/null @@ -1,7 +0,0 @@ -# 第 05 講的程式碼 - -此資料夾用於以下範例: - -- 中斷的多 session 任務 -- 缺少連續性產物 -- 連續性恢復模式 diff --git a/courses/learn-harness-engineering/docs/zh-TW/lectures/lecture-05-why-long-running-tasks-lose-continuity/code/session-handoff.md b/courses/learn-harness-engineering/docs/zh-TW/lectures/lecture-05-why-long-running-tasks-lose-continuity/code/session-handoff.md deleted file mode 100644 index 40b3920a..00000000 --- a/courses/learn-harness-engineering/docs/zh-TW/lectures/lecture-05-why-long-running-tasks-lose-continuity/code/session-handoff.md +++ /dev/null @@ -1,17 +0,0 @@ -# Session 交接範例 - -## 已完成 - -- 新增 markdown 匯入支援 -- 在 renderer 中新增了基本文件列表 - -## 已損壞或未驗證 - -- `.md` 匯入成功,但大型 `.txt` 檔案匯入失敗 -- 應用程式可以啟動,但詳細檢視尚未連接 - -## 下一步最佳步驟 - -- 修復 `.txt` 匯入路徑 -- 端到端驗證匯入功能 -- 然後新增文件詳細資訊面板 diff --git a/courses/learn-harness-engineering/docs/zh-TW/lectures/lecture-05-why-long-running-tasks-lose-continuity/code/session-simulator.ts b/courses/learn-harness-engineering/docs/zh-TW/lectures/lecture-05-why-long-running-tasks-lose-continuity/code/session-simulator.ts deleted file mode 100644 index 69f9c3e2..00000000 --- a/courses/learn-harness-engineering/docs/zh-TW/lectures/lecture-05-why-long-running-tasks-lose-continuity/code/session-simulator.ts +++ /dev/null @@ -1,162 +0,0 @@ -/** - * session-simulator.ts - * - * Simulates two sessions working on a multi-step task. - * Run 1: No handoff file -- Session B starts from scratch and duplicates work. - * Run 2: With handoff file -- Session B picks up where Session A left off. - * - * Run: npx tsx docs/lectures/lecture-05-why-long-running-tasks-lose-continuity/code/session-simulator.ts - */ - -// --------------------------------------------------------------------------- -// Types -// --------------------------------------------------------------------------- - -interface TaskStep { - id: number; - name: string; - durationMs: number; -} - -interface SessionResult { - session: string; - stepsCompleted: number; - totalDurationMs: number; - duplicatedSteps: number; - output: string[]; -} - -// --------------------------------------------------------------------------- -// Task definition -// --------------------------------------------------------------------------- - -const taskSteps: TaskStep[] = [ - { id: 1, name: "Read project structure", durationMs: 50 }, - { id: 2, name: "Understand existing auth module", durationMs: 80 }, - { id: 3, name: "Design new search endpoint", durationMs: 60 }, - { id: 4, name: "Implement search endpoint", durationMs: 100 }, - { id: 5, name: "Write integration tests", durationMs: 70 }, - { id: 6, name: "Update documentation", durationMs: 40 }, -]; - -// --------------------------------------------------------------------------- -// Simulated session executor -// --------------------------------------------------------------------------- - -/** Simulate a session doing work. */ -function runSession( - sessionName: string, - startStep: number, - endStep: number -): SessionResult { - const output: string[] = []; - let totalDuration = 0; - - for (let i = startStep; i <= endStep; i++) { - const step = taskSteps.find((s) => s.id === i)!; - totalDuration += step.durationMs; - output.push(` [${sessionName}] Step ${step.id}: ${step.name} (${step.durationMs}ms)`); - } - - return { - session: sessionName, - stepsCompleted: endStep - startStep + 1, - totalDurationMs: totalDuration, - duplicatedSteps: 0, - output, - }; -} - -// --------------------------------------------------------------------------- -// Run 1: No handoff file -// --------------------------------------------------------------------------- - -function simulateNoHandoff(): { sessionA: SessionResult; sessionB: SessionResult } { - // Session A does steps 1-3 before timing out - const sessionA = runSession("Session A", 1, 3); - - // Session B has no context, starts from scratch - const sessionB = runSession("Session B", 1, 6); - sessionB.duplicatedSteps = 3; // Redoes steps 1-3 that A already did - - return { sessionA, sessionB }; -} - -// --------------------------------------------------------------------------- -// Run 2: With handoff file -// --------------------------------------------------------------------------- - -function simulateWithHandoff(): { sessionA: SessionResult; sessionB: SessionResult } { - // Session A does steps 1-3 and writes a handoff file - const sessionA = runSession("Session A", 1, 3); - - // Session B reads handoff, starts from step 4 - const sessionB = runSession("Session B", 4, 6); - sessionB.duplicatedSteps = 0; - - return { sessionA, sessionB }; -} - -// --------------------------------------------------------------------------- -// Reporting -// --------------------------------------------------------------------------- - -function pad(s: string, len: number): string { - return s.length >= len ? s : s + " ".repeat(len - s.length); -} - -function printRun(title: string, result: { sessionA: SessionResult; sessionB: SessionResult }): void { - console.log("\n" + "=".repeat(80)); - console.log(` ${title}`); - console.log("=".repeat(80)); - - console.log("\n Session A output:"); - for (const line of result.sessionA.output) { - console.log(line); - } - - console.log("\n Session B output:"); - for (const line of result.sessionB.output) { - console.log(line); - } - - console.log("\n Session B notes:"); - if (result.sessionB.duplicatedSteps > 0) { - console.log(` - Redid ${result.sessionB.duplicatedSteps} steps that Session A already completed`); - console.log(` - Wasted ${result.sessionB.totalDurationMs - (taskSteps.slice(3).reduce((s, t) => s + t.durationMs, 0))}ms on duplicate work`); - } else { - console.log(" - Read handoff file, continued from step 4"); - console.log(" - No duplicate work performed"); - } -} - -function printComparison(): void { - const noHandoff = simulateNoHandoff(); - const withHandoff = simulateWithHandoff(); - - printRun("RUN 1: NO HANDOFF FILE", noHandoff); - printRun("RUN 2: WITH HANDOFF FILE", withHandoff); - - // Comparison table - console.log("\n" + "=".repeat(80)); - console.log(" COMPARISON TABLE"); - console.log("=".repeat(80) + "\n"); - - const header = `| ${pad("Metric", 35)}| ${pad("No Handoff", 15)}| ${pad("With Handoff", 15)}|`; - const sep = `|${"-".repeat(37)}|${"-".repeat(17)}|${"-".repeat(17)}|`; - console.log(header); - console.log(sep); - - const noTotal = noHandoff.sessionA.totalDurationMs + noHandoff.sessionB.totalDurationMs; - const withTotal = withHandoff.sessionA.totalDurationMs + withHandoff.sessionB.totalDurationMs; - - console.log(`| ${pad("Session A steps completed", 35)}| ${pad(String(noHandoff.sessionA.stepsCompleted), 15)}| ${pad(String(withHandoff.sessionA.stepsCompleted), 15)}|`); - console.log(`| ${pad("Session B steps completed", 35)}| ${pad(String(noHandoff.sessionB.stepsCompleted), 15)}| ${pad(String(withHandoff.sessionB.stepsCompleted), 15)}|`); - console.log(`| ${pad("Duplicated steps", 35)}| ${pad(String(noHandoff.sessionB.duplicatedSteps), 15)}| ${pad(String(withHandoff.sessionB.duplicatedSteps), 15)}|`); - console.log(`| ${pad("Total work (ms)", 35)}| ${pad(String(noTotal), 15)}| ${pad(String(withTotal), 15)}|`); - console.log(`| ${pad("Time saved by handoff", 35)}| ${pad("-", 15)}| ${pad(String(noTotal - withTotal) + "ms", 15)}|`); - - console.log("\n A handoff file eliminates duplicate work and ensures continuity across sessions.\n"); -} - -printComparison(); diff --git a/courses/learn-harness-engineering/docs/zh-TW/lectures/lecture-05-why-long-running-tasks-lose-continuity/index.md b/courses/learn-harness-engineering/docs/zh-TW/lectures/lecture-05-why-long-running-tasks-lose-continuity/index.md deleted file mode 100644 index a3fc2a7b..00000000 --- a/courses/learn-harness-engineering/docs/zh-TW/lectures/lecture-05-why-long-running-tasks-lose-continuity/index.md +++ /dev/null @@ -1,180 +0,0 @@ -[English Version →](../../../en/lectures/lecture-05-why-long-running-tasks-lose-continuity/) - -> 本篇代碼示例:[code/](https://github.com/walkinglabs/learn-harness-engineering/blob/main/docs/zh-TW/lectures/lecture-05-why-long-running-tasks-lose-continuity/code/) -> 實戰練習:[Project 03. 讓 agent 關掉再打開還能接著幹](./../../projects/project-03-multi-session-continuity/index.md) - -# 第五講. 讓跨會話的任務保持上下文連續 - -你讓 Claude Code 幫你實現一個完整的功能,它跑了 30 分鐘,做了大部分工作,但上下文快滿了。你開個新會話繼續,然後發現:它不記得上次做了什麼決策、為什麼選了方案 A 而不是方案 B、哪些檔案已經改過、測試跑到什麼狀態了。它得花 15 分鐘重新探索一遍項目,而且可能跟上次的做法不一致。 - -想象一下,如果你是一個工匠,每天早上醒來都不記得昨天幹了什麼。你得重新認識整個工地——哪面牆砌了一半、為什麼用的是紅磚而不是青磚、水電管道走到哪了。更糟的是,你可能會把昨天已經裝好的窗戶拆了重來,因為你不記得它已經裝過了。 - -這就是 AI coding agent 在跨會話任務中面對的困境。本節課講為什麼 agent 會"斷片",以及如何通過結構化的狀態持久化讓它像一個每天堅持寫日記的工匠——雖然還是會失憶,但日記本裡記著一切。 - -## 上下文窗口:不是無限的 - -上下文窗口是有限的。這不是一個可以通過模型升級解決的問題——即使窗口大小增長到 1M tokens,複雜任務依然會用完。因為 agent 不只是在生成代碼,它還要理解代碼庫、跟蹤自己的決策歷史、處理工具輸出、維護對話上下文。這些信息加起來增長得比窗口擴容快得多。 - -更深層的問題:agent 產生的信息不是均勻重要的。中間推理步驟包含決策的"為什麼"——為什麼選了方案 A 而不是方案 B,為什麼用了這個庫而不是那個庫,為什麼跳過了某個優化。最終輸出只包含"是什麼"——代碼本身。壓縮策略通常保留後者但丟了前者。下一個會話看到代碼但不知道為什麼這麼寫,可能會"優化"掉一個有意為之的設計決策。 - -Anthropic 在他們的長運行 agent 研究中發現了一個很有意思的現象:當 agent 感覺上下文快滿了,它們會表現出一種"過早收斂"的行為——匆忙結束當前工作,跳過驗證步驟,或者選一個簡單的方案而不是最優方案。這就像你考試時發現時間快到了,趕緊隨便選幾個選擇題一樣。Anthropic 把這叫"上下文焦慮"。 - -## 會話連續性流程 - -沒有連續性工件的時候,每個新會話都是一場災難: - -```mermaid -flowchart LR - S1["會話 1
    功能做到一半"] --> End1["上下文快满了
    會話結束"] - End1 --> S2["會話 2 重新開始"] - S2 --> Guess["重新讀目录、重跑測試、
    猜上次為什么這么寫"] - Guess --> Drift["程式碼會重复改
    恢复速度也很慢"] -``` - -有連續性工件的時候,新會話能快速接上: - -```mermaid -flowchart LR - Work["會話 1 的工作"] --> Progress["PROGRESS.md
    已完成 / 进行中 / 下一步"] - Work --> Decisions["DECISIONS.md
    為什么這样做"] - Work --> Verify["驗證記錄
    哪些測試通過或失敗"] - Work --> Commit["Git 檢查点
    当前倉庫狀態"] - - Progress --> Rebuild["會話 2 重建"] - Decisions --> Rebuild - Verify --> Rebuild - Commit --> Rebuild - - Rebuild --> Resume["新會話能很快接上"] -``` - -## 核心概念 - -- **上下文窗口是有限的**:不管模型吹多大的窗口(128K、200K、1M),長任務總會用完。用完之後要麼壓縮(丟信息),要麼重置(開新會話)。兩種方式都會丟東西。 -- **連續性工件**:持久化的狀態檔案,讓新會話能無歧義地恢復到上次離開的地方。最基本的形式:進度日誌 + 驗證記錄 + 下一步行動。就是那個工匠的日記本。 -- **重建成本**:新會話恢復到可執行狀態所需的時間。好的 harness 能把重建成本從 15 分鐘壓到 3 分鐘。 -- **漂移(Drift)**:agent 的理解跟代碼倉庫實際狀態之間的偏差。每次會話邊界都會引入漂移,不加控制會越漂越遠。 -- **上下文焦慮**:Anthropic 觀察到的現象——agent 在接近上下文限制時表現異常,過早結束任務以避免信息丟失。是一種非理性的資源焦慮。 -- **壓縮 vs 重置**:壓縮是在同一個會話裡把上下文摘要化(保留"是什麼",可能丟了"為什麼");重置是開新會話從持久化狀態重建(乾淨但依賴工件完備性)。 - -## 連續性斷了以後會發生什麼 - -上個會話花了很多上下文預算分析了三種方案的優劣,最終選了方案 B。這個會話的 agent 不知道這個分析過程,可能基於不完整的信息重新做了決策——而且可能選了方案 A。就像那個失憶的工匠不記得為什麼選了紅磚,今天看著覺得青磚更好看,就把昨天的牆拆了重砌。 - -更要命的是重複勞動。Agent 不確定某項工作是否已完成,重新做了一遍。或者更糟——做了一半發現跟已有的實現衝突,需要返工。工地上不能兩撥人同時砌同一面牆——但在沒有進度記錄的情況下,新來的人完全不知道這面牆已經有人在砌了。 - -幾個會話累積下來,實現方向可能已經悄悄偏離了原始需求。每個新會話對項目目標的理解都略有偏差,就像傳話遊戲——經過十個人傳話,"幫我買杯咖啡"可能變成了"幫我買個咖啡機"。 - -還有驗證缺口。上個會話的驗證結果(哪些測試通過、哪些失敗、為什麼失敗)沒有記錄,新會話得重新跑一遍驗證才能瞭解當前狀態。每次都重新診斷,每次都浪費寶貴的上下文。 - -OpenAI 和 Anthropic 都在他們的文檔裡強調了結構化狀態持久化的重要性。OpenAI 的 harness engineering 文章把倉庫當作"操作記錄"——每次操作的結果都應該在倉庫裡留下可追溯的痕跡。Anthropic 的 long-running agents 文檔則更具體地建議使用"交接檔案"——包含當前狀態、已知問題和下一步行動的結構化文檔。 - -## 給失憶工匠的日記本 - -核心思路:**把 agent 當成一個會失憶的超級工程師來管理。** 每次它要"下班"之前,必須把關鍵信息寫下來,讓下一個"接班"的 agent 能快速上手。 - -**工具 1:進度檔案(PROGRESS.md)**。這是最基本的連續性工件——日記本的核心部分: - -```markdown -# 專案進度 - -## 当前狀態 -- 最新 commit: abc1234 (feat: add user preferences endpoint) -- 測試狀態: 42/43 通過 (test_pagination_edge_case 失敗) -- Lint: 通過 - -## 已完成 -- [x] 使用者模型和資料庫迁移 -- [x] 基础 CRUD 端点 -- [x] 認證中间件集成 - -## 进行中 -- [ ] 分页功能 (90% - 邊界条件測試失敗) - -## 已知問題 -- test_pagination_edge_case 在空結果集時返回 500 -- 需要确認是否要在列表中包含已删除使用者 - -## 下一步 -1. 修復分页邊界条件 bug -2. 添加"是否包含已删除使用者"的查询参數 -3. 更新 API 文檔 -``` - -**工具 2:決策日誌(DECISIONS.md)**。記錄重要的設計決策和原因。不需要詳細的設計文檔,只需要"什麼決策、為什麼、什麼時候做的"——這是日記本裡的備忘: - -```markdown -# 設計决策 - -## 2024-01-15: 使用 Redis 缓存使用者偏好 -- 原因: 讀取频率高(每次 API 調用都需要),數據量小 -- 否决方案: 用 PostgreSQL 物化视图(變更频率高,物化视图維護成本不劃算) -- 约束: 缓存 TTL 设為 5 分钟,寫入時主動失效 -``` - -**工具 3:git 提交作為檢查點。** 每完成一個原子工作單元就提交。commit message 要說清楚做了什麼和為什麼。這是免費的、自動版本化的狀態快照。 - -**工具 4:init.sh 或 harness 的初始化流程。** 在 `AGENTS.md` 裡寫明每天"上班"和"下班"的流程: - -```markdown -## 每次會話開始時(上班打卡) -1. 讀 PROGRESS.md 了解当前狀態 -2. 讀 DECISIONS.md 了解重要决策 -3. 跑 make check 确認倉庫處于一致狀態 -4. 從 PROGRESS.md 的"下一步"部分继續工作 - -## 每次會話結束前(下班打卡) -1. 更新 PROGRESS.md -2. 跑 make check 确認一致狀態 -3. 提交所有已完成的工作 -``` - -**混合策略**:不需要每次都重置上下文。短任務(30 分鐘以內)可以在同一個會話裡完成。長任務(跨會話)必須用進度檔案和決策日誌來維持連續性。判斷標準:如果任務需要的上下文超過窗口的 60%,就開始準備交接。 - -### 上下文焦慮的深層分析 - -Anthropic 在 2026 年 3 月發佈的研究進一步揭示了上下文焦慮的具體表現:在 Sonnet 4.5 上,當上下文接近窗口限制時,agent 會表現出強烈的"過早收斂"行為。這就像考試時發現時間快到了,趕緊隨便填選擇題。 - -針對這個現象,有兩種策略: - -**壓縮(Compaction)**:在同一個會話裡把早期對話摘要化。優點是保留連續性,agent 能看到"是什麼"。缺點是"為什麼"經常在摘要中丟失——為什麼選了方案 B 而非 A,為什麼跳過了某個優化。更關鍵的是,壓縮並不能消除上下文焦慮——agent 知道上下文曾經很大,心理上仍然傾向於加速收尾。 - -**重置(Context Reset)**:完全清空上下文,開一個新會話,從持久化工件重建。優點是乾淨的心理狀態——新會話沒有"我快沒時間了"的焦慮。缺點是依賴交接工件的完備性。如果日記本裡漏了關鍵信息,新會話可能在錯誤方向上浪費時間。 - -Anthropic 的實際數據:對於 Sonnet 4.5,上下文焦慮足夠嚴重,以至於壓縮單獨不夠用,上下文重置成為 harness 設計的關鍵組件。但對於 Opus 4.5,這種行為大幅減弱,可以不依賴重置而靠壓縮管理上下文。這意味著:**harness 設計需要對目標模型有具體的理解,而不是套用通用範本。** - -> 來源:[Anthropic: Harness design for long-running application development](https://www.anthropic.com/engineering/harness-design-long-running-apps) - -## 實際案例 - -一個 agent 被要求實現一個帶用戶認證的博客系統,12 個功能點,預計需要 5 個會話。 - -**沒有日記本的基線**:會話 1 實現了用戶模型和基礎路由。會話 2 開始時,agent 不記得認證中間件的接口約定,花了約 15 分鐘推斷上次的設計意圖。到會話 3,累積漂移導致 agent 開始重複已實現的功能。到會話 5,倉庫有大量冗餘代碼,但核心認證功能仍未通過端到端測試。12 個功能點只完成了 7 個,其中 3 個有隱含的正確性問題。就像那個沒寫日記的工匠——到了第五天,工地上一團亂,有些牆砌了兩遍,有些該砌的根本沒砌。 - -**有日記本的對照**:使用進度檔案、決策日誌、驗證記錄和 git 檢查點。每個會話結束時自動更新狀態報告。會話 2 的重建成本降到約 3 分鐘。到會話 5,所有 12 個功能點完成且通過驗證。 - -定量對比:重建時間減少約 78%,功能完成率從 58% 提升到 100%,隱含缺陷率從 43% 降到 8%。工匠還是那個會失憶的工匠,但有了日記本,他的每一天都從昨天停下的地方開始,而不是從零開始。 - -## 關鍵要點 - -- 上下文窗口是有限的資源。長任務一定會跨會話,跨會話一定會丟信息——這就像工匠每天都會失憶一樣,是客觀現實。 -- 解決方案不是更大的窗口,而是更好的狀態持久化。進度檔案 + 決策日誌 + git 檢查點——給失憶的工匠一個靠譜的日記本。 -- 把 agent 當成會失憶的工程師來管理:每次"下班"前寫清楚做了什麼、為什麼、下一步做什麼。 -- 重建成本是關鍵指標。好的 harness 應該讓新會話在 3 分鐘內恢復到可執行狀態。 -- 混合策略:短任務在會話內完成,長任務用結構化工件維持連續性。 - -## 延伸閱讀 - -- [Anthropic: Effective Harnesses for Long-Running Agents](https://www.anthropic.com/engineering/effective-harnesses-for-long-running-agents) -- [OpenAI: Harness Engineering](https://openai.com/index/harness-engineering/) -- [Lost in the Middle: How Language Models Use Long Contexts](https://arxiv.org/abs/2307.03172) -- [Claude Code Documentation](https://docs.anthropic.com/en/docs/claude-code) -- [HumanLayer: Harness Engineering for Coding Agents](https://humanlayer.dev/articles/harness-engineering-for-coding-agents/) - -## 練習 - -1. **連續性損耗度量**:選一個需要至少 3 個會話的開發任務。不提供任何連續性工件,在每個會話開始時記錄 agent 花了多少上下文來"搞清楚上次做了什麼"。會話結束後,創建進度檔案,讓下一個會話從進度檔案開始。對比有進度檔案和沒有時的重建成本。 - -2. **交接範本設計**:設計一個最小化的交接範本,包含四個字段:倉庫狀態(commit hash)、運行時狀態(測試通過率)、阻塞項、下一步行動。讓一個全新的 agent 會話只憑這個範本恢復項目狀態,記錄恢復過程中出現的歧義點,迭代改進範本。 - -3. **混合策略實驗**:在一個包含 5 個會話的開發任務中,對比三種策略:(a) 每次都開全新會話 + 進度檔案,(b) 在同一個會話裡儘可能多做(上下文壓縮),(c) 混合策略(短任務在會話內,長任務跨會話 + 進度檔案)。對比重建時間、功能完成率和決策一致性。 diff --git a/courses/learn-harness-engineering/docs/zh-TW/lectures/lecture-06-why-initialization-needs-its-own-phase/code/index.md b/courses/learn-harness-engineering/docs/zh-TW/lectures/lecture-06-why-initialization-needs-its-own-phase/code/index.md deleted file mode 100644 index dee67ba1..00000000 --- a/courses/learn-harness-engineering/docs/zh-TW/lectures/lecture-06-why-initialization-needs-its-own-phase/code/index.md +++ /dev/null @@ -1,8 +0,0 @@ -# 第 06 講的程式碼 - -此資料夾用於以下範例: - -- 初始化輸出 -- init 腳本 -- 進度檔案 -- 首次執行腳手架 diff --git a/courses/learn-harness-engineering/docs/zh-TW/lectures/lecture-06-why-initialization-needs-its-own-phase/code/init-check.ts b/courses/learn-harness-engineering/docs/zh-TW/lectures/lecture-06-why-initialization-needs-its-own-phase/code/init-check.ts deleted file mode 100644 index 52dcd985..00000000 --- a/courses/learn-harness-engineering/docs/zh-TW/lectures/lecture-06-why-initialization-needs-its-own-phase/code/init-check.ts +++ /dev/null @@ -1,296 +0,0 @@ -/** - * init-check.ts - * - * Programmatically checks initialization prerequisites: - * - Node.js version - * - Dependencies installed (node_modules exists) - * - Data directory exists - * - Config files present - * - * Simulates running with and without an explicit init phase, - * showing how missing prerequisites cause silent failures later. - * - * Run: npx tsx docs/lectures/lecture-06-why-initialization-needs-its-own-phase/code/init-check.ts - */ - -import * as fs from "node:fs"; -import * as path from "node:path"; -import * as childProcess from "node:child_process"; - -// --------------------------------------------------------------------------- -// Types -// --------------------------------------------------------------------------- - -interface CheckItem { - name: string; - category: string; - check: () => { pass: boolean; detail: string }; - impactIfMissing: string; -} - -interface CheckResult { - name: string; - category: string; - passed: boolean; - detail: string; - impactIfMissing: string; -} - -// --------------------------------------------------------------------------- -// Checks -// --------------------------------------------------------------------------- - -function createChecks(targetDir: string): CheckItem[] { - return [ - { - name: "Node.js version >= 18", - category: "Runtime", - check: () => { - try { - const version = process.version; // e.g. "v20.11.0" - const major = parseInt(version.replace("v", "").split(".")[0], 10); - return { - pass: major >= 18, - detail: `Detected: ${version} (need >= v18)`, - }; - } catch { - return { pass: false, detail: "Could not detect Node.js version" }; - } - }, - impactIfMissing: "TypeScript features and built-in APIs unavailable", - }, - { - name: "package.json exists", - category: "Config", - check: () => { - const p = path.join(targetDir, "package.json"); - const exists = fs.existsSync(p); - return { - pass: exists, - detail: exists ? `Found: ${p}` : "Not found in target directory", - }; - }, - impactIfMissing: "Cannot install dependencies or run scripts", - }, - { - name: "Dependencies installed (node_modules)", - category: "Dependencies", - check: () => { - const p = path.join(targetDir, "node_modules"); - const exists = fs.existsSync(p); - return { - pass: exists, - detail: exists ? `Found: ${p}` : "node_modules/ not found -- run npm install", - }; - }, - impactIfMissing: "All imports fail at runtime", - }, - { - name: "TypeScript available", - category: "Toolchain", - check: () => { - try { - const result = childProcess.execSync("npx tsc --version", { - encoding: "utf-8", - timeout: 10000, - stdio: ["pipe", "pipe", "pipe"], - }).trim(); - return { pass: true, detail: `Detected: ${result}` }; - } catch { - return { pass: false, detail: "TypeScript not available via npx" }; - } - }, - impactIfMissing: "Cannot compile TypeScript files", - }, - { - name: "tsconfig.json exists", - category: "Config", - check: () => { - const p = path.join(targetDir, "tsconfig.json"); - const exists = fs.existsSync(p); - return { - pass: exists, - detail: exists ? `Found: ${p}` : "Not found", - }; - }, - impactIfMissing: "TypeScript compiler uses defaults, may not match project needs", - }, - { - name: "Source directory exists", - category: "Structure", - check: () => { - const candidates = ["src", "lib", "app"]; - const found = candidates.filter((d) => { - const p = path.join(targetDir, d); - return fs.existsSync(p) && fs.statSync(p).isDirectory(); - }); - return { - pass: found.length > 0, - detail: found.length > 0 ? `Found: ${found.join(", ")}` : "No src/ lib/ or app/ directory found", - }; - }, - impactIfMissing: "Agent cannot locate source files to modify", - }, - { - name: "Test directory exists", - category: "Structure", - check: () => { - const candidates = ["test", "tests", "__tests__", "spec"]; - const found = candidates.filter((d) => { - const p = path.join(targetDir, d); - return fs.existsSync(p) && fs.statSync(p).isDirectory(); - }); - return { - pass: found.length > 0, - detail: found.length > 0 ? `Found: ${found.join(", ")}` : "No test directory found", - }; - }, - impactIfMissing: "Agent cannot find or run existing tests", - }, - { - name: "Git repository initialized", - category: "Version Control", - check: () => { - const gitDir = path.join(targetDir, ".git"); - const exists = fs.existsSync(gitDir); - return { - pass: exists, - detail: exists ? "Git repository detected" : "No .git directory found", - }; - }, - impactIfMissing: "No rollback capability, no change history", - }, - ]; -} - -// --------------------------------------------------------------------------- -// Simulation: with and without init phase -// --------------------------------------------------------------------------- - -interface SimResult { - scenario: string; - checksRun: boolean; - failuresBeforeWork: number; - workAttempted: boolean; - workSucceeded: boolean; - timeWastedMs: number; -} - -function simulateWithoutInit(): SimResult { - // Agent skips init, goes straight to work. - // Encounters failures one at a time as it hits missing prerequisites. - const checks = createChecks(process.cwd()); - let failures = 0; - let timeWasted = 0; - - for (const check of checks) { - const result = check.check(); - if (!result.pass) { - failures++; - timeWasted += 200; // Agent spends time discovering each missing piece - } - } - - return { - scenario: "NO INIT PHASE", - checksRun: false, - failuresBeforeWork: 0, // Didn't check upfront - workAttempted: true, - workSucceeded: failures === 0, - timeWastedMs: timeWasted, - }; -} - -function simulateWithInit(): SimResult { - // Agent runs init phase first, discovers all issues upfront. - const checks = createChecks(process.cwd()); - let failures = 0; - - for (const check of checks) { - if (!check.check().pass) { - failures++; - } - } - - return { - scenario: "WITH INIT PHASE", - checksRun: true, - failuresBeforeWork: failures, - workAttempted: failures === 0, - workSucceeded: failures === 0, - timeWastedMs: 0, - }; -} - -// --------------------------------------------------------------------------- -// Reporting -// --------------------------------------------------------------------------- - -function pad(s: string, len: number): string { - return s.length >= len ? s : s + " ".repeat(len - s.length); -} - -function run(): void { - const targetDir = process.cwd(); - console.log("\n" + "=".repeat(80)); - console.log(" INITIALIZATION PREREQUISITE CHECK"); - console.log("=".repeat(80)); - console.log(` Target: ${targetDir}\n`); - - const checks = createChecks(targetDir); - const results: CheckResult[] = checks.map((c) => { - const r = c.check(); - return { - name: c.name, - category: c.category, - passed: r.pass, - detail: r.detail, - impactIfMissing: c.impactIfMissing, - }; - }); - - // Detailed report - const header = `| ${pad("Check", 35)}| ${pad("Category", 12)}| ${pad("Status", 6)}| Detail`; - const sep = `|${"-".repeat(37)}|${"-".repeat(14)}|${"-".repeat(8)}|${"-".repeat(40)}`; - console.log(header); - console.log(sep); - - for (const r of results) { - const status = r.passed ? "PASS" : "FAIL"; - console.log(`| ${pad(r.name, 35)}| ${pad(r.category, 12)}| ${pad(status, 6)}| ${r.detail}`); - } - - const passCount = results.filter((r) => r.passed).length; - const failCount = results.filter((r) => !r.passed).length; - - console.log("\n" + "-".repeat(80)); - console.log(` Results: ${passCount} passed, ${failCount} failed out of ${results.length} checks`); - - if (failCount > 0) { - console.log("\n FAILED CHECKS -- Impact if not resolved:"); - for (const r of results.filter((r) => !r.passed)) { - console.log(` - ${r.name}: ${r.impactIfMissing}`); - } - } - - // Comparison: with vs without init - const noInit = simulateWithoutInit(); - const withInit = simulateWithInit(); - - console.log("\n" + "=".repeat(80)); - console.log(" INIT PHASE COMPARISON"); - console.log("=".repeat(80) + "\n"); - - const cHeader = `| ${pad("Metric", 35)}| ${pad("No Init Phase", 18)}| ${pad("With Init Phase", 18)}|`; - const cSep = `|${"-".repeat(37)}|${"-".repeat(20)}|${"-".repeat(20)}|`; - console.log(cHeader); - console.log(cSep); - console.log(`| ${pad("Prerequisites checked upfront", 35)}| ${pad("No", 18)}| ${pad("Yes", 18)}|`); - console.log(`| ${pad("Work attempted despite issues", 35)}| ${pad(String(noInit.workAttempted), 18)}| ${pad(String(withInit.workAttempted), 18)}|`); - console.log(`| ${pad("Time wasted discovering issues late", 35)}| ${pad(noInit.timeWastedMs + "ms", 18)}| ${pad(withInit.timeWastedMs + "ms", 18)}|`); - console.log(`| ${pad("Work succeeded", 35)}| ${pad(String(noInit.workSucceeded), 18)}| ${pad(String(withInit.workSucceeded), 18)}|`); - - console.log("\n An explicit init phase catches problems before they waste time.\n"); -} - -run(); diff --git a/courses/learn-harness-engineering/docs/zh-TW/lectures/lecture-06-why-initialization-needs-its-own-phase/code/init.sh b/courses/learn-harness-engineering/docs/zh-TW/lectures/lecture-06-why-initialization-needs-its-own-phase/code/init.sh deleted file mode 100644 index 2c6e1b0c..00000000 --- a/courses/learn-harness-engineering/docs/zh-TW/lectures/lecture-06-why-initialization-needs-its-own-phase/code/init.sh +++ /dev/null @@ -1,10 +0,0 @@ -#!/usr/bin/env bash -set -euo pipefail - -echo "[init] installing dependencies" -npm install - -echo "[init] starting the docs site is optional" -echo "[init] use npm run docs:dev for course docs" - -echo "[init] project-specific startup would go here" diff --git a/courses/learn-harness-engineering/docs/zh-TW/lectures/lecture-06-why-initialization-needs-its-own-phase/code/initializer-output-checklist.md b/courses/learn-harness-engineering/docs/zh-TW/lectures/lecture-06-why-initialization-needs-its-own-phase/code/initializer-output-checklist.md deleted file mode 100644 index 2a24accb..00000000 --- a/courses/learn-harness-engineering/docs/zh-TW/lectures/lecture-06-why-initialization-needs-its-own-phase/code/initializer-output-checklist.md +++ /dev/null @@ -1,7 +0,0 @@ -# 初始化輸出檢查清單 - -- 是否有標準的啟動指令? -- 是否有標準的驗證指令? -- 是否有第一份進度產物? -- 是否有穩定的首次 commit? -- 是否有可供後續執行使用的可見功能範圍? diff --git a/courses/learn-harness-engineering/docs/zh-TW/lectures/lecture-06-why-initialization-needs-its-own-phase/index.md b/courses/learn-harness-engineering/docs/zh-TW/lectures/lecture-06-why-initialization-needs-its-own-phase/index.md deleted file mode 100644 index a9558935..00000000 --- a/courses/learn-harness-engineering/docs/zh-TW/lectures/lecture-06-why-initialization-needs-its-own-phase/index.md +++ /dev/null @@ -1,157 +0,0 @@ -[English Version →](../../../en/lectures/lecture-06-why-initialization-needs-its-own-phase/) - -> 本篇代碼示例:[code/](https://github.com/walkinglabs/learn-harness-engineering/blob/main/docs/zh-TW/lectures/lecture-06-why-initialization-needs-its-own-phase/code/) -> 實戰練習:[Project 03. 讓 agent 關掉再打開還能接著幹](./../../projects/project-03-multi-session-continuity/index.md) - -# 第六講. 讓 agent 每次工作前先初始化 - -你開了一個新的 agent 會話,讓它"幫我加個搜索功能"。它上來就開始改代碼——精神可嘉。改了 20 分鐘發現測試框架沒配好,又花 10 分鐘搞測試框架,然後發現數據庫遷移腳本格式不對,又折騰了一會兒。最後搜索功能倒是加了,但整個會話的效率很低——大部分時間花在了"搞清楚這個項目怎麼運作"上,而不是寫搜索功能。 - -更好的做法是:在讓 agent 開始幹活之前,先用一個獨立的階段把基礎環境搭好、驗證命令跑通、項目結構搞清楚。就像蓋房子——你不能邊打地基邊砌牆,不然牆砌到一半地基還沒幹,整棟樓都得推倒重來。先打地基,地基幹了,再砌牆,一氣呵成。 - -這節課講的就是為什麼初始化必須是獨立的階段,不能跟實現混在一起。 - -## 地基和牆:兩種完全不同的工作 - -初始化和實現的優化目標完全不同。實現階段的目標是:最大化已驗證功能的數量和質量。初始化階段的目標是:最大化後續所有實現的可靠性和效率。 - -當你把初始化和實現混在一起的時候,agent 面臨一個多目標優化問題——它要同時搭基礎設施和寫功能代碼。在沒有顯式優先級設定的情況下,agent 自然傾向於寫代碼(因為那是直接可見的產出),而犧牲基礎設施(因為它的價值只能在後續會話中體現)。這就像讓施工隊同時打地基和砌牆——他們大概率會急著砌牆,因為牆看得見、能交差。但地基沒打好的房子,後面出的問題是系統性的。 - -## 初始化生命週期 - -```mermaid -flowchart TB - subgraph Wrong["混在一起的一次會話(錯誤)"] - W1["一上來就開始做功能"] --> W2["做到一半才發现环境和測試缺口"] - W2 --> W3["累积未经驗證的程式碼"] - W3 --> W4["下個會話还得重新摸專案狀態"] - end - - subgraph Right["獨立初始化阶段(正确)"] - R1["會話 1:环境可執行"] --> R2["示例測試通過"] - R2 --> R3["寫出啟動契约 + 任務清單"] - R3 --> R4["提交干净檢查点"] - R4 --> R5["後續會話直接開始做已準备好的任務"] - end -``` - -## 混在一起做會怎樣 - -最直接的問題:地基打不牢。Agent 花了 80% 的精力寫功能代碼,剩下 20% 隨便搭了點基礎設施。測試框架配了但沒驗證過,lint 規則設了但太寬鬆,進度檔案沒創建。這些缺陷在第一個會話裡不明顯(因為 agent 還記得它做了什麼),但到第二個會話就暴露了——新 agent 不知道項目怎麼跑、怎麼測、做到哪了。地基不牢,地動山搖。 - -更隱蔽的代價是"未驗證的累積"——在測試框架配好之前寫的功能代碼,等回頭補測試的時候可能發現設計上就有問題,早知道的話應該用不同的方式實現。就像在地基沒幹的時候就開始貼瓷磚,等發現地面不平時,瓷磚全得撬掉重來。 - -上下文預算也在被浪費。初始化工作(配環境、配測試、理解項目結構)消耗了大量預算,留給實際功能實現的反而不夠了。結果第一個會話只完成了一半的功能,第二個會話還得從頭理解項目。預算花在了打地基上,但地基也沒打好——兩頭都沒佔著。 - -最容易被忽略的是隱式假設埋下的雷。Agent 在初始化過程中做的決策(用什麼測試框架、目錄怎麼組織、依賴怎麼管理)如果不顯式記錄下來,後續會話就可能做出矛盾的選擇。第一個施工隊用的是水泥地基,第二個施工隊不知道,往上面打了木樁——地基直接裂了。 - -Anthropic 在他們的長運行應用開發研究中明確建議把初始化和實現分離。他們的實驗數據:使用獨立初始化階段的項目,多會話場景中的功能完成率比混合方式高 31%。關鍵是——初始化階段投入的時間在後續 3-4 個會話中就能完全收回。地基打得越紮實,上面的樓蓋得越快。 - -OpenAI 的 Codex harness engineering 指南也強調"倉庫作為操作記錄"的原則——第一次運行就要建立清晰的操作結構,否則每次新會話都得重新推斷項目約定。 - -## 核心概念 - -- **初始化階段**:agent 生命週期中的第一個階段,不做功能實現,只建立後續所有實現階段的執行前提。輸出不是代碼,而是基礎設施。 -- **自舉契約**:一個項目能被全新 agent 會話無歧義操作的條件——能啟動、能測試、能看進度、能接手下一步。四個條件缺一不可。 -- **冷啟動 vs 熱啟動**:冷啟動是從空目錄開始,agent 要猜項目結構;熱啟動是從範本或已有項目開始,基礎設施已經就位。熱啟動的效果遠好於冷啟動——就像在有水電的工地上開工,比在荒郊野嶺從頭搞起快得多。 -- **交接就緒性**:項目在任何時刻都處於"可以被全新 agent 接手"的狀態。不需要口頭解釋,只看倉庫內容就能接著幹。 -- **首次驗證時間**:從項目開始到第一個功能點通過驗證的時間。這是衡量初始化效率的核心指標。 -- **下游可用性**:初始化質量的最佳衡量標準——後續會話不需要依賴隱式知識就能成功執行任務的比例。 - -## 怎麼做好初始化 - -**把初始化當作一個獨立的階段來執行。** 第一個會話只做初始化,不寫任何業務功能代碼。初始化的產出是: - -**1. 可運行的環境。** 項目能啟動、依賴都裝好、沒有環境問題。地基澆好了,沒裂縫。 - -**2. 可驗證的測試框架。** 至少有一個示例測試能通過。這證明測試框架本身是配對的——就像地基上立了一根柱子,證明地基能承重。 - -**3. 自舉契約文檔。** 一個明確的文檔告訴後續會話: -```markdown -# 初始化契约 - -## 啟動命令 -- 安装依赖:`make setup` -- 啟動開發服務器:`make dev` -- 執行測試:`make test` -- 完整驗證:`make check` - -## 当前狀態 -- 所有依赖已安装並锁定 -- 測試框架已配置(Vitest + React Testing Library) -- 示例測試通過(1/1) -- Lint 规则已配置(ESLint + Prettier) - -## 專案結構 -- src/ — 源程式碼 -- src/components/ — React 组件 -- src/api/ — API 客户端 -- tests/ — 測試檔案 -``` - -**4. 任務分解。** 把整個項目拆成有序的任務列表,每個任務有明確的驗收標準: -```markdown -# 任務分解 - -## Task 1: 使用者認證基础 -- 實作 JWT 認證中间件 -- 添加登录/注册端点 -- 验收標準:pytest tests/test_auth.py 全部通過 - -## Task 2: 使用者資料页面 -- 實作使用者資料 CRUD -- 添加資料编辑表單 -- 验收標準:pytest tests/test_profile.py 全部通過 - -## Task 3: 搜索功能 -- ... -``` - -**5. Git 提交作為檢查點。** 初始化完成後提交一個乾淨的 checkpoint。後續所有工作都從這個 checkpoint 開始。 - -**熱啟動策略**:不要從空目錄開始。用一個項目範本(create-react-app、fastapi-template 等)預置好標準的目錄結構、依賴配置和測試框架。把通用的初始化步驟預置到範本裡,只留下項目特有的初始化工作。就像在有通水通電的工地上開工,比從荒郊野嶺開始強一萬倍。 - -**初始化的完成條件**:不是"寫了多少代碼",而是自舉契約的四個條件都滿足了——能啟動、能測試、能看進度、能接手下一步。用這個檢查清單驗收初始化: - -```markdown -## 初始化验收清單 -- [ ] `make setup` 從零開始能成功 -- [ ] `make test` 至少有一個測試通過 -- [ ] 新的 agent 會話能只看倉庫回答"怎么跑"和"怎么測" -- [ ] 任務分解檔案存在且有至少 3 個任務 -- [ ] 所有内容已提交到 git -``` - -## 實際案例 - -一個 React 前端項目的兩種初始化方式對比: - -**混合方式(邊打地基邊砌牆)**:agent 在第一個會話中同時做了項目腳手架創建和首個功能實現。會話結束時,倉庫有可運行的代碼,但:沒有顯式的啟動/測試命令文檔、沒有進度跟蹤檔案、沒有任務分解。第二個會話花了約 20 分鐘推斷項目結構、測試框架和構建流程——就像新來的施工隊看著一片工地,不知道地基打到什麼程度、水電管道走到哪了,只能一個個挖開來看。 - -**獨立初始化(先打地基)**:第一個會話只做初始化——用項目範本創建目錄結構、配置測試框架(Vitest + React Testing Library)、寫一個示例測試並驗證通過、創建自舉契約文檔和任務分解檔案、提交初始檢查點。第二個會話的重建時間不到 3 分鐘,直接從任務列表開始工作——施工隊來了,看了一眼施工圖就知道從哪裡接著幹。 - -整個項目週期對比:混合方式的總重建時間(跨所有會話)比獨立初始化多約 60%。獨立初始化多花的那 20 分鐘在後續會話中被成倍收回。就像地基打得紮實,上面蓋樓的效率反而更高——慢即是快。 - -## 關鍵要點 - -- 初始化和實現的優化目標不同,混在一起只會互相拖後腿。先打地基,再砌牆。 -- 初始化的產出不是代碼,是基礎設施:可運行的環境、可驗證的測試、自舉契約、任務分解。 -- 用"自舉契約"的四個條件驗收初始化:能啟動、能測試、能看進度、能接手下一步。 -- 熱啟動優於冷啟動。用項目範本預置標準化的基礎設施。 -- 初始化投入的時間會在後續 3-4 個會話中完全收回。這不是額外的成本,是前期投資——地基打得越紮實,樓蓋得越快。 - -## 延伸閱讀 - -- [Anthropic: Effective Harnesses for Long-Running Agents](https://www.anthropic.com/engineering/effective-harnesses-for-long-running-agents) -- [OpenAI: Harness Engineering](https://openai.com/index/harness-engineering/) -- [HumanLayer: Harness Engineering for Coding Agents](https://humanlayer.dev/articles/harness-engineering-for-coding-agents/) -- [Infrastructure as Code — Martin Fowler](https://martinfowler.com/bliki/InfrastructureAsCode.html) -- [SWE-agent: Agent-Computer Interfaces](https://github.com/princeton-nlp/SWE-agent) - -## 練習 - -1. **自舉契約設計**:為一個你正在開發的項目寫一個完整的自舉契約。然後開一個全新的 agent 會話,只給它看倉庫內容(不給任何口頭上下文),讓它嘗試啟動項目、跑測試、瞭解當前進度。記錄它遇到的問題——每個問題都對應自舉契約中缺失的一個條款。 - -2. **對比實驗**:選一箇中等複雜度的新項目。方式 A:讓 agent 初始化和首次實現同時做。方式 B:先花一個會話做獨立初始化,第二個會話開始實現。在 4 個會話後對比:首次驗證時間、重建成本、功能完成率。 - -3. **初始化驗收清單**:為你的項目設計一個初始化驗收清單。讓一個全新的 agent 會話執行清單上的每一項,記錄哪些項通過了、哪些沒通過。沒通過的項就是你的 harness 需要補強的地方。 diff --git a/courses/learn-harness-engineering/docs/zh-TW/lectures/lecture-07-why-agents-overreach-and-under-finish/code/index.md b/courses/learn-harness-engineering/docs/zh-TW/lectures/lecture-07-why-agents-overreach-and-under-finish/code/index.md deleted file mode 100644 index a672da76..00000000 --- a/courses/learn-harness-engineering/docs/zh-TW/lectures/lecture-07-why-agents-overreach-and-under-finish/code/index.md +++ /dev/null @@ -1,8 +0,0 @@ -# 第 07 講的程式碼 - -此資料夾用於以下範例: - -- 一次性的失敗案例 -- 過大的任務提示 -- 漸進式任務塑造 -- 結構化功能範圍 diff --git a/courses/learn-harness-engineering/docs/zh-TW/lectures/lecture-07-why-agents-overreach-and-under-finish/code/next-task-template.md b/courses/learn-harness-engineering/docs/zh-TW/lectures/lecture-07-why-agents-overreach-and-under-finish/code/next-task-template.md deleted file mode 100644 index 3a2709a9..00000000 --- a/courses/learn-harness-engineering/docs/zh-TW/lectures/lecture-07-why-agents-overreach-and-under-finish/code/next-task-template.md +++ /dev/null @@ -1,6 +0,0 @@ -# 下一個任務模板 - -- 目前最高優先順序的功能: -- 為什麼這個功能是下一個: -- 什麼算作通過: -- 在此步驟中不得更改什麼: diff --git a/courses/learn-harness-engineering/docs/zh-TW/lectures/lecture-07-why-agents-overreach-and-under-finish/code/scope-surface-example.md b/courses/learn-harness-engineering/docs/zh-TW/lectures/lecture-07-why-agents-overreach-and-under-finish/code/scope-surface-example.md deleted file mode 100644 index 55ee28cf..00000000 --- a/courses/learn-harness-engineering/docs/zh-TW/lectures/lecture-07-why-agents-overreach-and-under-finish/code/scope-surface-example.md +++ /dev/null @@ -1,17 +0,0 @@ -# 範圍範例 - -任務: - -- 為 Electron 知識庫應用程式新增索引功能 - -不良的範圍定義: - -- 「實作索引功能」 - -較好的範圍定義: - -- 解析已匯入的文件 -- 將文件分割為區塊 -- 持久化區塊詮釋資料 -- 在 UI 中顯示索引狀態 -- 新增重新索引操作 diff --git a/courses/learn-harness-engineering/docs/zh-TW/lectures/lecture-07-why-agents-overreach-and-under-finish/code/scope-tracker.ts b/courses/learn-harness-engineering/docs/zh-TW/lectures/lecture-07-why-agents-overreach-and-under-finish/code/scope-tracker.ts deleted file mode 100644 index c587a1dc..00000000 --- a/courses/learn-harness-engineering/docs/zh-TW/lectures/lecture-07-why-agents-overreach-and-under-finish/code/scope-tracker.ts +++ /dev/null @@ -1,151 +0,0 @@ -/** - * scope-tracker.ts - * - * Reads a feature list and a change log. Enforces single-active-feature - * policy. Given a log of changes, flags any changes outside the active - * feature scope. Demonstrates how scope drift happens and how the tracker - * catches it. - * - * Run: npx tsx docs/lectures/lecture-07-why-agents-overreach-and-under-finish/code/scope-tracker.ts - */ - -// --------------------------------------------------------------------------- -// Types -// --------------------------------------------------------------------------- - -interface Feature { - id: string; - name: string; - status: "active" | "pending" | "done"; -} - -interface ChangeLogEntry { - step: number; - file: string; - description: string; - featureId: string; // The feature this change claims to belong to -} - -// --------------------------------------------------------------------------- -// Sample data -// --------------------------------------------------------------------------- - -const features: Feature[] = [ - { id: "F-001", name: "Search endpoint", status: "active" }, - { id: "F-002", name: "Delete endpoint", status: "pending" }, - { id: "F-003", name: "Rate limiting", status: "pending" }, - { id: "F-004", name: "User dashboard", status: "pending" }, -]; - -// A realistic change log where the agent gradually drifts from the active feature -const changeLog: ChangeLogEntry[] = [ - { step: 1, file: "src/routes/search.ts", description: "Add search route handler", featureId: "F-001" }, - { step: 2, file: "src/routes/search.ts", description: "Add query parameter validation", featureId: "F-001" }, - { step: 3, file: "src/routes/search.ts", description: "Add search results pagination", featureId: "F-001" }, - { step: 4, file: "src/routes/delete.ts", description: "Add delete route handler", featureId: "F-002" }, // DRIFT - { step: 5, file: "src/middleware/rate-limit.ts", description: "Add rate limiter middleware", featureId: "F-003" }, // DRIFT - { step: 6, file: "src/routes/search.ts", description: "Integrate rate limiter into search", featureId: "F-001" }, - { step: 7, file: "src/dashboard/ui.tsx", description: "Create dashboard layout component", featureId: "F-004" }, // DRIFT - { step: 8, file: "src/routes/search.ts", description: "Add search response formatting", featureId: "F-001" }, - { step: 9, file: "src/routes/delete.ts", description: "Add delete confirmation logic", featureId: "F-002" }, // DRIFT - { step: 10, file: "src/routes/search.ts", description: "Add search tests", featureId: "F-001" }, -]; - -// --------------------------------------------------------------------------- -// Scope tracker -// --------------------------------------------------------------------------- - -interface ScopeCheckResult { - step: number; - file: string; - description: string; - featureId: string; - inScope: boolean; - activeFeature: string; -} - -function trackScope( - featureList: Feature[], - changes: ChangeLogEntry[] -): ScopeCheckResult[] { - const activeFeatures = featureList.filter((f) => f.status === "active"); - const activeIds = new Set(activeFeatures.map((f) => f.id)); - const activeNames = activeFeatures.map((f) => f.name).join(", "); - - return changes.map((change) => ({ - step: change.step, - file: change.file, - description: change.description, - featureId: change.featureId, - inScope: activeIds.has(change.featureId), - activeFeature: activeNames, - })); -} - -// --------------------------------------------------------------------------- -// Reporting -// --------------------------------------------------------------------------- - -function pad(s: string, len: number): string { - return s.length >= len ? s : s + " ".repeat(len - s.length); -} - -function run(): void { - const results = trackScope(features, changeLog); - - console.log("\n" + "=".repeat(100)); - console.log(" SCOPE TRACKER -- Single Active Feature Enforcement"); - console.log("=".repeat(100)); - - console.log("\n Active feature: " + features.filter((f) => f.status === "active").map((f) => `${f.id} (${f.name})`).join(", ")); - console.log(" Pending features: " + features.filter((f) => f.status === "pending").map((f) => `${f.id} (${f.name})`).join(", ")); - - // Detailed change log - console.log("\n" + "-".repeat(100)); - const header = `| ${pad("Step", 5)}| ${pad("File", 35)}| ${pad("Description", 40)}| ${pad("Feature", 8)}| ${pad("In Scope", 10)}|`; - const sep = `|${"-".repeat(7)}|${"-".repeat(37)}|${"-".repeat(42)}|${"-".repeat(10)}|${"-".repeat(12)}|`; - console.log(header); - console.log(sep); - - let inScopeCount = 0; - let driftCount = 0; - - for (const r of results) { - const scopeLabel = r.inScope ? "OK" : "DRIFT"; - if (r.inScope) inScopeCount++; - else driftCount++; - - const marker = r.inScope ? " " : ">>"; - console.log(`${marker}| ${pad(String(r.step), 5)}| ${pad(r.file, 35)}| ${pad(r.description, 40)}| ${pad(r.featureId, 8)}| ${pad(scopeLabel, 10)}|`); - } - - // Summary - console.log("\n" + "=".repeat(100)); - console.log(" SCOPE DRIFT SUMMARY"); - console.log("=".repeat(100) + "\n"); - - const sHeader = `| ${pad("Metric", 40)}| ${pad("Value", 15)}|`; - const sSep = `|${"-".repeat(42)}|${"-".repeat(17)}|`; - console.log(sHeader); - console.log(sSep); - console.log(`| ${pad("Total changes", 40)}| ${pad(String(results.length), 15)}|`); - console.log(`| ${pad("Changes within active scope (F-001)", 40)}| ${pad(String(inScopeCount), 15)}|`); - console.log(`| ${pad("Changes outside active scope (DRIFT)", 40)}| ${pad(String(driftCount), 15)}|`); - console.log(`| ${pad("Features touched (total)", 40)}| ${pad(String(new Set(results.map((r) => r.featureId)).size), 15)}|`); - - // Drift detail - const driftFeatures = [...new Set(results.filter((r) => !r.inScope).map((r) => r.featureId))]; - if (driftFeatures.length > 0) { - console.log("\n DRIFTED FEATURES:"); - for (const fid of driftFeatures) { - const feat = features.find((f) => f.id === fid); - const driftChanges = results.filter((r) => r.featureId === fid); - console.log(` ${fid} (${feat?.name}): ${driftChanges.length} unauthorized changes`); - } - } - - console.log("\n Without a scope tracker, the agent silently worked on " + driftFeatures.length + " unrelated features."); - console.log(" The tracker catches this drift and enforces the single-active-feature policy.\n"); -} - -run(); diff --git a/courses/learn-harness-engineering/docs/zh-TW/lectures/lecture-07-why-agents-overreach-and-under-finish/index.md b/courses/learn-harness-engineering/docs/zh-TW/lectures/lecture-07-why-agents-overreach-and-under-finish/index.md deleted file mode 100644 index 2bc1eaf5..00000000 --- a/courses/learn-harness-engineering/docs/zh-TW/lectures/lecture-07-why-agents-overreach-and-under-finish/index.md +++ /dev/null @@ -1,134 +0,0 @@ -[English Version →](../../../en/lectures/lecture-07-why-agents-overreach-and-under-finish/) - -> 本篇代碼示例:[code/](https://github.com/walkinglabs/learn-harness-engineering/blob/main/docs/zh-TW/lectures/lecture-07-why-agents-overreach-and-under-finish/code/) -> 實戰練習:[Project 04. 用運行反饋修正 agent 的行為](./../../projects/project-04-incremental-indexing/index.md) - -# 第七講. 給 agent 劃清每次任務的邊界 - -你讓 Claude Code "給這個項目加上用戶認證功能",結果它同時開始改數據庫 schema、寫路由、改前端組件、還順手重構了錯誤處理中間件。兩個小時後你一看——12 個檔案被修改,800 行新代碼,但沒有一個功能是端到端跑通的。 - -貪多嚼不爛,這句話放到 AI agent 身上格外貼切。Agent 天生就有"多做一點"的衝動——看到相關的事情就順手一起做了,和那種在超市本來只打算買瓶醬油、結果推著滿滿一車出來的人一個德行。問題是,人類買了太多東西最多浪費錢,agent 同時做太多事情則是每一件都做不好。 - -Anthropic 在 "Effective harnesses for long-running agents" 工程博客中明確指出:當提示太寬泛時,agent 傾向於"同時啟動多件事"而非"先做完一件事"。OpenAI 在 Codex 工程實踐中也發現,沒有顯式範圍控制的任務,完成率會暴跌。這不是模型的問題——是你沒有在 harness 裡給它劃清邊界。 - -## 注意力是有限的資源 - -這不是比喻,是數學。假設 agent 的上下文容量為 C,同時激活 k 個任務,每個任務平均獲得 C/k 的推理資源。當 C/k 低於完成單個任務所需的最小閾值時,所有任務都做不完。這就像你的胃就那麼大——同時塞十個包子進去,不是一個都消化不了,而是十個都消化不良。 - -Claude Code 的真實行為很說明問題。你讓它"添加用戶註冊功能",它很可能這樣做: - -1. 創建 User model -2. 寫註冊路由 -3. 發現需要郵箱驗證,於是加郵件服務 -4. 看到密碼需要加密,於是引入 bcrypt -5. 注意到錯誤處理不統一,於是重構全局錯誤中間件 -6. 看到測試檔案結構不清晰,於是重組目錄結構 - -6 步之後,每一個都是半成品。沒有端到端驗證,代碼之間耦合複雜,下一個會話來接手時會一臉懵。就像一個人同時炒六道菜,每道菜都下了鍋但沒有一道出鍋——全糊了。 - -Anthropic 的實驗數據直接支持這一點:使用"小下一步"策略(等價於 WIP=1)的 agent,任務完成率比使用寬泛提示的 agent 高 37%。更有意思的是,agent 生成的代碼行數和實際完成的功能數量呈弱負相關——寫得越多,完成得越少。貪多嚼不爛,數據為證。 - -## WIP=1 工作流 - -```mermaid -flowchart LR - Queue["功能队列"] --> Pick["只選一個任務"] - Pick --> Active["仅允许一個 active"] - Active --> Verify["跑端到端驗證"] - Verify -->|通過| Commit["提交並解锁下一個任務"] - Verify -->|失敗| Active - Commit --> Queue -``` - -```mermaid -flowchart TB - Budget["可用推理预算 = C"] --> One["WIP = 1
    每個任務拿到 C / 1"] - Budget --> Many["WIP = 5
    每個任務只有 C / 5"] - - One --> Finish["一個功能进入 passing"] - Many --> Partial["五個功能都只做了一半"] - Partial --> VCR["已驗證完成率低
    下一會話返工高"] -``` - -## 核心概念 - -- **過度延伸(Overreach)**:agent 在一次會話中激活的任務數量超過最優值。它不是主觀判斷,而是可量化的——同時做 5 個功能但 0 個跑通,就是 overreach。 -- **不足完成(Under-finish)**:已啟動的任務中,通過端到端驗證的比例低於閾值。寫了代碼但沒跑通測試,就是 under-finish。 -- **WIP 限制(Work-in-Progress Limit)**:來自 Kanban 方法論。核心思想:限制同時在進行的任務數量。對於 agent,WIP=1 是最安全的默認值——做完一個再做下一個。就像自助餐廳不要一次拿太多盤子,吃完一盤再去拿下一盤。 -- **完成證據(Completion Evidence)**:一個任務從"進行中"變成"已完成"必須滿足的可驗證條件。沒有這個,agent 會用"代碼看起來沒問題"代替"行為通過測試"。 -- **範圍表面(Scope Surface)**:一個 DAG 結構,每個節點是一個工作單元,邊是依賴關係。狀態只有四種:未開始、進行中、阻塞、已通過。 -- **完成壓力(Completion Pressure)**:harness 通過 WIP 限制和完成證據要求共同產生的約束力,迫使 agent 先完成當前任務再開始新任務。 - -## Overreach 和 Under-finish 是一對難兄難弟 - -這兩個問題不是獨立的,而是互相加劇。overreach 導致注意力分散,注意力分散導致 under-finish,under-finish 留下的半成品代碼又增加了系統複雜度,進一步導致下一個任務的 overreach。惡性循環。 - -用 Kanban 的語言說:Little 法則告訴我們 L = lambda * W。如果在製品數量 L 過大(同時做太多事),每個任務的前置時間 W 必然增加。對於 agent 來說,這意味著每個功能從開始到驗證通過的時間被拉長,失敗概率被放大。 - -這在人類世界也是老問題了——Steve McConnell 在《Rapid Development》中記錄,範圍蔓延是項目失敗的首要原因。但人類至少有"我已經做得夠多了"的直覺,agent 完全沒有。生成下一個想法的成本對模型來說太低了——寫一行"順便把這個也改了"幾乎不消耗額外 token,但每個額外的修改都會稀釋 agent 的注意力。就像在自助餐廳裡,每多拿一個盤子的邊際成本幾乎為零,但你的胃只有那麼大。 - -## 怎麼做才對 - -### 1. 強制 WIP=1 - -這是最直接有效的方法。在你的 harness 裡,明確告訴 agent:**任何時刻只允許一個任務處於"進行中"狀態。** 在 Claude Code 的 CLAUDE.md 或 Codex 的 AGENTS.md 裡寫: - -``` -## 工作规则 -- 每次只做一個功能点 -- 当前功能点端到端驗證通過後,才能開始下一個 -- 不要在實作功能 A 時"顺便"重构功能 B -``` - -就像吃自助餐——一次只拿一盤,吃完再去拿。 - -### 2. 給每個任務定義顯式的完成證據 - -完成不是"代碼寫完了",而是"行為驗證通過了"。在你的功能列表裡,每個條目都要有驗證命令: - -``` -F01: 使用者注册 - 驗證: curl -X POST /api/register -d '{"email":"test@example.com","password":"123456"}' | jq .status == 201 - 狀態: passing -``` - -### 3. 把範圍表面外部化 - -用一個機器可讀的檔案(JSON 或 Markdown)記錄所有任務的狀態。任何新會話都能直接讀這個檔案,知道:哪個任務在做?什麼行為算完成?已經通過了什麼驗證? - -### 4. 監控驗證完成率 - -harness 應該持續跟蹤 VCR(Verified Completion Rate)= 已通過驗證的任務數 / 已啟動的任務數。VCR < 1.0 時,阻止新任務啟動。 - -## 實際案例 - -一個 8 個功能點的 REST API 項目,兩種策略對比: - -**自助餐模式(無約束)**:agent 在第一個會話同時啟動 5 個功能。產出約 800 行代碼,涉及 12 個檔案。端到端測試通過率只有 20%——只有用戶註冊跑通了。其餘 4 個功能:數據庫 schema 建了但缺驗證邏輯,路由定義了但返回格式錯誤。就像一個人同時炒六道菜,只有一道勉強能吃。到第 3 個會話結束,8 個功能只完成 3 個。 - -**單盤模式(WIP=1)**:agent 在第一個會話只做用戶註冊。產出約 200 行代碼,涉及 4 個檔案。端到端測試 100% 通過。提交乾淨的、已驗證的實現。到第 4 個會話結束,8 個功能完成 7 個(第 8 個因外部依賴被阻塞)。 - -結果:總代碼量更少(800 行 vs 1200 行),但有效代碼更多。完成率 87.5% vs 37.5%。一口一口吃,反而吃得最多。 - -## 關鍵要點 - -- **WIP=1 是 agent harness 的默認安全設置**——做完一個再做下一個,不要試圖並行。一口吃不成胖子。 -- **完成證據必須是可執行的**——"代碼看起來沒問題"不算完成,"curl 返回 201"才算。 -- **範圍表面必須外部化為檔案**——不能只在對話裡說,必須在倉庫裡有機器可讀的記錄。 -- **overreach 和 under-finish 是共生問題**——解決一個就解決了另一個。 -- **"少做但做完"永遠優於"多做但做半"**——agent 代碼行數和功能完成率呈負相關。質量永遠比數量重要。 - -## 延伸閱讀 - -- [Effective harnesses for long-running agents - Anthropic](https://www.anthropic.com/engineering/effective-harnesses-for-long-running-agents) — Anthropic 工程博客,詳細論述了"小下一步"策略 -- [Harness Engineering - OpenAI](https://openai.com/index/harness-engineering/) — OpenAI 對 harness 工程的完整論述 -- [Kanban: Successful Evolutionary Change - David Anderson](https://www.goodreads.com/book/show/1070822.Kanban) — WIP 限制的經典來源 -- [Rapid Development - Steve McConnell](https://www.goodreads.com/book/show/125171.Rapid_Development) — 範圍蔓延作為項目失敗首要原因的實證數據 - -## 練習 - -1. **任務原子化練習**:選一個寬泛需求(如"實現用戶管理系統"),把它拆成至少 5 個原子工作單元。每個單元寫清楚:(a) 單一行為描述,(b) 可執行的驗證命令,(c) 依賴關係。檢查是否滿足 WIP=1 的約束。 - -2. **對比實驗**:在同一個項目上跑兩次——一次不給約束,一次強制 WIP=1。比較:驗證完成率、總代碼行數、有效代碼比例。 - -3. **完成證據審計**:回顧一個最近的 agent 運行結果,把每個代碼變更分類為"已完成行為"、"未完成行為"或"腳手架"。給每個未完成行為補充缺失的驗證命令。 diff --git a/courses/learn-harness-engineering/docs/zh-TW/lectures/lecture-08-why-feature-lists-are-harness-primitives/code/feature-list-validator.ts b/courses/learn-harness-engineering/docs/zh-TW/lectures/lecture-08-why-feature-lists-are-harness-primitives/code/feature-list-validator.ts deleted file mode 100644 index 52d6a2b9..00000000 --- a/courses/learn-harness-engineering/docs/zh-TW/lectures/lecture-08-why-feature-lists-are-harness-primitives/code/feature-list-validator.ts +++ /dev/null @@ -1,218 +0,0 @@ -/** - * feature-list-validator.ts - * - * Reads a feature_list.json, validates its schema, and checks for features - * marked "pass" without verification evidence. Outputs a structured report. - * Can run against any project directory that has a feature_list.json. - * - * Usage: - * npx tsx docs/lectures/lecture-08.../code/feature-list-validator.ts [path-to-dir] - * (defaults to the directory containing this script) - * - * Run: npx tsx docs/lectures/lecture-08-why-feature-lists-are-harness-primitives/code/feature-list-validator.ts - */ - -import * as fs from "node:fs"; -import * as path from "node:path"; - -// --------------------------------------------------------------------------- -// Types -// --------------------------------------------------------------------------- - -interface FeatureEntry { - id?: string; - category?: string; - description?: string; - verification?: string[]; - passes?: boolean; - // Allow unknown fields for flexibility - [key: string]: unknown; -} - -interface ValidationResult { - featureId: string; - schemaValid: boolean; - schemaErrors: string[]; - hasVerification: boolean; - markedPassWithoutEvidence: boolean; - passes: boolean; - verificationCount: number; -} - -// --------------------------------------------------------------------------- -// Schema validation -// --------------------------------------------------------------------------- - -function validateSchema(entry: FeatureEntry, index: number): string[] { - const errors: string[] = []; - const label = entry.id ?? `entry ${index + 1}`; - - if (!entry.id || typeof entry.id !== "string") { - errors.push(`[${label}] Missing or invalid 'id' field`); - } - if (!entry.category || typeof entry.category !== "string") { - errors.push(`[${label}] Missing or invalid 'category' field`); - } - if (!entry.description || typeof entry.description !== "string") { - errors.push(`[${label}] Missing or invalid 'description' field`); - } - if (entry.verification !== undefined && !Array.isArray(entry.verification)) { - errors.push(`[${label}] 'verification' must be an array if present`); - } - if (entry.passes !== undefined && typeof entry.passes !== "boolean") { - errors.push(`[${label}] 'passes' must be a boolean if present`); - } - - return errors; -} - -// --------------------------------------------------------------------------- -// Evidence validation -// --------------------------------------------------------------------------- - -function checkEvidence(entry: FeatureEntry): { - hasVerification: boolean; - markedPassWithoutEvidence: boolean; -} { - const hasVerification = Array.isArray(entry.verification) && entry.verification.length > 0; - const markedPassWithoutEvidence = entry.passes === true && !hasVerification; - - return { hasVerification, markedPassWithoutEvidence }; -} - -// --------------------------------------------------------------------------- -// Process feature list -// --------------------------------------------------------------------------- - -function processFeatureList(entries: FeatureEntry[]): ValidationResult[] { - return entries.map((entry, index) => { - const schemaErrors = validateSchema(entry, index); - const evidence = checkEvidence(entry); - - return { - featureId: (entry.id as string) ?? `entry-${index + 1}`, - schemaValid: schemaErrors.length === 0, - schemaErrors, - hasVerification: evidence.hasVerification, - markedPassWithoutEvidence: evidence.markedPassWithoutEvidence, - passes: entry.passes === true, - verificationCount: Array.isArray(entry.verification) ? entry.verification.length : 0, - }; - }); -} - -// --------------------------------------------------------------------------- -// Reporting -// --------------------------------------------------------------------------- - -function pad(s: string, len: number): string { - return s.length >= len ? s : s + " ".repeat(len - s.length); -} - -function run(): void { - // Resolve target directory - const scriptDir = path.dirname(new URL(import.meta.url).pathname); - const targetDir = process.argv[2] - ? path.resolve(process.argv[2]) - : scriptDir; - - const filePath = path.join(targetDir, "feature_list.json"); - - console.log("\n" + "=".repeat(90)); - console.log(" FEATURE LIST VALIDATOR"); - console.log("=".repeat(90)); - console.log(` Reading: ${filePath}\n`); - - if (!fs.existsSync(filePath)) { - console.error(` ERROR: feature_list.json not found at ${filePath}`); - console.error(" Usage: npx tsx feature-list-validator.ts [path-to-directory-containing-feature_list.json]\n"); - process.exit(1); - } - - let entries: FeatureEntry[]; - try { - const raw = fs.readFileSync(filePath, "utf-8"); - entries = JSON.parse(raw); - } catch (err) { - console.error(` ERROR: Could not parse feature_list.json: ${err}`); - process.exit(1); - } - - if (!Array.isArray(entries)) { - console.error(" ERROR: feature_list.json must contain a JSON array at the top level."); - process.exit(1); - } - - // For demo purposes, also validate an extended test set - const demoEntries: FeatureEntry[] = [ - ...entries, - { - id: "qna-002", - category: "import", - description: "User can import a PDF document.", - verification: ["Upload a PDF file", "Verify it appears in the document list"], - passes: true, - }, - { - id: "qna-003", - category: "grounded_qa", - description: "System hallucination rate is below 5%.", - verification: [], // Empty -- no evidence - passes: true, // Marked as pass WITHOUT evidence - }, - { - id: "missing-fields", - // Missing 'category' and 'description' - passes: true, - } as FeatureEntry, - ]; - - const results = processFeatureList(demoEntries); - - // Print report - const header = `| ${pad("Feature ID", 14)}| ${pad("Schema", 8)}| ${pad("Passes", 7)}| ${pad("Verifications", 14)}| ${pad("Evidence?", 12)}| Notes`; - const sep = `|${"-".repeat(16)}|${"-".repeat(10)}|${"-".repeat(9)}|${"-".repeat(16)}|${"-".repeat(14)}|${"-".repeat(30)}`; - console.log(header); - console.log(sep); - - for (const r of results) { - const schemaLabel = r.schemaValid ? "OK" : "INVALID"; - const passesLabel = r.passes ? "PASS" : "FAIL"; - const evidenceLabel = r.hasVerification ? "Present" : "MISSING"; - const notes = r.markedPassWithoutEvidence - ? "FLAGGED: passes without evidence!" - : r.schemaErrors.length > 0 - ? r.schemaErrors[0] - : ""; - - const marker = r.markedPassWithoutEvidence ? ">>" : r.schemaValid ? " " : "!!"; - console.log( - `${marker}| ${pad(r.featureId, 14)}| ${pad(schemaLabel, 8)}| ${pad(passesLabel, 7)}| ${pad(String(r.verificationCount), 14)}| ${pad(evidenceLabel, 12)}| ${notes}` - ); - } - - // Summary - const total = results.length; - const schemaOk = results.filter((r) => r.schemaValid).length; - const passing = results.filter((r) => r.passes).length; - const flagged = results.filter((r) => r.markedPassWithoutEvidence).length; - const withEvidence = results.filter((r) => r.hasVerification).length; - - console.log("\n" + "-".repeat(90)); - console.log(" SUMMARY"); - console.log("-".repeat(90)); - console.log(` Total features: ${total}`); - console.log(` Schema valid: ${schemaOk}/${total}`); - console.log(` Marked as passing: ${passing}/${total}`); - console.log(` With verification evidence: ${withEvidence}/${total}`); - console.log(` Flagged (pass without evidence): ${flagged}`); - - if (flagged > 0) { - console.log(`\n WARNING: ${flagged} feature(s) marked as "pass" without any verification evidence.`); - console.log(" These features need verification before they can be trusted.\n"); - } else { - console.log("\n All passing features have verification evidence. Feature list is healthy.\n"); - } -} - -run(); diff --git a/courses/learn-harness-engineering/docs/zh-TW/lectures/lecture-08-why-feature-lists-are-harness-primitives/code/feature_list.json b/courses/learn-harness-engineering/docs/zh-TW/lectures/lecture-08-why-feature-lists-are-harness-primitives/code/feature_list.json deleted file mode 100644 index 8b973e31..00000000 --- a/courses/learn-harness-engineering/docs/zh-TW/lectures/lecture-08-why-feature-lists-are-harness-primitives/code/feature_list.json +++ /dev/null @@ -1,15 +0,0 @@ -[ - { - "id": "qna-001", - "category": "grounded_qa", - "description": "User can ask a question about an imported document and receive an answer with visible citations.", - "verification": [ - "Import a markdown document", - "Open the Q&A panel", - "Ask a question about known content", - "Verify the answer is returned", - "Verify one or more citations are displayed" - ], - "passes": false - } -] diff --git a/courses/learn-harness-engineering/docs/zh-TW/lectures/lecture-08-why-feature-lists-are-harness-primitives/code/index.md b/courses/learn-harness-engineering/docs/zh-TW/lectures/lecture-08-why-feature-lists-are-harness-primitives/code/index.md deleted file mode 100644 index dde5693c..00000000 --- a/courses/learn-harness-engineering/docs/zh-TW/lectures/lecture-08-why-feature-lists-are-harness-primitives/code/index.md +++ /dev/null @@ -1,8 +0,0 @@ -# 第 08 講的程式碼 - -此資料夾用於以下範例: - -- 通過狀態閘門控制 -- 端到端驗證 -- 弱式與強式完成標準 -- 評估迴圈範例 diff --git a/courses/learn-harness-engineering/docs/zh-TW/lectures/lecture-08-why-feature-lists-are-harness-primitives/code/pass-gate-policy.md b/courses/learn-harness-engineering/docs/zh-TW/lectures/lecture-08-why-feature-lists-are-harness-primitives/code/pass-gate-policy.md deleted file mode 100644 index 9a7fa687..00000000 --- a/courses/learn-harness-engineering/docs/zh-TW/lectures/lecture-08-why-feature-lists-are-harness-primitives/code/pass-gate-policy.md +++ /dev/null @@ -1,8 +0,0 @@ -# 通過閘門策略 - -一個功能只有在以下條件滿足時,才能從 `passes: false` 變更為 `passes: true`: - -- 預期的工作流程已被執行 -- 成功的證據已被記錄 -- 已測試的路徑中不存在阻礙性錯誤 -- 實作未使應用程式處於損壞或不明確的狀態 diff --git a/courses/learn-harness-engineering/docs/zh-TW/lectures/lecture-08-why-feature-lists-are-harness-primitives/index.md b/courses/learn-harness-engineering/docs/zh-TW/lectures/lecture-08-why-feature-lists-are-harness-primitives/index.md deleted file mode 100644 index 6bd4ef73..00000000 --- a/courses/learn-harness-engineering/docs/zh-TW/lectures/lecture-08-why-feature-lists-are-harness-primitives/index.md +++ /dev/null @@ -1,142 +0,0 @@ -[English Version →](../../../en/lectures/lecture-08-why-feature-lists-are-harness-primitives/) - -> 本篇代碼示例:[code/](https://github.com/walkinglabs/learn-harness-engineering/blob/main/docs/zh-TW/lectures/lecture-08-why-feature-lists-are-harness-primitives/code/) -> 實戰練習:[Project 04. 用運行反饋修正 agent 的行為](./../../projects/project-04-incremental-indexing/index.md) - -# 第八講. 用功能清單約束 agent 該做什麼 - -你讓 agent 做一個電商網站,跑完之後它告訴你"做完了"。你打開代碼一看——用戶認證有了,但購物車的結算按鈕點了沒反應,支付流程根本沒接上。問題是:你從來沒告訴它"做完"的標準是什麼,所以它用自己的標準——"代碼寫了不少,看起來挺完整"。 - -功能清單(feature list)在很多人眼裡就是個備忘錄——寫下來怕忘了,寫完扔在一邊。但在 harness 的世界裡,功能清單不是給人看的備忘錄,而是整個 harness 的脊樑骨。調度器靠它選任務,驗證器靠它判完成,交接器靠它生成報告。脊樑骨斷了,全身都癱。 - -Anthropic 和 OpenAI 都強調:**工件必須外部化**。功能狀態必須是倉庫裡機器可讀的檔案,不能是對話裡的非結構化描述。 - -## Agent 不知道"做完"是什麼意思 - -Claude Code 和 Codex 都不會自動知道你心目中的"做完"是什麼意思。你說"加一個購物車功能",模型的理解可能是"寫一個 Cart 組件和 addToCart 方法"。而你的意思是"用戶能從瀏覽商品到下單支付完整走通"。 - -這個理解鴻溝在沒有功能清單的情況下會持續存在。agent 用自己的隱式標準判斷完成——通常是"代碼沒有明顯的語法錯誤"。而你需要的是端到端的行為驗證。就像你讓朋友幫你買菜,說"買點水果",他拎了一袋檸檬回來——他要的水果,你要的水果,不是一個水果。 - -看看這種常見的進度記錄: - -``` -做了使用者認證、购物车基本完成了、还需要做支付 -``` - -新的 agent 會話看到這個記錄,能回答以下問題嗎?"基本完成"意味著什麼?購物車通過了哪些測試?支付的阻塞條件是什麼?答案都是"不知道"。就像你看病時跟醫生說"我肚子疼,最近還行",醫生能開出什麼藥來? - -結果是:新會話花 20 分鐘推斷項目狀態,最終可能重複實現已完成的功能。Anthropic 的工程實踐數據表明,好的進度記錄可以減少 60-80% 的會話啟動診斷時間。 - -## 功能狀態機 - -```mermaid -flowchart LR - Feature["一行功能项"] --> Behavior["行為
    例如:POST /cart/items 返回 201"] - Feature --> Check["驗證命令
    具体要跑什么檢查"] - Feature --> State["狀態
    not_started / active / blocked / passing"] - - Behavior --> Complete["三列都齐了
    這行功能项才能用"] - Check --> Complete - State --> Complete -``` - -```mermaid -flowchart LR - List["feature_list.json / features.md"] --> Scheduler["選下一個 not_started"] - Scheduler --> Agent["agent 只做這一项"] - Agent --> Verifier["跑這一项自己的驗證命令"] - Verifier -->|通過| Passing["寫成 passing
    並补上驗證證據"] - Verifier -->|失敗| Active["继續保持 active"] - Verifier -->|依赖問題| Blocked["標成 blocked"] - Passing --> Handoff["更新交接說明
    和当前進度"] - Active --> Agent -``` - -## 核心概念 - -- **功能清單是 harness 原語**:它不是"可選的規劃工具",而是其他所有 harness 組件依賴的基礎數據結構。就像數據庫裡的表結構——你不能說"要不我們省掉主鍵吧",省掉了整個系統就散架了。 -- **三元組結構**:每個功能項是 `(行為描述, 驗證命令, 当前狀態)` 的三元組。缺了任何一項,這個功能項就不完整。行為描述告訴 agent 做什麼,驗證命令告訴它怎麼算做完,狀態告訴它現在到哪了。 -- **狀態機模型**:每個功能項有四種狀態——`not_started`、`active`、`blocked`、`passing`。狀態轉移由 harness 控制,不是 agent 想改就能改。 -- **通過狀態門控**:功能從 `active` 變成 `passing` 的唯一方式是驗證命令執行成功。這是不可逆的——`passing` 了就不能退回去。就像考試及格了就是及格了,不能事後改成分數。 -- **單一權威來源**:項目裡關於"該做什麼"的所有信息,必須從一個功能清單派生。不能出現功能清單和對話記錄矛盾的情況。 -- **反向壓力**:還沒通過的功能項數量就是 harness 對 agent 施加的壓力。壓力歸零 = 項目完成。 - -## 為什麼功能清單必須是"原語" - -文檔是給人看的,原語是給系統用的。文檔可以被忽略,原語不能被繞過。 - -類比數據庫的觸發器約束和應用層的檢查邏輯:前者由數據庫引擎強制執行,任何 SQL 都無法跳過;後者依賴於應用代碼的正確性,可能被意外繞過。功能清單作為 harness 原語,就是數據庫級別的約束——agent 不能繞過它。 - -具體來說,功能清單服務四個 harness 組件: - -1. **調度器**:讀狀態,選下一個 `not_started` 的功能。就像工廠的排產系統——看完訂單才知道下一步做什麼。 -2. **驗證器**:執行驗證命令,判斷是否允許狀態轉移。就像質檢——不是你說合格就合格,得通過檢驗。 -3. **交接報告器**:從功能清單自動生成會話交接摘要。就像換班時自動生成的交接表——不用手寫,系統自己出。 -4. **進度追蹤器**:統計各狀態分佈,提供項目健康度指標。就像儀表盤——一眼看出項目走到哪了。 - -## 怎麼做 - -### 1. 定義一個最小化的功能清單格式 - -不需要複雜的系統,一個結構化的 Markdown 或 JSON 檔案就夠了。關鍵是每個條目必須有三元組: - -```json -{ - "id": "F03", - "behavior": "POST /cart/items with {product_id, quantity} returns 201", - "verification": "curl -X POST http://localhost:3000/api/cart/items -H 'Content-Type: application/json' -d '{\"product_id\":1,\"quantity\":2}' | jq .status == 201", - "state": "passing", - "evidence": "commit abc123, test output log" -} -``` - -### 2. 讓 harness 控制狀態轉移 - -agent 不能直接把狀態改成 `passing`。它只能提交驗證請求,harness 執行驗證命令,根據結果決定是否允許狀態轉移。這就是"通過狀態門控"——不是你說考過了就考過了,得看成績單。 - -### 3. 在 CLAUDE.md 裡寫清楚規則 - -``` -## 功能清單规则 -- 功能清單檔案: /docs/features.md -- 每次只激活一個功能项 -- 功能项驗證命令必須通過才能標為 passing -- 不要修改功能清單的狀態——由驗證脚本自動更新 -``` - -### 4. 粒度校準 - -每個功能項應該是"一次會話能完成"的範圍。太粗了做不完,太細了管理開銷大。"用戶可以添加商品到購物車"是一個好粒度,"實現購物車"太粗了,"創建 Cart 模型的 name 字段"太細了。就像切牛排——不能整塊啃,也不能切成肉沫。 - -## 實際案例 - -一個電商平臺的開發任務,10 個功能項。對比兩種追蹤方式: - -**備忘錄模式**:agent 用非結構化筆記記錄進度。3 個會話後,筆記變成了"做了用戶認證和商品列表、購物車基本完成但還有 bug、支付沒開始"。新會話需要 20 分鐘推斷狀態,最終重複實現了已完成的功能。就像你的購物清單上寫著"牛奶、麵包、還有那個什麼來著"——到超市了你還是不知道要買什麼。 - -**脊樑骨模式**:每個功能項有明確的狀態和驗證命令。新會話讀取功能清單,3 分鐘內知道:F01-F05 是 `passing`,F06 是 `active`(正在做),F07-F10 是 `not_started`。直接從 F06 繼續,零重複。 - -定量結果:使用結構化功能清單的項目,功能完成率比自由形式高 45%,零重複實現。 - -## 關鍵要點 - -- **功能清單是 harness 的脊樑骨**,不是給人看的備忘錄。調度器、驗證器、交接器都依賴它。 -- **每個功能項必須有三元組**:行為描述 + 驗證命令 + 當前狀態。缺一項就不完整——就像三條腿的凳子少一條腿。 -- **狀態轉移由 harness 控制**,agent 不能自己改狀態。通過驗證 = 唯一的升級路徑。 -- **功能清單是項目的單一權威來源**——任何關於"該做什麼"的信息都從這裡派生。 -- **粒度控制在"一次會話能完成"的範圍**。太粗做不完,太細管不過來。 - -## 延伸閱讀 - -- [Building Effective Agents - Anthropic](https://www.anthropic.com/research/building-effective-agents) — 明確指出功能列表是控制 agent 執行範圍的"核心數據結構" -- [Harness Engineering - OpenAI](https://openai.com/index/harness-engineering/) — 強調"將工件外部化"的原則 -- [Design by Contract - Bertrand Meyer](https://www.goodreads.com/book/show/130439.Object_Oriented_Software_Construction) — 契約式設計原則,功能列表的理論基礎 -- [How Google Tests Software](https://www.goodreads.com/book/show/13563030-how-google-tests-software) — 測試金字塔和行為規格的工程實踐 - -## 練習 - -1. **功能清單設計**:定義一個最小化的功能清單 JSON schema。包含:id、行為描述、驗證命令、當前狀態、證據引用。用它描述一個包含 5 個功能的真實項目。 - -2. **驗證嚴格性對比**:選 3 個功能,分別設計"寬鬆"驗證(如"代碼無語法錯誤")和"嚴格"驗證(如"端到端測試通過")。對比兩種驗證下的假陽性率。 - -3. **單一來源原則審查**:審查一個已有的 agent 項目,檢查是否存在與功能清單矛盾的範圍信息(對話裡的隱式需求、代碼裡的 TODO 註釋等)。設計一個方案,把所有信息統一到功能清單中。 diff --git a/courses/learn-harness-engineering/docs/zh-TW/lectures/lecture-09-why-agents-declare-victory-too-early/code/clean-state-checklist.md b/courses/learn-harness-engineering/docs/zh-TW/lectures/lecture-09-why-agents-declare-victory-too-early/code/clean-state-checklist.md deleted file mode 100644 index 81f6d0ad..00000000 --- a/courses/learn-harness-engineering/docs/zh-TW/lectures/lecture-09-why-agents-declare-victory-too-early/code/clean-state-checklist.md +++ /dev/null @@ -1,6 +0,0 @@ -# 乾淨狀態檢查清單 - -- 應用程式無需手動修復即可啟動 -- 目前的進度已被記錄 -- 沒有任何未完成的匯入或索引步驟未被記錄 -- 下一個 Agent 可以立即執行標準的啟動和驗證路徑 diff --git a/courses/learn-harness-engineering/docs/zh-TW/lectures/lecture-09-why-agents-declare-victory-too-early/code/index.md b/courses/learn-harness-engineering/docs/zh-TW/lectures/lecture-09-why-agents-declare-victory-too-early/code/index.md deleted file mode 100644 index 4d0e3826..00000000 --- a/courses/learn-harness-engineering/docs/zh-TW/lectures/lecture-09-why-agents-declare-victory-too-early/code/index.md +++ /dev/null @@ -1,8 +0,0 @@ -# 第 09 講的程式碼 - -此資料夾用於以下範例: - -- 日誌作為反饋 -- 執行狀態可視性 -- 乾淨狀態檢查 -- 恢復範例 diff --git a/courses/learn-harness-engineering/docs/zh-TW/lectures/lecture-09-why-agents-declare-victory-too-early/code/victory-detector.ts b/courses/learn-harness-engineering/docs/zh-TW/lectures/lecture-09-why-agents-declare-victory-too-early/code/victory-detector.ts deleted file mode 100644 index fad1a3ba..00000000 --- a/courses/learn-harness-engineering/docs/zh-TW/lectures/lecture-09-why-agents-declare-victory-too-early/code/victory-detector.ts +++ /dev/null @@ -1,241 +0,0 @@ -/** - * victory-detector.ts - * - * Simulates an agent that claims task completion, then checks the claimed - * state against actual verification. Outputs: claimed vs actual, highlighting - * gaps between what the agent said and what's really true. - * - * Run: npx tsx docs/lectures/lecture-09-why-agents-declare-victory-too-early/code/victory-detector.ts - */ - -// --------------------------------------------------------------------------- -// Types -// --------------------------------------------------------------------------- - -interface Task { - name: string; - claimedComplete: boolean; - checks: VerificationCheck[]; -} - -interface VerificationCheck { - description: string; - claimed: boolean; // What the agent says - actual: boolean; // What is actually true - severity: "critical" | "warning"; -} - -// --------------------------------------------------------------------------- -// Simulated tasks with claimed vs actual states -// --------------------------------------------------------------------------- - -const tasks: Task[] = [ - { - name: "Add search endpoint", - claimedComplete: true, - checks: [ - { - description: "Route handler exists", - claimed: true, - actual: true, - severity: "critical", - }, - { - description: "Authentication middleware applied", - claimed: true, - actual: false, // Agent forgot auth - severity: "critical", - }, - { - description: "Input validation added", - claimed: true, - actual: true, - severity: "critical", - }, - { - description: "Unit tests written", - claimed: true, - actual: false, // Agent skipped tests - severity: "critical", - }, - { - description: "Integration tests pass", - claimed: true, - actual: false, // No tests to run - severity: "critical", - }, - { - description: "Documentation updated", - claimed: true, - actual: false, // Agent skipped docs - severity: "warning", - }, - ], - }, - { - name: "Fix pagination bug", - claimedComplete: true, - checks: [ - { - description: "Bug reproduction case confirmed", - claimed: true, - actual: true, - severity: "critical", - }, - { - description: "Root cause identified", - claimed: true, - actual: true, - severity: "critical", - }, - { - description: "Fix applied", - claimed: true, - actual: true, - severity: "critical", - }, - { - description: "Edge cases tested", - claimed: true, - actual: false, // Only happy path tested - severity: "warning", - }, - { - description: "Regression tests added", - claimed: true, - actual: false, - severity: "critical", - }, - ], - }, - { - name: "Refactor auth module", - claimedComplete: true, - checks: [ - { - description: "Old code removed", - claimed: true, - actual: false, // Old code still present - severity: "warning", - }, - { - description: "All existing tests still pass", - claimed: true, - actual: false, // Two tests broke - severity: "critical", - }, - { - description: "New auth flow works", - claimed: true, - actual: true, - severity: "critical", - }, - { - description: "Migration guide written", - claimed: true, - actual: false, - severity: "warning", - }, - ], - }, -]; - -// --------------------------------------------------------------------------- -// Verification -// --------------------------------------------------------------------------- - -interface TaskVerification { - taskName: string; - claimedComplete: boolean; - actuallyComplete: boolean; - totalChecks: number; - claimedPassing: number; - actualPassing: number; - gaps: { description: string; severity: string }[]; -} - -function verifyTask(task: Task): TaskVerification { - const claimedPassing = task.checks.filter((c) => c.claimed).length; - const actualPassing = task.checks.filter((c) => c.actual).length; - const gaps = task.checks.filter((c) => c.claimed && !c.actual); - - return { - taskName: task.name, - claimedComplete: task.claimedComplete, - actuallyComplete: gaps.length === 0, - totalChecks: task.checks.length, - claimedPassing, - actualPassing, - gaps: gaps.map((g) => ({ description: g.description, severity: g.severity })), - }; -} - -// --------------------------------------------------------------------------- -// Reporting -// --------------------------------------------------------------------------- - -function pad(s: string, len: number): string { - return s.length >= len ? s : s + " ".repeat(len - s.length); -} - -function run(): void { - console.log("\n" + "=".repeat(90)); - console.log(" VICTORY DETECTOR -- Claimed vs Actual Verification"); - console.log("=".repeat(90)); - - const verifications = tasks.map(verifyTask); - - for (const v of verifications) { - console.log("\n Task: " + v.taskName); - console.log(" " + "-".repeat(70)); - console.log(` Agent claimed: ${v.claimedComplete ? "COMPLETE" : "NOT COMPLETE"}`); - console.log(` Actually is: ${v.actuallyComplete ? "COMPLETE" : "INCOMPLETE"}`); - - if (!v.actuallyComplete) { - console.log(` MISMATCH: Agent declared victory but ${v.gaps.length} check(s) failed!`); - } - - // Detailed checks for this task - const task = tasks.find((t) => t.name === v.taskName)!; - console.log("\n Check detail:"); - const header = ` | ${pad("Check", 40)}| ${pad("Claimed", 9)}| ${pad("Actual", 9)}| ${pad("Gap?", 6)}|`; - const sep = ` |${"-".repeat(42)}|${"-".repeat(11)}|${"-".repeat(11)}|${"-".repeat(8)}|`; - console.log(header); - console.log(sep); - - for (const check of task.checks) { - const claimedLabel = check.claimed ? "PASS" : "FAIL"; - const actualLabel = check.actual ? "PASS" : "FAIL"; - const gapLabel = check.claimed && !check.actual ? "GAP" : ""; - const marker = gapLabel ? ">>" : " "; - console.log(`${marker}| ${pad(check.description, 40)}| ${pad(claimedLabel, 9)}| ${pad(actualLabel, 9)}| ${pad(gapLabel, 6)}|`); - } - } - - // Overall summary - console.log("\n" + "=".repeat(90)); - console.log(" OVERALL SUMMARY"); - console.log("=".repeat(90) + "\n"); - - const sHeader = `| ${pad("Task", 28)}| ${pad("Claimed", 10)}| ${pad("Actual", 10)}| ${pad("Gaps", 6)}| ${pad("Accuracy", 10)}|`; - const sSep = `|${"-".repeat(30)}|${"-".repeat(12)}|${"-".repeat(12)}|${"-".repeat(8)}|${"-".repeat(12)}|`; - console.log(sHeader); - console.log(sSep); - - for (const v of verifications) { - const accuracy = Math.round((v.actualPassing / v.totalChecks) * 100); - const cLabel = v.claimedComplete ? "Done" : "Pending"; - const aLabel = v.actuallyComplete ? "Done" : "Incomplete"; - console.log(`| ${pad(v.taskName, 28)}| ${pad(cLabel, 10)}| ${pad(aLabel, 10)}| ${pad(String(v.gaps.length), 6)}| ${pad(accuracy + "%", 10)}|`); - } - - const totalGaps = verifications.reduce((s, v) => s + v.gaps.length, 0); - const falseVictories = verifications.filter((v) => v.claimedComplete && !v.actuallyComplete).length; - - console.log("\n False victories: " + falseVictories + " of " + verifications.length + " tasks"); - console.log(" Total undetected gaps: " + totalGaps); - console.log("\n The agent declared 'done' on every task, but verification reveals " + totalGaps + " unmet criteria."); - console.log(" Without explicit verification, premature victory declarations go unchecked.\n"); -} - -run(); diff --git a/courses/learn-harness-engineering/docs/zh-TW/lectures/lecture-09-why-agents-declare-victory-too-early/index.md b/courses/learn-harness-engineering/docs/zh-TW/lectures/lecture-09-why-agents-declare-victory-too-early/index.md deleted file mode 100644 index 0c1551e8..00000000 --- a/courses/learn-harness-engineering/docs/zh-TW/lectures/lecture-09-why-agents-declare-victory-too-early/index.md +++ /dev/null @@ -1,145 +0,0 @@ -[English Version →](../../../en/lectures/lecture-09-why-agents-declare-victory-too-early/) - -> 本篇代碼示例:[code/](https://github.com/walkinglabs/learn-harness-engineering/blob/main/docs/zh-TW/lectures/lecture-09-why-agents-declare-victory-too-early/code/) -> 實戰練習:[Project 05. 讓 agent 自己檢查自己做的對不對](./../../projects/project-05-grounded-qa-verification/index.md) - -# 第九講. 防止 agent 提前宣告完成 - -你讓 agent 實現"密碼重置"功能。它改了數據庫 schema、寫了 API 端點、加了郵件範本,跑了單元測試(全部通過),然後自信地告訴你"做完了"。你實際一跑——密碼重置鏈接發不出去(郵件服務配置缺失)、數據庫遷移半途失敗(schema 不一致)、端到端流程根本沒走過一遍。 - -這種感覺你一定不陌生——考試時把卷子寫得滿滿當當,信心滿滿地第一個交卷,結果成績出來不及格。卷子寫滿了,不代表做對了。 - -這不是偶然事件。Guo 等人 2017 年在 ICML 上的經典論文證明:**現代神經網絡系統性地過度自信**——模型自報的置信度顯著高於實際準確率。AI 編碼 agent 也一樣:它"覺得"做完了,但實際上差得遠。你的 harness 必須用外部化的、基於執行的驗證來替代 agent 的"感覺"。 - -## 滑坡效應 - -過早完成聲明幾乎總是一樣的套路:代碼看著還行——語法正確、邏輯似乎合理,靜態檢查沒有明顯錯誤。但 harness 沒有強制要求全面執行驗證,agent 跳過了實際運行或只跑了部分測試。跑了單元測試但跳過集成測試,跑了測試但沒檢查覆蓋率。最後"代碼看起來沒問題"就被當作了"功能已完成"的證據。交卷了。 - -每一步都在丟失信息。從任務規範到代碼實現到運行時行為,每次轉換都可能引入偏差,而每次跳過的驗證都加劇了信息不對稱。 - -## 三層終止檢查 - -```mermaid -flowchart LR - Claim["agent 說:做完了"] --> L1["先跑
    lint / typecheck"] - L1 --> L2["再跑
    測試和啟動檢查"] - L2 --> L3["最後跑
    完整使用者流程"] - L3 --> Done["三层都過才算完成"] -``` - -```mermaid -flowchart LR - A["程式碼寫完了
    單測也绿了"] --> B["但應用没真正啟動
    完整流程也没跑"] - B --> C["配置、資料庫、外部服務問題
    还都藏着"] - C --> D["于是 agent 太早宣告完成"] -``` - -## 核心概念 - -- **過早完成聲明**:agent 斷言任務完成,但實際上存在未滿足的正確性規範。核心問題:agent 依據代碼層面的局部信心做判斷,系統級正確性需要全局驗證。 -- **置信度校準偏差**:agent 自報的完成信心與實際完成質量之間的系統性差距。對複雜多檔案任務,這個偏差顯著為正——agent 總是比實際做得更自信。就像學生考完試估分總是偏高。 -- **終止標準**:一組明確的、可執行的判定條件,定義在 harness 裡。agent 必須滿足所有條件才能聲明完成。"完成"從主觀判斷變成了客觀判定。 -- **驗證-確認雙閘門**:第一層驗證檢查"代碼是否正確實現了指定行為";第二層確認檢查"系統級行為是否滿足端到端需求"。兩層都通過才算完成。 -- **運行時反饋信號**:來自程序執行的日誌、進程狀態、健康檢查。這是 harness 判定完成質量的客觀基礎。 -- **完成優先級約束**:先驗證功能正確性,再處理性能,最後管風格。核心功能沒驗證通過之前,不許做重構。 - -## 單元測試通過 ≠ 任務完成 - -這是最常見的陷阱,也是最危險的一個。agent 寫了代碼,跑了單元測試,全部綠色,然後說"做完了"。但單元測試的設計哲學——隔離被測單元、模擬依賴——恰好使其無法檢測跨組件問題: - -**接口不匹配**:渲染進程傳給預加載腳本的檔案路徑是相對路徑,但預加載腳本期望絕對路徑。各自的單元測試都用了 mock,都通過了。只有端到端跑通時才發現問題。就像樂隊裡每個樂手各自練習都完美,但合在一起才發現定調不一樣。 - -**狀態傳播錯誤**:數據庫遷移改了表結構,但 ORM 的緩存層還持有舊結構的緩存條目。單元測試每次都是全新的 mock 環境,不會暴露這種跨層狀態不一致。 - -**環境依賴性**:代碼在測試環境(一切 mock)行為正確,在真實環境因配置差異、網絡延遲、服務不可用而失敗。就像在排練室唱得很好,上臺演出時音響設備出了問題。 - -### "順便重構"是完成判定的毒藥 - -Claude Code 有一個常見行為模式:在核心功能還沒驗證通過時就開始重構代碼、優化性能、改進風格。Knuth 說的"過早優化是萬惡之源"在 agent 場景中有了新含義——重構會改變已完成驗證和未完成驗證之間的邊界,可能破壞之前隱式正確的代碼路徑。就像你數學大題還沒做完,就跑去把前面選擇題的答案重新抄一遍格式——浪費時間不說,還可能抄錯了。 - -### 自我評價的系統性偏差 - -Anthropic 在 2026 年的研究中發現了一個更深層的失敗模式:**當 agent 被要求評估自己的工作時,它系統性地過度正面評價——即使人類觀察者認為質量明顯不達標。** 這就像讓學生自己給自己判卷——對自己的答案總是特別寬容。 - -這個問題在主觀任務(如設計審美)上尤其嚴重——"佈局是否精緻"是一個判斷題,agent 可靠地偏向正面。即使在有可驗證結果的任務上,agent 也會因為判斷失誤而影響表現。 - -解決方案不是讓 agent "更客觀"——同一個模型既生成又評估,內在地傾向對自己慷慨。**解決方案是把"幹活的人"和"檢查的人"分開。** 就像考試不能讓學生自己批改自己的卷子——得有個獨立的閱卷老師。 - -一個獨立的評估 agent,經過專門調校為"挑剔"之後,比讓生成 agent 自我評估有效得多。Anthropic 的實驗數據: - -| 架構 | 運行時長 | 成本 | 核心功能是否可用 | -|------|---------|------|---------------| -| 單 agent 裸跑 | 20 分鐘 | $9 | 否(遊戲實體無法響應輸入) | -| 三 agent(planner + generator + evaluator) | 6 小時 | $200 | 是(遊戲可以正常遊玩) | - -這是同一個模型(Opus 4.5),同一段提示詞("做一個 2D 復古遊戲編輯器")。區別只在 harness——從"裸奔"到"planner 擴展需求 → generator 逐功能實現 → evaluator 用 Playwright 實際點擊測試"。 - -> 來源:[Anthropic: Harness design for long-running application development](https://www.anthropic.com/engineering/harness-design-long-running-apps) - -## 怎麼防止提前交卷 - -### 1. 外部化終止判定 - -完成判定不應該由 agent 自己做。harness 獨立執行終止校驗,輸入是運行時信號,不是 agent 的置信度。在 CLAUDE.md 裡寫清楚: - -``` -## 完成定義 -- 功能完成 = 端到端驗證通過,不是"程式碼寫完了" -- 必須執行的驗證層級: - 1. 單元測試通過 - 2. 集成測試通過 - 3. 端到端流程驗證通過 -- 在第 1 层没通過時,不许进入第 2 层 -- 在第 2 层没通過時,不许进入第 3 层 -``` - -### 2. 構建三層終止校驗 - -- **第一層:語法與靜態分析**。成本最低,信息量最小,但必須通過。這是最低限度的檢查——字都沒寫錯才能往下看。 -- **第二層:運行時行為驗證**。測試執行、應用啟動檢查、關鍵路徑驗證。這是核心完成證據。不僅要寫了,還要能跑。 -- **第三層:系統級確認**。端到端測試、集成驗證、用戶場景模擬。防止過早聲明的最後一道防線。不僅要能跑,還要跑對。 - -### 3. 為 agent 設計好的"紅筆批註" - -OpenAI 在 Codex 實踐中提出了一個特別有效的模式:**給 agent 寫的錯誤消息要包含修復指導**。不要像閱卷老師只畫個大紅叉,要像好老師一樣在旁邊寫上"這裡應該怎麼改"。不要用 `"Test failed"`,而用 `"Test failed: POST /api/reset-password returned 500. Check that the email service config exists in environment variables. The template file should be at templates/reset-email.html."` 這種具體的、可操作的反饋讓 agent 能自我修正,而不需要人類介入。 - -### 4. 捕獲運行時信號 - -有效的運行時信號包括: -- 應用是否成功啟動並達到就緒狀態? -- 關鍵功能路徑在運行時是否執行成功? -- 數據庫寫入、檔案操作等副作用是否正確? -- 臨時資源是否被清理? - -## 實際案例 - -**任務**:實現用戶密碼重置功能。涉及數據庫操作、郵件發送和 API 端點修改。 - -**提前交卷路徑**:agent 修改數據庫 schema、編寫 API 端點、添加郵件範本、跑單元測試(通過)、聲明完成。卷子寫得滿滿當當。 - -**實際扣分項**:(1) 端到端流程未測試——重置鏈接的實際發送和驗證未確認。(2) 數據庫遷移在部分執行後失敗,導致 schema 不一致。(3) 郵件服務配置在目標環境中缺失。 - -**harness 介入**:終止校驗強制執行——(1) 啟動完整應用驗證重置端點可訪問;(2) 執行完整重置流程;(3) 驗證數據庫狀態一致性。所有缺陷在會話內被發現,節省了 5-10 倍的後續修復成本。獨立閱卷老師批出了真正的問題。 - -## 關鍵要點 - -- **agent 系統性地過度自信**——置信度校準偏差是客觀存在的。卷子寫滿了不代表做對了。 -- **完成判定必須外部化**——harness 獨立驗證,不信任 agent 的"感覺"。不能讓學生自己批自己的卷子。 -- **三層校驗缺一不可**——語法通過、行為通過、系統通過,層層遞進。 -- **錯誤消息要像好老師的紅筆批註**——包含具體修復步驟,讓 agent 能自我修正。 -- **核心功能驗證通過之前不許重構**——完成優先級約束是防止過早優化的關鍵。 - -## 延伸閱讀 - -- [On Calibration of Modern Neural Networks - Guo et al.](https://arxiv.org/abs/1706.04599) — 證明現代深度網絡系統性地過度自信 -- [Building Effective Agents - Anthropic](https://www.anthropic.com/research/building-effective-agents) — 運行時證據在完成判定中的關鍵作用 -- [Harness Engineering - OpenAI](https://openai.com/index/harness-engineering/) — 過早完成聲明是 agent 的主要失敗模式之一 -- [The Art of Software Testing - Myers](https://www.goodreads.com/book/show/137543.The_Art_of_Software_Testing) — 測試方法層次和有效性的經典參考 - -## 練習 - -1. **終止校驗函數設計**:為一個涉及數據庫遷移和 API 修改的任務設計完整的終止校驗。列出需要的運行時信號和每個信號的通過/失敗標準。在一個實際任務上運行,記錄它發現了哪些隱藏問題。 - -2. **校準偏差測量**:選 10 個不同類型的編碼任務,記錄 agent 的自報完成信心和實際完成質量。計算偏差值,分析它和任務複雜度的關係。 - -3. **多層防禦實驗**:對同一組任務跑三種配置——(a) 僅靜態分析,(b) 加單元測試,(c) 完整三層校驗。比較過早完成聲明的比例和未捕獲缺陷的數量。 diff --git a/courses/learn-harness-engineering/docs/zh-TW/lectures/lecture-10-why-end-to-end-testing-changes-results/code/architecture-rules.md b/courses/learn-harness-engineering/docs/zh-TW/lectures/lecture-10-why-end-to-end-testing-changes-results/code/architecture-rules.md deleted file mode 100644 index d027de16..00000000 --- a/courses/learn-harness-engineering/docs/zh-TW/lectures/lecture-10-why-end-to-end-testing-changes-results/code/architecture-rules.md +++ /dev/null @@ -1,6 +0,0 @@ -# Electron 架構規則 - -- Renderer 程式碼不得直接存取檔案系統。 -- Preload 是 renderer 與 Electron main 之間唯一的橋樑。 -- 檢索和索引邏輯應存在於服務模組中,而非 UI 元件中。 -- 日誌應為結構化格式,並從服務邊界發出。 diff --git a/courses/learn-harness-engineering/docs/zh-TW/lectures/lecture-10-why-end-to-end-testing-changes-results/code/e2e-runner.ts b/courses/learn-harness-engineering/docs/zh-TW/lectures/lecture-10-why-end-to-end-testing-changes-results/code/e2e-runner.ts deleted file mode 100644 index 24284165..00000000 --- a/courses/learn-harness-engineering/docs/zh-TW/lectures/lecture-10-why-end-to-end-testing-changes-results/code/e2e-runner.ts +++ /dev/null @@ -1,253 +0,0 @@ -/** - * e2e-runner.ts - * - * A minimal E2E test harness. Defines test cases as user action sequences - * (import doc -> index -> ask question -> verify citation). Simulates - * running them and shows the difference between "unit tests pass" and - * "full pipeline works". - * - * Run: npx tsx docs/lectures/lecture-10-why-end-to-end-testing-changes-results/code/e2e-runner.ts - */ - -// --------------------------------------------------------------------------- -// Types -// --------------------------------------------------------------------------- - -interface PipelineStep { - name: string; - unitTestPasses: boolean; - // Simulated actual behavior in the pipeline - actualBehavior: "works" | "fails" | "partial"; - failureReason?: string; -} - -interface TestCase { - name: string; - steps: PipelineStep[]; -} - -interface TestResult { - testCase: string; - unitTestsPassed: number; - unitTestsTotal: number; - unitTestResult: "PASS" | "FAIL"; - e2eResult: "PASS" | "FAIL"; - e2eFailureStep?: string; - e2eFailureReason?: string; -} - -// --------------------------------------------------------------------------- -// Test cases -- realistic scenarios where unit tests pass but E2E fails -// --------------------------------------------------------------------------- - -const testCases: TestCase[] = [ - { - name: "Import document and ask question", - steps: [ - { - name: "Parse uploaded document", - unitTestPasses: true, - actualBehavior: "works", - }, - { - name: "Store document chunks", - unitTestPasses: true, - actualBehavior: "works", - }, - { - name: "Index chunks for retrieval", - unitTestPasses: true, - actualBehavior: "partial", // Indexes but with wrong embedding dimensions - failureReason: "Embedding dimension mismatch between indexer and retriever", - }, - { - name: "Retrieve relevant chunks", - unitTestPasses: true, // Unit test uses mock data with correct dimensions - actualBehavior: "fails", - failureReason: "Empty results due to dimension mismatch from previous step", - }, - { - name: "Generate answer with citations", - unitTestPasses: true, // Unit test provides pre-retrieved chunks - actualBehavior: "fails", - failureReason: "No chunks retrieved, so answer has no citations", - }, - ], - }, - { - name: "Delete document and verify removal", - steps: [ - { - name: "Find document by ID", - unitTestPasses: true, - actualBehavior: "works", - }, - { - name: "Delete document record", - unitTestPasses: true, - actualBehavior: "works", - }, - { - name: "Remove indexed chunks", - unitTestPasses: true, - actualBehavior: "fails", // Orphaned chunks remain in the index - failureReason: "Index cleanup query timed out, chunks remain orphaned", - }, - { - name: "Verify document not in search results", - unitTestPasses: true, // Unit test mocks the search - actualBehavior: "fails", - failureReason: "Orphaned chunks from previous step still appear in results", - }, - ], - }, - { - name: "Multi-user concurrent access", - steps: [ - { - name: "User A imports document", - unitTestPasses: true, - actualBehavior: "works", - }, - { - name: "User B imports document", - unitTestPasses: true, - actualBehavior: "works", - }, - { - name: "User A queries their document", - unitTestPasses: true, - actualBehavior: "partial", // Cross-contamination of results - failureReason: "No user-scoping on retrieval, returns chunks from User B's doc", - }, - { - name: "Verify only User A's results returned", - unitTestPasses: true, - actualBehavior: "fails", - failureReason: "Results include documents from other users", - }, - ], - }, -]; - -// --------------------------------------------------------------------------- -// Run tests -// --------------------------------------------------------------------------- - -function runUnitTests(tc: TestCase): { passed: number; total: number } { - const passed = tc.steps.filter((s) => s.unitTestPasses).length; - return { passed, total: tc.steps.length }; -} - -function runE2ETest(tc: TestCase): { pass: boolean; failureStep?: string; failureReason?: string } { - // Pipeline: if any step actually fails, the whole E2E fails - for (const step of tc.steps) { - if (step.actualBehavior === "fails") { - return { - pass: false, - failureStep: step.name, - failureReason: step.failureReason ?? "Unknown failure", - }; - } - // "partial" means the step technically completes but creates problems downstream - // We let it continue but track it - } - // Check if any step was "partial" (which may cause downstream issues) - const partialSteps = tc.steps.filter((s) => s.actualBehavior === "partial"); - if (partialSteps.length > 0) { - // The partial steps may or may not cause overall failure - // In our simulation, partial steps always lead to failure downstream - // unless there's an explicit "fails" step that already caught it - // This case means all steps were either "works" or "partial" - return { - pass: false, - failureStep: partialSteps[partialSteps.length - 1].name, - failureReason: partialSteps[partialSteps.length - 1].failureReason ?? "Partial completion", - }; - } - - return { pass: true }; -} - -// --------------------------------------------------------------------------- -// Reporting -// --------------------------------------------------------------------------- - -function pad(s: string, len: number): string { - return s.length >= len ? s : s + " ".repeat(len - s.length); -} - -function run(): void { - console.log("\n" + "=".repeat(95)); - console.log(" E2E TEST RUNNER -- Unit Tests vs Full Pipeline"); - console.log("=".repeat(95)); - - const results: TestResult[] = testCases.map((tc) => { - const unit = runUnitTests(tc); - const e2e = runE2ETest(tc); - - return { - testCase: tc.name, - unitTestsPassed: unit.passed, - unitTestsTotal: unit.total, - unitTestResult: unit.passed === unit.total ? "PASS" : "FAIL", - e2eResult: e2e.pass ? "PASS" : "FAIL", - e2eFailureStep: e2e.failureStep, - e2eFailureReason: e2e.failureReason, - }; - }); - - // Per-test-case detail - for (let i = 0; i < testCases.length; i++) { - const tc = testCases[i]; - const r = results[i]; - - console.log("\n Test Case: " + tc.name); - console.log(" " + "-".repeat(70)); - - const stepHeader = ` | ${pad("Step", 40)}| ${pad("Unit Test", 11)}| ${pad("E2E Actual", 12)}|`; - const stepSep = ` |${"-".repeat(42)}|${"-".repeat(13)}|${"-".repeat(14)}|`; - console.log(stepHeader); - console.log(stepSep); - - for (const step of tc.steps) { - const utLabel = step.unitTestPasses ? "PASS" : "FAIL"; - let e2eLabel: string; - if (step.actualBehavior === "works") e2eLabel = "PASS"; - else if (step.actualBehavior === "partial") e2eLabel = "PARTIAL*"; - else e2eLabel = "FAIL"; - - const marker = step.actualBehavior !== "works" ? ">>" : " "; - console.log(`${marker}| ${pad(step.name, 40)}| ${pad(utLabel, 11)}| ${pad(e2eLabel, 12)}|`); - } - - if (r.e2eFailureStep) { - console.log(`\n E2E Failure at: ${r.e2eFailureStep}`); - console.log(` Reason: ${r.e2eFailureReason}`); - } - } - - // Summary comparison - console.log("\n" + "=".repeat(95)); - console.log(" COMPARISON: Unit Tests vs E2E Tests"); - console.log("=".repeat(95) + "\n"); - - const header = `| ${pad("Test Case", 35)}| ${pad("Unit Tests", 15)}| ${pad("E2E Result", 12)}| Discrepancy`; - const sep = `|${"-".repeat(37)}|${"-".repeat(17)}|${"-".repeat(14)}|${"-".repeat(30)}`; - console.log(header); - console.log(sep); - - for (const r of results) { - const utLabel = `${r.unitTestsPassed}/${r.unitTestsTotal} ${r.unitTestResult}`; - const discrepancy = r.unitTestResult === "PASS" && r.e2eResult === "FAIL"; - const discLabel = discrepancy ? "UNIT PASS BUT E2E FAIL" : "Consistent"; - - console.log(`| ${pad(r.testCase, 35)}| ${pad(utLabel, 15)}| ${pad(r.e2eResult, 12)}| ${discLabel}`); - } - - const falseConfidence = results.filter((r) => r.unitTestResult === "PASS" && r.e2eResult === "FAIL").length; - console.log("\n False confidence count: " + falseConfidence + " of " + results.length); - console.log(" Unit tests pass but E2E reveals integration failures that units cannot catch.\n"); -} - -run(); diff --git a/courses/learn-harness-engineering/docs/zh-TW/lectures/lecture-10-why-end-to-end-testing-changes-results/code/index.md b/courses/learn-harness-engineering/docs/zh-TW/lectures/lecture-10-why-end-to-end-testing-changes-results/code/index.md deleted file mode 100644 index 2b3ec31b..00000000 --- a/courses/learn-harness-engineering/docs/zh-TW/lectures/lecture-10-why-end-to-end-testing-changes-results/code/index.md +++ /dev/null @@ -1,8 +0,0 @@ -# 第 10 講的程式碼 - -此資料夾用於以下範例: - -- 架構約束 -- 結構化測試 -- 品味不變量 -- 以修復為導向的 lint 訊息 diff --git a/courses/learn-harness-engineering/docs/zh-TW/lectures/lecture-10-why-end-to-end-testing-changes-results/code/review-feedback-to-rule.md b/courses/learn-harness-engineering/docs/zh-TW/lectures/lecture-10-why-end-to-end-testing-changes-results/code/review-feedback-to-rule.md deleted file mode 100644 index 300cba08..00000000 --- a/courses/learn-harness-engineering/docs/zh-TW/lectures/lecture-10-why-end-to-end-testing-changes-results/code/review-feedback-to-rule.md +++ /dev/null @@ -1,10 +0,0 @@ -# 範例:將審查反饋轉化為規則 - -重複的審查意見: - -> 不要從 renderer 呼叫檔案系統工具。使用 preload 橋樑。 - -提升為 harness 規則: - -- 新增 lint 或匯入規則以防止在 renderer 程式碼中使用 `fs` -- 新增修復說明文字,解釋 preload 邊界 diff --git a/courses/learn-harness-engineering/docs/zh-TW/lectures/lecture-10-why-end-to-end-testing-changes-results/index.md b/courses/learn-harness-engineering/docs/zh-TW/lectures/lecture-10-why-end-to-end-testing-changes-results/index.md deleted file mode 100644 index db5ec73f..00000000 --- a/courses/learn-harness-engineering/docs/zh-TW/lectures/lecture-10-why-end-to-end-testing-changes-results/index.md +++ /dev/null @@ -1,158 +0,0 @@ -[English Version →](../../../en/lectures/lecture-10-why-end-to-end-testing-changes-results/) - -> 本篇代碼示例:[code/](https://github.com/walkinglabs/learn-harness-engineering/blob/main/docs/zh-TW/lectures/lecture-10-why-end-to-end-testing-changes-results/code/) -> 實戰練習:[Project 05. 讓 agent 自己檢查自己做的對不對](./../../projects/project-05-grounded-qa-verification/index.md) - -# 第十講. 跑通完整流程才算真正驗證 - -你讓 agent 給 Electron 應用加一個檔案導出功能。它寫了渲染進程組件、預加載腳本、服務層邏輯,每個組件的單元測試都通過了。agent 說"做完了"。你實際一點擊導出按鈕——檔案路徑格式不對、進度條沒反應、大檔案導出時內存洩漏。5 個組件邊界缺陷,單元測試一個都沒發現。 - -這就像一個合唱團排練——每個聲部單獨唱的時候都完美,但合在一起的時候,女高音比男低音快了半拍,伴奏的調子和主旋律差了半個音。每個部分都"對"了,但整體跑調了。 - -Google 的測試金字塔告訴我們:大量單元測試是基礎,但如果你止步於此,就會系統性地漏掉組件交互問題。對於 AI 編碼 agent 來說,這個問題更嚴重——agent 傾向於只跑最快的測試然後宣告完成。**只有端到端測試能證明系統級缺陷不存在**。 - -## 單元測試的盲區 - -單元測試的設計哲學是隔離——模擬依賴,專注被測單元。這個哲學使單元測試快速且精確,但也製造了系統性的盲區。就像合唱排練時每個聲部戴著耳機對著伴奏唱——聽著都挺好,但真正合在一起才發現問題: - -**接口不匹配**:渲染進程傳給預加載腳本的檔案路徑是相對路徑,但預加載腳本期望絕對路徑。各自的單元測試都用了 mock,都通過了。只有端到端跑通時才發現問題——就像兩個聲部各自練的時候都覺得節奏沒問題,一合才發現一個用 4/4 拍一個用 3/4 拍。 - -**狀態傳播錯誤**:數據庫遷移改了表結構,但 ORM 的緩存層還持有舊結構的緩存條目。單元測試每次都是全新的 mock 環境,不會暴露這種跨層狀態不一致。就像換了一首歌的歌詞,但有人還在唱舊版本。 - -**資源生命週期問題**:檔案句柄、數據庫連接、網絡套接字的獲取和釋放跨越多個組件。單元測試為每個測試創建和銷燬獨立資源,不會暴露資源競爭或洩漏。就像排練時每個聲部輪流用麥克風,但演出時所有聲部同時上臺——話筒不夠用了。 - -**環境依賴性**:代碼在測試環境(一切 mock)行為正確,在真實環境因配置差異、網絡延遲、服務不可用而失敗。就像排練廳裡唱得好好的,到了戶外音樂節風一吹話筒一嘯叫就全亂了。 - -## 端到端測試不僅改變結果,還改變行為 - -這是很多人沒意識到的一點:當 agent 知道它的工作要過端到端測試時,它的編碼行為會改變。 - -1. **考慮組件交互**:寫代碼時會想"這個接口和上游怎麼對接",而不是隻關注單個函數。就像知道最終要合在一起唱,練習的時候就會注意聽其他聲部。 -2. **尊重架構邊界**:有架構約束的系統裡,端到端測試迫使 agent 遵守邊界規則。就像樂譜上標註了"此處漸強",你得跟著來。 -3. **處理錯誤路徑**:端到端測試通常包含故障場景,迫使 agent 考慮異常處理。就像排練時模擬了"話筒突然沒聲了"的情況,你知道該怎麼做。 - -## 測試金字塔與審查反饋提升 - -```mermaid -flowchart TB - subgraph Unit["單元測試只看孤立部件"] - U1["渲染层測試"] - U2["Preload 測試"] - U3["服務层測試"] - end - - subgraph E2E["端到端執行會穿過真實系統"] - R["点击渲染层按钮"] --> P["Preload 桥"] - P --> S["服務层"] - S --> F["檔案系統 / 操作系統"] - F --> Result["真實导出檔案"] - end -``` - -```mermaid -flowchart LR - Review["審查意见:
    renderer 不能直接 import fs"] --> Rule["加一条 direct fs import 檢查"] - Rule --> Message["报錯裡直接告诉 agent
    把檔案访问移到 preload"] - Message --> Harness["把這条檢查加入 harness"] - Harness --> Stronger["以後再犯會第一時间报錯"] -``` - -OpenAI 在 Codex 工程實踐中強調:**為 agent 寫的錯誤消息必須包含修復指導**。不寫 `"Direct filesystem access in renderer"`,而寫 `"Direct filesystem access in renderer. All file operations must go through the preload bridge. Move this call to preload/file-ops.ts and invoke it via window.api."` 這把架構規則變成了自動修正的閉環。就像合唱排練時指揮不只說"你唱錯了",而是說"這裡你快了半拍,聽一下女低音的節奏,在第 32 小節進入"。 - -## 核心概念 - -- **組件邊界缺陷**:組件 A 和 B 各自單元測試通過,但它們的交互產生了不正確的行為。這是端到端測試最擅長捕獲的問題類型——合唱裡各聲部單獨都對但合起來跑調的那種。 -- **測試充分性梯度**:單元測試能檢測的缺陷 <= 集成測試能檢測的缺陷 <= 端到端測試能檢測的缺陷。每往上一層,檢測能力增強。 -- **架構邊界執行規則**:把架構文檔裡的規則(如"渲染進程不能直接訪問檔案系統")變成可執行的自動化檢查。從"寫在紙上"變成"跑在 CI 裡"。 -- **審查反饋提升**:把重複出現的代碼審查意見轉化為自動化測試。每次發現重複問題就加一條規則,harness 會自動變強。就像合唱排練時指揮把常見的錯誤編成練習曲——下次再犯同樣的錯誤,不用指揮說,練習曲自己就暴露了。 -- **面向 agent 的錯誤消息**:失敗信息不只是說"出了什麼問題",還要告訴 agent 具體怎麼修。這把測試失敗變成自我修正的反饋循環。 - -## 怎麼做 - -### 0. 先定好架構邊界,再寫端到端測試 - -端到端測試的前提是系統有清晰的邊界。如果架構是一團麵條,端到端測試只會證明"這團麵條整體能跑",不會告訴你哪裡違反了設計意圖。就像合唱團如果連分聲部都沒分好,排練再多也是亂唱。 - -OpenAI 的經驗:**對 agent 生成的代碼庫,架構約束必須是第一天就建立的早期前置條件,不是等團隊規模大了再考慮的事。** 原因很直接——agent 會複製倉庫中已有的模式,即使那些模式是不均勻的或次優的。沒有架構約束,agent 會在每次會話中引入更多偏差。 - -OpenAI 採用了"分層領域架構"——每個業務領域被分成固定的層:Types → Config → Repo → Service → Runtime → UI。依賴方向嚴格向前,跨領域關注點通過顯式的 Providers 接口進入。任何其他依賴都是禁止的,並且通過自定義 lint 機械執行。 - -關鍵原則:**執行不變量,不微管實現。** 比如要求"數據在邊界解析",但不規定用哪個庫。錯誤消息要包含修復指導——不只是說"違規了",而是告訴 agent 具體怎麼改。 - -> 來源:[OpenAI: Harness engineering: leveraging Codex in an agent-first world](https://openai.com/index/harness-engineering/) - -### 1. harness 必須包含端到端層 - -在你的驗證流程裡明確:對於涉及跨組件修改的任務,端到端測試通過是完成的前置條件: - -``` -## 驗證層級 -- 層級 1: 單元測試 (必須通過) -- 層級 2: 集成測試 (必須通過) -- 層級 3: 端到端測試 (涉及跨组件修改時必須通過) -- 跳過任何必須層級的任務 = 未完成 -``` - -### 2. 把架構規則變成可執行檢查 - -每條架構約束都應該有對應的測試或 lint 規則: - -```bash -# 檢查渲染进程是否直接調用 Node.js API -grep -r "require('fs')" src/renderer/ && exit 1 || echo "OK: no direct fs access in renderer" -``` - -### 3. 設計面向 agent 的錯誤消息 - -失敗信息要包含三要素:什麼出了問題、為什麼、怎麼修: - -``` -ERROR: Found direct import of 'fs' in src/renderer/App.tsx:12 -WHY: Renderer process has no access to Node.js APIs for security -FIX: Move file operations to src/preload/file-ops.ts and call via window.api.readFile() -``` - -### 4. 建立審查反饋提升流程 - -每次在代碼審查中發現新類型的 agent 錯誤,就把它變成自動化檢查。一個月後你的 harness 會比月初強得多。就像合唱團的排練筆記——每次排練發現的問題都記下來,下次排練前先檢查這些點。久而久之,常見錯誤越來越少,音樂越來越和諧。 - -## 實際案例 - -**任務**:在 Electron 應用中實現檔案導出功能。涉及渲染進程 UI、預加載腳本檔案系統代理、服務層數據轉換。 - -**各聲部單獨唱(單元測試通過)**:渲染組件測試(通過,mock 檔案操作)、預加載腳本測試(通過,mock 檔案系統)、服務層測試(通過,mock 數據源)。agent 聲明完成。 - -**合唱合在一起(端到端測試揭示的缺陷)**: - -| 缺陷 | 描述 | 單元測試 | 端到端 | -|------|------|---------|--------| -| 接口不匹配 | 檔案路徑格式不一致 | 未檢測 | 檢測 | -| 狀態傳播 | 導出進度未通過 IPC 傳回 UI | 未檢測 | 檢測 | -| 資源洩漏 | 大檔案導出句柄未釋放 | 未檢測 | 檢測 | -| 權限問題 | 打包環境權限不同 | 未檢測 | 檢測 | -| 錯誤傳播 | 服務層異常未到 UI 層 | 未檢測 | 檢測 | - -5 個缺陷全部被端到端測試捕獲,單元測試一個都沒發現。代價是測試時間從 2 秒增加到 15 秒——在 agent 工作流裡完全可以接受。每個聲部單獨唱得再好,也比不上一次完整的合唱排練。 - -## 關鍵要點 - -- **單元測試對組件邊界缺陷系統性盲視**——它們的隔離設計恰好使其無法檢測交互問題。每個人唱得都對,不代表合唱不跑調。 -- **端到端測試不僅檢測缺陷,還改變 agent 的編碼行為**——讓它更關注集成和邊界。 -- **架構規則必須可執行**——不是寫在文檔裡等人來看,而是每次提交自動檢查。 -- **錯誤消息要面向 agent 設計**——包含"怎麼修"的具體步驟,形成自我修正閉環。 -- **審查反饋提升讓 harness 自動變強**——每個被捕獲的缺陷類別都變成永久防線。 - -## 延伸閱讀 - -- [How Google Tests Software - Whittaker et al.](https://www.goodreads.com/book/show/13563030-how-google-tests-software) — 測試金字塔模型的經典來源 -- [Harness Engineering - OpenAI](https://openai.com/index/harness-engineering/) — 架構約束自動化執行的工程實踐 -- [Chaos Engineering - Netflix (Basiri et al.)](https://ieeexplore.ieee.org/document/7466237) — 主動注入故障驗證系統彈性 -- [QuickCheck - Claessen & Hughes](https://www.cs.tufts.edu/~nr/cs257/archive/john-hughes/quick.pdf) — 屬性測試方法,介於示例測試和形式化驗證之間 - -## 練習 - -1. **跨組件缺陷檢測**:選一個涉及至少三個組件的修改任務。先只跑單元測試記錄結果,再跑端到端測試。分析每個額外發現的缺陷屬於哪種跨層交互問題。 - -2. **架構規則自動化**:選項目裡的一條架構約束,把它變成可執行檢查(含面向 agent 的錯誤消息)。集成到 harness 裡,用基準任務驗證效果。 - -3. **審查反饋提升**:從代碼審查歷史中找一個重複出現的意見類型,按五步流程轉化為自動化檢查。比較提升前後該類問題的出現頻率。 diff --git a/courses/learn-harness-engineering/docs/zh-TW/lectures/lecture-11-why-observability-belongs-inside-the-harness/code/evaluator-rubric.md b/courses/learn-harness-engineering/docs/zh-TW/lectures/lecture-11-why-observability-belongs-inside-the-harness/code/evaluator-rubric.md deleted file mode 100644 index 36e465c3..00000000 --- a/courses/learn-harness-engineering/docs/zh-TW/lectures/lecture-11-why-observability-belongs-inside-the-harness/code/evaluator-rubric.md +++ /dev/null @@ -1,8 +0,0 @@ -# 評估評分標準範例 - -對每個維度使用 1-5 分評分: - -- 基礎性:答案是否清楚地與匯入的來源相關聯? -- 引用品質:來源參考是否可見且具體? -- 功能性:使用者能否完成問答流程? -- 產品一致性:工作流程是否感覺整合良好? diff --git a/courses/learn-harness-engineering/docs/zh-TW/lectures/lecture-11-why-observability-belongs-inside-the-harness/code/index.md b/courses/learn-harness-engineering/docs/zh-TW/lectures/lecture-11-why-observability-belongs-inside-the-harness/code/index.md deleted file mode 100644 index 318336e4..00000000 --- a/courses/learn-harness-engineering/docs/zh-TW/lectures/lecture-11-why-observability-belongs-inside-the-harness/code/index.md +++ /dev/null @@ -1,8 +0,0 @@ -# 第 11 講的程式碼 - -此資料夾用於以下範例: - -- 規劃輸出 -- 評估評分標準 -- 產生器/評估器迴圈 -- 單一 Agent 與多角色比較 diff --git a/courses/learn-harness-engineering/docs/zh-TW/lectures/lecture-11-why-observability-belongs-inside-the-harness/code/runtime-logger.ts b/courses/learn-harness-engineering/docs/zh-TW/lectures/lecture-11-why-observability-belongs-inside-the-harness/code/runtime-logger.ts deleted file mode 100644 index 213dc198..00000000 --- a/courses/learn-harness-engineering/docs/zh-TW/lectures/lecture-11-why-observability-belongs-inside-the-harness/code/runtime-logger.ts +++ /dev/null @@ -1,260 +0,0 @@ -/** - * runtime-logger.ts - * - * A structured logging module demo. Shows ad-hoc console.log output vs - * structured JSON log output when diagnosing a failure. Includes a seeded - * failure scenario and demonstrates how structured logs pinpoint the issue - * faster. - * - * Run: npx tsx docs/lectures/lecture-11-why-observability-belongs-inside-the-harness/code/runtime-logger.ts - */ - -// --------------------------------------------------------------------------- -// Types -// --------------------------------------------------------------------------- - -interface StructuredLogEntry { - timestamp: string; - level: "info" | "warn" | "error" | "debug"; - component: string; - action: string; - durationMs?: number; - input?: unknown; - output?: unknown; - error?: string; - correlationId: string; -} - -// --------------------------------------------------------------------------- -// Simulated pipeline with a seeded failure -// --------------------------------------------------------------------------- - -const CORRELATION_ID = "req-" + Math.random().toString(36).slice(2, 8); - -// Simulated stages of a document Q&A pipeline -interface PipelineStage { - component: string; - action: string; - durationMs: number; - success: boolean; - errorMessage?: string; - input?: unknown; - output?: unknown; -} - -function runPipeline(): PipelineStage[] { - return [ - { - component: "DocumentLoader", - action: "parse_upload", - durationMs: 45, - success: true, - input: { filename: "report.pdf", size: "2.3MB" }, - output: { chunks: 47 }, - }, - { - component: "ChunkIndexer", - action: "embed_and_store", - durationMs: 230, - success: true, - input: { chunks: 47 }, - output: { indexed: 47, embeddingDim: 1536 }, - }, - { - component: "QueryRouter", - action: "route_query", - durationMs: 12, - success: true, - input: { query: "What is the revenue target?" }, - output: { routedTo: "RetrievalEngine" }, - }, - { - // SEEDED FAILURE: Retrieval returns 0 results due to dimension mismatch - component: "RetrievalEngine", - action: "semantic_search", - durationMs: 180, - success: false, - errorMessage: "Vector dimension mismatch: query embedding dim=768, index embedding dim=1536", - input: { query: "What is the revenue target?", topK: 5 }, - output: { results: 0 }, - }, - { - component: "AnswerGenerator", - action: "generate_with_citations", - durationMs: 1500, - success: true, // Doesn't crash, but produces a bad answer - input: { context: [], query: "What is the revenue target?" }, - output: { answer: "I could not find relevant information.", citations: 0 }, - }, - ]; -} - -// --------------------------------------------------------------------------- -// Ad-hoc logging (console.log style) -// --------------------------------------------------------------------------- - -function printAdHocLog(stages: PipelineStage[]): void { - console.log("Starting document Q&A pipeline..."); - console.log("User uploaded report.pdf"); - - for (const stage of stages) { - if (stage.success) { - console.log(`${stage.component}: ${stage.action} done (${stage.durationMs}ms)`); - } else { - console.log(`${stage.component}: something went wrong`); - } - } - - console.log("Pipeline finished. Answer: I could not find relevant information."); -} - -// --------------------------------------------------------------------------- -// Structured logging (JSON) -// --------------------------------------------------------------------------- - -function printStructuredLog(stages: PipelineStage[]): StructuredLogEntry[] { - const entries: StructuredLogEntry[] = []; - - for (const stage of stages) { - entries.push({ - timestamp: new Date().toISOString(), - level: stage.success ? "info" : "error", - component: stage.component, - action: stage.action, - durationMs: stage.durationMs, - input: stage.input, - output: stage.output, - error: stage.errorMessage, - correlationId: CORRELATION_ID, - }); - } - - return entries; -} - -// --------------------------------------------------------------------------- -// Diagnose failure from structured logs -// --------------------------------------------------------------------------- - -function diagnoseFromStructured(entries: StructuredLogEntry[]): string[] { - const diagnosis: string[] = []; - - // Find errors - const errors = entries.filter((e) => e.level === "error"); - if (errors.length > 0) { - diagnosis.push("ERRORS FOUND:"); - for (const err of errors) { - diagnosis.push(` - ${err.component}.${err.action}: ${err.error}`); - } - } - - // Check for latency spikes - const slowStages = entries.filter((e) => (e.durationMs ?? 0) > 1000); - if (slowStages.length > 0) { - diagnosis.push("LATENCY SPIKES:"); - for (const s of slowStages) { - diagnosis.push(` - ${s.component}.${s.action}: ${s.durationMs}ms`); - } - } - - // Check for cascading failures - const emptyOutputs = entries.filter((e) => { - const out = e.output as Record | undefined; - return out && typeof out === "object" && "results" in out && (out.results as number) === 0; - }); - if (emptyOutputs.length > 0) { - diagnosis.push("EMPTY OUTPUTS (possible cascade):"); - for (const e of emptyOutputs) { - diagnosis.push(` - ${e.component}.${e.action}: returned 0 results`); - } - } - - return diagnosis; -} - -// --------------------------------------------------------------------------- -// Reporting -// --------------------------------------------------------------------------- - -function pad(s: string, len: number): string { - return s.length >= len ? s : s + " ".repeat(len - s.length); -} - -function run(): void { - const pipeline = runPipeline(); - - console.log("\n" + "=".repeat(90)); - console.log(" OBSERVABILITY DEMO -- Ad-hoc vs Structured Logging"); - console.log("=".repeat(90)); - - // --- Ad-hoc output --- - console.log("\n" + "-".repeat(90)); - console.log(" SCENARIO A: Ad-hoc console.log output"); - console.log("-".repeat(90) + "\n"); - printAdHocLog(pipeline); - console.log("\n Diagnosis from ad-hoc logs: ??? Hard to tell what went wrong."); - console.log(" The failure message is vague: 'something went wrong'."); - console.log(" No dimensions, no input/output data, no correlation ID."); - - // --- Structured output --- - console.log("\n" + "-".repeat(90)); - console.log(" SCENARIO B: Structured JSON log output"); - console.log("-".repeat(90) + "\n"); - - const structuredEntries = printStructuredLog(pipeline); - - const header = `| ${pad("Timestamp", 26)}| ${pad("Level", 6)}| ${pad("Component", 20)}| ${pad("Action", 25)}| ${pad("Duration", 9)}| Error?`; - const sep = `|${"-".repeat(28)}|${"-".repeat(8)}|${"-".repeat(22)}|${"-".repeat(27)}|${"-".repeat(11)}|${"-".repeat(30)}`; - console.log(header); - console.log(sep); - - for (const entry of structuredEntries) { - const hasError = entry.error ? entry.error.slice(0, 30) : ""; - const marker = entry.level === "error" ? ">>" : " "; - console.log( - `${marker}| ${pad(entry.timestamp, 26)}| ${pad(entry.level, 6)}| ${pad(entry.component, 20)}| ${pad(entry.action, 25)}| ${pad(String(entry.durationMs) + "ms", 9)}| ${hasError}` - ); - } - - // --- Diagnosis --- - console.log("\n" + "-".repeat(90)); - console.log(" AUTOMATED DIAGNOSIS FROM STRUCTURED LOGS"); - console.log("-".repeat(90) + "\n"); - - const errors = structuredEntries.filter((e) => e.level === "error"); - for (const err of errors) { - console.log(` ROOT CAUSE: ${err.component}.${err.action}`); - console.log(` Error: ${err.error}`); - console.log(` Input: ${JSON.stringify(err.input)}`); - console.log(` Output: ${JSON.stringify(err.output)}`); - console.log(` Correlation ID: ${err.correlationId}`); - } - - // Downstream impact - console.log("\n DOWNSTREAM IMPACT:"); - const answerGen = structuredEntries.find((e) => e.component === "AnswerGenerator"); - if (answerGen) { - const out = answerGen.output as Record; - console.log(` AnswerGenerator received empty context (${JSON.stringify(answerGen.input)})`); - console.log(` Produced answer: "${out.answer}" with ${out.citations} citations`); - } - - // Comparison summary - console.log("\n" + "=".repeat(90)); - console.log(" COMPARISON"); - console.log("=".repeat(90) + "\n"); - - const cHeader = `| ${pad("Metric", 35)}| ${pad("Ad-hoc Logs", 18)}| ${pad("Structured Logs", 18)}|`; - const cSep = `|${"-".repeat(37)}|${"-".repeat(20)}|${"-".repeat(20)}|`; - console.log(cHeader); - console.log(cSep); - console.log(`| ${pad("Root cause identifiable", 35)}| ${pad("No", 18)}| ${pad("Yes", 18)}|`); - console.log(`| ${pad("Input/output traceable", 35)}| ${pad("No", 18)}| ${pad("Yes", 18)}|`); - console.log(`| ${pad("Correlation across steps", 35)}| ${pad("No", 18)}| ${pad("Yes", 18)}|`); - console.log(`| ${pad("Machine-parseable", 35)}| ${pad("No", 18)}| ${pad("Yes", 18)}|`); - console.log(`| ${pad("Time to diagnose", 35)}| ${pad("Minutes (manual)", 18)}| ${pad("Seconds (auto)", 18)}|`); - - console.log("\n Structured logging transforms debugging from guesswork into a deterministic lookup.\n"); -} - -run(); diff --git a/courses/learn-harness-engineering/docs/zh-TW/lectures/lecture-11-why-observability-belongs-inside-the-harness/code/sprint-contract.md b/courses/learn-harness-engineering/docs/zh-TW/lectures/lecture-11-why-observability-belongs-inside-the-harness/code/sprint-contract.md deleted file mode 100644 index 7fb0c07b..00000000 --- a/courses/learn-harness-engineering/docs/zh-TW/lectures/lecture-11-why-observability-belongs-inside-the-harness/code/sprint-contract.md +++ /dev/null @@ -1,12 +0,0 @@ -# Sprint 合約範例 - -Sprint 目標: - -- 為基於來源的問答結果新增可見的引用 - -完成的定義: - -- 使用者提出問題 -- 應用程式回傳答案 -- 顯示至少一個引用 -- 點擊引用會在文件檢視中開啟來源位置 diff --git a/courses/learn-harness-engineering/docs/zh-TW/lectures/lecture-11-why-observability-belongs-inside-the-harness/index.md b/courses/learn-harness-engineering/docs/zh-TW/lectures/lecture-11-why-observability-belongs-inside-the-harness/index.md deleted file mode 100644 index a2bb66a7..00000000 --- a/courses/learn-harness-engineering/docs/zh-TW/lectures/lecture-11-why-observability-belongs-inside-the-harness/index.md +++ /dev/null @@ -1,173 +0,0 @@ -[English Version →](../../../en/lectures/lecture-11-why-observability-belongs-inside-the-harness/) - -> 本篇代碼示例:[code/](https://github.com/walkinglabs/learn-harness-engineering/blob/main/docs/zh-TW/lectures/lecture-11-why-observability-belongs-inside-the-harness/code/) -> 實戰練習:[Project 06. 搭建一套完整的 agent 工作環境](./../../projects/project-06-runtime-observability-and-debugging/index.md) - -# 第十一講. 讓 agent 的運行過程可觀測 - -你讓 agent 做一個功能,它跑了 20 分鐘,改了一堆檔案,然後告訴你"做完了但有兩個測試失敗"。你問它為什麼失敗,它說"不太確定,可能是時序問題"。你問它改了哪些關鍵路徑,它說"讓我看看代碼……"。 - -這不是 agent 能力不夠,是你的 harness 沒有給它裝儀表盤。想象你在開一輛沒有儀表盤的車——沒有速度表、沒有油量表、沒有發動機故障燈。你能開,但你不知道開多快、還剩多少油、發動機是不是快爆了。技術再好的司機,蒙著眼也得出事。 - -**沒有可觀測性,agent 在不確定狀態中做決策,評估變成主觀判斷,重試變成盲目摸索。** OpenAI 和 Anthropic 都將可靠性定義為證據問題——harness 必須以可指導下一步決策的形式暴露運行時行為和評估信號。 - -## 可觀測性缺失的真實代價 - -當 harness 缺乏可觀測性時,四類問題系統性出現: - -**無法區分"正確"和"看似正確"**:一個函數在代碼審查時看起來完全正確——語法對、邏輯通。但運行時因為邊界條件處理錯誤,在特定輸入下產生了不正確結果。只有運行時追蹤能揭示實際執行路徑偏離了預期。就像一個演員排練時臺詞全對了,但上臺演出時燈光一打,表情和走位全都變了——你不看現場是發現不了的。 - -**評估變成玄學**:沒有評分標準和驗收條件時,評估者(人或 agent)依賴隱式假設。同一個輸出,不同評估者可能給出截然不同的評價。質量評估不可復現。就像體操比賽沒有評分標準——這個裁判覺得你的動作優雅,那個裁判覺得你落地不穩,誰說了算? - -**重試變成盲猜**:agent 不知道為什麼失敗時,重試方向是隨機的。它可能在錯誤的方向上反覆嘗試——修復了不相關的代碼路徑而忽略真正的故障根源。就像你開車發現車跑偏了,但你沒有儀表盤——你猜是輪胎的問題換了輪胎,實際是方向盤的 alignment 出了問題。每次盲重試都消耗 token 和時間。 - -**會話交接信息斷崖**:當未完成的工作移交給下一個會話時,缺乏可觀測性意味著新會話必須從零診斷系統狀態。Anthropic 的長期運行 agent 觀察表明,這種重複診斷可能佔會話總時間的 30-50%。就像換班司機上車發現沒有交接記錄——他得花半小時檢查油量、胎壓、發動機狀態才能出發。 - -## Claude Code 的真實場景 - -想象一個使用"計劃者-生成者-評估者"三角色工作流的 harness,執行"為應用添加暗色模式"任務。 - -**沒有儀表盤**:計劃者輸出模糊描述。生成者根據模糊描述實現暗色模式,但和計劃者的隱式預期不一致。評估者基於自己的隱式標準拒絕,但說不出具體哪裡不對——"感覺不太對"。生成者基於模糊拒絕理由盲重試。循環 3-4 次,總耗時約 45 分鐘,最終勉強產出。 - -**有完整儀表盤**:計劃者輸出衝刺合同——列明要改哪些組件、每個組件的驗證標準、排除項(不處理打印樣式)。生成者按合同實現。運行時可觀測性記錄每個組件的樣式加載和應用過程。評估者用評分標準逐維度評估,附具體證據引用——"按鈕顏色對比度不足(WCAG AA 標準 4.5:1,實測 2.1:1)"。一次迭代出高質量結果,總耗時約 15 分鐘。 - -效率差 3 倍。區別只在可觀測性——給車裝上了儀表盤。 - -## 雙層可觀測性 - -可觀測性不是"多打點日誌"那麼簡單。它分兩層,缺一不可: - -```mermaid -flowchart LR - Contract["先把這次任務寫清楚
    改哪些檔案 / 不改哪些部分 / 怎么算通過"] --> Generator["生成器"] - Generator --> Signals["執行時收集
    日誌 / 追踪 / 健康檢查"] - Contract --> Review["按檢查表逐项看
    功能 / 測試 / 邊界"] - Signals --> Review - Review --> Verdict["指出哪一项没過
    以及应該去改哪裡"] - Verdict --> Generator -``` - -**運行時可觀測性**:系統層的信號——日誌、追蹤、進程事件、健康檢查。回答"系統做了什麼"。這是你車上的儀表盤——速度、油量、發動機溫度。 - -**過程可觀測性**:harness 決策工件的可見性——計劃、評分標準、驗收條件。回答"為什麼這個變更應該被接受"。這是你的導航系統——不光知道現在在哪,還知道為什麼走這條路。 - -## 核心概念 - -- **運行時可觀測性**:系統層的信號——日誌、追蹤、進程事件、健康檢查。回答"系統做了什麼"。 -- **過程可觀測性**:harness 決策工件的可見性——計劃、評分標準、驗收條件。回答"為什麼這個變更應該被接受"。 -- **任務軌跡**:一個任務從開始到完成的完整決策路徑記錄,類似分佈式系統中的請求追蹤。agent 的每一步操作及其上下文都被記錄。就像黑匣子——出了問題可以回放完整過程。 -- **衝刺合同**:編碼開始前協商的短期協議——明確任務範圍、驗證標準、排除項。是過程可觀測性的核心工具。 -- **評估評分標準**:把質量評估從主觀判斷變成基於證據的結構化評分。使不同評估者對同一輸出產生相似結論。就像體操比賽的評分標準——有了它,十個裁判的分才不會差太遠。 -- **雙層可觀測性**:系統層和過程層同時設計、相互增強。運行時信號解釋行為,過程工件解釋意圖。 - -## 為什麼 agent 自己解決不了這個問題 - -你可能在想:"agent 不能自己打日誌嗎?" 問題在於: - -1. **agent 不知道它不知道什麼**——它不會主動記錄自己沒意識到需要的信號。就像你不知道油箱快漏了的時候,你不會去看油量表——因為你壓根不知道該看。 -2. **日誌格式不統一**——不同會話用不同的日誌格式,無法做系統化分析。就像十個司機各寫各的交接記錄,格式都不一樣,下一個司機看不懂。 -3. **過程可觀測性不是日誌能解決的**——衝刺合同和評分標準是結構化的工件,需要 harness 層面的支持。不是多 print 幾行就能搞定的。 - -## 怎麼裝儀表盤 - -### 1. 在 harness 裡內置運行時信號採集 - -不要依賴 agent 自己打日誌。harness 應該自動採集以下信號: - -- **應用生命週期**:啟動、就緒、運行、關閉各階段狀態 -- **功能路徑執行**:關鍵路徑的執行記錄,包括入口、檢查點和出口 -- **數據流**:數據在組件間的流轉記錄 -- **資源利用**:異常的資源使用模式(如內存持續增長) -- **錯誤和異常**:完整的錯誤上下文,不只是錯誤消息 - -### 2. 實施衝刺合同 - -在每個任務開始前,生成者和評估者(可能是同一個 agent 的不同調用)協商一份合同——就像施工隊開工前籤的施工協議: - -```markdown -# 冲刺合同: 暗色模式支持 - -## 範圍 -- 修改主题切換组件 -- 更新全局 CSS 變量 -- 添加暗色模式測試 - -## 驗證標準 -- 每個组件的视觉回歸測試通過 -- 主流程端到端測試通過 -- 无样式闪烁 (FOUC) - -## 排除项 -- 不處理打印样式 -- 不處理第三方组件暗色模式 -``` - -### 3. 建立評估評分標準 - -把"好不好"變成可量化的評分——就像給體操比賽定評分標準: - -```markdown -# 評分標準 - -| 维度 | A | B | C | D | -|------|---|---|---|---| -| 程式碼正确性 | 所有測試通過 | 主流程通過 | 部分通過 | 编译失敗 | -| 架构合规 | 完全合规 | 轻微偏离 | 明顯偏离 | 严重违反 | -| 測試覆盖 | 主流程+邊缘 | 仅主流程 | 仅有骨架 | 无測試 | -``` - -### 4. 用 OpenTelemetry 標準化 - -為每個 harness 會話創建一個 trace,每個任務創建一個 span,每個驗證步驟創建子 span。使用標準屬性標註關鍵信息。這樣可觀測性數據可以和標準工具鏈(Jaeger、Zipkin)集成。 - -## Anthropic 的三 agent 架構實驗 - -Anthropic 在 2026 年 3 月發佈了一項系統性的 harness 實驗。他們用三種架構跑同一個任務("用 Web Audio API 做一個瀏覽器端 DAW"),記錄了詳細的階段數據: - -| Agent 和階段 | 時長 | 成本 | -|------------|------|------| -| Planner(規劃者) | 4.7 分鐘 | $0.46 | -| Build 第 1 輪 | 2 小時 7 分鐘 | $71.08 | -| QA 第 1 輪 | 8.8 分鐘 | $3.24 | -| Build 第 2 輪 | 1 小時 2 分鐘 | $36.89 | -| QA 第 2 輪 | 6.8 分鐘 | $3.09 | -| Build 第 3 輪 | 10.9 分鐘 | $5.88 | -| QA 第 3 輪 | 9.6 分鐘 | $4.06 | -| **總計** | **3 小時 50 分鐘** | **$124.70** | - -三個 agent 各司其職,每個都有明確的可觀測性角色: - -**Planner(規劃者)**:接收一段 1-4 句話的用戶需求,擴展成完整產品規格。被要求"大膽設定範圍"並且"專注於產品上下文和高層技術設計,而不是詳細的技術實現"。原因是:如果 planner 過早指定了粒度技術細節且搞錯了,錯誤會級聯到下游實現。更好的做法是約束交付物,讓 agent 在執行中自己找到路徑。就像建築設計師只畫效果圖和結構圖,不規定每塊磚怎麼砌。 - -**Generator(生成者)**:按 sprint 逐個功能實現。每個 sprint 前和 evaluator 協商一份 sprint 合同——約定這個功能塊"做完"的標準。然後按合同實現,自評後交給 QA。按合同施工,不按感覺施工。 - -**Evaluator(評估者)**:用 Playwright MCP 像用戶一樣點擊運行中的應用,測試 UI 功能、API 端點和數據庫狀態。對每個 sprint 按四個維度評分——產品深度、功能性、視覺設計和代碼質量。每個維度有硬性閾值,任一不達標則 sprint 失敗,generator 收到詳細反饋後修復。就像驗收工程師拿著驗收標準逐項檢查——不達標就打回去重做。 - -QA 第 1 輪反饋的示例——"這是一個視覺上令人印象深刻的應用,AI 集成工作良好,但核心 DAW 功能有幾個是展示性的,沒有交互深度:剪輯不能拖拽/移動,沒有樂器 UI 面板(合成器旋鈕、鼓墊),沒有視覺效果編輯器(EQ 曲線、壓縮器儀表)"。這些不是邊緣情況——它們是讓 DAW 可用的核心交互。具體的、有證據的反饋,不是"感覺不對"。 - -Evaluator 不是一開始就這麼強。早期版本會識別出合理的問題,然後說服自己這些問題不嚴重,最終批準工作。調校方式是:讀 evaluator 的日誌,找到它的判斷和人類判斷分叉的地方,更新 QA 的 prompt 解決那些問題。經過幾輪這種開發循環,evaluator 的評分才變得合理。就像訓練一個新驗收工程師——一開始他太寬容,出了幾次事故後學會了嚴格。 - -> 來源:[Anthropic: Harness design for long-running application development](https://www.anthropic.com/engineering/harness-design-long-running-apps) - -## 關鍵要點 - -- **可觀測性是 harness 的架構屬性**——不是事後添加的功能,而是設計時必須考慮的核心能力。儀表盤不是可選配件,是出廠標配。 -- **雙層可觀測性缺一不可**——運行時信號解釋"發生了什麼",過程工件解釋"為什麼這樣做"。速度表和導航系統各有各的用處。 -- **衝刺合同前置對齊工作**——防止"生成者做了評估者因可預見原因立即拒絕的東西"。施工協議要在開工前籤,不是完工後補。 -- **評分標準讓評估可復現**——不同評估者對同一輸出產生相似評分。有了評分標準,十個裁判的分才不會差太遠。 -- **可觀測性缺失導致 30-50% 的會話時間浪費在重複診斷上**。 - -## 延伸閱讀 - -- [Observability Engineering - Charity Majors](https://www.honeycomb.io/blog/observability-engineering-book) — 現代可觀測性工程的理論和實踐框架 -- [Dapper - Google (Sigelman et al.)](https://research.google/pubs/pub36356/) — 大規模分佈式追蹤的開創性實踐 -- [Harness Design - Anthropic](https://www.anthropic.com/engineering/harness-design-long-running-apps) — 引入衝刺合同和評估評分標準 -- [Site Reliability Engineering - Google](https://sre.google/sre-book/table-of-contents/) — 可觀測性在生產系統中的系統化應用 - -## 練習 - -1. **可觀測性差距分析**:審查你當前的 harness,評估系統層和過程層可觀測性。找出無法從現有信號區分的系統狀態,提出補充方案。 - -2. **衝刺合同實踐**:為一個真實任務寫衝刺合同。讓 agent 按合同執行,對比沒有合同時的效率和質量差異。 - -3. **任務軌跡構建**:記錄一個完整編碼任務中 agent 的每一步操作。用 OpenTelemetry 語義約定標註。分析軌跡中的信息瓶頸——哪些步驟的決策缺乏足夠的信號支持。 diff --git a/courses/learn-harness-engineering/docs/zh-TW/lectures/lecture-12-why-every-session-must-leave-a-clean-state/code/benchmark-comparison-template.md b/courses/learn-harness-engineering/docs/zh-TW/lectures/lecture-12-why-every-session-must-leave-a-clean-state/code/benchmark-comparison-template.md deleted file mode 100644 index d895097f..00000000 --- a/courses/learn-harness-engineering/docs/zh-TW/lectures/lecture-12-why-every-session-must-leave-a-clean-state/code/benchmark-comparison-template.md +++ /dev/null @@ -1,18 +0,0 @@ -# 基準比較模板 - -Harness A: - -- 完成率 -- 平均重試次數 -- 人工審查前捕捉到的錯誤數 - -Harness B: - -- 完成率 -- 平均重試次數 -- 人工審查前捕捉到的錯誤數 - -解讀: - -- 哪個 harness 改變了結果? -- 哪個 harness 改變了獲得結果的成本? diff --git a/courses/learn-harness-engineering/docs/zh-TW/lectures/lecture-12-why-every-session-must-leave-a-clean-state/code/benchmark-runner.ts b/courses/learn-harness-engineering/docs/zh-TW/lectures/lecture-12-why-every-session-must-leave-a-clean-state/code/benchmark-runner.ts deleted file mode 100644 index 0388cad7..00000000 --- a/courses/learn-harness-engineering/docs/zh-TW/lectures/lecture-12-why-every-session-must-leave-a-clean-state/code/benchmark-runner.ts +++ /dev/null @@ -1,216 +0,0 @@ -/** - * benchmark-runner.ts - * - * Reads a benchmark task definition (JSON array of tasks with pass criteria), - * "executes" each task, records timing and pass/fail, outputs a comparison - * report showing which tasks pass and which fail. - * - * Run: npx tsx docs/lectures/lecture-12-why-every-session-must-leave-a-clean-state/code/benchmark-runner.ts - */ - -// --------------------------------------------------------------------------- -// Types -// --------------------------------------------------------------------------- - -interface BenchmarkTask { - id: string; - name: string; - category: string; - passCriteria: string[]; - expectedDurationMs: number; - // Simulated actual results - actualDurationMs: number; - actualPass: boolean; - failureReason?: string; -} - -interface BenchmarkResult { - id: string; - name: string; - category: string; - criteriaTotal: number; - criteriaPassed: number; - pass: boolean; - expectedMs: number; - actualMs: number; - durationDelta: number; - failureReason?: string; -} - -// --------------------------------------------------------------------------- -// Benchmark task definitions -// --------------------------------------------------------------------------- - -const benchmarkTasks: BenchmarkTask[] = [ - { - id: "bench-001", - name: "Import markdown document", - category: "Document Pipeline", - passCriteria: ["File accepted", "Chunks created", "Metadata stored"], - expectedDurationMs: 100, - actualDurationMs: 95, - actualPass: true, - }, - { - id: "bench-002", - name: "Import PDF document", - category: "Document Pipeline", - passCriteria: ["File accepted", "Text extracted", "Chunks created", "Metadata stored"], - expectedDurationMs: 200, - actualDurationMs: 310, - actualPass: false, - failureReason: "PDF text extraction failed on page 3 -- encoding issue", - }, - { - id: "bench-003", - name: "Ask grounded question", - category: "Q&A Pipeline", - passCriteria: ["Query received", "Relevant chunks retrieved", "Answer generated", "Citations present"], - expectedDurationMs: 500, - actualDurationMs: 480, - actualPass: true, - }, - { - id: "bench-004", - name: "Ask question with no relevant docs", - category: "Q&A Pipeline", - passCriteria: ["Query received", "Graceful 'no results' message", "No hallucinated citations"], - expectedDurationMs: 400, - actualDurationMs: 420, - actualPass: false, - failureReason: "Model hallucinated a citation instead of saying 'no results'", - }, - { - id: "bench-005", - name: "Delete imported document", - category: "Document Pipeline", - passCriteria: ["Document removed from list", "Chunks removed from index", "No orphan data"], - expectedDurationMs: 150, - actualDurationMs: 145, - actualPass: true, - }, - { - id: "bench-006", - name: "Concurrent user access", - category: "Security", - passCriteria: ["Users isolated", "No cross-user data leak", "Performance within SLA"], - expectedDurationMs: 300, - actualDurationMs: 850, - actualPass: false, - failureReason: "Cross-user data leak detected + response time exceeded SLA (850ms > 500ms)", - }, - { - id: "bench-007", - name: "Session continuity after restart", - category: "Reliability", - passCriteria: ["State persisted", "Session recovers", "No data loss"], - expectedDurationMs: 200, - actualDurationMs: 180, - actualPass: true, - }, - { - id: "bench-008", - name: "API rate limiting", - category: "Security", - passCriteria: ["Rate limit enforced", "429 response after limit", "Legitimate traffic unaffected"], - expectedDurationMs: 100, - actualDurationMs: 100, - actualPass: true, - }, -]; - -// --------------------------------------------------------------------------- -// Execution simulation -// --------------------------------------------------------------------------- - -function executeBenchmark(tasks: BenchmarkTask[]): BenchmarkResult[] { - return tasks.map((task) => ({ - id: task.id, - name: task.name, - category: task.category, - criteriaTotal: task.passCriteria.length, - criteriaPassed: task.actualPass ? task.passCriteria.length : Math.floor(task.passCriteria.length * 0.5), - pass: task.actualPass, - expectedMs: task.expectedDurationMs, - actualMs: task.actualDurationMs, - durationDelta: task.actualDurationMs - task.expectedDurationMs, - failureReason: task.failureReason, - })); -} - -// --------------------------------------------------------------------------- -// Reporting -// --------------------------------------------------------------------------- - -function pad(s: string, len: number): string { - return s.length >= len ? s : s + " ".repeat(len - s.length); -} - -function run(): void { - console.log("\n" + "=".repeat(100)); - console.log(" BENCHMARK RUNNER -- Task Execution Report"); - console.log("=".repeat(100)); - - const results = executeBenchmark(benchmarkTasks); - - // Detailed results - const header = `| ${pad("ID", 10)}| ${pad("Task", 35)}| ${pad("Category", 18)}| ${pad("Pass?", 6)}| ${pad("Criteria", 10)}| ${pad("Expected", 10)}| ${pad("Actual", 10)}| ${pad("Delta", 8)}|`; - const sep = `|${"-".repeat(12)}|${"-".repeat(37)}|${"-".repeat(20)}|${"-".repeat(8)}|${"-".repeat(12)}|${"-".repeat(12)}|${"-".repeat(12)}|${"-".repeat(10)}|`; - console.log("\n" + header); - console.log(sep); - - for (const r of results) { - const passLabel = r.pass ? "PASS" : "FAIL"; - const criteriaLabel = `${r.criteriaPassed}/${r.criteriaTotal}`; - const deltaLabel = r.durationDelta >= 0 ? `+${r.durationDelta}ms` : `${r.durationDelta}ms`; - const marker = r.pass ? " " : ">>"; - - console.log( - `${marker}| ${pad(r.id, 10)}| ${pad(r.name, 35)}| ${pad(r.category, 18)}| ${pad(passLabel, 6)}| ${pad(criteriaLabel, 10)}| ${pad(r.expectedMs + "ms", 10)}| ${pad(r.actualMs + "ms", 10)}| ${pad(deltaLabel, 8)}|` - ); - } - - // Failure details - const failures = results.filter((r) => !r.pass); - if (failures.length > 0) { - console.log("\n" + "-".repeat(100)); - console.log(" FAILURE DETAILS"); - console.log("-".repeat(100)); - for (const f of failures) { - console.log(`\n [${f.id}] ${f.name}`); - console.log(` Category: ${f.category}`); - console.log(` Criteria: ${f.criteriaPassed}/${f.criteriaTotal} passed`); - console.log(` Reason: ${f.failureReason ?? "Unknown"}`); - console.log(` Timing: Expected ${f.expectedMs}ms, actual ${f.actualMs}ms (delta: ${f.durationDelta >= 0 ? "+" : ""}${f.durationDelta}ms)`); - } - } - - // Summary - console.log("\n" + "=".repeat(100)); - console.log(" SUMMARY BY CATEGORY"); - console.log("=".repeat(100) + "\n"); - - const categories = [...new Set(results.map((r) => r.category))]; - const catHeader = `| ${pad("Category", 20)}| ${pad("Total", 8)}| ${pad("Passed", 8)}| ${pad("Failed", 8)}| ${pad("Pass Rate", 12)}|`; - const catSep = `|${"-".repeat(22)}|${"-".repeat(10)}|${"-".repeat(10)}|${"-".repeat(10)}|${"-".repeat(14)}|`; - console.log(catHeader); - console.log(catSep); - - for (const cat of categories) { - const catResults = results.filter((r) => r.category === cat); - const catPassed = catResults.filter((r) => r.pass).length; - const catFailed = catResults.filter((r) => !r.pass).length; - const rate = Math.round((catPassed / catResults.length) * 100); - - console.log(`| ${pad(cat, 20)}| ${pad(String(catResults.length), 8)}| ${pad(String(catPassed), 8)}| ${pad(String(catFailed), 8)}| ${pad(rate + "%", 12)}|`); - } - - // Overall - const totalPassed = results.filter((r) => r.pass).length; - const totalFailed = results.filter((r) => !r.pass).length; - console.log("\n" + "-".repeat(100)); - console.log(` OVERALL: ${totalPassed}/${results.length} passed (${Math.round((totalPassed / results.length) * 100)}%), ${totalFailed} failures`); - console.log("=".repeat(100) + "\n"); -} - -run(); diff --git a/courses/learn-harness-engineering/docs/zh-TW/lectures/lecture-12-why-every-session-must-leave-a-clean-state/code/cleanup-loop.md b/courses/learn-harness-engineering/docs/zh-TW/lectures/lecture-12-why-every-session-must-leave-a-clean-state/code/cleanup-loop.md deleted file mode 100644 index 6f81fef0..00000000 --- a/courses/learn-harness-engineering/docs/zh-TW/lectures/lecture-12-why-every-session-must-leave-a-clean-state/code/cleanup-loop.md +++ /dev/null @@ -1,9 +0,0 @@ -# 清理迴圈範例 - -週期性清理任務: - -- 掃描過時的文件 -- 掃描結構性違規 -- 更新品質評分 -- 開啟針對性的清理 PR -- 清理後重新執行固定的基準切片 diff --git a/courses/learn-harness-engineering/docs/zh-TW/lectures/lecture-12-why-every-session-must-leave-a-clean-state/code/cleanup-scanner.ts b/courses/learn-harness-engineering/docs/zh-TW/lectures/lecture-12-why-every-session-must-leave-a-clean-state/code/cleanup-scanner.ts deleted file mode 100644 index 6b35905c..00000000 --- a/courses/learn-harness-engineering/docs/zh-TW/lectures/lecture-12-why-every-session-must-leave-a-clean-state/code/cleanup-scanner.ts +++ /dev/null @@ -1,336 +0,0 @@ -/** - * cleanup-scanner.ts - * - * Scans a project directory for stale artifacts, dead code, structural - * violations, and outputs a cleanup report. Helps enforce the "clean state - * at end of every session" principle. - * - * Usage: - * npx tsx docs/lectures/lecture-12.../code/cleanup-scanner.ts [path] - * (defaults to current working directory) - * - * Run: npx tsx docs/lectures/lecture-12-why-every-session-must-leave-a-clean-state/code/cleanup-scanner.ts - */ - -import * as fs from "node:fs"; -import * as path from "node:path"; - -// --------------------------------------------------------------------------- -// Types -// --------------------------------------------------------------------------- - -interface ScanResult { - category: string; - check: string; - severity: "critical" | "warning" | "info"; - found: string[]; - description: string; -} - -// --------------------------------------------------------------------------- -// Scanner checks -// --------------------------------------------------------------------------- - -interface ScannerCheck { - category: string; - name: string; - severity: "critical" | "warning" | "info"; - description: string; - scan: (dir: string) => string[]; -} - -function createChecks(): ScannerCheck[] { - return [ - { - category: "Stale Artifacts", - name: "Temporary files (*.tmp, *.bak, *.swp)", - severity: "warning", - description: "Leftover temp files from editing or processing", - scan: (dir) => findFiles(dir, [".tmp", ".bak", ".swp", "~"]), - }, - { - category: "Stale Artifacts", - name: "Debug/log files in source", - severity: "warning", - description: "Log files that should not be in the source tree", - scan: (dir) => { - const found: string[] = []; - const logPatterns = [".log"]; - for (const p of findFiles(dir, logPatterns)) { - // Only flag log files in source directories - if (p.includes("/src/") || p.includes("/lib/") || p.includes("/app/")) { - found.push(p); - } - } - return found; - }, - }, - { - category: "Dead Code", - name: "Unused imports (placeholder detection)", - severity: "info", - description: "Files that may contain unused code (heuristic scan)", - scan: (dir) => { - const found: string[] = []; - const srcDirs = ["src", "lib", "app"]; - for (const srcDir of srcDirs) { - const srcPath = path.join(dir, srcDir); - if (fs.existsSync(srcPath)) { - scanForPatterns(srcPath, ["TODO:", "FIXME:", "HACK:", "XXX:"], found, dir); - } - } - return found; - }, - }, - { - category: "Structural Violations", - name: "Missing .gitignore", - severity: "warning", - description: "No .gitignore file found -- risk of committing build artifacts", - scan: (dir) => fs.existsSync(path.join(dir, ".gitignore")) ? [] : [".gitignore (MISSING)"], - }, - { - category: "Structural Violations", - name: "Build artifacts in source", - severity: "critical", - description: "Compiled output (dist/, build/) mixed with source", - scan: (dir) => { - const found: string[] = []; - // Check if dist/ or build/ exists inside src/ - const dirs = ["src/dist", "src/build", "lib/dist", "lib/build"]; - for (const d of dirs) { - if (fs.existsSync(path.join(dir, d))) { - found.push(d + "/"); - } - } - return found; - }, - }, - { - category: "Structural Violations", - name: "node_modules in source tree", - severity: "critical", - description: "node_modules directory found outside root (nested dependency)", - scan: (dir) => { - const found: string[] = []; - const srcDirs = ["src", "lib", "app", "test", "tests"]; - for (const srcDir of srcDirs) { - const nmPath = path.join(dir, srcDir, "node_modules"); - if (fs.existsSync(nmPath)) { - found.push(srcDir + "/node_modules/"); - } - } - return found; - }, - }, - { - category: "Session Cleanliness", - name: "Uncommitted changes indicator", - severity: "info", - description: "Checks for common artifacts of incomplete sessions", - scan: (dir) => { - const found: string[] = []; - const indicators = [ - "WIP.md", "IN_PROGRESS.md", "scratch.ts", "scratch.js", - "debug.ts", "test-manual.ts", "temp.ts", - ]; - for (const f of indicators) { - if (fs.existsSync(path.join(dir, f))) { - found.push(f); - } - } - return found; - }, - }, - { - category: "Session Cleanliness", - name: "Empty directories", - severity: "info", - description: "Empty directories that may be leftover from incomplete work", - scan: (dir) => { - const found: string[] = []; - const commonDirs = ["src", "lib", "app", "test", "tests", "docs"]; - for (const d of commonDirs) { - const dp = path.join(dir, d); - if (fs.existsSync(dp) && fs.statSync(dp).isDirectory()) { - try { - const contents = fs.readdirSync(dp); - if (contents.length === 0) { - found.push(d + "/"); - } - } catch { - // Skip directories we can't read - } - } - } - return found; - }, - }, - { - category: "Configuration", - name: "Environment files in source", - severity: "critical", - description: ".env files that may contain secrets", - scan: (dir) => { - const found: string[] = []; - const envFiles = [".env", ".env.local", ".env.production", ".env.staging"]; - for (const f of envFiles) { - if (fs.existsSync(path.join(dir, f))) { - found.push(f); - } - } - return found; - }, - }, - ]; -} - -// --------------------------------------------------------------------------- -// Helper functions -// --------------------------------------------------------------------------- - -function findFiles(dir: string, extensions: string[]): string[] { - const found: string[] = []; - - function walk(current: string, depth: number): void { - if (depth > 4) return; // Limit recursion depth - try { - const entries = fs.readdirSync(current, { withFileTypes: true }); - for (const entry of entries) { - // Skip common non-source directories - if (entry.isDirectory() && ["node_modules", ".git", "dist", "build", ".next", "coverage"].includes(entry.name)) { - continue; - } - - const fullPath = path.join(current, entry.name); - const relativePath = path.relative(dir, fullPath); - - if (entry.isDirectory()) { - walk(fullPath, depth + 1); - } else if (entry.isFile()) { - if (extensions.some((ext) => entry.name.endsWith(ext))) { - found.push(relativePath); - } - } - } - } catch { - // Skip directories we can't read - } - } - - walk(dir, 0); - return found; -} - -function scanForPatterns(dir: string, patterns: string[], results: string[], baseDir: string): void { - try { - const entries = fs.readdirSync(dir, { withFileTypes: true }); - for (const entry of entries) { - if (entry.isDirectory()) { - if (!["node_modules", ".git", "dist", "build"].includes(entry.name)) { - scanForPatterns(path.join(dir, entry.name), patterns, results, baseDir); - } - continue; - } - - const fullPath = path.join(dir, entry.name); - if (!entry.name.endsWith(".ts") && !entry.name.endsWith(".tsx") && !entry.name.endsWith(".js")) { - continue; - } - - try { - const content = fs.readFileSync(fullPath, "utf-8"); - const lines = content.split("\n"); - for (let i = 0; i < Math.min(lines.length, 50); i++) { - for (const pattern of patterns) { - if (lines[i].includes(pattern)) { - const relative = path.relative(baseDir, fullPath); - results.push(`${relative}:${i + 1} contains ${pattern}`); - } - } - } - } catch { - // Skip files we can't read - } - } - } catch { - // Skip directories we can't read - } -} - -// --------------------------------------------------------------------------- -// Reporting -// --------------------------------------------------------------------------- - -function pad(s: string, len: number): string { - return s.length >= len ? s : s + " ".repeat(len - s.length); -} - -function run(): void { - const targetDir = process.argv[2] ? path.resolve(process.argv[2]) : process.cwd(); - - console.log("\n" + "=".repeat(90)); - console.log(" CLEANUP SCANNER -- Stale Artifact & Violation Report"); - console.log("=".repeat(90)); - console.log(` Scanning: ${targetDir}\n`); - - const checks = createChecks(); - const results: ScanResult[] = []; - - for (const check of checks) { - const found = check.scan(targetDir); - results.push({ - category: check.category, - check: check.name, - severity: check.severity, - found, - description: check.description, - }); - } - - // Report - const header = `| ${pad("Category", 20)}| ${pad("Check", 40)}| ${pad("Severity", 10)}| ${pad("Count", 6)}| Details`; - const sep = `|${"-".repeat(22)}|${"-".repeat(42)}|${"-".repeat(12)}|${"-".repeat(8)}|${"-".repeat(30)}`; - console.log(header); - console.log(sep); - - for (const r of results) { - const count = r.found.length; - const detail = count > 0 ? r.found.slice(0, 2).join(", ") : "(clean)"; - const marker = count > 0 && r.severity === "critical" ? ">>" : count > 0 ? " * " : " "; - - console.log(`${marker}| ${pad(r.category, 20)}| ${pad(r.check, 40)}| ${pad(r.severity, 10)}| ${pad(String(count), 6)}| ${detail}`); - } - - // Summary - const criticals = results.filter((r) => r.severity === "critical" && r.found.length > 0); - const warnings = results.filter((r) => r.severity === "warning" && r.found.length > 0); - const infos = results.filter((r) => r.severity === "info" && r.found.length > 0); - const clean = results.filter((r) => r.found.length === 0); - - console.log("\n" + "=".repeat(90)); - console.log(" CLEANUP SUMMARY"); - console.log("=".repeat(90) + "\n"); - - console.log(` Critical issues: ${criticals.length}`); - console.log(` Warnings: ${warnings.length}`); - console.log(` Info items: ${infos.length}`); - console.log(` Clean checks: ${clean.length}/${results.length}`); - - if (criticals.length > 0) { - console.log("\n ACTION REQUIRED (Critical):"); - for (const c of criticals) { - console.log(` - ${c.check}: ${c.found.length} item(s) found`); - console.log(` ${c.description}`); - } - } - - const totalIssues = results.reduce((s, r) => s + r.found.length, 0); - if (totalIssues === 0) { - console.log("\n Project is in a clean state. No issues found.\n"); - } else { - console.log(`\n Total issues found: ${totalIssues}. Run this scanner at the end of every session to maintain clean state.\n`); - } -} - -run(); diff --git a/courses/learn-harness-engineering/docs/zh-TW/lectures/lecture-12-why-every-session-must-leave-a-clean-state/code/index.md b/courses/learn-harness-engineering/docs/zh-TW/lectures/lecture-12-why-every-session-must-leave-a-clean-state/code/index.md deleted file mode 100644 index f8f4fd02..00000000 --- a/courses/learn-harness-engineering/docs/zh-TW/lectures/lecture-12-why-every-session-must-leave-a-clean-state/code/index.md +++ /dev/null @@ -1,8 +0,0 @@ -# 第 12 講的程式碼 - -此資料夾用於以下範例: - -- 基準切片 -- 清理任務 -- 熵減範例 -- 可重複的 harness 執行 diff --git a/courses/learn-harness-engineering/docs/zh-TW/lectures/lecture-12-why-every-session-must-leave-a-clean-state/index.md b/courses/learn-harness-engineering/docs/zh-TW/lectures/lecture-12-why-every-session-must-leave-a-clean-state/index.md deleted file mode 100644 index bb5193e7..00000000 --- a/courses/learn-harness-engineering/docs/zh-TW/lectures/lecture-12-why-every-session-must-leave-a-clean-state/index.md +++ /dev/null @@ -1,198 +0,0 @@ -[English Version →](../../../en/lectures/lecture-12-why-every-session-must-leave-a-clean-state/) - -> 本篇代碼示例:[code/](https://github.com/walkinglabs/learn-harness-engineering/blob/main/docs/zh-TW/lectures/lecture-12-why-every-session-must-leave-a-clean-state/code/) -> 實戰練習:[Project 06. 搭建一套完整的 agent 工作環境](./../../projects/project-06-runtime-observability-and-debugging/index.md) - -# 第十二講. 每次會話結束前都做好交接 - -你的 agent 跑了一下午,改了 20 個檔案,提交了代碼,會話結束。下一個 agent 會話開始,一上來就發現:構建失敗了、測試紅了、臨時調試檔案到處都是、功能清單沒更新、進度完全不清楚。新會話的前 30 分鐘全花在"搞清楚上一個會話到底幹了什麼"上。 - -這就像大學宿舍——你不倒垃圾,下一個室友進來就得替你收拾。他本來的計劃是複習考試,結果先花半小時收拾你留下的爛攤子。更要命的是,收拾完了他也不想複習了——環境太差,心情也差。 - -OpenAI 和 Anthropic 都明確指出:**長期可靠性取決於操作紀律,不僅是單次運行的成功。** 每個會話結束時的狀態質量,直接決定下一個會話的效率。 - -## 熵增是默認狀態 - -Lehman 的軟件演化定律告訴我們:持續變更的系統,除非主動管理,否則複雜性必然增加。這對 AI 編碼 agent 尤其成立——agent 每次會話都會引入變更,如果不在退出時清理,技術債務會指數級累積。宿舍不打掃,髒衣服和外賣盒只會越堆越多,不會自己消失。 - -OpenAI 在 5 個月的 Codex 實驗中觀察到:**agent 會複製倉庫中已有的模式——即使那些模式是不均勻的或次優的。** 隨著時間的推移,這種複製必然導致漂移。就像宿舍裡的公共區域——第一個人在桌上放了一個杯子,第二個人覺得"反正已經亂了"又放了一個,一週後桌上堆滿了。 - -OpenAI 團隊最初花每週五(20% 的工作時間)手動清理 "AI slop"。不出所料,這種方式不可擴展。就像宿舍每週大掃除一次——其他六天的髒亂差還是得忍。他們的解決方案是: - -1. **把"黃金原則"編碼進倉庫**:比如"優先使用共享工具包而非手寫的 ad-hoc 輔助函數"(保持不變量集中)、"不要 YOLO 式地猜數據結構"(驗證邊界或依賴類型化 SDK)。這些原則是具體的、機械的、可自動檢查的。 -2. **建立週期性的清理流程**:一組後臺 Codex 任務定期掃描偏差,更新質量評分,開針對性的重構 PR。大多數可以在一分鐘內審查並自動合併。就像宿舍安裝了自動掃地機器人——不用你動手,定期清理。 -3. **人類品味捕獲一次,持續執行**:審查意見、重構 PR、用戶側 bug 都被轉化為文檔更新或直接編碼到工具中。當文檔不夠用時,把規則提升為代碼。就像把宿舍公約從"口頭約定"變成"貼在門上的檢查表"。 - -這個機制就像垃圾回收——技術債是高息貸款,持續小額還清幾乎總是比攢到一次性爆發好得多。 - -> 來源:[OpenAI: Harness engineering: leveraging Codex in an agent-first world](https://openai.com/index/harness-engineering/) - -## 清潔狀態:不只看地上有沒有垃圾 - -清潔狀態不是單一的"代碼能編譯"。代碼能無錯構建——這是最基本的,下一個會話不應該先修構建錯誤。就像你搬出去的時候水電不能是斷的。所有測試也得通過,包括會話之前就存在的測試——會話有責任不破壞已有功能。而且要在 CI 環境驗證,不是"在我機器上通過"。 - -```mermaid -flowchart LR - Work["功能工作已完成"] --> Build{"建構通過?"} - Build -->|是| Test{"測試通過?"} - Build -->|否| Fix["先修好再退出"] - Test -->|是| Record["更新功能清單 + 進度"] - Test -->|否| Fix - Record --> Cleanup["清理临時工件 / 調試程式碼"] - Cleanup --> Startup{"標準啟動路徑可用?"} - Startup -->|是| Clean["干净交接"] - Startup -->|否| Fix - Fix --> Build -``` - -但這還不夠。當前進度必須記錄在機器可讀的工件中——已完成的子任務和通過標準、進行中但未完成的子任務和當前狀態、尚未開始的子任務。好的進度記錄減少 60-80% 的會話啟動診斷時間。調試日誌、臨時檔案、註釋掉的代碼、TODO 標記這些臨時工件得清理乾淨,它們增加下一個會話的認知負擔。標準啟動路徑也必須可用——下一個會話能不能不人工干預就開始工作?環境初始化、代碼庫加載、上下文獲取、任務選擇,這些路徑不能被破壞。 - -```mermaid -flowchart LR - Dirty["這次退出時
    測試是红的、临時檔案没删、進度也没寫"] --> Diagnose["下個會話先花時间
    搞清楚發生了什么"] - Diagnose --> Fragile["接着在一個很乱的倉庫上继續改"] - Fragile --> More["更多調試檔案、更多坏檢查、
    更多說不清的進度"] - More --> Dirty - - Clean["這次退出前
    測試是绿的、進度寫了、临時檔案删了"] --> Fast["下個會話打開倉庫就能继續寫程式碼"] - Fast --> Stable["不用先救火"] - Stable --> Clean -``` - -## 核心概念 - -- **清潔狀態**:會話結束時系統滿足五個條件——構建通過、測試通過、進度已記錄、無過時工件、啟動路徑可用。缺一個都不算"做完"。 -- **會話完整性**:類比數據庫事務——要麼全部提交併留下清潔狀態,要麼回滾到上一致狀態。沒有中間地帶。 -- **質量文檔**:對每個模塊的質量等級做持續記錄的活躍工件。不是一次性評估,而是跟蹤代碼庫是變強了還是變弱了。 -- **清理循環**:定期的維護會話,目標是系統性減少代碼庫中的熵。不是緊急修復,而是常規運維——就像宿舍的每週值日表。 -- **harness 簡化**:隨著模型能力提升,定期移除不再必要的 harness 組件。今天必要的約束,三個月後可能是多餘的開銷。 -- **冪等清理**:清理操作無論執行多少次都產生相同結果。確保清理在失敗重試場景中仍然安全。 - -## "以後再清理"是永遠不清理 - -最常見的心理陷阱是"這次來不及清理了,下次再弄"。但下次的 agent 不知道你上次留下了什麼——它看到的是一堆混亂的代碼和不確定的狀態。它會花大量時間推斷"這堆代碼裡哪些是有意的,哪些是臨時的"。 - -更糟的是,每個會話都有自己的任務目標。新會話來的時候是要做新功能的,不是來清理上一個會話的爛攤子的。它會忽略混亂直接開始新工作,然後在混亂的基礎上引入更多混亂。這是熵增的正反饋循環——就像宿舍越亂你越不想收拾,越不收拾越亂,最後誰都不想回宿舍了。 - -數據為證。一個使用 agent 持續開發 12 周的項目,沒有清潔策略的情況下: - -- 第 1 周:構建通過率 100%,測試通過率 100%,新會話啟動 5 分鐘 -- 第 4 周:構建 95%,測試 92%,啟動 15 分鐘 -- 第 8 周:構建 82%,測試 78%,啟動 35 分鐘 -- 第 12 周:構建 68%,測試 61%,啟動 60+ 分鐘 - -同樣的項目,有清潔策略的情況下: - -- 第 1 周:100%,100%,5 分鐘 -- 第 12 周:97%,95%,9 分鐘 - -12 周後,構建通過率差 29 個百分點,新會話啟動時間差 85%。這不是理論推導,是實際可觀測到的差異。每週不倒垃圾的宿舍和每週倒垃圾的宿舍,12 周後的差距是驚人的。 - -## 怎麼做 - -### 1. 清潔狀態是完成的必要條件 - -在 harness 裡明確定義:**會話完成 = 任務通過驗證 AND 清潔狀態檢查通過。** 缺任何一個,會話不算完成。在 CLAUDE.md 裡寫: - -``` -## 會話退出檢查清單 -- [ ] 建構通過 (npm run build) -- [ ] 所有測試通過 (npm test) -- [ ] 功能清單已更新 -- [ ] 无調試程式碼残留 (console.log, debugger, TODO) -- [ ] 標準啟動路徑可用 (npm run dev) -``` - -### 2. 雙模式清理策略 - -結合兩種清理模式: - -**即時清理(每個會話結束時)**:清理本次會話創建的臨時工件、更新功能清單狀態、確保構建和測試通過。這是"引用計數式"清理——用完就清。就像吃完飯馬上洗碗,不留到第二天。 - -**定期清理(每週一次)**:全系統掃描——處理累積的結構性問題、更新質量文檔、運行基準測試檢測漂移。這是"追蹤式"清理——定期做一次大掃除。 - -### 3. 維護質量文檔 - -質量文檔是對每個模塊持續評分的活躍工件——就像宿舍的衛生檢查評分表: - -```markdown -# 品質文檔 - -## 使用者認證模块 (品質: A) -- 驗證通過: 是 -- agent 可理解: 是 -- 測試稳定性: 稳定 -- 架构邊界: 合规 -- 程式碼規範: 遵循 - -## 支付模块 (品質: C) -- 驗證通過: 部分(支付回調未測試) -- agent 可理解: 困难(逻辑分散在 3 個檔案) -- 測試稳定性: 不稳定(2 個 flaky 測試) -- 架构邊界: 有违规 -- 程式碼規範: 部分遵循 -``` - -新會話讀這個文檔就知道優先處理哪裡。質量評分最低的模塊先修。就像衛生檢查表上標記了"需要重點打掃的區域"——下一個值日生知道重點在哪裡。 - -### 4. 定期簡化 harness - -harness 裡的每個組件之所以存在,是因為模型無法獨立做好某件事。但隨著模型改進,這些假設會過時。就像你大一的時候需要學長帶著選課,到了大三你自己就知道怎麼選了——學長的作用變小了,但有些事情你還是需要問。 - -Anthropic 的實驗直接展示了這一點。他們最初的 harness 包含 sprint 拆分機制——把工作分成小塊讓 Sonnet 4.5 逐個完成。當 Opus 4.6 發佈後,模型的原生能力已經可以自主處理工作分解,sprint 構造變成了不必要的開銷。移除後,builder agent 能連續工作超過兩小時而不會跑偏,反而更流暢。 - -但 evaluator 的情況不同。即使 Opus 4.6 能力更強,在任務接近模型能力邊界時,evaluator 仍然提供了實際價值——捕獲 generator 的遺漏功能和存根實現。這意味著 evaluator 不是一個固定的是/否決策,而是取決於任務難度相對於模型能力的位置。 - -**推薦做法**:每月挑一個 harness 組件,暫時禁用它,跑基準任務。如果結果沒退化,永久移除。如果退化了,恢復或用更輕量的替代。就像定期檢查宿舍公約——大四了還在執行"每天 10 點熄燈"就不太合理了。 - -一個更具體的原則:**隨著模型改進,harness 的有趣組合不是變少了,而是移動了。** 以前必須解決的問題被模型能力覆蓋了,但新的能力邊界打開了以前不可能的 harness 設計。AI 工程師的工作是持續找到下一個有價值的組合。 - -### 5. 清理操作必須冪等 - -清理腳本要能安全地重複執行——就像打掃衛生,掃兩遍比掃一遍更乾淨,但不會更髒: - -```bash -# 幂等的清理操作 -rm -f /tmp/debug-*.log # -f 确保檔案不存在時不报錯 -git checkout -- .env.local # 恢复到已知狀態 -npm run test # 驗證清理未破坏功能 -``` - -### 6. 高吞吐量改變了 merge 哲學 - -當 agent 的產出遠超人類審查能力時,傳統的 merge 哲學需要調整。OpenAI 團隊的經驗:在一個 agent 每天開 3.5 個 PR(且後來增加到更多)的環境裡,最小化阻塞式 merge gate 是正確的。PR 應該短命。測試 flake 通常用後續運行解決,而不是無限期阻塞進度。在一個修正成本很低、等待成本很高的系統裡,快速前進 + 快速修正是比緩慢確認更好的策略。 - -**注意**:這在低產出環境裡是不負責任的。但在 agent 產出遠超人類注意力的環境裡,這往往是正確的權衡。關鍵判斷標準:**修正一個 bug 的平均成本 vs 等待人類審查一個 PR 的平均成本。** 前者低於後者時,快速合併是對的。 - -## 實際案例 - -一個使用 agent 持續開發的 Electron 應用,12 周演化過程的對比數據: - -**無清潔策略(對照組)**——從不倒垃圾的宿舍:第 12 周,構建通過率 68%,測試通過率 61%,新會話啟動 60+ 分鐘,過時工件 103 個。 - -**有清潔策略(實驗組)**——每天值日的宿舍:每個會話結束時執行完整清潔檢查 + 每週清理循環。第 12 周,構建通過率 97%,測試通過率 95%,新會話啟動 9 分鐘,過時工件 11 個。 - -到第 12 周,實驗組的構建通過率比對照組高 29 個百分點,測試通過率高 34 個百分點,新會話啟動時間減少 85%。每天多花 5 分鐘打掃衛生,12 周後省下了幾十個小時的混亂時間。 - -## 關鍵要點 - -- **清潔狀態是會話完成的必要條件**——不是可選的善後工作,是"完成定義"的一部分。你不倒垃圾,下一個室友就得替你倒。 -- **五個維度缺一不可**——構建、測試、進度、工件、啟動,每個都要顯式檢查。 -- **質量文檔讓代碼庫健康可追蹤**——知道哪裡在退化才能主動修復。衛生檢查表不是形式主義,是讓你知道哪塊地還沒拖。 -- **定期簡化 harness**——隨著模型能力提升,移除不再必要的約束。大四了就別執行大一的宿舍公約了。 -- **"以後再清理"等於永遠不清理**——熵增是默認狀態,只有主動的清潔操作才能對抗它。 - -## 延伸閱讀 - -- [Clean Code - Robert C. Martin](https://www.goodreads.com/book/show/3735293-clean-code) — 代碼清潔性的系統化原則 -- [Harness Engineering - OpenAI](https://openai.com/index/harness-engineering/) — 可重複性作為 harness 設計的核心要求 -- [Effective Harnesses - Anthropic](https://www.anthropic.com/engineering/effective-harnesses-for-long-running-agents) — 清潔會話退出對長期可靠性的關鍵作用 -- [Programs, Life Cycles, and Laws of Software Evolution - Lehman](https://ieeexplore.ieee.org/document/1702314) — 軟件演化定律,證明系統複雜性在無主動維護時必然增長 - -## 練習 - -1. **清潔狀態檢查表**:為你的代碼庫設計一個會話退出檢查表,涵蓋五個維度。在 5 個連續會話中應用,記錄每個維度上的違反次數。 - -2. **基準對比實驗**:固定任務集,兩種 harness 變體(有/無清潔狀態要求)各跑一遍。比較完成率、重試次數和缺陷逃逸率。 - -3. **harness 簡化實踐**:選一個 harness 組件,暫時禁用,跑基準任務。比較有無該組件的結果。決定保留、移除還是替換。 diff --git a/courses/learn-harness-engineering/docs/zh-TW/projects/index.md b/courses/learn-harness-engineering/docs/zh-TW/projects/index.md deleted file mode 100644 index beb52de5..00000000 --- a/courses/learn-harness-engineering/docs/zh-TW/projects/index.md +++ /dev/null @@ -1,23 +0,0 @@ -# 歡迎來到專案實戰 - -這裡是 Learn Harness Engineering 的動手實踐部分。僅僅閱讀講義是不夠的,你需要親自搭建環境,觀察 Codex 或 Claude Code 等 Agent 在不同規則下的真實表現。 - -## 專案概覽 - -本課程包含 6 個漸進式的實戰專案,帶你從零開始建構一個完整的 Agent 工作環境: - -1. **提示詞驅動 vs 規則驅動**:對比一個純 Prompt 驅動的 Agent 和帶有基礎 Harness 的 Agent 在表現上的差異。 -2. **讓專案可讀並接住上次工作**:學習如何重構程式碼倉庫結構,使其對 AI 更加友好,並建立工作交接機制。 -3. **跨會話工作連續性**:設計狀態檔案和初始化腳本,讓 Agent 可以在多次對話中斷後無縫恢復工作。 -4. **執行回饋與行為修正**:引入執行時回饋機制,讓 Agent 能夠自己檢查程式碼是否能跑通,並修正錯誤。 -5. **工作評審與自我驗證**:建立獨立的評審與驗證環節,防止 Agent 產生「幻覺」或提前宣告勝利。 -6. **綜合 Agent 工作環境**:搭建一套完整的、帶有可觀測性的最終 Agent 工作環境(綜合演練)。 - -## 如何進行 - -每個專案資料夾中通常包含: -- `starter/`:你的起始工作區。 -- `solution/`:參考的最終實作(如果你卡住了可以參考)。 -- 相關的指導檔案,說明本次任務的具體背景和目標。 - -請使用你習慣的 AI Coding Agent(如 Claude Code、Cursor、Trae 等)在每個專案的 `starter/` 目錄下完成任務。 diff --git a/courses/learn-harness-engineering/docs/zh-TW/projects/project-01-baseline-vs-minimal-harness/index.md b/courses/learn-harness-engineering/docs/zh-TW/projects/project-01-baseline-vs-minimal-harness/index.md deleted file mode 100644 index 22bbe769..00000000 --- a/courses/learn-harness-engineering/docs/zh-TW/projects/project-01-baseline-vs-minimal-harness/index.md +++ /dev/null @@ -1,70 +0,0 @@ -# Project 01. 只寫提示詞讓 agent 做,和定好規則再讓它做,差多少 - -> 相關講義:[L01. 模型能力強,不等於執行可靠](./../../lectures/lecture-01-why-capable-agents-still-fail/index.md) · [L02. Harness 到底是什麼](./../../lectures/lecture-02-what-a-harness-actually-is/index.md) -> 本篇範本檔案:[templates/](https://github.com/walkinglabs/learn-harness-engineering/blob/main/docs/zh-TW/resources/templates/) - -## 你要做什麼 - -用 Electron 搭一個最簡的知識庫應用殼子——能啟動窗口、左邊顯示文檔列表、右邊顯示問答面板、本地有一個數據目錄。任務本身不復雜,複雜的是你怎麼讓 agent 完成它。 - -你需要跑兩次。第一次只給一段提示詞,什麼都不準備,看 agent 能做到什麼程度。第二次提前在倉庫裡放好 `AGENTS.md`、`init.sh`、`feature_list.json`,用結構化的方式告訴 agent 該幹什麼、怎麼驗證、什麼時候算做完。然後對比兩次結果。 - -這個項目的核心不是寫代碼,是搞清楚"提前花 15 分鐘準備規則"和"上來就讓 agent 幹"之間到底差多少。 - -## 用什麼工具 - -- Claude Code 或 Codex(選一個,兩次都用同一個) -- Git(管理分支和對比) -- Node.js + Electron(項目技術棧) -- 一個計時器(記錄每次運行時間) - -## 具體步驟 - -### 準備工作 - -1. 從一個乾淨的 commit 出發,記錄 commit hash。 -2. 創建兩個分支:`p01-baseline` 和 `p01-improved`。 -3. 準備同一段任務提示詞,內容是:"用 Electron 做一個知識庫應用,窗口左邊是文檔列表區域,右邊是問答面板區域,應用需要創建並使用本地數據目錄。" - -### 第一次運行(弱 harness) - -切到 `p01-baseline` 分支。 - -1. 只用上面那段提示詞啟動 agent。 -2. 不提供 `AGENTS.md`,不提供啟動腳本,不提供驗收標準。 -3. 設定相同的時間上限和輪次上限(建議 30 分鐘 / 20 輪)。 -4. agent 停下後,運行 `npm start`(或對應啟動命令),看應用能不能跑起來。 -5. 記錄:終端輸出、關鍵 diff、agent 的最終總結。 -6. **不要手動修代碼**。跑不起來就是跑不起來,如實記錄。 - -### 第二次運行(強 harness) - -切到 `p01-improved` 分支。在啟動 agent 之前,先在倉庫裡準備好: - -- `AGENTS.md`:寫明項目結構、啟動命令、Electron 層邊界規則 -- `init.sh`:一鍵恢復可運行狀態(`npm install && npm start`) -- `feature_list.json`:列出四個功能點及其完成狀態 - -然後用和第一次相同的提示詞啟動 agent,同樣的時間上限和輪次上限。agent 停下後,跑 `./init.sh`,記錄結果。 - -## 怎麼衡量結果 - -| 指標 | 說明 | -|------|------| -| 完成狀態 | 完全完成 / 部分完成 / 失敗 | -| 首次成功啟動時間 | 從開始到 `npm start` 第一次成功運行 | -| 重試次數 | 中間需要人工干預幾次才能跑起來 | -| 遺漏項 | agent 宣佈完成時還有哪些功能沒做 | -| 過早停止 | agent 是否在不可運行狀態就宣佈完成 | - -## 要交什麼 - -- 弱 harness 運行記錄:提示詞、日誌/對話記錄、最終 diff、啟動證據 -- 強 harness 運行記錄:同上,加上你準備的 harness 檔案 -- 一份對比筆記(1-2 頁):兩次運行的差異、數據、結論 - -## 對應講義 - -- [Lecture 01 — 為什麼強 agent 仍然失敗](../../lectures/lecture-01-why-capable-agents-still-fail/index.md) -- [Lecture 02 — harness 到底是什麼](../../lectures/lecture-02-what-a-harness-actually-is/index.md) -- [Lecture 06 — 為什麼初始化需要單獨一個階段](../../lectures/lecture-06-why-initialization-needs-its-own-phase/index.md) diff --git a/courses/learn-harness-engineering/docs/zh-TW/projects/project-02-agent-readable-workspace/index.md b/courses/learn-harness-engineering/docs/zh-TW/projects/project-02-agent-readable-workspace/index.md deleted file mode 100644 index b5c4c553..00000000 --- a/courses/learn-harness-engineering/docs/zh-TW/projects/project-02-agent-readable-workspace/index.md +++ /dev/null @@ -1,88 +0,0 @@ -# Project 02. 讓 agent 看懂項目、接住上次的工作 - -> 相關講義:[L03. 讓代碼倉庫成為唯一的事實來源](./../../lectures/lecture-03-why-the-repository-must-become-the-system-of-record/index.md) · [L04. 為什麼一個巨大的指令檔案會失敗](./../../lectures/lecture-04-why-one-giant-instruction-file-fails/index.md) -> 本篇範本檔案:[templates/](https://github.com/walkinglabs/learn-harness-engineering/blob/main/docs/zh-TW/resources/templates/) - -## 你要做什麼 - -P01 證明了準備規則有用。但 P01 的任務一次會話就幹完了。真實開發不是這樣的——你昨天干了一半,今天開新會話,agent 得從倉庫狀態裡搞清楚"做了什麼、沒做什麼、接下來幹什麼"。 - -這個項目要求你給倉庫加上"可讀性":讓一個全新的 agent 打開倉庫後,能快速理解項目結構、知道當前進度、接手上次的工作。具體任務是給知識庫應用加上三個功能:文檔導入、文檔詳情頁、導入後的本地持久化。這些功能必須跨至少兩個 agent 會話完成。 - -你需要跑兩次。第一次不給 agent 任何幫助,看它在第二個會話裡要花多久才能"接上"。第二次提前放好 `ARCHITECTURE.md`、`PRODUCT.md`、`session-handoff.md`,讓它快速對齊上下文。 - -## 用什麼工具 - -- Claude Code 或 Codex(和 P01 保持一致) -- Git -- Node.js + Electron -- 文本編輯器(寫文檔用) - -## 具體步驟 - -### 準備工作 - -1. 基於 P01 完成後的代碼,從同一個 commit 出發。 -2. 創建兩個分支:`p02-baseline` 和 `p02-improved`。 -3. 列出要實現的三個功能:文檔導入流程、文檔詳情視圖、文檔持久化。兩條分支任務範圍完全一致。 - -### 第一次運行(弱 harness) - -切到 `p02-baseline` 分支。 - -**會話 A:** - -1. 啟動 agent,只給任務描述,不給架構文檔,不給進度檔案。 -2. 故意在功能完成之前停止會話(比如只完成文檔導入)。 -3. 不寫任何交接檔案。直接結束。 - -**會話 B:** - -1. 開一個全新的 agent 會話。 -2. 只說"繼續開發",不給額外上下文。 -3. 記錄 agent 花了多久才做出第一個有意義的代碼修改。 -4. 記錄 agent 哪些時候在"重新發現"本來已經知道的東西。 - -### 第二次運行(強 harness) - -切到 `p02-improved` 分支。在第一個會話之前,先在倉庫裡準備好: - -- `ARCHITECTURE.md`:描述項目結構、Electron 各層職責、數據流 -- `PRODUCT.md`:描述產品功能範圍和當前階段目標 -- `AGENTS.md`:啟動命令、工作規則、驗證方式 -- `init.sh`:一鍵恢復可運行狀態 - -**會話 A:** - -1. 啟動 agent,讓它開始工作。 -2. 同樣在功能完成之前停止。 -3. **這次要求 agent 更新 `session-handoff.md`**:記錄做了什麼、沒做什麼、下一步是什麼。 - -**會話 B:** - -1. 開一個全新的 agent 會話。 -2. 讓 agent 讀 `session-handoff.md` 和 `feature_list.json`,然後繼續。 -3. 同樣記錄接手速度和重複工作比例。 - -## 怎麼衡量結果 - -| 指標 | 說明 | -|------|------| -| 會話 B 接手時間 | 從開始到第一個有效代碼修改的時間 | -| 重新發現次數 | agent 重新瞭解架構、命令、狀態等已有信息的次數 | -| 交接檔案質量 | 交接記錄是否完整、準確、可操作 | -| 重複工作比例 | 會話 B 中有多少工作量是在重複會話 A 已做過的事 | -| 最終完成狀態 | 三個功能是否全部完成 | - -## 要交什麼 - -- 弱 harness 的會話 A + B 日誌/對話記錄 -- 強 harness 的會話 A + B 日誌/對話記錄 -- 兩次運行中產生的交接檔案 -- 一份對比筆記:重點比較接手速度和上下文恢復質量 - -## 對應講義 - -- [Lecture 03 — 為什麼倉庫必須成為唯一事實來源](../../lectures/lecture-03-why-the-repository-must-become-the-system-of-record/index.md) -- [Lecture 04 — 為什麼一個大而全的指令檔案不行](../../lectures/lecture-04-why-one-giant-instruction-file-fails/index.md) -- [Lecture 05 — 為什麼長任務會丟失連續性](../../lectures/lecture-05-why-long-running-tasks-lose-continuity/index.md) diff --git a/courses/learn-harness-engineering/docs/zh-TW/projects/project-03-multi-session-continuity/index.md b/courses/learn-harness-engineering/docs/zh-TW/projects/project-03-multi-session-continuity/index.md deleted file mode 100644 index a6cf4c8b..00000000 --- a/courses/learn-harness-engineering/docs/zh-TW/projects/project-03-multi-session-continuity/index.md +++ /dev/null @@ -1,77 +0,0 @@ -# Project 03. 讓 agent 關掉再打開還能接著幹 - -> 相關講義:[L05. 為什麼長時任務會丟失上下文](./../../lectures/lecture-05-why-long-running-tasks-lose-continuity/index.md) · [L06. 為什麼初始化需要單獨一個階段](./../../lectures/lecture-06-why-initialization-needs-its-own-phase/index.md) -> 本篇範本檔案:[templates/](https://github.com/walkinglabs/learn-harness-engineering/blob/main/docs/zh-TW/resources/templates/) - -## 你要做什麼 - -P02 解決了"接手"的問題,但 agent 接手之後能不能把活幹完、幹對了,又是另一回事。這個項目要你給 agent 加上範圍控制和驗證關卡。 - -你要實現的知識庫功能是:文檔分塊、元數據提取、索引進度顯示、帶引用的問答流程。這些功能比前兩個項目複雜,agent 更容易跑偏——要麼多做了不該做的,要麼說做完了但其實沒通過驗證。 - -你需要一個 `feature_list.json`,每個功能有明確的狀態(failing / passing)。規則很簡單:一次只做一個功能,沒有可運行的驗證證據就不能標成 passing。跑兩次,一次不給這些約束,一次嚴格執行,看結果差多少。 - -## 用什麼工具 - -- Claude Code 或 Codex -- Git -- Node.js + Electron -- `feature_list.json`(範本參考 `docs/zh-TW/resources/templates/feature_list.json`) - -## 具體步驟 - -### 準備工作 - -1. 基於 P02 完成後的代碼,從同一個 commit 出發。 -2. 創建兩個分支:`p03-baseline` 和 `p03-improved`。 -3. 定義四個功能:文檔分塊、元數據提取、索引進度 UI 顯示、帶引用的問答。兩條分支的功能定義完全一致。 - -### 第一次運行(弱 harness) - -切到 `p03-baseline` 分支。 - -1. 啟動 agent,給一段模糊的任務提示詞。 -2. 不提供 `feature_list.json`,沒有狀態追蹤。 -3. 不限制 agent 一次做幾個功能。 -4. 沒有明確的驗證標準——agent 自己說"完成了"就算。 -5. 運行結束後,手動檢查每個功能是否真的能用。 -6. 記錄哪些功能 agent 聲稱完成但實際沒通過驗證。 - -### 第二次運行(強 harness) - -切到 `p03-improved` 分支。在啟動 agent 之前: - -- 在倉庫根目錄放好 `feature_list.json`,四個功能全部標為 `failing`。 -- 在 `AGENTS.md` 裡寫明規則:一次只做一個功能;狀態只能從 `failing` 切到 `passing`,且必須有驗證證據。 -- 準備好 `init.sh`。 - -然後啟動 agent: - -1. agent 開始工作,每完成一個功能必須更新 `feature_list.json` 並附上驗證證據(截圖、測試輸出等)。 -2. 至少要有一個功能展示從 `failing` 到 `passing` 的完整轉換過程。 -3. 問答功能的驗證必須檢查引用是否存在、引用是否相關,不能只看有沒有輸出。 -4. 運行結束後歸檔所有驗證證據。 - -## 怎麼衡量結果 - -| 指標 | 說明 | -|------|------| -| 範圍漂移次數 | agent 做了功能清單之外的事的次數 | -| 虛假完成率 | agent 聲稱完成但驗證不通過的功能比例 | -| 驗證覆蓋率 | 有明確驗證證據的功能佔總功能的百分比 | -| 問答質量 | 引用是否存在、引用是否相關 | -| 重試次數 | 從開始到所有功能 passing 總共重試了幾次 | - -## 要交什麼 - -- 弱 harness 運行記錄:提示詞、日誌、驗證結果 -- 強 harness 運行記錄:`feature_list.json` 變更歷史、日誌、驗證證據 -- 至少一個功能從 `failing` 到 `passing` 的轉換證據 -- 一份對比筆記:重點看範圍紀律和完成準確度 - -## 對應講義 - -- [Lecture 07 — 為什麼 agent 總是做過頭、做不完](../../lectures/lecture-07-why-agents-overreach-and-under-finish/index.md) -- [Lecture 08 — 為什麼 feature list 是 harness 的基礎原語](../../lectures/lecture-08-why-feature-lists-are-harness-primitives/index.md) -- [Lecture 09 — 為什麼 agent 總是過早宣佈勝利](../../lectures/lecture-09-why-agents-declare-victory-too-early/index.md) -- [Lecture 10 — 為什麼端到端測試能改變結果](../../lectures/lecture-10-why-end-to-end-testing-changes-results/index.md) diff --git a/courses/learn-harness-engineering/docs/zh-TW/projects/project-04-incremental-indexing/index.md b/courses/learn-harness-engineering/docs/zh-TW/projects/project-04-incremental-indexing/index.md deleted file mode 100644 index bd48f665..00000000 --- a/courses/learn-harness-engineering/docs/zh-TW/projects/project-04-incremental-indexing/index.md +++ /dev/null @@ -1,76 +0,0 @@ -# Project 04. 用運行反饋修正 agent 的行為 - -> 相關講義:[L07. 為什麼 agent 會多做或少做](./../../lectures/lecture-07-why-agents-overreach-and-under-finish/index.md) · [L08. 為什麼功能清單是 harness 原語](./../../lectures/lecture-08-why-feature-lists-are-harness-primitives/index.md) -> 本篇範本檔案:[templates/](https://github.com/walkinglabs/learn-harness-engineering/blob/main/docs/zh-TW/resources/templates/) - -## 你要做什麼 - -前三個項目關注的是"讓 agent 把活幹完"。這個項目關注的是"出了問題怎麼修"——而且不是你修,是讓 agent 自己通過運行時信號來修。 - -你要做兩件事。第一,給知識庫應用加上運行時可觀測性:啟動日誌、導入和索引的日誌、問答失敗時的用戶可見錯誤狀態。第二,在倉庫裡編碼架構約束,讓 agent 不可能靜默地跨層違規(main / preload / renderer / services 之間的邊界)。 - -然後你會在代碼裡埋一個運行時 bug,讓 agent 去修。跑兩次:一次不給日誌和約束,看 agent 怎麼修;一次給好日誌和架構規則,看差別。 - -## 用什麼工具 - -- Claude Code 或 Codex -- Git -- Node.js + Electron -- 日誌庫(如 `electron-log` 或簡單的 console 封裝) -- 結構化檢查工具(ESLint 自定義規則、guard 腳本、或測試) - -## 具體步驟 - -### 準備工作 - -1. 基於 P03 完成後的代碼,從同一個 commit 出發。 -2. 創建兩個分支:`p04-baseline` 和 `p04-improved`。 -3. 在兩個分支中埋入同一個運行時 bug(比如:索引時 chunk 大小讀取錯誤導致問答返回空結果)。 -4. 記錄 bug 的位置和表現,但不要告訴 agent bug 在哪。 - -### 第一次運行(弱 harness) - -切到 `p04-baseline` 分支。 - -1. 啟動 agent,告訴它"問答功能返回空結果,請修復"。 -2. 倉庫裡沒有運行時日誌,沒有架構約束檔案。 -3. 記錄 agent 花了多久找到根因、怎麼確認修復的、修復過程中是否引入了新的層邊界違規。 -4. 修復後重啟應用,確認是否能幹淨啟動。 - -### 第二次運行(強 harness) - -切到 `p04-improved` 分支。在啟動 agent 之前,先在倉庫裡準備好: - -- **運行時日誌**:啟動時打印初始化步驟,導入時打印檔案數量和 chunk 結果,索引時打印進度,問答失敗時打印錯誤原因。 -- **架構約束**:在 `AGENTS.md` 裡明確寫 Electron 四層邊界(main / preload / renderer / services),說明哪些調用路徑是允許的。用 ESLint 規則或 guard 腳本檢查違規。 -- **乾淨狀態要求**:最終交付前必須能幹淨重啟。 - -然後啟動 agent: - -1. 先讓 agent 加上日誌和結構化檢查(這是任務的一部分)。 -2. 然後告訴它"問答功能返回空結果,請修復"。 -3. 這次 agent 有日誌可以看,有架構規則可以參照。 -4. 記錄診斷速度、修復質量、是否引入了邊界違規。 -5. 修復後重啟應用,確認乾淨啟動。 - -## 怎麼衡量結果 - -| 指標 | 說明 | -|------|------| -| 根因定位時間 | 從開始到 agent 準確描述 bug 原因 | -| 修復確認時間 | 從定位到驗證修復成功 | -| 邊界違規數 | 修復過程中引入的跨層違規次數 | -| 日誌有用性 | 日誌是否直接指向了失敗原因 | -| 乾淨重啟 | 修復後是否能幹淨重啟、無報錯 | - -## 要交什麼 - -- 弱 harness 的調試記錄:診斷過程、修復 diff、啟動證據 -- 強 harness 的調試記錄:同上,加上日誌和架構約束檔案 -- 結構化檢查產物(lint 規則、guard 腳本、或測試檔案) -- 一份對比筆記:重點比較診斷速度和修復健壯性 - -## 對應講義 - -- [Lecture 11 — 為什麼可觀測性屬於 harness 的一部分](../../lectures/lecture-11-why-observability-belongs-inside-the-harness/index.md) -- [Lecture 12 — 為什麼每個會話都必須留下乾淨狀態](../../lectures/lecture-12-why-every-session-must-leave-a-clean-state/index.md) diff --git a/courses/learn-harness-engineering/docs/zh-TW/projects/project-05-grounded-qa-verification/index.md b/courses/learn-harness-engineering/docs/zh-TW/projects/project-05-grounded-qa-verification/index.md deleted file mode 100644 index 022be090..00000000 --- a/courses/learn-harness-engineering/docs/zh-TW/projects/project-05-grounded-qa-verification/index.md +++ /dev/null @@ -1,93 +0,0 @@ -# Project 05. 讓 agent 自己檢查自己做的對不對 - -> 相關講義:[L09. 為什麼 agent 會提前宣告完成](./../../lectures/lecture-09-why-agents-declare-victory-too-early/index.md) · [L10. 為什麼端到端測試會改變結果](./../../lectures/lecture-10-why-end-to-end-testing-changes-results/index.md) -> 本篇範本檔案:[templates/](https://github.com/walkinglabs/learn-harness-engineering/blob/main/docs/zh-TW/resources/templates/) - -## 你要做什麼 - -前四個項目裡,"檢查做得對不對"這件事要麼是你手動做的,要麼是靠檔案規則強制執行的。這個項目要讓 agent 自己來檢查。 - -方法是角色分離。以前是同一個 agent 既寫代碼又判斷質量。現在拆開:一個 agent 負責實現(生成者),另一個 agent 負責審查(評估者)。更進一步,還可以加一個負責規劃的角色(規劃者)。你要跑三次,看每加一層角色分離,結果好多少。 - -選一個實質性的功能升級(比如多輪對話歷史、引用面板重設計、文檔集合與篩選),三次都做同一個升級,唯一變量是 harness 的角色分工。 - -## 用什麼工具 - -- Claude Code 或 Codex(用於生成和規劃角色) -- 同一個或另一個 agent 實例(用於評估角色) -- 評估量表(參考 `docs/zh-TW/resources/templates/evaluator-rubric.md`) -- Sprint contract 範本(參考 `docs/lectures/lecture-11-why-observability-belongs-inside-the-harness/code/sprint-contract.md`) - -## 具體步驟 - -### 準備工作 - -1. 基於 P04 完成後的代碼,從同一個 commit 出發。 -2. 創建三個分支:`p05-single`、`p05-gen-eval`、`p05-plan-gen-eval`。 -3. 選定一個功能升級,寫清楚驗收標準。三次運行的功能範圍和驗收標準完全一致。 -4. 寫好評估量表,打分維度至少包括:正確性、可靠性、可維護性、用戶體驗。 - -### 第一次運行(弱 harness — 單角色) - -切到 `p05-single` 分支。 - -1. 一個 agent 包攬規劃、實現、自查。 -2. 沒有外部評估量表,agent 自己判斷做得好不好。 -3. 記錄最終產出和未解決的缺陷。 - -### 第二次運行(強 harness — 生成者 + 評估者) - -切到 `p05-gen-eval` 分支。 - -1. 生成者和評估者先用 sprint contract 對齊:做什麼、怎麼驗證、什麼不算在範圍內。 -2. 生成者實現功能。 -3. 評估者用評估量表打分,反饋問題。 -4. 生成者根據反饋修改。至少經過一輪修訂。 -5. 記錄評估者抓住了多少缺陷、修訂了多少內容。 - -### 第三次運行(更強 harness — 規劃者 + 生成者 + 評估者) - -切到 `p05-plan-gen-eval` 分支。 - -1. 規劃者先拆解任務、定義步驟和依賴關係。 -2. 生成者按規劃實現。 -3. 評估者用同一份評估量表打分。 -4. 至少經過一輪修訂。 -5. 記錄規劃是否減少了返工。 - -### 評估者調優 - -評估者不是一次就能用好的。初始版本的評估者往往會"發現問題然後自己說服自己通過"。你需要: - -1. 讓評估者給一個已完成的 sprint 打分。 -2. 和你自己的判斷對比。 -3. 哪裡有分歧,就把量表寫得更具體。 -4. 重新跑評估者,檢查對齊程度。 -5. 重複 3-5 輪,直到評估者判斷和你的判斷基本一致。記錄每一輪調優的內容。 - -## 怎麼衡量結果 - -| 指標 | 說明 | -|------|------| -| 範圍定義質量 | 編碼前的任務拆解清晰度 | -| 缺陷檢出數 | 交付前被評估者抓到的缺陷數量和嚴重程度 | -| 量表評分 | 各維度的得分(正確性、可靠性、可維護性、UX) | -| 返工量 | 評估反饋後需要重做的比例 | -| 最終健壯性 | 升級後功能在實際運行中的穩定程度 | -| 評估者調優輪數 | 評估者判斷與你對齊需要的迭代次數 | -| Sprint contract 效果 | 合同是否減少了評估中的模糊地帶 | - -## 要交什麼 - -- Sprint contract 文檔 -- 評估者調優日誌(每輪改了什麼、為什麼改) -- 單角色運行記錄:提示詞、日誌、可運行證據 -- 生成者+評估者運行記錄:含量表評分和修訂記錄 -- 規劃者+生成者+評估者運行記錄:含規劃文檔、量表評分、修訂記錄 -- 一份對比筆記:質量提升幅度和協調成本 - -## 對應講義 - -- [Lecture 09 — 為什麼 agent 總是過早宣佈勝利](../../lectures/lecture-09-why-agents-declare-victory-too-early/index.md) -- [Lecture 10 — 為什麼端到端測試能改變結果](../../lectures/lecture-10-why-end-to-end-testing-changes-results/index.md) -- [Lecture 11 — 為什麼可觀測性屬於 harness 的一部分](../../lectures/lecture-11-why-observability-belongs-inside-the-harness/index.md) diff --git a/courses/learn-harness-engineering/docs/zh-TW/projects/project-06-runtime-observability-and-debugging/index.md b/courses/learn-harness-engineering/docs/zh-TW/projects/project-06-runtime-observability-and-debugging/index.md deleted file mode 100644 index bae97c40..00000000 --- a/courses/learn-harness-engineering/docs/zh-TW/projects/project-06-runtime-observability-and-debugging/index.md +++ /dev/null @@ -1,101 +0,0 @@ -# Project 06. 搭建一套完整的 agent 工作環境 - -> 相關講義:[L11. 為什麼可觀測性屬於 harness](./../../lectures/lecture-11-why-observability-belongs-inside-the-harness/index.md) · [L12. 為什麼每次會話都要留乾淨狀態](./../../lectures/lecture-12-why-every-session-must-leave-a-clean-state/index.md) -> 本篇範本檔案:[templates/](https://github.com/walkinglabs/learn-harness-engineering/blob/main/docs/zh-TW/resources/templates/) - -## 你要做什麼 - -這是結業項目。把前五個項目學到的所有東西組裝起來,跑一次完整的基準測試,然後做一輪清理,驗證質量是可以持續維護的。 - -你要用一套固定的多功能任務集,覆蓋知識庫應用的完整產品切片:導入文檔、構建索引、帶引用的問答、運行時可觀測性、可讀可重啟的倉庫狀態。先跑一次弱 harness 基線,再跑一次你組裝的最強 harness,然後做一輪清理和重跑。最後還要做一次 harness 精簡實驗——刪掉一個組件看看結果會不會變差,判斷哪些組件是真正有用的、哪些是多餘的開銷。 - -## 用什麼工具 - -- Claude Code 或 Codex -- Git -- Node.js + Electron -- 質量文檔範本(`docs/zh-TW/resources/templates/quality-document.md`) -- 評估量表(`docs/zh-TW/resources/templates/evaluator-rubric.md`) -- 前五個項目積累的所有 harness 組件 - -## 具體步驟 - -### 準備工作 - -1. 基於 P05 完成後的代碼,從同一個 commit 出發。 -2. 創建兩個分支:`p06-baseline` 和 `p06-improved`。 -3. 用質量文檔範本給當前代碼打一次初始評分(每個產品領域和架構層的等級)。 -4. 定義一套固定的基準任務集和評分表——在跑任何 agent 之前就定好,跑的過程中不改。 - -基準任務集至少包括: - -- 導入一篇文檔 -- 構建或刷新索引 -- 回答一個帶引用的問題 -- 查看運行時日誌確認可觀測性 -- 關掉重開後狀態仍在 - -### 第一次運行(弱 harness) - -切到 `p06-baseline` 分支。 - -1. 用課程早期階段的弱 harness(沒有完整交接檔案、沒有嚴格驗證、可觀測性不足)。 -2. 用 agent 跑完整個基準任務集。 -3. 立刻評分。記錄每個任務的完成狀態、重試次數、缺陷數。 -4. 更新質量文檔,記錄每個領域和層的等級變化。 - -### 第二次運行(強 harness) - -切到 `p06-improved` 分支。 - -1. 用你在這門課裡組裝的最強 harness:交接檔案和啟動腳本、明確的範圍和驗證關卡、運行時信號和架構約束、評估者或多角色審查、質量文檔追蹤。 -2. 同樣的基準任務集,同樣的模型和預算。 -3. 立刻評分。記錄結果。 -4. 更新質量文檔。 - -### 清理和重跑 - -在 `p06-improved` 分支上: - -1. 做一輪清理:刪死代碼、修不清楚的文檔、理順不穩定的運行路徑。 -2. 清理後重跑同樣的基準任務集,重新評分。 -3. 更新質量文檔。 - -對比三個快照的質量文檔:基線、強 harness、清理後。 - -### Harness 精簡實驗 - -1. 從 `p06-improved` 分支中刪掉一個 harness 組件(比如刪掉 sprint contract,或者刪掉顯式範圍關卡)。 -2. 重跑基準任務集。 -3. 如果結果沒變差——說明這個組件是多餘的開銷,可以去掉。 -4. 如果結果變差了——說明這個組件是承重的,必須保留。 -5. 記錄實驗結果。可以多試幾個組件。 - -## 怎麼衡量結果 - -| 指標 | 說明 | -|------|------| -| 基準完成率 | 基準任務集中成功完成的比例 | -| 重試次數 | 每個任務需要重試幾次 | -| 缺陷數 | 人工干預前發現的缺陷數量 | -| 清理工作量 | 清理花了多長時間、改了多少檔案 | -| 清理後可讀性和重啟成功率 | 清理後倉庫的可維護程度 | -| 質量文檔等級變化 | 三個快照的等級對比 | -| Harness 精簡結果 | 哪些組件可以刪、哪些是承重的 | - -## 要交什麼 - -- 質量文檔的三個快照(基線、強 harness、清理後) -- 基線基準測試記錄:評分和證據 -- 強 harness 基準測試記錄:評分和證據 -- 清理運行記錄:清理前後評分變化 -- Harness 精簡日誌:刪了什麼組件、基準結果、決定保留還是刪 -- 最終結業總結:關鍵經驗教訓 - -## 對應講義 - -- [Lecture 01 — 為什麼強 agent 仍然失敗](../../lectures/lecture-01-why-capable-agents-still-fail/index.md) -- [Lecture 03 — 為什麼倉庫必須成為唯一事實來源](../../lectures/lecture-03-why-the-repository-must-become-the-system-of-record/index.md) -- [Lecture 08 — 為什麼 feature list 是 harness 的基礎原語](../../lectures/lecture-08-why-feature-lists-are-harness-primitives/index.md) -- [Lecture 11 — 為什麼可觀測性屬於 harness 的一部分](../../lectures/lecture-11-why-observability-belongs-inside-the-harness/index.md) -- [Lecture 12 — 為什麼每個會話都必須留下乾淨狀態](../../lectures/lecture-12-why-every-session-must-leave-a-clean-state/index.md) diff --git a/courses/learn-harness-engineering/docs/zh-TW/resources/index.md b/courses/learn-harness-engineering/docs/zh-TW/resources/index.md deleted file mode 100644 index b410cb09..00000000 --- a/courses/learn-harness-engineering/docs/zh-TW/resources/index.md +++ /dev/null @@ -1,52 +0,0 @@ -# 繁體中文資源庫 - -這個資料夾把課程裡的方法整理成可以直接參考和複用的材料,不是再講一遍概念,而是盡量回答兩個問題: - -- 我現在應該先抄哪些檔案 -- 這些檔案分別解決什麼問題 - -## 適合什麼時候用 - -當你準備讓 Codex、Claude Code 或其他 coding agent 在一個倉庫裡持續工作時,就可以從這裡開始。它特別適合這些場景: - -- 多輪會話開發,擔心上下文斷裂 -- 功能多,容易出現做一半就停的半成品 -- 常常提前宣布完成,但實際沒測透 -- 每次開工都要重新摸索啟動方式 - -## 從這裡開始 - -如果你想先搭一個最小可用版本,優先看這些檔案: - -- 根指令檔案:[`templates/AGENTS.md`](./templates/AGENTS.md) 或 [`templates/CLAUDE.md`](./templates/CLAUDE.md) -- 功能狀態檔案:[`templates/feature_list.json`](./templates/feature_list.json) -- 進度日誌:[`templates/claude-progress.md`](./templates/claude-progress.md) -- 啟動腳本參考:`docs/zh-TW/resources/templates/init.sh` - -然後按需要補上: - -- 會話交接範本:[`templates/session-handoff.md`](./templates/session-handoff.md) -- 收尾檢查清單:[`templates/clean-state-checklist.md`](./templates/clean-state-checklist.md) -- 評審範本:[`templates/evaluator-rubric.md`](./templates/evaluator-rubric.md) - -如果你想直接採用 OpenAI 那篇 harness engineering 文章裡更完整的倉庫組織方式,可以繼續看: - -- [`openai-advanced/index.md`](./openai-advanced/index.md) - -## 資源庫結構 - -- [`templates/`](./templates/index.md):可以直接複製到真實倉庫裡的範本 -- [`reference/`](./reference/index.md):方法說明、啟動流程和問題對照表 -- [`openai-advanced/`](./openai-advanced/index.md):更完整的 OpenAI 風格高階資源包,包括倉庫骨架、品質檔案、執行計畫和系統級治理檔案 - -## 推薦最小組合 - -- `AGENTS.md` 或 `CLAUDE.md` -- `feature_list.json` -- `claude-progress.md` -- `init.sh` - -先把這四樣放進專案裡,再開始讓 agent 持續工作,通常就已經能明顯降低返工和瞎猜。 - -如果你的倉庫已經進入多模組、多階段、多角色協作,可以直接升級到 -[`openai-advanced/`](./openai-advanced/index.md) 這一套高階結構,而不是繼續把最小範本硬撐成一個大而亂的系統。 diff --git a/courses/learn-harness-engineering/docs/zh-TW/resources/openai-advanced/index.md b/courses/learn-harness-engineering/docs/zh-TW/resources/openai-advanced/index.md deleted file mode 100644 index faad1356..00000000 --- a/courses/learn-harness-engineering/docs/zh-TW/resources/openai-advanced/index.md +++ /dev/null @@ -1,76 +0,0 @@ -# OpenAI 高級資源包 - -這個檔案夾把 OpenAI 那篇《Harness engineering: leveraging Codex in an -agent-first world》裡更完整、更高階的倉庫組織方式整理成可以直接參考和複製的範本。 - -當最小 harness 已經不夠用,而你的倉庫開始需要下面這些能力時,就該看這一套: - -- 一個簡短、負責路由的 `AGENTS.md` -- 放在倉庫裡的“唯一事實來源”文檔體系 -- 活躍執行計劃與已完成計劃的分層管理 -- 明確的產品、可靠性、安全、前端治理檔案 -- 按產品領域和架構層持續更新的質量評分 -- 面向模型閱讀的參考材料目錄 -- 針對架構、知識沉澱、運行驗證的標準 SOP - -## 包含的目錄骨架 - -[`repo-template/`](./repo-template/index.md) 裡提供了一套可直接複製的起步 -結構,核心佈局如下: - -```text -AGENTS.md -ARCHITECTURE.md -docs/ -├── design-docs/ -│ ├── index.md -│ └── core-beliefs.md -├── exec-plans/ -│ ├── active/ -│ ├── completed/ -│ └── tech-debt-tracker.md -├── generated/ -│ └── db-schema.md -├── product-specs/ -│ ├── index.md -│ └── new-user-onboarding.md -├── references/ -│ ├── design-system-reference-llms.txt -│ ├── nixpacks-llms.txt -│ └── uv-llms.txt -├── DESIGN.md -├── FRONTEND.md -├── PLANS.md -├── PRODUCT_SENSE.md -├── QUALITY_SCORE.md -├── RELIABILITY.md -└── SECURITY.md -``` - -## 怎麼用 - -1. 如果倉庫還小,先用最小資源包。 -2. 一旦進入多模塊、多輪會話、長期演化階段,就把 - [`repo-template/`](./repo-template/index.md) 裡的骨架複製到你的倉庫。 -3. 保持 `AGENTS.md` 很短,把深層規則拆到 `docs/` 裡。 -4. 把質量文檔、可靠性文檔、執行計劃當成日常開發的一部分,而不是事後補寫。 -5. 把生成物和外部參考材料明確收進倉庫,避免 agent 依賴聊天上下文或人的記憶。 - -## SOP 資料庫 - -[`sops/`](./sops/index.md) 把文章裡的幾張關鍵圖,整理成可以逐步執行的標準流程: - -- 分層領域架構搭建 SOP -- 把不可見知識編碼進倉庫的 SOP -- 本地可觀測性棧與反饋迴路 SOP -- 用 Chrome DevTools 做 UI 驗證閉環的 SOP - -## 設計原則 - -- 短入口,深鏈接 -- 倉庫就是唯一事實來源 -- 機械約束優先於口頭約定 -- 計劃、質量和技術債都和代碼一起版本化 -- 清理與 harness 簡化是常規工作,不是臨時救火 - -這套資源包是有傾向性的範本,不是必須逐字照抄。最好的用法是把它當成一套高質量起點,再按你的項目改造。 diff --git a/courses/learn-harness-engineering/docs/zh-TW/resources/openai-advanced/repo-template/AGENTS.md b/courses/learn-harness-engineering/docs/zh-TW/resources/openai-advanced/repo-template/AGENTS.md deleted file mode 100644 index 81467850..00000000 --- a/courses/learn-harness-engineering/docs/zh-TW/resources/openai-advanced/repo-template/AGENTS.md +++ /dev/null @@ -1,55 +0,0 @@ -# AGENTS.md - -這個倉庫面向長時運行的 coding-agent 工作流。保持這個檔案簡短,把它當成“唯一事實來源”文檔的入口和路由層,而不是一個不斷膨脹的大說明書。 - -## 開工流程 - -改代碼前先做這些事: - -1. 用 `pwd` 確認倉庫根目錄。 -2. 讀取 `ARCHITECTURE.md`,理解當前系統地圖和硬性依賴規則。 -3. 讀取 `docs/QUALITY_SCORE.md`,先知道最弱的產品領域和架構層。 -4. 讀取 `docs/PLANS.md`,再打開當前要執行的 active plan。 -5. 讀取相關的 `docs/product-specs/` 規格文檔。 -6. 跑這個倉庫約定的 bootstrap 與驗證路徑。 -7. 如果基礎驗證先失敗,先修 baseline,再加新範圍。 - -## 路由地圖 - -- `ARCHITECTURE.md`:領域地圖、分層模型、依賴規則 -- `docs/design-docs/index.md`:設計決策與核心信念 -- `docs/product-specs/index.md`:當前產品行為與驗收目標 -- `docs/PLANS.md`:計劃生命週期與執行計劃規則 -- `docs/QUALITY_SCORE.md`:產品領域與架構層健康度 -- `docs/RELIABILITY.md`:運行信號、benchmark、重啟要求 -- `docs/SECURITY.md`:密鑰、沙箱、數據和外部動作規則 -- `docs/FRONTEND.md`:UI 約束、設計系統規則、可訪問性檢查 - -## 工作約定 - -- 一次只圍繞一個有邊界的計劃或功能切片工作。 -- 不能只靠讀代碼就宣佈完成,必須有可運行證據。 -- 只要改了行為,就同步更新對應的產品、計劃或可靠性文檔。 -- 如果某類 review feedback 反覆出現,把它升級成機械規則、檢查或 linter,不要一直在聊天裡重複解釋。 -- 生成物放進 `docs/generated/`,外部 reference 放進 `docs/references/`。 -- 需要更多細節時,優先補小而新的文檔,而不是繼續把這個檔案寫長。 - -## 完成定義 - -一個改動只有在以下條件都滿足時才算完成: - -- 目標行為已實現 -- 要求的驗證真的跑過 -- 證據已經掛到相關 plan 或質量文檔裡 -- 受影響的文檔仍然是最新的 -- 倉庫能按標準啟動路徑乾淨重啟 - -## 收尾 - -結束會話前: - -1. 更新當前 active execution plan。 -2. 如果產品領域或架構層有明顯變化,更新 `docs/QUALITY_SCORE.md`。 -3. 如果有延期處理的債務,記到 `docs/exec-plans/tech-debt-tracker.md`。 -4. 已完成的計劃及時移到 `docs/exec-plans/completed/`。 -5. 保證倉庫可重啟,並留下清晰的下一步動作。 diff --git a/courses/learn-harness-engineering/docs/zh-TW/resources/openai-advanced/repo-template/ARCHITECTURE.md b/courses/learn-harness-engineering/docs/zh-TW/resources/openai-advanced/repo-template/ARCHITECTURE.md deleted file mode 100644 index ddcd6a8f..00000000 --- a/courses/learn-harness-engineering/docs/zh-TW/resources/openai-advanced/repo-template/ARCHITECTURE.md +++ /dev/null @@ -1,55 +0,0 @@ -# ARCHITECTURE.md - -這份檔案是系統的頂層地圖。它應該保持簡短,只提供最關鍵的結構信息,並把更深的內容指向其他文檔。 - -## 系統形態 - -- 產品:`[替換成产品名]` -- 主用戶流程:`[替換成核心流程]` -- 運行面:`[desktop / web / cli / services / workers]` -- 產品行為真相來源:`docs/product-specs/` - -## 領域地圖 - -| 領域 | 負責什麼 | 主要入口 | 對應規格 | -|------|---------|---------|---------| -| `[domain-a]` | `[职责]` | `[模块 / 路由 / 命令]` | `[spec path]` | -| `[domain-b]` | `[职责]` | `[模块 / 路由 / 命令]` | `[spec path]` | - -## 分層模型 - -用固定方向的分層模型,避免 agent 臨場發明架構: - -`Types -> Config -> Repo -> Service -> Runtime -> UI` - -跨領域關注點應該通過明確的 provider 或 adapter 邊界進入,而不是直接跨層穿透。 - -## 硬性依賴規則 - -- 低層不能依賴高層。 -- UI 不能繞過 runtime 或 service 契約。 -- 數據訪問必須通過 repo 或等價 adapter 進入。 -- 共享 util 必須保持通用,不能慢慢堆成領域邏輯垃圾桶。 -- 新依賴要在對應 plan 或 design doc 裡說明理由。 - -## 橫切接口 - -| 關注點 | 允許的邊界 | 備註 | -|-------|-----------|------| -| 日誌與 tracing | `[provider / utility path]` | `[只允许結構化日誌,不允许临時 console]` | -| Auth | `[provider path]` | `[token/session 规则]` | -| 外部 API | `[client 或 provider path]` | `[限流 / 重試原则]` | -| Feature flags | `[flag boundary]` | `[歸属]` | - -## 當前熱點 - -- `[最难安全修改的区域]` -- `[邊界最弱或測試最脆的区域]` - -## 變更檢查 - -當你修改了會影響架構的代碼: - -1. 如果領域地圖或允許邊界變了,就更新這份檔案。 -2. 如果背後的設計理由變了,就更新 `docs/design-docs/` 裡的相關文檔。 -3. 如果規則應該機械執行,就補一個可執行檢查。 diff --git a/courses/learn-harness-engineering/docs/zh-TW/resources/openai-advanced/repo-template/docs/DESIGN.md b/courses/learn-harness-engineering/docs/zh-TW/resources/openai-advanced/repo-template/docs/DESIGN.md deleted file mode 100644 index d0cd37f6..00000000 --- a/courses/learn-harness-engineering/docs/zh-TW/resources/openai-advanced/repo-template/docs/DESIGN.md +++ /dev/null @@ -1,26 +0,0 @@ -# DESIGN.md - -這份檔案是設計文檔入口。保持簡短,把更細的內容路由到 -`docs/design-docs/` 裡的具體檔案。 - -## 目的 - -記錄那些應該跨越單次聊天、單個 sprint、單個 reviewer 記憶而持續存在的產品與系統設計決策。 - -## 什麼時候先看它 - -- 你需要理解當前的設計哲學 -- 你準備引入新模式 -- 你要判斷哪些設計已經定了,哪些還在開放狀態 - -## 核心設計文檔 - -- `docs/design-docs/index.md`:accepted / proposed / deprecated 文檔索引 -- `docs/design-docs/core-beliefs.md`:項目級 agent-first 核心信念 - -## 設計規則 - -- 設計文檔要小而新。 -- 一個決策領域儘量對應一份文檔。 -- 某個改動依賴設計文檔時,要在 plan 和 spec 裡顯式鏈接它。 -- 如果某條設計規則已經變成運行上的硬要求,就把它升級成自動化檢查或更新到 `ARCHITECTURE.md`。 diff --git a/courses/learn-harness-engineering/docs/zh-TW/resources/openai-advanced/repo-template/docs/FRONTEND.md b/courses/learn-harness-engineering/docs/zh-TW/resources/openai-advanced/repo-template/docs/FRONTEND.md deleted file mode 100644 index b38d6ad5..00000000 --- a/courses/learn-harness-engineering/docs/zh-TW/resources/openai-advanced/repo-template/docs/FRONTEND.md +++ /dev/null @@ -1,23 +0,0 @@ -# FRONTEND.md - -這份檔案定義穩定的前端預期,避免 agent 每次都臨場發明一套 UI 模式。 - -## UI 原則 - -- 先保證清晰,再追求新鮮感。 -- 交互流程要可發現、可重走、可重啟。 -- 優先沉澱少量可複用組件,而不是到處長一次性變體。 -- 可訪問性檢查屬於正常驗證,不是最後的美化工作。 - -## 護欄 - -- 把設計系統或組件庫參考材料放進 `docs/references/`。 -- 顯式記錄關鍵用戶狀態:empty、loading、success、error、retry。 -- 在不同流程裡保持文案、鍵盤行為、視覺層級一致。 -- 修復 UI bug 後,順手補上對應驗證步驟。 - -## 驗證要求 - -- 為關鍵用戶旅程留下證據。 -- 把瀏覽器或運行時驗證步驟寫進相關 plan。 -- 如果視覺迴歸常見,就標準化截圖或 DOM 檢查。 diff --git a/courses/learn-harness-engineering/docs/zh-TW/resources/openai-advanced/repo-template/docs/PLANS.md b/courses/learn-harness-engineering/docs/zh-TW/resources/openai-advanced/repo-template/docs/PLANS.md deleted file mode 100644 index 41534ede..00000000 --- a/courses/learn-harness-engineering/docs/zh-TW/resources/openai-advanced/repo-template/docs/PLANS.md +++ /dev/null @@ -1,34 +0,0 @@ -# PLANS.md - -這份檔案定義執行計劃如何創建、更新、完成和歸檔。 - -## 什麼時候必須有 plan - -當工作滿足以下任一條件時,就創建 execution plan: - -- 會跨越多個會話 -- 會同時影響多個子系統 -- 驗證或上線風險不小 -- 存在需要顯式記錄的開放決策 - -## 計劃放哪 - -- `docs/exec-plans/active/`:當前正在驅動工作的計劃 -- `docs/exec-plans/completed/`:已完成但仍然要保留上下文的計劃 -- `docs/exec-plans/tech-debt-tracker.md`:延期處理的債務與 follow-up - -## 最少要包含的部分 - -- 目標 -- 範圍與明確不做什麼 -- 驗證路徑 -- 風險與 blocker -- 進度日誌 -- 開放決策 - -## 運行規則 - -- 一份 active plan 同一時間應該只有一個清晰的當前步驟。 -- plan 要隨著工作推進而更新,不要把它當成靜態散文。 -- 只要某個決策改變了實現方向,就記到 plan 裡。 -- 已完成的計劃要移到 `completed/`,讓後續 agent 仍然能發現歷史上下文。 diff --git a/courses/learn-harness-engineering/docs/zh-TW/resources/openai-advanced/repo-template/docs/PRODUCT_SENSE.md b/courses/learn-harness-engineering/docs/zh-TW/resources/openai-advanced/repo-template/docs/PRODUCT_SENSE.md deleted file mode 100644 index c0ea13b2..00000000 --- a/courses/learn-harness-engineering/docs/zh-TW/resources/openai-advanced/repo-template/docs/PRODUCT_SENSE.md +++ /dev/null @@ -1,24 +0,0 @@ -# PRODUCT_SENSE.md - -這份檔案記錄那些代碼本身並不能可靠表達出來的產品判斷。 - -## 產品核心 - -- 主要用戶:`[替換]` -- 要完成的任務:`[替換]` -- 最想解決的核心痛點:`[替換]` -- 可接受質量門檻:`[替換]` - -## 產品規則 - -- 優先保證用戶可感知的可靠性,而不是一味加功能。 -- 行為有歧義時,把它當成 spec 缺口,不要默認允許猜。 -- 只要實現改變了用戶看見或信任的東西,就更新對應 spec。 -- 具體流程寫在 product spec 裡,跨流程的產品優先級寫在這裡。 - -## 禁區模式 - -- 隱蔽的破壞性操作 -- 沒有用戶反饋的靜默失敗 -- 用戶可見狀態沒有明確真相來源 -- 不能用一句話解釋清楚的功能 diff --git a/courses/learn-harness-engineering/docs/zh-TW/resources/openai-advanced/repo-template/docs/QUALITY_SCORE.md b/courses/learn-harness-engineering/docs/zh-TW/resources/openai-advanced/repo-template/docs/QUALITY_SCORE.md deleted file mode 100644 index 060fea99..00000000 --- a/courses/learn-harness-engineering/docs/zh-TW/resources/openai-advanced/repo-template/docs/QUALITY_SCORE.md +++ /dev/null @@ -1,39 +0,0 @@ -# QUALITY_SCORE.md - -這份文檔用來跟蹤倉庫是在變強還是變弱。 - -## 評級標準 - -- `A`:驗證通過、可讀、穩定、邊界執行到位 -- `B`:可用,只有少量缺口 -- `C`:部分可用,存在明顯混亂或不穩定 -- `D`:損壞、不安全,或結構上說不清楚 - -## 產品領域 - -| 領域 | 評級 | 驗證狀態 | Agent 可讀性 | 測試穩定性 | 關鍵缺口 | 上次更新 | -|------|------|---------|-------------|-----------|---------|---------| -| `[domain-a]` | - | - | - | - | - | - | -| `[domain-b]` | - | - | - | - | - | - | -| `[domain-c]` | - | - | - | - | - | - | - -## 架構層 - -| 層級 | 評級 | 邊界執行 | Agent 可讀性 | 關鍵缺口 | 上次更新 | -|------|------|---------|-------------|---------|---------| -| Types | - | - | - | - | - | -| Services | - | - | - | - | - | -| Runtime | - | - | - | - | - | -| UI | - | - | - | - | - | - -## Benchmark 快照 - -| 日期 | Harness 變體 | 完成率 | 重試次數 | Review 前缺陷數 | 備註 | -|------|-------------|-------|---------|----------------|------| -| YYYY-MM-DD | `[baseline / improved / simplified]` | - | - | - | - | - -## 簡化實驗日誌 - -| 日期 | 移除的組件 | 結果 | 決策 | -|------|-----------|------|------| -| YYYY-MM-DD | `[component]` | `[degraded / unchanged]` | `[restore / keep removed]` | diff --git a/courses/learn-harness-engineering/docs/zh-TW/resources/openai-advanced/repo-template/docs/RELIABILITY.md b/courses/learn-harness-engineering/docs/zh-TW/resources/openai-advanced/repo-template/docs/RELIABILITY.md deleted file mode 100644 index 1c5b4cfd..00000000 --- a/courses/learn-harness-engineering/docs/zh-TW/resources/openai-advanced/repo-template/docs/RELIABILITY.md +++ /dev/null @@ -1,32 +0,0 @@ -# RELIABILITY.md - -這份檔案定義系統如何證明自己健康、可診斷、可重啟。 - -## 標準路徑 - -- Bootstrap:`[command]` -- Verification:`[command]` -- 啟動應用或服務:`[command]` -- 調試或查看運行態:`[command]` - -## 必需運行信號 - -- 啟動與關鍵流程的結構化日誌 -- 關鍵服務的 health check -- 條件允許時為慢路徑提供 trace 或 timing 數據 -- 對可恢復失敗提供用戶可見的錯誤狀態 - -## 黃金旅程 - -- `[journey 1]` -- `[journey 2]` -- `[journey 3]` - -每條黃金旅程都應該有可重複的驗證路徑和清晰的失敗信號。 - -## 可靠性規則 - -- 只要系統不能幹淨重啟,功能就不能算完成。 -- 運行失敗必須能從 repo-local 信號裡定位。 -- 某類失敗模式一旦反覆出現,就給它補 benchmark 或 guardrail。 -- cleanup 屬於可靠性的一部分,不是另外一件事。 diff --git a/courses/learn-harness-engineering/docs/zh-TW/resources/openai-advanced/repo-template/docs/SECURITY.md b/courses/learn-harness-engineering/docs/zh-TW/resources/openai-advanced/repo-template/docs/SECURITY.md deleted file mode 100644 index ef80922c..00000000 --- a/courses/learn-harness-engineering/docs/zh-TW/resources/openai-advanced/repo-template/docs/SECURITY.md +++ /dev/null @@ -1,27 +0,0 @@ -# SECURITY.md - -這份檔案定義那些 agent 絕不能靠猜來處理的安全規則。 - -## Secrets 與憑證 - -- 絕不把 secrets 硬編碼進源碼或文檔。 -- 在這裡記錄允許的 secret 加載路徑。 -- 從日誌和截圖裡去掉 token、API key 與個人數據。 - -## 不可信輸入 - -- 外部內容在驗證前一律視為不可信。 -- 把允許的抓取或執行邊界寫在這裡。 -- 如果存在 prompt injection 或 command injection 風險,要把 guardrail 記錄清楚。 - -## 外部動作 - -- 列出哪些動作必須顯式批準。 -- 記錄哪些生產或破壞性命令默認不能跑。 -- 調試和驗證優先走 sandbox-safe 路徑。 - -## 依賴與評審規則 - -- 新依賴必須在 active plan 裡說明理由。 -- 涉及安全的改動必須有顯式驗證步驟。 -- 反覆出現的安全 review 評論要升級成檢查,而不是變成口口相傳的經驗。 diff --git a/courses/learn-harness-engineering/docs/zh-TW/resources/openai-advanced/repo-template/docs/design-docs/core-beliefs.md b/courses/learn-harness-engineering/docs/zh-TW/resources/openai-advanced/repo-template/docs/design-docs/core-beliefs.md deleted file mode 100644 index daba7fa5..00000000 --- a/courses/learn-harness-engineering/docs/zh-TW/resources/openai-advanced/repo-template/docs/design-docs/core-beliefs.md +++ /dev/null @@ -1,9 +0,0 @@ -# 核心信念 - -- 倉庫就是 agent 的唯一事實來源。 -- `AGENTS.md` 是路由器,不是百科全書。 -- 驗證證據比自信更重要。 -- 一次做好一個有邊界的任務,勝過同時開很多半成品。 -- 反覆出現的人類反饋要升級成可複用的 harness 規則。 -- 清理與簡化都是交付的一部分,不是事後補救。 -- 如果 agent 不能在倉庫裡發現某個事實,就把這個事實視為運行上不存在。 diff --git a/courses/learn-harness-engineering/docs/zh-TW/resources/openai-advanced/repo-template/docs/design-docs/index.md b/courses/learn-harness-engineering/docs/zh-TW/resources/openai-advanced/repo-template/docs/design-docs/index.md deleted file mode 100644 index e20b47e8..00000000 --- a/courses/learn-harness-engineering/docs/zh-TW/resources/openai-advanced/repo-template/docs/design-docs/index.md +++ /dev/null @@ -1,21 +0,0 @@ -# 設計文檔索引 - -把這份索引當成設計歷史的可發現地圖。 - -## Accepted - -- `core-beliefs.md`:agent-first 的運行信念與持久項目規範 - -## Proposed - -- `[在這裡添加新的設計文檔路徑]` - -## Deprecated - -- `[把被替代或過期的設計文檔移到這裡,並给出替代鏈接]` - -## 維護規則 - -- 每份設計文檔都應該有 owner 或更新觸發條件。 -- 過期文檔要麼刪除,要麼明確標成 deprecated,不要讓它悄悄漂移。 -- active execution plan 要鏈接自己依賴的設計文檔。 diff --git a/courses/learn-harness-engineering/docs/zh-TW/resources/openai-advanced/repo-template/docs/exec-plans/active/index.md b/courses/learn-harness-engineering/docs/zh-TW/resources/openai-advanced/repo-template/docs/exec-plans/active/index.md deleted file mode 100644 index e06af850..00000000 --- a/courses/learn-harness-engineering/docs/zh-TW/resources/openai-advanced/repo-template/docs/exec-plans/active/index.md +++ /dev/null @@ -1,9 +0,0 @@ -# 當前執行計劃 - -這個檔案夾裡每一份 markdown 都代表一份當前仍在執行的計劃。 - -建議檔案名格式: - -- `YYYY-MM-DD-short-topic.md` - -每份 active plan 都應該足夠新,讓一個沒有聊天上下文的新 agent 也能只靠倉庫繼續推進。 diff --git a/courses/learn-harness-engineering/docs/zh-TW/resources/openai-advanced/repo-template/docs/exec-plans/completed/index.md b/courses/learn-harness-engineering/docs/zh-TW/resources/openai-advanced/repo-template/docs/exec-plans/completed/index.md deleted file mode 100644 index 9bd8a1c8..00000000 --- a/courses/learn-harness-engineering/docs/zh-TW/resources/openai-advanced/repo-template/docs/exec-plans/completed/index.md +++ /dev/null @@ -1,3 +0,0 @@ -# 已完成計劃 - -已完成的計劃移動到這裡,不要直接刪掉。完成計劃也是倉庫記憶面的一部分,後續 agent 需要靠它理解代碼為什麼會變成現在這樣。 diff --git a/courses/learn-harness-engineering/docs/zh-TW/resources/openai-advanced/repo-template/docs/exec-plans/tech-debt-tracker.md b/courses/learn-harness-engineering/docs/zh-TW/resources/openai-advanced/repo-template/docs/exec-plans/tech-debt-tracker.md deleted file mode 100644 index 2f29987e..00000000 --- a/courses/learn-harness-engineering/docs/zh-TW/resources/openai-advanced/repo-template/docs/exec-plans/tech-debt-tracker.md +++ /dev/null @@ -1,7 +0,0 @@ -# 技術債跟蹤 - -這份檔案只記錄那些真實存在、已經確認、並且被有意識延後的技術債。 - -| 日期 | 區域 | 債務 | 為什麼延後 | 風險 | 下次觸發點 | -|------|------|------|-----------|------|-----------| -| YYYY-MM-DD | `[area]` | `[debt]` | `[reason]` | `[risk]` | `[when to revisit]` | diff --git a/courses/learn-harness-engineering/docs/zh-TW/resources/openai-advanced/repo-template/docs/generated/db-schema.md b/courses/learn-harness-engineering/docs/zh-TW/resources/openai-advanced/repo-template/docs/generated/db-schema.md deleted file mode 100644 index f974f017..00000000 --- a/courses/learn-harness-engineering/docs/zh-TW/resources/openai-advanced/repo-template/docs/generated/db-schema.md +++ /dev/null @@ -1,13 +0,0 @@ -# 數據庫結構 - -這個目錄用來存放那些生成出來但又值得 agent 直接閱讀的工件,避免它每次都去源碼裡反推。 - -## 來源 - -- 生成命令或來源:`[command or source path]` -- 最近刷新時間:`YYYY-MM-DD` - -## 備註 - -- 生成部分不要手改。 -- 底層 schema 一變,就重新生成這份檔案。 diff --git a/courses/learn-harness-engineering/docs/zh-TW/resources/openai-advanced/repo-template/docs/product-specs/index.md b/courses/learn-harness-engineering/docs/zh-TW/resources/openai-advanced/repo-template/docs/product-specs/index.md deleted file mode 100644 index 8d3b4238..00000000 --- a/courses/learn-harness-engineering/docs/zh-TW/resources/openai-advanced/repo-template/docs/product-specs/index.md +++ /dev/null @@ -1,13 +0,0 @@ -# 產品規格索引 - -這個目錄用來放當前仍然有效的用戶可見行為規格。 - -## 當前有效規格 - -- `new-user-onboarding.md` - -## 規則 - -- spec 重點描述用戶可見行為和驗收標準。 -- 如果實現和 spec 不一致,要在同一輪會話裡更新其中一方。 -- 保持這份索引是最新的,讓新 agent 能快速發現產品範圍。 diff --git a/courses/learn-harness-engineering/docs/zh-TW/resources/openai-advanced/repo-template/docs/product-specs/new-user-onboarding.md b/courses/learn-harness-engineering/docs/zh-TW/resources/openai-advanced/repo-template/docs/product-specs/new-user-onboarding.md deleted file mode 100644 index 9498ffd4..00000000 --- a/courses/learn-harness-engineering/docs/zh-TW/resources/openai-advanced/repo-template/docs/product-specs/new-user-onboarding.md +++ /dev/null @@ -1,26 +0,0 @@ -# 新用戶引導 - -## 目標 - -描述一個新用戶第一次進入產品時應該經歷的流程。 - -## 起始條件 - -- `[流程開始前的狀態]` - -## 用戶流程 - -1. `[步骤一]` -2. `[步骤二]` -3. `[步骤三]` - -## 驗收標準 - -- `[使用者可觀察到的結果]` -- `[使用者可觀察到的結果]` -- `[使用者可觀察到的結果]` - -## 失敗狀態 - -- `[可恢复錯誤與使用者反馈]` -- `[被阻塞狀態與兜底方案]` diff --git a/courses/learn-harness-engineering/docs/zh-TW/resources/openai-advanced/repo-template/docs/references/design-system-reference-llms.txt b/courses/learn-harness-engineering/docs/zh-TW/resources/openai-advanced/repo-template/docs/references/design-system-reference-llms.txt deleted file mode 100644 index 415075c3..00000000 --- a/courses/learn-harness-engineering/docs/zh-TW/resources/openai-advanced/repo-template/docs/references/design-system-reference-llms.txt +++ /dev/null @@ -1,9 +0,0 @@ -用途:存放给模型阅读的 design system 参考材料。 - -建议内容: -- 组件命名规则 -- 间距与字体 token -- 状态变体 -- 可访问性要求 - -保持文件简洁;上游设计系统变化时及时刷新。 diff --git a/courses/learn-harness-engineering/docs/zh-TW/resources/openai-advanced/repo-template/docs/references/nixpacks-llms.txt b/courses/learn-harness-engineering/docs/zh-TW/resources/openai-advanced/repo-template/docs/references/nixpacks-llms.txt deleted file mode 100644 index a8295b1b..00000000 --- a/courses/learn-harness-engineering/docs/zh-TW/resources/openai-advanced/repo-template/docs/references/nixpacks-llms.txt +++ /dev/null @@ -1,7 +0,0 @@ -用途:存放仓库依赖的部署或打包规则的 agent-readable 摘要。 - -建议内容: -- build 入口 -- runtime 假设 -- 环境变量要求 -- 常见失败特征 diff --git a/courses/learn-harness-engineering/docs/zh-TW/resources/openai-advanced/repo-template/docs/references/uv-llms.txt b/courses/learn-harness-engineering/docs/zh-TW/resources/openai-advanced/repo-template/docs/references/uv-llms.txt deleted file mode 100644 index 0f33a52d..00000000 --- a/courses/learn-harness-engineering/docs/zh-TW/resources/openai-advanced/repo-template/docs/references/uv-llms.txt +++ /dev/null @@ -1,7 +0,0 @@ -用途:当仓库依赖 `uv` 或类似工具时,存放一份紧凑的 Python 包与环境工作流参考。 - -建议内容: -- install 与 sync 命令 -- lockfile 策略 -- virtualenv 约定 -- verification 命令 diff --git a/courses/learn-harness-engineering/docs/zh-TW/resources/openai-advanced/repo-template/index.md b/courses/learn-harness-engineering/docs/zh-TW/resources/openai-advanced/repo-template/index.md deleted file mode 100644 index 808e3031..00000000 --- a/courses/learn-harness-engineering/docs/zh-TW/resources/openai-advanced/repo-template/index.md +++ /dev/null @@ -1,22 +0,0 @@ -# 高級倉庫範本 - -當你希望把倉庫升級成更接近 OpenAI 文中那種 agent-first 文檔面,而不只是一個最小 harness 時,就複製這套起步範本。 - -## 推薦複製順序 - -1. 先把 `AGENTS.md` 和 `ARCHITECTURE.md` 放到倉庫根目錄。 -2. 再複製整個 `docs/` 目錄樹。 -3. 優先把 `docs/PRODUCT_SENSE.md`、`docs/QUALITY_SCORE.md`、 - `docs/RELIABILITY.md` 填起來。 -4. 在 `docs/exec-plans/active/` 下創建你的第一份 active plan。 -5. 始終保持入口檔案很短,詳細規則拆到鏈接文檔裡。 - -## 這套範本主要優化什麼 - -- 持久、repo-local 的上下文 -- 漸進披露,而不是一個超長 instruction 檔案 -- 明確的計劃生命週期 -- 隨時間演化的質量追蹤 -- 對 agent 和人都更可讀的邊界 - -這裡的每個檔案都只是起點。真正投入使用前,把佔位文字、示例命令和樣例規則替換成你自己的項目現實。 diff --git a/courses/learn-harness-engineering/docs/zh-TW/resources/openai-advanced/sops/chrome-devtools-validation-loop.md b/courses/learn-harness-engineering/docs/zh-TW/resources/openai-advanced/sops/chrome-devtools-validation-loop.md deleted file mode 100644 index 04fd9a3e..00000000 --- a/courses/learn-harness-engineering/docs/zh-TW/resources/openai-advanced/sops/chrome-devtools-validation-loop.md +++ /dev/null @@ -1,48 +0,0 @@ -# SOP:Chrome DevTools 驗證閉環 - -當 UI 改動必須經過真實交互、截圖、DOM 狀態和 console 輸出來判斷,而不是隻靠讀代碼時,就用這份 SOP。 - -## 目標 - -把 UI 驗證變成 agent 可以反覆執行的交互閉環,直到這條用戶旅程變乾淨。 - -## 核心閉環 - -1. 選定目標頁面或 app 實例。 -2. 清掉舊的 console 噪聲。 -3. 記錄 BEFORE 狀態。 -4. 觸發 UI 路徑。 -5. 觀察交互過程中的 runtime events。 -6. 記錄 AFTER 狀態。 -7. 修復問題,必要時重啟 app。 -8. 反覆重跑驗證,直到這條旅程 clean。 - -## 必需輸入 - -- 穩定的啟動命令 -- 可重複的 UI journey -- 能抓 DOM、console 或截圖的方式 -- 明確“什麼算 clean”的規則 - -## 執行 SOP - -1. 把目標旅程寫進 active plan。 -2. 用可觀察行為定義成功:文本出現、按鈕可點、錯誤消失、console 乾淨、請求成功。 -3. 交互前先拍初始狀態。 -4. 一次只觸發一條路徑。 -5. 記錄 runtime event、DOM 變化和可見輸出。 -6. 如果失敗,只修最小負責層,然後重啟。 -7. 重跑同一路徑,對比 BEFORE/AFTER 證據。 - -## Clean 標準 - -- 目標可見狀態已經出現 -- 沒有意外錯誤 -- console 噪聲已理解或清理 -- 重跑同一路徑結果一致 - -## 需要同步更新的倉庫工件 - -- 當前 active execution plan -- 如果它變成黃金旅程,更新 `docs/RELIABILITY.md` -- 如果可見行為改變了,更新 product spec diff --git a/courses/learn-harness-engineering/docs/zh-TW/resources/openai-advanced/sops/encode-knowledge-into-repo.md b/courses/learn-harness-engineering/docs/zh-TW/resources/openai-advanced/sops/encode-knowledge-into-repo.md deleted file mode 100644 index 37ac5828..00000000 --- a/courses/learn-harness-engineering/docs/zh-TW/resources/openai-advanced/sops/encode-knowledge-into-repo.md +++ /dev/null @@ -1,42 +0,0 @@ -# SOP:把不可見知識編碼進倉庫 - -當重要上下文還散落在 Google Docs、聊天記錄、ticket 或人的腦子裡時,就用這份 SOP。 - -## 目標 - -讓那些 agent 以前看不見的知識,變成 codebase 裡可發現、可讀取、可執行的事實。 - -## 觸發信號 - -- agent 總在追問系統到底怎麼運作。 -- 人開始說“這個是 Slack 裡定過的”或“按某某上週說的做”。 -- review 裡經常引用倉庫裡根本沒寫下來的產品、安全或架構規則。 -- 新會話總在重複那些本該已經穩定下來的發現工作。 - -## 執行 SOP - -1. 先列出所有不可見知識來源:外部文檔、聊天、默認團隊規則、口頭決策。 -2. 對每條知識判斷:它屬於架構、產品行為、安全策略、可靠性要求、執行上下文,還是參考材料? -3. 按類別編碼到對應倉庫工件: - - 架構 -> `ARCHITECTURE.md` - - 產品行為 -> `docs/product-specs/` - - 設計理由 -> `docs/design-docs/` - - 執行狀態 -> `docs/exec-plans/` - - 外部參考材料 -> `docs/references/` - - 質量或可靠性要求 -> `docs/QUALITY_SCORE.md` 或 `docs/RELIABILITY.md` -4. 把模糊表達改寫成運行上真正有用的表達。 -5. 刪除或廢棄舊副本,保證倉庫裡有一個可發現的真相來源。 - -## 好的編碼規則 - -- 為“可發現”而寫,不是為“寫得很全”而寫。 -- 檔案儘量短,名字儘量明確。 -- 相關工件要互相鏈接。 -- 記錄耐久規則,不要把會議流水賬原封不動塞進來。 -- 決策形成的同一輪會話裡,就把倉庫更新掉。 - -## 完成定義 - -- 一個全新 agent 不問人也能找到相關規則。 -- 同一個事實不會散落在多個互相打架的檔案裡。 -- 新工件放在離它所治理的代碼或流程最近的地方。 diff --git a/courses/learn-harness-engineering/docs/zh-TW/resources/openai-advanced/sops/index.md b/courses/learn-harness-engineering/docs/zh-TW/resources/openai-advanced/sops/index.md deleted file mode 100644 index 9f437354..00000000 --- a/courses/learn-harness-engineering/docs/zh-TW/resources/openai-advanced/sops/index.md +++ /dev/null @@ -1,23 +0,0 @@ -# OpenAI 高級 SOP - -這些 SOP 把文章裡的工作方式翻成可以直接參考、執行、改造的操作手冊。 - -## 包含哪些 SOP - -- [`layered-domain-architecture.md`](./layered-domain-architecture.md): - 搭建顯式分層與跨領域邊界 -- [`encode-knowledge-into-repo.md`](./encode-knowledge-into-repo.md): - 把聊天、外部文檔、腦內知識變成 repo-local 文檔 -- [`observability-feedback-loop.md`](./observability-feedback-loop.md): - 給 agent 提供 logs、metrics、traces 與可重複的調試閉環 -- [`chrome-devtools-validation-loop.md`](./chrome-devtools-validation-loop.md): - 用瀏覽器自動化和快照反覆驗證 UI,直到 clean - -## 怎麼用 - -1. 先選和你當前瓶頸最匹配的 SOP。 -2. 按檢查清單補齊缺失工件或工具。 -3. 把得到的規則編碼回 `repo-template/` 裡的文檔。 -4. 把反覆出現的 review 評論升級成檢查、腳本或 guardrail。 - -這些 SOP 不是讓你機械照抄,而是為了讓 harness 更可讀、更可執行、更可複用。 diff --git a/courses/learn-harness-engineering/docs/zh-TW/resources/openai-advanced/sops/layered-domain-architecture.md b/courses/learn-harness-engineering/docs/zh-TW/resources/openai-advanced/sops/layered-domain-architecture.md deleted file mode 100644 index ff201f99..00000000 --- a/courses/learn-harness-engineering/docs/zh-TW/resources/openai-advanced/sops/layered-domain-architecture.md +++ /dev/null @@ -1,47 +0,0 @@ -# SOP:分層領域架構 - -當 agent 反覆跨層亂連、在不同層重複邏輯、或者幾輪會話後代碼越來越難審時,就用這份 SOP。 - -## 目標 - -把領域邊界寫清楚、立起來、能執行,讓 agent 在高速度下也不至於悄悄把結構做爛。 - -## 目標模型 - -在一個業務領域內部,優先使用這條單向流: - -`Types -> Config -> Repo -> Service -> Runtime -> UI` - -跨領域關注點通過明確的 provider 或 adapter 進入。共享 utils 保持在領域之外,不能慢慢長成業務邏輯垃圾場。 - -## 建設檢查清單 - -- 在 `ARCHITECTURE.md` 裡列清當前 domains。 -- 在 `ARCHITECTURE.md` 裡寫清允許的依賴方向。 -- 記錄 auth、telemetry、外部 API 等 cross-cutting interface。 -- 為當前最難處理的邊界違規寫一條短說明。 -- 決定哪些規則應該升級成 lint、test 或 script。 - -## 執行 SOP - -1. 先給代碼庫畫 domain map,再談實現風格。 -2. 對每個 domain,明確允許的 layer sequence。 -3. 找出所有橫切關注點,並把它們改走 provider 或 adapter。 -4. 把含糊的 shared logic 重新放回所屬 domain,或抽成真正通用的 utils。 -5. 把規則寫進 `ARCHITECTURE.md`。 -6. 先為代價最高的一類違規補一個可執行 guardrail。 -7. 改完後更新質量評分。 - -## 完成定義 - -- 一個全新的 agent 能判斷某個改動應該落在哪一層。 -- UI 不再直接連 repo 或外部副作用。 -- 橫切能力都有明確入口。 -- 至少一條重要邊界已經被機械執行。 - -## 需要同步更新的倉庫工件 - -- `ARCHITECTURE.md` -- `docs/QUALITY_SCORE.md` -- 如果設計理由變了,更新 `docs/design-docs/` -- `docs/PLANS.md` 或當前 active execution plan diff --git a/courses/learn-harness-engineering/docs/zh-TW/resources/openai-advanced/sops/observability-feedback-loop.md b/courses/learn-harness-engineering/docs/zh-TW/resources/openai-advanced/sops/observability-feedback-loop.md deleted file mode 100644 index fea72386..00000000 --- a/courses/learn-harness-engineering/docs/zh-TW/resources/openai-advanced/sops/observability-feedback-loop.md +++ /dev/null @@ -1,42 +0,0 @@ -# SOP:可觀測性反饋閉環 - -當調試太慢、agent 總在沒證據的情況下宣佈成功、或者運行時行為比代碼本身還難看懂時,就用這份 SOP。 - -## 目標 - -給 agent 一套本地閉環,讓它可以基於 logs、metrics、traces 和可重複 workload 來判斷系統,而不是隻靠看代碼猜。 - -## 最小可用棧 - -- 應用輸出結構化日誌 -- 條件允許時輸出 metrics 和 traces -- 本地採集或 fan-out 層 -- 可查詢 logs / metrics / traces 的接口 -- 每次改動後都能重跑的 workload 或 user journey - -## 執行 SOP - -1. 先定義最重要的黃金運行旅程。 -2. 給啟動流程和關鍵路徑補結構化日誌。 -3. 在合適位置補 latency、failure count、queue depth 之類的 metrics。 -4. 為慢路徑或多階段流程補 traces 或 timing 標記。 -5. 讓這些信號能從本地開發環境查詢到。 -6. 給 agent 一條可以反覆重跑的 workload 或場景。 -7. 強制執行這條閉環:query -> correlate -> reason -> implement -> restart - -> rerun -> verify。 - -## 調試會話檢查清單 - -- 到底哪裡失敗了? -- 哪條信號能證明它失敗? -- 失敗歸屬哪一層? -- 修復後哪條信號發生了變化? -- App 是否能幹淨重啟? -- 同一 workload 重跑後是否通過? - -## 完成定義 - -- agent 能用運行證據解釋失敗模式。 -- 每次改動後都能重跑同一 workload。 -- restart 與 rerun 已經成為正常任務循環的一部分。 -- 可靠性信號已經記錄到 `docs/RELIABILITY.md`。 diff --git a/courses/learn-harness-engineering/docs/zh-TW/resources/reference/coding-agent-startup-flow.md b/courses/learn-harness-engineering/docs/zh-TW/resources/reference/coding-agent-startup-flow.md deleted file mode 100644 index aa01f4b1..00000000 --- a/courses/learn-harness-engineering/docs/zh-TW/resources/reference/coding-agent-startup-flow.md +++ /dev/null @@ -1,33 +0,0 @@ -# 編碼代理開工流程 - -初始化完成後,每輪編碼會話都按這個流程開始。 - -## 固定開工範本 - -1. 運行 `pwd`,確認倉庫根目錄 -2. 讀取 `claude-progress.md` -3. 讀取 `feature_list.json` -4. 用 `git log --oneline -5` 查看最近提交 -5. 運行 `./init.sh` -6. 跑一條基礎 smoke test 或端到端路徑 -7. 如果基礎狀態已壞,先修這個 -8. 選擇最高優先級的未完成功能 -9. 只圍繞這個功能工作,直到它被驗證或明確 blocked - -## 為什麼順序不能亂 - -- `pwd` 能防止在錯誤目錄裡幹活 -- progress 和 feature 檔案先恢復持久狀態 -- 最近提交能解釋剛剛發生了什麼 -- `init.sh` 讓啟動過程標準化,而不是靠記憶 -- 基礎驗證先跑,可以避免在壞狀態上繼續疊改動 - -## 對應的收尾流程 - -同一輪會話結束時,也應該鏡像地做這些事: - -1. 記錄進度 -2. 更新功能狀態 -3. 必要時寫交接摘要 -4. 提交安全狀態的代碼 -5. 留下可直接重啟的乾淨環境 diff --git a/courses/learn-harness-engineering/docs/zh-TW/resources/reference/index.md b/courses/learn-harness-engineering/docs/zh-TW/resources/reference/index.md deleted file mode 100644 index 040b2ab4..00000000 --- a/courses/learn-harness-engineering/docs/zh-TW/resources/reference/index.md +++ /dev/null @@ -1,71 +0,0 @@ -# 中文參考 - -這一部分解釋這些範本該怎麼配合使用,而不是把它們當成一堆孤立檔案。 - -## 內部參考材料 - -- [`method-map.md`](./method-map.md):把常見長時任務翻車點映射到對應方法和工件 -- [`initializer-agent-playbook.md`](./initializer-agent-playbook.md):初始化代理在第一輪應該產出什麼 -- [`coding-agent-startup-flow.md`](./coding-agent-startup-flow.md):後續編碼代理每次開工的固定流程 -- [`prompt-calibration.md`](./prompt-calibration.md):根指令應該寫到什麼程度才合適 - -## 重點參考文章 - -這裡的篩選標準很窄:只保留能直接解釋 harness 機制的文章。Harness 在這裡指模型外部的運行系統,包括 agent loop、工具執行、沙箱、狀態、上下文、驗證、終止條件、控制平面和觀測反饋;不是泛泛的 prompt engineering 或 agent 框架介紹。 - -保留原始三篇作為課程主軸: - -- [OpenAI: Harness engineering: leveraging Codex in an agent-first world](https://openai.com/index/harness-engineering/)(2026-02-11):agent-first 倉庫、repo-local context、custom lint、結構性 guardrail。 -- [Anthropic: Effective harnesses for long-running agents](https://www.anthropic.com/engineering/effective-harnesses-for-long-running-agents)(2025-11-26):initializer agent、coding agent、feature list、progress log、跨上下文窗口交接。 -- [Anthropic: Harness design for long-running application development](https://www.anthropic.com/engineering/harness-design-long-running-apps)(2026-03-24):planner / generator / evaluator 三角色、context reset、harness 簡化和組件過期問題。 - -額外只加入幾篇高相關、高含金量的 2026 文章: - -- [OpenAI: Unrolling the Codex agent loop](https://openai.com/index/unrolling-the-codex-agent-loop/)(2026-01-23):解釋 Codex runtime harness 的核心循環、工具調用、上下文增長和終止狀態。 -- [Anthropic: Demystifying evals for AI agents](https://www.anthropic.com/engineering/demystifying-evals-for-ai-agents)(2026-01-09):明確評估 agent 時評的是 model + harness,並區分 evaluation harness 與 agent harness。 -- [LangChain: Improving Deep Agents with harness engineering](https://www.langchain.com/blog/improving-deep-agents-with-harness-engineering)(2026-02-17):同一模型不變,只改 system prompt、tools、middleware、tracing 和 self-verification,讓 coding agent 在 Terminal Bench 2.0 上從 Top 30 進到 Top 5。 -- [Thoughtworks / Martin Fowler: Harness engineering for coding agent users](https://martinfowler.com/articles/harness-engineering.html)(2026-04-02):把 coding-agent user harness 拆成 feedforward guides 和 feedback sensors,並區分 deterministic controls 與 inferential controls。 -- [Cursor: Continually improving our agent harness](https://cursor.com/blog/continually-improving-agent-harness)(2026-04-30):把 harness 當成持續迭代的產品系統,用離線評估、線上指標、工具錯誤分類、模型定製和 mid-chat model switching 改善 agent 行為。 - -## 2026 擴展參考 - -這些文章不作為課程主軸,但在設計特定 harness 模塊時很有借鑑價值。只保留文章正文直接涉及 agent loop、工具執行、上下文管理、驗證、沙箱、控制層、迴歸治理等機制的材料;純 agent 產品、平臺發佈、團隊實踐或 benchmark 不放進這裡。 - -- [OpenAI: Unlocking the Codex harness: how we built the App Server](https://openai.com/index/unlocking-the-codex-harness/)(2026-02-04):把 harness 抽象成 App Server 協議,覆蓋 thread lifecycle、resume、fork、diff 和客戶端集成。 -- [OpenAI Developers: Run long horizon tasks with Codex](https://developers.openai.com/blog/run-long-horizon-tasks-with-codex)(2026-02-23):長時任務中的 durable project memory、milestone validation 和 done-when 例子。 -- [OpenAI: The next evolution of the Agents SDK](https://openai.com/index/the-next-evolution-of-the-agents-sdk/)(2026-04-15):model-native harness、sandbox execution、檔案與命令執行能力。 -- [OpenAI: An open-source spec for Codex orchestration: Symphony](https://openai.com/index/open-source-codex-orchestration-symphony/)(2026-04-27):把 issue tracker / Linear 變成多 agent 控制平面。 -- [Anthropic: Building a C compiler with a team of parallel Claudes](https://www.anthropic.com/engineering/building-c-compiler)(2026-02-05):並行 agent 團隊、任務鎖、git 同步、容器隔離和自主循環。 -- [Anthropic: Scaling Managed Agents: Decoupling the brain from the hands](https://www.anthropic.com/engineering/managed-agents)(2026-04-08):meta-harness 視角,把 session、harness、sandbox 拆成可替換接口。 -- [Anthropic: An update on recent Claude Code quality reports](https://www.anthropic.com/engineering/april-23-postmortem)(2026-04-23):reasoning effort、context pruning、system prompt 都屬於 harness 變更,且需要回歸治理。 -- [LangChain: Context Management for Deep Agents](https://www.langchain.com/blog/context-management-for-deepagents)(2026-01-28):filesystem offloading、tool-call truncation、summarization 和 targeted evals 組成的 context-management harness。 -- [LangChain: Tuning Deep Agents to Work Well with Different Models](https://www.langchain.com/blog/tuning-deep-agents-different-models)(2026-04-29):用 model-specific harness profiles 調整 prompt、tool names、middleware 和 subagent 配置。 -- [LangChain: Continual learning for AI agents](https://www.langchain.com/blog/continual-learning-for-ai-agents)(2026-04-05):把 agent 改進拆成 model、harness、context 三層,並把 traces 作為改進信號。 -- [Microsoft: Agent Harness in Agent Framework](https://devblogs.microsoft.com/agent-framework/agent-harness-in-agent-framework/)(2026-03-12):shell/filesystem harness、approval flow、hosted shell、context compaction。 -- [Google: Announcing ADK for Java 1.0.0](https://developers.googleblog.com/announcing-adk-for-java-100-building-the-future-of-ai-agents-in-java/)(2026-03-30):插件、event compaction、HITL、session/memory service、A2A 等可複用 harness primitives。 -- [GitHub: Automate repository tasks with GitHub Agentic Workflows](https://github.blog/ai-and-ml/automate-repository-tasks-with-github-agentic-workflows/)(2026-02-13):把 GitHub Actions 變成 agentic workflow runner,包含 safe outputs、sandboxing、permissions、review。 -- [AWS: AI agents in enterprises: Best practices with Amazon Bedrock AgentCore](https://aws.amazon.com/blogs/machine-learning/ai-agents-in-enterprises-best-practices-with-amazon-bedrock-agentcore/)(2026-02-03):Runtime、Memory、Gateway、Identity/Policy、Observability、Evaluations 的企業級 harness 分層。 -- [Stripe: Minions: Stripe's one-shot, end-to-end coding agents](https://stripe.dev/blog/minions-stripes-one-shot-end-to-end-coding-agents)(2026-02-09)和 [Part 2](https://stripe.dev/blog/minions-stripes-one-shot-end-to-end-coding-agents-part-2)(2026-02-19):devbox 隔離、custom agent harness、blueprints 狀態機、規則檔案、MCP tool curation、安全控制、pre-push/CI 反饋循環。 -- [Cognition: What We Learned Building Cloud Agents](https://cognition.ai/blog/what-we-learned-building-cloud-agents)(2026-04-23):雲端 agent runtime 的 VM 隔離、session snapshot/resume、orchestration、governance、audit logging 和 integrations。 -- [Cognition: Multi-Agents: What's Actually Working](https://cognition.ai/blog/multi-agents-working)(2026-04-22):generator-verifier loop、clean-context reviewer、smart-friend routing、manager-child coordination 和跨 agent 通信邊界。 -- [Replit: Decision-Time Guidance: Keeping Replit Agent Reliable](https://blog.replit.com/decision-time-guidance)(2026-01-20,2026-01-23 更新):用輕量分類器在關鍵決策點注入短指令,而不是把所有規則塞進系統提示詞。 -- [Vercel: How we made v0 an effective coding agent](https://vercel.com/blog/how-we-made-v0-an-effective-coding-agent)(2026-01-07):動態系統提示、streaming rewrite layer 和 deterministic/model-driven autofixers。 -- [Vercel: Introducing deepsec](https://vercel.com/blog/introducing-deepsec-find-and-fix-vulnerabilities-in-your-code-base)(2026-05-04):面向安全掃描的 coding-agent harness,包含 scan、investigate、revalidate、enrich、export、plugin 和 refusal-checker。 -- [Sourcegraph: CodeScaleBench](https://sourcegraph.com/blog/codescalebench-testing-coding-agents-on-large-codebases-and-multi-repo-software-engineering-tasks)(2026-03-03):偏 eval/tooling harness,包含 MCP tool adoption、tool-use transcripts、benchmark QA、verifier/reproducibility gates 和 prompt/preamble 迭代。 - -嚴格按時間篩選時,2025-only 的泛參考不進入主列表。原始三篇中的 Anthropic 2025 文章保留,是因為它是本課程方法的基礎來源。 - -## 推薦閱讀順序 - -1. `method-map.md` -2. `initializer-agent-playbook.md` -3. `coding-agent-startup-flow.md` -4. `prompt-calibration.md` -5. OpenAI Harness engineering -6. Anthropic Effective harnesses -7. Anthropic Harness design for long-running application development -8. OpenAI Codex agent loop -9. Anthropic agent evals -10. LangChain Improving Deep Agents -11. Thoughtworks / Martin Fowler Harness engineering for coding agent users -12. Cursor Continually improving our agent harness diff --git a/courses/learn-harness-engineering/docs/zh-TW/resources/reference/initializer-agent-playbook.md b/courses/learn-harness-engineering/docs/zh-TW/resources/reference/initializer-agent-playbook.md deleted file mode 100644 index ac387515..00000000 --- a/courses/learn-harness-engineering/docs/zh-TW/resources/reference/initializer-agent-playbook.md +++ /dev/null @@ -1,35 +0,0 @@ -# 初始化代理操作手冊 - -這個手冊用於倉庫裡的第一輪重要會話,也就是正式開始增量開發之前的初始化階段。 - -## 目標 - -先搭出穩定的工作面,讓後續會話不必重新推導啟動命令、當前狀態和任務邊界。 - -## 必需產出 - -初始化階段至少應該留下這些工件: - -- 一個根指令檔案,例如 `AGENTS.md` 或 `CLAUDE.md` -- 一個機器可讀的功能面,例如 `feature_list.json` -- 一個持久進度工件,例如 `claude-progress.md` -- 一個標準啟動輔助腳本,例如 `init.sh` -- 一個記錄基礎腳手架狀態的初始安全提交 - -## 檢查清單 - -1. 定義標準啟動路徑 -2. 定義標準驗證路徑 -3. 建立進度日誌並寫下初始狀態 -4. 把工作拆成功能,並給出狀態字段 -5. 建立第一個乾淨的 baseline commit - -## 成功標準 - -一個完全沒有前文聊天上下文的新會話,應該能回答: - -- 這個倉庫是做什麼的 -- 怎麼啟動 -- 怎麼驗證 -- 還有什麼沒做完 -- 下一步最佳動作是什麼 diff --git a/courses/learn-harness-engineering/docs/zh-TW/resources/reference/method-map.md b/courses/learn-harness-engineering/docs/zh-TW/resources/reference/method-map.md deleted file mode 100644 index 3c3ea37a..00000000 --- a/courses/learn-harness-engineering/docs/zh-TW/resources/reference/method-map.md +++ /dev/null @@ -1,16 +0,0 @@ -# 方法對照表 - -這個表把最常見的長時 coding-agent 失敗模式,對應到最先該補的工件或規則。 - -| 失敗模式 | 實際表現 | 首要修復 | 主要工件 | -| --- | --- | --- | --- | -| 冷啟動混亂 | 新會話花大量時間重新摸索狀態和啟動方式 | 讓倉庫成為唯一事實來源 | `claude-progress.md` | -| 範圍蔓延 | 一次啟動多個功能,最後沒有一個完整收尾 | 限制當前活躍範圍 | `feature_list.json` | -| 提前宣佈完成 | 代碼改了就說“完成了”,但沒有可運行證據 | 把完成綁定到驗證證據 | `clean-state-checklist.md` | -| 啟動脆弱 | 每輪會話都要重新學怎麼啟動項目 | 統一啟動與驗證路徑 | `init.sh` | -| 交接薄弱 | 下一輪看不出哪裡可用、哪裡壞了、接下來做什麼 | 明確寫交接摘要 | `session-handoff.md` | -| 評審主觀 | 質量判斷依賴個人記憶和感覺 | 用固定維度做評分 | `evaluator-rubric.md` | - -## 使用原則 - -優先補最能直接消除當前失敗模式的那個工件,不要一出問題就把更多規則堆進一個超長 instruction 檔案裡。 diff --git a/courses/learn-harness-engineering/docs/zh-TW/resources/reference/prompt-calibration.md b/courses/learn-harness-engineering/docs/zh-TW/resources/reference/prompt-calibration.md deleted file mode 100644 index 37cae1e0..00000000 --- a/courses/learn-harness-engineering/docs/zh-TW/resources/reference/prompt-calibration.md +++ /dev/null @@ -1,23 +0,0 @@ -# Prompt 校準 - -根指令檔案應該定義工作框架,而不是把所有動作寫死。 - -## 應該留在根檔案裡的內容 - -- 倉庫用途和範圍 -- 啟動路徑 -- 驗證路徑 -- 不可違反的約束 -- 必需的狀態工件 -- 會話結束規則 - -## 應該移出根檔案的內容 - -- 過長的歷史邊角案例 -- 只屬於某個子系統的細節實現說明 -- 更適合貼在代碼附近的局部架構筆記 -- 只對單一模塊成立的示例 - -## 工作原則 - -根檔案的職責是讓新會話快速建立方向感。如果它開始變成“過去每次失敗都往裡加一句”的堆疊場,就應該把細節拆到更小、更明確的文檔裡,再從根檔案跳轉過去。 diff --git a/courses/learn-harness-engineering/docs/zh-TW/resources/templates/AGENTS.md b/courses/learn-harness-engineering/docs/zh-TW/resources/templates/AGENTS.md deleted file mode 100644 index 2cbbce1e..00000000 --- a/courses/learn-harness-engineering/docs/zh-TW/resources/templates/AGENTS.md +++ /dev/null @@ -1,50 +0,0 @@ -# AGENTS.md - -這個倉庫面向長時運行的 coding agent 工作流。目標不是儘可能快地產出代碼,而是讓每一輪會話結束後,下一個會話仍然能無猜測地繼續工作。 - -## 開工流程 - -寫代碼前先做這些事: - -1. 用 `pwd` 確認當前目錄。 -2. 讀取 `claude-progress.md`,瞭解最新已驗證狀態和下一步。 -3. 讀取 `feature_list.json`,選擇優先級最高的未完成功能。 -4. 用 `git log --oneline -5` 看最近提交。 -5. 運行 `./init.sh`。 -6. 在開始新功能前,先跑必需的 smoke test 或端到端驗證。 - -如果基礎驗證一開始就失敗,先修基礎狀態,不要在壞的起點上繼續疊新功能。 - -## 工作規則 - -- 一次只做一個功能。 -- 不要因為“代碼已經寫了”就把功能標記為完成。 -- 除非為了消除當前 blocker 的窄範圍修復,否則不要擴大到其他功能。 -- 實現過程中不要悄悄改弱驗證規則。 -- 優先依賴倉庫裡的持久化檔案,而不是聊天記錄。 - -## 必需檔案 - -- `feature_list.json`:功能狀態的唯一事實來源 -- `claude-progress.md`:會話進度和當前已驗證狀態 -- `init.sh`:統一的啟動與驗證入口 -- `session-handoff.md`:較長會話可選的交接摘要 - -## 完成定義 - -一個功能只有在以下條件都滿足時才算完成: - -- 目標行為已經實現 -- 要求的驗證真的跑過 -- 證據記錄在 `feature_list.json` 或 `claude-progress.md` -- 倉庫仍然能按標準啟動路徑重新開始工作 - -## 收尾 - -結束會話前: - -1. 更新 `claude-progress.md` -2. 更新 `feature_list.json` -3. 記錄仍未解決的風險或 blocker -4. 在工作處於安全狀態後,用清晰的提交信息提交 -5. 保證下一輪會話可以直接運行 `./init.sh` diff --git a/courses/learn-harness-engineering/docs/zh-TW/resources/templates/CLAUDE.md b/courses/learn-harness-engineering/docs/zh-TW/resources/templates/CLAUDE.md deleted file mode 100644 index 58293de2..00000000 --- a/courses/learn-harness-engineering/docs/zh-TW/resources/templates/CLAUDE.md +++ /dev/null @@ -1,43 +0,0 @@ -# CLAUDE.md - -你正在一個為長時實現工作設計的倉庫中工作。優先保證可靠完成、跨會話連續性和顯式驗證,而不是表面上的速度。 - -## 固定工作循環 - -每輪會話開始時: - -1. 運行 `pwd`,確認當前在正確的倉庫根目錄 -2. 讀取 `claude-progress.md` -3. 讀取 `feature_list.json` -4. 用 `git log --oneline -5` 查看最近提交 -5. 運行 `./init.sh` -6. 檢查基礎 smoke test 或端到端路徑是否已經損壞 - -然後只選擇一個未完成功能,圍繞它工作,直到它被驗證通過,或者被明確記錄為 blocked。 - -## 規則 - -- 同一時間只能有一個 active feature -- 沒有可運行證據時,不要聲稱完成 -- 不要通過重寫功能清單來隱藏未完成工作 -- 不要為了“看起來完成”而刪除或削弱測試 -- 以倉庫內檔案作為唯一事實來源 - -## 必需檔案 - -- `feature_list.json` -- `claude-progress.md` -- `init.sh` -- 需要簡短交接時使用 `session-handoff.md` - -## 完成門檻 - -只有在要求的驗證成功且結果被記錄後,功能狀態才可以切換到 `passing`。 - -## 結束前 - -1. 更新進度日誌 -2. 更新功能狀態 -3. 記錄仍然損壞或未驗證的內容 -4. 在倉庫可安全恢復後提交 -5. 給下一輪會話留下乾淨的重啟路徑 diff --git a/courses/learn-harness-engineering/docs/zh-TW/resources/templates/claude-progress.md b/courses/learn-harness-engineering/docs/zh-TW/resources/templates/claude-progress.md deleted file mode 100644 index c4aac3f1..00000000 --- a/courses/learn-harness-engineering/docs/zh-TW/resources/templates/claude-progress.md +++ /dev/null @@ -1,35 +0,0 @@ -# 進度日誌 - -## 當前已驗證狀態 - -- 倉庫根目錄: -- 標準啟動路徑: -- 標準驗證路徑: -- 當前最高優先級未完成功能: -- 當前 blocker: - -## 會話記錄 - -### Session 001 - -- 日期: -- 本輪目標: -- 已完成: -- 運行過的驗證: -- 已記錄證據: -- 提交記錄: -- 更新過的檔案或工件: -- 已知風險或未解決問題: -- 下一步最佳動作: - -### Session 002 - -- 日期: -- 本輪目標: -- 已完成: -- 運行過的驗證: -- 已記錄證據: -- 提交記錄: -- 更新過的檔案或工件: -- 已知風險或未解決問題: -- 下一步最佳動作: diff --git a/courses/learn-harness-engineering/docs/zh-TW/resources/templates/clean-state-checklist.md b/courses/learn-harness-engineering/docs/zh-TW/resources/templates/clean-state-checklist.md deleted file mode 100644 index edf08396..00000000 --- a/courses/learn-harness-engineering/docs/zh-TW/resources/templates/clean-state-checklist.md +++ /dev/null @@ -1,8 +0,0 @@ -# 乾淨狀態檢查清單 - -- [ ] 標準啟動路徑仍然可用 -- [ ] 標準驗證路徑仍然可運行 -- [ ] 當前進度已經記錄到進度日誌 -- [ ] 功能狀態真實反映了 passing 和未驗證的邊界 -- [ ] 沒有任何半成品步驟處於未記錄狀態 -- [ ] 下一輪會話無需人工修復即可繼續 diff --git a/courses/learn-harness-engineering/docs/zh-TW/resources/templates/evaluator-rubric.md b/courses/learn-harness-engineering/docs/zh-TW/resources/templates/evaluator-rubric.md deleted file mode 100644 index 456a7155..00000000 --- a/courses/learn-harness-engineering/docs/zh-TW/resources/templates/evaluator-rubric.md +++ /dev/null @@ -1,24 +0,0 @@ -# 評審評分表 - -在實現完成後、正式驗收前,用這張表做一次評審。 - -| 維度 | 問題 | 分數 (0-2) | 備註 | -| --- | --- | --- | --- | -| 正確性 | 實現出來的行為是否符合目標功能? | | | -| 驗證 | 要求的檢查是否真的跑過,並留下證據? | | | -| 範圍紀律 | 這一輪是否基本保持在選定功能範圍內? | | | -| 可靠性 | 結果是否能在重啟或重跑後繼續工作? | | | -| 可維護性 | 代碼和文檔是否清楚到足以交給下一輪會話? | | | -| 交接準備度 | 新會話是否能只靠倉庫內工件繼續推進? | | | - -## 結論 - -- Accept -- Revise -- Block - -## 後續動作 - -- 缺失的證據: -- 必須補的修復: -- 下次複審觸發條件: diff --git a/courses/learn-harness-engineering/docs/zh-TW/resources/templates/feature_list.json b/courses/learn-harness-engineering/docs/zh-TW/resources/templates/feature_list.json deleted file mode 100644 index 41b97536..00000000 --- a/courses/learn-harness-engineering/docs/zh-TW/resources/templates/feature_list.json +++ /dev/null @@ -1,64 +0,0 @@ -{ - "project": "替换成你的项目名", - "last_updated": "YYYY-MM-DD", - "rules": { - "single_active_feature": true, - "passing_requires_evidence": true, - "do_not_skip_verification": true - }, - "status_legend": { - "not_started": "功能还没开始做。", - "in_progress": "这个功能是当前唯一正在进行的任务。", - "blocked": "因为已记录的阻塞问题,当前无法继续推进。", - "passing": "要求的验证已经通过,并且证据已经记录。" - }, - "features": [ - { - "id": "chat-001", - "priority": 1, - "area": "chat", - "title": "创建新会话", - "user_visible_behavior": "用户点击 New Chat 后,可以看到一个新的空白对话。", - "status": "not_started", - "verification": [ - "打开应用。", - "点击 New Chat。", - "确认侧边栏出现一个新会话。", - "确认主面板显示空白对话状态。" - ], - "evidence": [], - "notes": "" - }, - { - "id": "chat-002", - "priority": 2, - "area": "chat", - "title": "在当前会话里发送消息", - "user_visible_behavior": "用户提交一条消息后,可以在当前线程中看到它。", - "status": "not_started", - "verification": [ - "打开一个已有会话。", - "在输入框中输入消息。", - "提交消息。", - "确认线程中出现这条新消息。" - ], - "evidence": [], - "notes": "" - }, - { - "id": "chat-003", - "priority": 3, - "area": "chat", - "title": "持久化保存会话列表", - "user_visible_behavior": "应用重启后,用户仍能看到之前创建的会话。", - "status": "not_started", - "verification": [ - "创建两个会话。", - "重启应用。", - "确认两个会话仍显示在侧边栏中。" - ], - "evidence": [], - "notes": "" - } - ] -} diff --git a/courses/learn-harness-engineering/docs/zh-TW/resources/templates/index.md b/courses/learn-harness-engineering/docs/zh-TW/resources/templates/index.md deleted file mode 100644 index 6db4c84e..00000000 --- a/courses/learn-harness-engineering/docs/zh-TW/resources/templates/index.md +++ /dev/null @@ -1,226 +0,0 @@ -# 範本使用指南 - -這些範本可以直接複製到你的項目裡用。每個檔案在 agent 的工作流程中各有分工。複製過去後,把裡面的命令、路徑、功能名稱和驗證步驟換成你自己項目的。 - -## 怎麼開始 - -先把這四個檔案複製到項目根目錄: - -1. `AGENTS.md` 或 `CLAUDE.md` -2. `init.sh` -3. `claude-progress.md` -4. `feature_list.json` - -等項目變複雜了,再補其餘的檔案。 - ---- - -## AGENTS.md - -根指令檔案。agent 每次開工會先讀這個檔案。它定義了工作規則:寫代碼前要做什麼、工作過程中怎麼守規矩、收尾時要檢查什麼。 - -**怎麼用:** - -- 複製到項目根目錄 -- 把開工流程裡的步驟換成你自己項目的路徑和命令 -- 工作規則按你們團隊的約定調整 -- 完成定義那一段別改——那是整個 harness 最關鍵的部分 - -**它幫 agent 做什麼:** - -- 讓它在開始工作前先讀進度和功能狀態 -- 逼它一次只做一個功能 -- 要求它拿出證據才能標記完成 -- 定義了什麼叫"乾淨收尾" - -用 `AGENTS.md` 給 Codex 或其他 agent。用 `CLAUDE.md` 給 Claude Code——內容一樣,格式按 Claude 的指令風格來的。 - -## init.sh - -啟動腳本。一條命令完成依賴安裝、驗證和打印啟動命令。 - -**怎麼用:** - -- 複製到項目根目錄 -- 改頂部這三個變量: - - `INSTALL_CMD` — 你的依賴安裝命令(比如 `npm install`、`pip install -r requirements.txt`) - - `VERIFY_CMD` — 你的基礎驗證命令(比如 `npm test`、`pytest`) - - `START_CMD` — 你的開發服務器啟動命令(比如 `npm run dev`) -- 加執行權限:`chmod +x init.sh` - -**它做什麼:** - -1. 打印當前目錄(確認跑在正確的地方) -2. 安裝依賴 -3. 跑驗證命令 -4. 打印啟動命令(如果設了 `RUN_START_COMMAND=1` 就直接啟動) - -如果驗證失敗了,agent 應該停下來先修基礎狀態,不要在壞的基礎上繼續疊新功能。 - -## claude-progress.md - -進度日誌。每輪會話往裡寫,每輪新會話先讀它。 - -**怎麼用:** - -- 複製到項目根目錄 -- 把"當前已驗證狀態"那段填上你的項目信息 -- 每輪會話結束後更新會話記錄 - -**每個字段的意思:** - -- **當前已驗證狀態** — 項目當前進展的唯一真相 - - `倉庫根目录` — 項目在哪 - - `標準啟動路徑` — 把項目跑起來的命令 - - `標準驗證路徑` — 跑測試的命令 - - `当前最高優先級未完成功能` — 下一輪該做什麼 - - `当前 blocker` — 哪裡卡住了 -- **會話記錄** — 每輪一條 - - `本輪目標` — 打算做什麼 - - `已完成` — 實際做了什麼 - - `執行過的驗證` — 跑了什麼測試 - - `已記錄證據` — 留下了什麼證明 - - `提交記錄` — 提了什麼 commit - - `已知风险或未解决問題` — 哪裡可能有問題 - - `下一步最佳動作` — 下一輪從哪開始 - -## feature_list.json - -功能清單。機器可讀的功能列表,每個功能有狀態、驗證步驟和證據。 - -**怎麼用:** - -- 複製到項目根目錄 -- 把示例功能換成你自己的 -- 每個功能需要填: - - `id` — 短的唯一標識 - - `priority` — 整數,越小越優先 - - `area` — 屬於應用的哪塊(比如 "chat"、"import"、"search") - - `title` — 簡短描述 - - `user_visible_behavior` — 功能正常時用戶能看到什麼 - - `status` — 四種狀態之一:`not_started`、`in_progress`、`blocked`、`passing` - - `verification` — 逐步驗證步驟 - - `evidence` — 驗證通過的記錄(agent 填) - - `notes` — 額外說明 - -**狀態規則:** - -- `not_started` — 還沒碰 -- `in_progress` — 當前正在做的那個(同一時間只能有一個) -- `blocked` — 有記錄的阻塞問題,推不動 -- `passing` — 驗證通過,證據已記錄 - -agent 任何時候只能有一個功能處於 `in_progress`。 - -## session-handoff.md - -會話交接摘要。一輪會話結束時寫,下一輪開始時讀。讓接手的人(或 agent)快速瞭解現狀。 - -**怎麼用:** - -- 複製到項目根目錄 -- 每輪會話結束時填寫(也可以讓 agent 自己填) - -**每段寫什麼:** - -- **當前已驗證** — 哪些是確認能用的,跑過什麼驗證 -- **本輪改動** — 改了什麼代碼或基礎設施 -- **仍損壞或未驗證** — 已知問題和風險區 -- **下一步最佳動作** — 下一輪該做什麼,哪些東西不要動 -- **命令** — 啟動、驗證、調試命令,方便快速參考 - -短會話可以不寫這個檔案。會話長了或者項目有多個並行區域時,它就很關鍵了。 - -## clean-state-checklist.md - -收尾檢查清單。每次會話結束前過一遍,確保倉庫處於下一輪可以直接開工的狀態。 - -**怎麼用:** - -- 複製到項目根目錄 -- 關掉會話前逐項檢查 -- agent 的收尾流程裡也應該包含這些檢查 - -**檢查什麼:** - -- 標準啟動路徑還能用 -- 標準驗證還能跑 -- 進度日誌已更新 -- 功能清單真實反映了 passing 和未驗證的邊界(沒有假 passing) -- 沒有半成品處於未記錄狀態 -- 下一輪會話不需要人工修復就能繼續 - -## evaluator-rubric.md - -評審評分表。會話結束後或到裡程碑時,用它評估 agent 做的東西夠不夠格。 - -**怎麼用:** - -- 複製到項目根目錄 -- 一輪(或幾輪)會話後,按六個維度打分 -- 每個維度 0-2 分 - -**六個維度:** - -1. **正確性** — 實現出來的行為是否符合目標功能 -2. **驗證** — 要求的檢查是否真的跑過,並留下證據 -3. **範圍紀律** — 是否基本保持在選定功能範圍內 -4. **可靠性** — 結果是否能在重啟或重跑後繼續工作 -5. **可維護性** — 代碼和文檔是否清楚到足以交給下一輪會話 -6. **交接準備度** — 新會話是否能只靠倉庫內檔案繼續推進 - -**結論選項:** - -- Accept — 達標 -- Revise — 需要修補才能接受 -- Block — 有根本性問題,需要先解決 - -**Evaluator 需要校準。** 開箱即用的 agent 做評審很弱——它會發現問題,然後把自己說服到通過。你需要反覆校準: - -1. 用 evaluator 給一個已完成的 sprint 打分。 -2. 把它的分數和你自己的人工判斷對比。 -3. 有分歧的地方,把 rubric 裡的通過/失敗標準寫得更具體。 -4. 對同一個輸出重新跑 evaluator,看對齊了沒有。 -5. 重複直到 evaluator 的判斷和人工評審基本一致。 - -預計需要 3-5 輪校準。每輪記錄改了什麼、為什麼改。 - -## quality-document.md - -質量快照。給項目的每個產品領域和架構層打分,跟蹤代碼庫隨時間是變強了還是變弱了。 - -**怎麼用:** - -- 複製到項目根目錄 -- 開始會話前:讀它,瞭解代碼庫哪裡最弱 -- 會話結束後:更新評級 -- 長期:對比不同時間點的快照,看哪些 harness 改動真正改善了代碼庫健康度 - -**評什麼:** - -- **產品領域**(如文檔導入、問答流程、索引):每個領域按驗證狀態、Agent 可讀性、測試穩定性、關鍵缺口打分 -- **架構層**(如 Main Process、Preload、Renderer、Services):每層按邊界執行和 Agent 可讀性打分 - -**為什麼需要它:** - -Evaluator rubric 評的是單次 agent 輸出的質量。Quality document 評的是代碼庫本身的質量。它們回答的是不同的問題: - -- Evaluator rubric:"這輪 agent 做得好不好?" -- Quality document:"這個項目是在變強還是變弱?" - -**什麼時候更新:** - -- 每輪重要會話之後 -- 做基準對比之前 -- 做清理或簡化之後 -- 換新 agent 或新模型時 - -**和 harness 簡化的關係:** - -Quality document 也可以用來驗證 harness 是否可以簡化。Harness 裡的每個組件都編碼了一個假設——"模型做不到這件事"。模型變強後,這些假設就過時了。檢查某個組件是否還有必要: - -1. 拍一份 quality document 快照。 -2. 移除一個 harness 組件。 -3. 跑基準測試。 -4. 再拍一份快照。 -5. 對比——評級沒降,說明那個組件是多餘的。降了,就恢復。 diff --git a/courses/learn-harness-engineering/docs/zh-TW/resources/templates/init.sh b/courses/learn-harness-engineering/docs/zh-TW/resources/templates/init.sh deleted file mode 100755 index c153b2af..00000000 --- a/courses/learn-harness-engineering/docs/zh-TW/resources/templates/init.sh +++ /dev/null @@ -1,29 +0,0 @@ -#!/usr/bin/env bash - -set -euo pipefail - -ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" -cd "$ROOT_DIR" - -# 按你的项目实际情况替换这些命令。 -INSTALL_CMD=(npm install) -VERIFY_CMD=(npm test) -START_CMD=(npm run dev) - -echo "==> 当前目录: $PWD" -echo "==> 同步依赖" -"${INSTALL_CMD[@]}" - -echo "==> 运行基础验证" -"${VERIFY_CMD[@]}" - -echo "==> 启动命令" -printf ' %q' "${START_CMD[@]}" -printf '\n' - -if [ "${RUN_START_COMMAND:-0}" = "1" ]; then - echo "==> 启动应用" - exec "${START_CMD[@]}" -fi - -echo "如果希望 init.sh 直接启动应用,请设置 RUN_START_COMMAND=1。" diff --git a/courses/learn-harness-engineering/docs/zh-TW/resources/templates/quality-document.md b/courses/learn-harness-engineering/docs/zh-TW/resources/templates/quality-document.md deleted file mode 100644 index 170c7f37..00000000 --- a/courses/learn-harness-engineering/docs/zh-TW/resources/templates/quality-document.md +++ /dev/null @@ -1,43 +0,0 @@ -# 質量文檔 - -每個產品領域和架構層的質量快照。agent 和人都能通過這份文檔快速瞭解項目哪裡強、哪裡弱。 - -**更新時機:** 每輪重要會話結束後,或開始新一階段工作前。 - -**評級標準:** - -- **A**:驗證全部通過,架構乾淨,agent 能讀懂,測試穩定 -- **B**:驗證通過,基本乾淨,可讀性或測試覆蓋有少量缺口 -- **C**:部分可用,有已知缺口,部分代碼 agent 不容易理解 -- **D**:不可用,或存在重大結構問題 - ---- - -## 產品領域 - -| 領域 | 評級 | 驗證狀態 | Agent 可讀性 | 測試穩定性 | 關鍵缺口 | 上次更新 | -|------|------|---------|-------------|-----------|---------|---------| -| 文檔導入 | - | - | - | - | - | - | -| 文檔管理 | - | - | - | - | - | - | -| 文檔索引 | - | - | - | - | - | - | -| 問答流程 | - | - | - | - | - | - | -| 引用式回答 | - | - | - | - | - | - | - -## 架構層 - -| 層級 | 評級 | 邊界執行 | Agent 可讀性 | 關鍵缺口 | 上次更新 | -|------|------|---------|-------------|---------|---------| -| Main Process | - | - | - | - | - | -| Preload | - | - | - | - | - | -| Renderer | - | - | - | - | - | -| Services | - | - | - | - | - | - -## 變更歷史 - -### YYYY-MM-DD - -- 變更內容: -- 提升: -- 下降: -- 新發現的缺口: -- 已關閉的缺口: diff --git a/courses/learn-harness-engineering/docs/zh-TW/resources/templates/session-handoff.md b/courses/learn-harness-engineering/docs/zh-TW/resources/templates/session-handoff.md deleted file mode 100644 index 7d7f2f9c..00000000 --- a/courses/learn-harness-engineering/docs/zh-TW/resources/templates/session-handoff.md +++ /dev/null @@ -1,30 +0,0 @@ -# 會話交接 - -## 當前已驗證 - -- 現在明確可用的部分: -- 這輪實際跑過的驗證: - -## 本輪改動 - -- 新增了哪些代碼或行為: -- 基礎設施或 harness 發生了哪些變化: - -## 仍損壞或未驗證 - -- 已知缺陷: -- 未驗證路徑: -- 下一輪會話需要注意的風險: - -## 下一步最佳動作 - -- 最高優先級未完成功能: -- 為什麼它是下一步: -- 什麼結果才算 passing: -- 這一步中哪些東西不要動: - -## 命令 - -- 啟動命令: -- 驗證命令: -- 定向調試命令: diff --git a/courses/learn-harness-engineering/docs/zh-TW/skills/index.md b/courses/learn-harness-engineering/docs/zh-TW/skills/index.md deleted file mode 100644 index b1d7560c..00000000 --- a/courses/learn-harness-engineering/docs/zh-TW/skills/index.md +++ /dev/null @@ -1,49 +0,0 @@ -# Skills(技能集) - -本目錄包含課程附帶的 AI agent 技能。每個技能都是自包含的提示詞範本,可被 AI 程式設計智慧體(Claude Code、Codex、Cursor、Windsurf 等)載入以執行專業任務。 - -## harness-creator - -面向 AI 程式設計智慧體的生產級 harness 工程技能。幫助建立、評估和改進五個核心 harness 子系統:指令、狀態、驗證、範圍和會話生命週期。 - -### 它能做什麼 - -- **從零建立 harness** — AGENTS.md、功能清單、驗證工作流 -- **改進已有 harness** — 五子系統評分 + 優先級改進建議 -- **設計會話連續性** — 記憶持久化、進度追蹤、交接機制 -- **應用生產級模式** — 記憶、上下文工程、工具安全、多智慧體協調 - -### 快速開始 - -技能檔案位於倉庫的 [`skills/harness-creator/`](https://github.com/walkinglabs/learn-harness-engineering/tree/main/skills/harness-creator) 目錄。 - -在 Claude Code 中使用時,將 `harness-creator/` 目錄複製到你專案的技能路徑下,或讓 agent 直接讀取 SKILL.md 檔案即可。 - -### 參考模式 - -技能包含 6 個深入的模式參考檔案: - -| 模式 | 適用場景 | -|------|----------| -| 記憶持久化 | agent 在會話間遺忘專案知識 | -| 上下文工程 | 上下文預算管理、按需載入、委託隔離 | -| 工具註冊 | 工具安全、併發控制、權限管道 | -| 多智慧體協調 | 平行化、專業化、研究員→實作者工作流 | -| 生命週期與引導 | 鉤子、背景任務、初始化序列 | -| 常見陷阱 | 15 個容易踩坑的失敗模式及修復方案 | - -### 範本 - -技能附帶開箱即用的範本: - -- `agents.md` — AGENTS.md 腳手架,包含工作規則 -- `feature-list.json` — JSON Schema + 功能列表示例 -- `init.sh` — 標準初始化腳本 -- `progress.md` — 會話進度日誌範本 - -### 開發過程 - -`harness-creator` 基於 **skill-creator** 方法論開發——這是 Anthropic 官方提供的元技能,用於建立、測試和反覆改進 agent 技能。skill-creator 提供了結構化的工作流(起草 → 測試 → 評估 → 迭代),內建評估執行器、評分器和基準檢視器。 - -- **skill-creator 來源**:[anthropics/skills — skill-creator](https://github.com/anthropics/skills/tree/main/skills/skill-creator) -- **Claude Code 技能檔案**:[anthropics/claude-code — plugin-dev/skills](https://github.com/anthropics/claude-code/tree/main/plugins/plugin-dev/skills) diff --git a/courses/learn-harness-engineering/docs/zh/index.md b/courses/learn-harness-engineering/docs/zh/index.md deleted file mode 100644 index 999b45c7..00000000 --- a/courses/learn-harness-engineering/docs/zh/index.md +++ /dev/null @@ -1,73 +0,0 @@ -# 欢迎来到 Learn Harness Engineering - -Learn Harness Engineering 是一门专注于 AI 编程智能体工程化落地的课程。本课程深度研究并总结了业内最前沿的 Harness Engineering(工具马具/脚手架工程)理论与实践,参考资料包括: -- [OpenAI: Harness engineering: leveraging Codex in an agent-first world](https://openai.com/index/harness-engineering/) -- [Anthropic: Effective harnesses for long-running agents](https://www.anthropic.com/engineering/effective-harnesses-for-long-running-agents) -- [Anthropic: Harness design for long-running application development](https://www.anthropic.com/engineering/harness-design-long-running-apps) -- [Awesome Harness Engineering](https://github.com/walkinglabs/awesome-harness-engineering) - -通过系统的环境设计、状态管理、验证与控制机制,本课程旨在帮助你让 Codex 和 Claude Code 等 AI Agent 能够真正可靠地完成真实工程任务。它通过明确的规则和边界约束你的 AI 编程助手,帮助你更可靠地构建功能、修复 Bug 并自动化开发任务。 - -## 开始学习 - -选择适合你的学习路径。本课程分为理论讲义、实战项目和开箱即用的资料库。 - - - -## Harness 的核心机制 - -Harness 的本质不是“让模型变聪明”,而是给模型建立一套闭环的**工作系统**。你可以通过下面的简单图示理解它的核心运作流: - -```mermaid -graph TD - A["明确目标
    AGENTS.md"] --> B("初始化检查
    init.sh") - B --> C{"运行任务
    AI Agent"} - C -->|遇到障碍| D["运行反馈
    CLI / Logs"] - D -->|自动修复| C - C -->|代码完成| E{"验证与评审
    Test & QA"} - E -->|未通过| D - E -->|通过| F["清理与交接
    claude-progress.md"] - - classDef primary fill:#D95C41,stroke:#C14E36,color:#fff,font-weight:bold; - classDef process fill:#F4F3EE,stroke:#D1D1D1,color:#1A1A1A; - classDef check fill:#EAE8E1,stroke:#B3B3B3,color:#1A1A1A; - - class A,F primary; - class B,D process; - class C,E check; -``` - -## 你将学到什么 - -你将在本课程中掌握以下核心概念: - -
      -
    • 用明确的规则和边界约束 Agent 的行为
    • -
    • 在跨会话的长时任务中保持上下文连续性
    • -
    • 防止 Agent 提前宣告任务完成。
    • -
    • 让 Agent 学会通过完整的流水线测试来验证自己的工作
    • -
    • 让 Agent 的运行过程可观测、可调试
    • -
    - -## 下一步 - -了解核心概念后,可以通过以下内容深入学习: - - diff --git a/courses/learn-harness-engineering/docs/zh/lectures/lecture-01-why-capable-agents-still-fail/code/failure-pattern-demo.ts b/courses/learn-harness-engineering/docs/zh/lectures/lecture-01-why-capable-agents-still-fail/code/failure-pattern-demo.ts deleted file mode 100644 index 311e665b..00000000 --- a/courses/learn-harness-engineering/docs/zh/lectures/lecture-01-why-capable-agents-still-fail/code/failure-pattern-demo.ts +++ /dev/null @@ -1,170 +0,0 @@ -/** - * failure-pattern-demo.ts - * - * Simulates the 4-step failure pattern that capable agents fall into: - * 1. Incomplete context - * 2. Locally reasonable changes - * 3. No global verification - * 4. Premature completion - * - * Run: npx tsx docs/lectures/lecture-01-why-capable-agents-still-fail/code/failure-pattern-demo.ts - */ - -// --------------------------------------------------------------------------- -// Types -// --------------------------------------------------------------------------- - -interface StepState { - step: number; - name: string; - contextAvailable: string[]; - contextMissing: string[]; - actionTaken: string; - localOutcome: string; - globalImpact: string; - completed: boolean; -} - -// --------------------------------------------------------------------------- -// Simulated "model" -- a simple decision function that bases its output -// solely on whatever context it has been given. -// --------------------------------------------------------------------------- - -function modelDecide(context: string[], task: string): string { - const has = (s: string) => context.some((c) => c.includes(s)); - - // The task is to "add a search endpoint to the API". - // Correct answer requires knowing about auth middleware and rate limiting. - - if (!has("auth")) { - return "Created new route handler /search without authentication checks"; - } - if (!has("rate-limit")) { - return "Added search route with auth but forgot rate limiting"; - } - if (!has("test-standards")) { - return "Implemented search with auth and rate-limit, but no tests"; - } - return "Fully implemented search endpoint with auth, rate-limit, and tests"; -} - -// --------------------------------------------------------------------------- -// Failure simulation -// --------------------------------------------------------------------------- - -function simulateFailurePattern(): StepState[] { - const steps: StepState[] = []; - - // ---- Step 1: Incomplete Context ---- - const step1Context = ["project structure", "route definitions"]; - const step1Missing = ["auth middleware", "rate-limiting policy", "test standards"]; - const step1Decision = modelDecide(step1Context, "add search endpoint"); - - steps.push({ - step: 1, - name: "Incomplete Context", - contextAvailable: step1Context, - contextMissing: step1Missing, - actionTaken: step1Decision, - localOutcome: "Looks good -- route compiles, returns data", - globalImpact: "Missing auth means unauthenticated access to search", - completed: false, - }); - - // ---- Step 2: Locally Reasonable Changes ---- - // The agent adds auth after a hint, but still lacks other context. - const step2Context = [...step1Context, "auth middleware"]; - const step2Missing = ["rate-limiting policy", "test standards"]; - const step2Decision = modelDecide(step2Context, "add search endpoint"); - - steps.push({ - step: 2, - name: "Locally Reasonable Changes", - contextAvailable: step2Context, - contextMissing: step2Missing, - actionTaken: step2Decision, - localOutcome: "Route has auth -- looks complete locally", - globalImpact: "No rate limiting means the endpoint can be abused", - completed: false, - }); - - // ---- Step 3: No Global Verification ---- - const step3Context = [...step2Context, "rate-limiting policy"]; - const step3Missing = ["test standards"]; - const step3Decision = modelDecide(step3Context, "add search endpoint"); - - steps.push({ - step: 3, - name: "No Global Verification", - contextAvailable: step3Context, - contextMissing: step3Missing, - actionTaken: step3Decision, - localOutcome: "Feature appears fully implemented", - globalImpact: "No tests -- regression risk, violates project standards", - completed: false, - }); - - // ---- Step 4: Premature Completion ---- - steps.push({ - step: 4, - name: "Premature Completion", - contextAvailable: step3Context, - contextMissing: step3Missing, - actionTaken: 'Agent outputs: "Done. Added search endpoint."', - localOutcome: "Agent is satisfied, task marked complete", - globalImpact: "Task is incomplete -- missing tests, no E2E verification", - completed: true, - }); - - return steps; -} - -// --------------------------------------------------------------------------- -// Comparison table -// --------------------------------------------------------------------------- - -function printComparisonTable(steps: StepState[]): void { - console.log("\n" + "=".repeat(90)); - console.log(" FAILURE PATTERN DEMO -- 4 Steps to a Broken Deliverable"); - console.log("=".repeat(90)); - - for (const s of steps) { - console.log(`\n Step ${s.step}: ${s.name}`); - console.log(" " + "-".repeat(60)); - console.log(` Context available : ${s.contextAvailable.join(", ")}`); - console.log(` Context missing : ${s.contextMissing.join(", ") || "(none)"}`); - console.log(` Action taken : ${s.actionTaken}`); - console.log(` Local outcome : ${s.localOutcome}`); - console.log(` Global impact : ${s.globalImpact}`); - console.log(` Marked complete? : ${s.completed ? "YES (premature)" : "No"}`); - } - - // Summary comparison - console.log("\n" + "=".repeat(90)); - console.log(" COMPARISON: What the agent saw vs. what was actually needed"); - console.log("=".repeat(90)); - - const totalRequired = ["project structure", "route definitions", "auth middleware", "rate-limiting policy", "test standards"]; - const finalAvailable = steps[steps.length - 1].contextAvailable; - - const header = "| Criterion | Available | Missing | Status |"; - const sep = "|------------------------|-----------|---------|----------|"; - console.log("\n" + header); - console.log(sep); - - for (const item of totalRequired) { - const avail = finalAvailable.includes(item); - const row = `| ${item.padEnd(23)}| ${(avail ? "Yes" : "No").padEnd(10)}| ${(!avail ? "Yes" : "").padEnd(8)}| ${(avail ? "OK" : "GAP").padEnd(9)}|`; - console.log(row); - } - - const gapCount = totalRequired.filter((i) => !finalAvailable.includes(i)).length; - console.log("\n Result: Agent completed with " + gapCount + " of " + totalRequired.length + " context items missing."); - console.log(" This is the core failure pattern: each step looked reasonable in isolation.\n"); -} - -// --------------------------------------------------------------------------- -// Run -// --------------------------------------------------------------------------- - -printComparisonTable(simulateFailurePattern()); diff --git a/courses/learn-harness-engineering/docs/zh/lectures/lecture-01-why-capable-agents-still-fail/code/failure-signals-checklist.md b/courses/learn-harness-engineering/docs/zh/lectures/lecture-01-why-capable-agents-still-fail/code/failure-signals-checklist.md deleted file mode 100644 index 17c8865d..00000000 --- a/courses/learn-harness-engineering/docs/zh/lectures/lecture-01-why-capable-agents-still-fail/code/failure-signals-checklist.md +++ /dev/null @@ -1,9 +0,0 @@ -# 失败信号检查清单 - -在审查一次弱 harness 运行结果时使用此清单。 - -- Agent 是否询问了如何启动应用,或者做出了错误的推断? -- 它是否创建了与预期产品不匹配的目录或抽象? -- 它是否在完成一个可见的 UI 外壳后就停止了,而没有完成完整的工作流? -- 它是否留下了有助于后续运行继续的笔记或产物? -- 一个全新的会话是否能在五分钟内理解之前发生了什么? diff --git a/courses/learn-harness-engineering/docs/zh/lectures/lecture-01-why-capable-agents-still-fail/code/index.md b/courses/learn-harness-engineering/docs/zh/lectures/lecture-01-why-capable-agents-still-fail/code/index.md deleted file mode 100644 index 90d07336..00000000 --- a/courses/learn-harness-engineering/docs/zh/lectures/lecture-01-why-capable-agents-still-fail/code/index.md +++ /dev/null @@ -1,7 +0,0 @@ -# 第 01 讲代码 - -使用此文件夹存放小型示例,用于展示: - -- 强大模型在弱环境中的失败 -- 规格不完整的仓库设置 -- 缺失的反馈循环 diff --git a/courses/learn-harness-engineering/docs/zh/lectures/lecture-01-why-capable-agents-still-fail/code/underspecified-task.md b/courses/learn-harness-engineering/docs/zh/lectures/lecture-01-why-capable-agents-still-fail/code/underspecified-task.md deleted file mode 100644 index 68468fb2..00000000 --- a/courses/learn-harness-engineering/docs/zh/lectures/lecture-01-why-capable-agents-still-fail/code/underspecified-task.md +++ /dev/null @@ -1,18 +0,0 @@ -# 规格不足的任务示例 - -构建一个带有 AI 问答功能的桌面知识库应用。 - -约束条件: - -- 未指定任何约束 -- 未给出启动命令 -- 未提供文件夹结构指引 -- 未定义数据模型 -- 未明确完成标准 - -这类提示的典型结果: - -- Agent 临时编造一个结构 -- 应用可能编译通过但无法一致地启动 -- UI 可能在没有任何可用的导入/查询路径之前就出现了 -- Agent 通常在表面成功后就停止了 diff --git a/courses/learn-harness-engineering/docs/zh/lectures/lecture-01-why-capable-agents-still-fail/index.md b/courses/learn-harness-engineering/docs/zh/lectures/lecture-01-why-capable-agents-still-fail/index.md deleted file mode 100644 index 2a2d3dd9..00000000 --- a/courses/learn-harness-engineering/docs/zh/lectures/lecture-01-why-capable-agents-still-fail/index.md +++ /dev/null @@ -1,117 +0,0 @@ -[English Version →](../../../en/lectures/lecture-01-why-capable-agents-still-fail/) - -> 本篇代码示例:[code/](https://github.com/walkinglabs/learn-harness-engineering/blob/main/docs/zh/lectures/lecture-01-why-capable-agents-still-fail/code/) -> 实战练习:[Project 01. 只写提示词让 agent 做,和定好规则再让它做,差多少](./../../projects/project-01-baseline-vs-minimal-harness/index.md) - -# 第一讲. 模型能力强,不等于执行可靠 - -你说你也算见过世面的人了——Claude Pro 订着,GPT-4o 的 API key 也有,SWE-bench 排行榜上的数字你比谁都清楚。有一天你终于想让 AI agent 帮你改一个真实的项目,信心满满地交代下去。结果呢?加了功能但测试挂了,改了 bug 但引入了新 bug,跑了 20 分钟然后自信满满地说"完成了"——你一看代码,根本不是你要的东西。 - -这时候你的第一反应是什么?"这模型不行,得换一个更贵的。"且慢。先别急着掏钱包。问题可能根本不在模型身上。 - -让我们先看一组数据。截至 2025 年底,最强的 coding agent 在 SWE-bench Verified 上的通过率大约在 50-60%。这还是精心挑选过的、有明确 issue 描述和测试用例的任务。换到你日常开发的场景——需求模糊、没有现成测试、隐含的业务规则散落在各处——这个数字只会更低。 - -但这组数据背后藏着一个反直觉的事实。 - -## 同一匹马,两种命运 - -Anthropic 做过一个对照实验。同一个 prompt("做一个 2D 复古游戏编辑器"),同一个模型(Opus 4.5)。第一次让它裸跑——20 分钟,花了 $9,游戏核心功能根本跑不起来。第二次给它配上完整的 harness(planner + generator + evaluator 三 agent 架构)——6 小时,花了 $200,游戏可以正常游玩。 - -模型没换。Opus 4.5 还是那个 Opus 4.5。换的是马具。 - -OpenAI 在 2025 年发布的 harness engineering 文章里说得更直白:Codex 在一个 harness 搭得好的仓库里,表现能从"不可靠"变成"可靠"。注意他们的用词——不是"好了一点",是质变。就像一匹千里马,没马具你也能骑,但控不稳、走不远、摔下来也不稀奇。harness 就是那整套马具——**模型权重之外的一切工程基础设施**。 - -## agent 到底卡在哪 - -那具体是什么在出问题? - -最常见的:你根本没把任务说清楚。你说"加个搜索功能",agent 理解的跟你完全不一样——搜什么?全文本还是结构化?要不要分页?要不要高亮?你没说清楚,agent 就自己猜。猜对了是运气,猜错了你再改,改的成本比一开始说清楚还高。这就像你去饭馆跟师傅说"来个鱼",端上来是红烧还是清蒸还是酸菜——全看运气。 - -就算你说清了,项目里隐含的架构约定 agent 也不知道。你们团队统一用 SQLAlchemy 2.0 的新语法,但 agent 默认写了 1.x 的代码。所有 API 端点必须走 OAuth 2.0 认证,但这个规则只存在于你脑子里和一个三个月前的 Slack 消息里。Agent 看不到这些——它不是不想遵守,是压根不知道有这回事。 - -环境也是个坑。开发环境配置不完整、依赖缺了、工具版本不对。Agent 把宝贵的上下文窗口花在了 `pip install` 失败、Node 版本不对这些事上,而不是解决你的核心任务。就好比你让一个木匠来干活,结果锤子没带、钉子找不到、工作台还是歪的——手艺再好也使不上劲。 - -更常见的:压根没有验证手段。没有测试、没有 lint、或者验证命令根本没告诉 agent。Agent 写完代码,自己看了看觉得没问题,就说完成了。这就像让学生交作业但不给答案检查——他自己觉得自己做对了,但批改的时候一堆错误。Anthropic 还观察到一个有意思的现象:当 agent 感觉上下文快满了,它们会匆忙结束当前工作,跳过验证步骤,选一个简单的方案而不是最优方案。他们把这叫"上下文焦虑"——跟你考试时发现时间快到了赶紧随便选几个选择题是一回事。 - -长任务跨会话就更惨了——上次会话的发现全丢了,每个新会话都得重新探索项目结构、理解代码组织。缺乏持久化状态的 agent 在超过 30 分钟的任务中失败率急剧上升。 - -## 关键名词解释 - -理解了上面的场景,这些概念就不再是一堆术语了: - -- **能力鸿沟(Capability Gap)**:模型在基准测试上的表现和真实任务上的表现之间的巨大落差。SWE-bench Verified 上 50-60% 的通过率意味着近一半的真实 issue 解不了。 -- **Harness**:模型之外的一切——指令、工具、环境、状态管理、验证反馈。不是模型权重的部分,全是 harness。也就是我们说的"马具"。 -- **Harness 诱导失败**:模型本身能力足够,但因为执行环境有结构性缺陷而失败。Anthropic 的对照实验已经证明了这一点。 -- **验证缺口**:agent 对自己输出的信心评估和实际正确性之间的偏差。agent 说"我做完了"但实际没做完——这是最常见的失败模式。 -- **诊断循环**:执行 → 观察失败 → 定位到 harness 的哪一层出了问题 → 修补那一层 → 重新执行。这是 harness 工程的核心方法论。 -- **完成定义(Definition of Done)**:一组可以用命令验证的条件——测试通过、lint 没报错、类型检查通过。没有显式的完成定义,agent 就会自己编一个。 - -## 遇到失败,先修 harness - -核心原则:**遇到失败,先别换模型,先检查 harness。** 如果同一个模型在类似的结构良好的任务中能成功,那优先假设是 harness 的问题。这就像汽车抛锚——你不会第一时间怀疑是发动机坏了,你会先看看是不是没油了。 - -具体怎么做: - -**每次失败都归因到具体层。** 不要笼统地说"模型不行",而是问:是任务没说清楚?是上下文不够?是没有验证手段?把每次失败归到五层防御:任务规范、上下文供给、执行环境、验证反馈、状态管理。这里说的是实操诊断用的五层防御,不是上面那组六个术语。养成这个习惯,你会发现"模型不行"这个结论在你的日志里出现得越来越少。 - -**给每个任务写显式的完成定义。** 不要说"加个搜索功能",要说: -``` -完成标准: -- 新增 GET /api/search?q=xxx 端点 -- 支持分页,默认 20 条 -- 返回结果包含高亮片段 -- 所有新代码通过 pytest -- 类型检查通过(mypy --strict) -``` - -**创建 AGENTS.md 文件。** 在仓库根目录放一个文件,告诉 agent 这个项目的技术栈、架构约定、验证命令。这是 harness 工程的第一步,也是投入产出比最高的一步。一个 `AGENTS.md` 文件可能比你换一个更贵的模型更有效——我不是在开玩笑。 - -**建立诊断循环。** 不要把失败当作"模型又犯傻了",而是当作"harness 又暴露了一个缺陷"的信号。每次失败 → 定位层 → 修补 → 下次不再犯。几轮下来,你的 harness 会越来越强,agent 的表现会稳定提升。就像修路——每填一个坑,下一段路就更平坦。 - -**量化改进。** 记个简单的日志:每个任务成功了没有,失败了是哪一层的问题。跑几轮之后你就能看出来哪个层是瓶颈,集中火力修那个层。 - -## 一百万行代码的实验 - -OpenAI 在 2025 年做了一个激进的实验:用 Codex 从一个空的 git 仓库起步,构建一个完整的内部产品。五个月后,这个仓库有大约 100 万行代码——应用逻辑、基础设施、工具、文档、内部开发工具——全部由 agent 生成。三个工程师驱动 Codex,开了大约 1,500 个 PR 并合并。平均每人每天 3.5 个 PR。 - -这个实验的关键约束是:**人类永远不直接写代码。** 这不是噱头,而是为了逼团队搞清楚——当工程师的主要工作不再是写代码,而是设计环境、表达意图、构建反馈回路时,到底什么变了? - -早期进展比预期慢。不是 Codex 不行,而是环境不够完整——agent 缺少必要的工具、抽象和内部结构来推进高层次目标。工程师的工作变成了:把大目标拆成小积木(设计、编码、审查、测试),让 agent 去搭建,然后用这些积木解锁更复杂的任务。当某件事失败了,修复几乎从来不是"更努力",而是"agent 缺什么能力,怎么让它既可理解又可执行"。 - -这个实验直接证明了本讲的核心论点:**同一个模型,在空白环境里和在有完整 harness 的环境里,产出有本质差异。** 模型没变,变的是环境。 - -> 来源:[OpenAI: Harness engineering: leveraging Codex in an agent-first world](https://openai.com/index/harness-engineering/) - -## 一个更接地气的例子 - -一个团队用 Claude Sonnet 给一个中等规模的 Python Web 应用(FastAPI + PostgreSQL + Redis,约 15,000 行代码)添加新的 API 端点。 - -起初他们只给了一句话:"在 `/api/v2/users` 下添加用户偏好设置端点"。结果呢?agent 花了 40% 的上下文窗口探索仓库结构,产出了看似合理的代码但没遵循项目的错误处理模式,用了旧版 SQLAlchemy 语法,宣称完成但端点实际有运行时错误。下一个会话还得重新做发现工作。 - -后来他们加了 `AGENTS.md`(描述项目架构和技术栈版本)、显式的验证命令(`pytest tests/api/v2/ && python -m mypy src/`)、和架构决策记录。同一模型在三次独立运行中全部成功,上下文使用效率提高了约 60%。 - -模型没变。变的还是 harness。 - -## 带走什么 - -- 模型能力和执行可靠性是两回事。千里马也得配上好马具。 -- 失败的时候先看 harness,再看模型。换模型是成本最高的选择——而且很多时候根本不是模型的问题。 -- 每次失败都是一个信号:你的 harness 有结构性缺陷。把它找出来、修掉。 -- 五层防御:任务规范、上下文供给、执行环境、验证反馈、状态管理。逐层排查,就像医生看病先检查最常见的病因。 -- 一个 `AGENTS.md` 文件可能比你换一个更贵的模型更有效。真的。 - -## 延伸阅读 - -- [OpenAI: Harness Engineering — Leveraging Codex in an Agent-First World](https://openai.com/index/harness-engineering/) -- [Anthropic: Effective Harnesses for Long-Running Agents](https://www.anthropic.com/engineering/effective-harnesses-for-long-running-agents) -- [HumanLayer: Skill Issue — Harness Engineering for Coding Agents](https://humanlayer.dev/articles/harness-engineering-for-coding-agents/) -- [SWE-bench Leaderboard](https://www.swebench.com/) -- [Thoughtworks Technology Radar: Harness Engineering](https://www.thoughtworks.com/radar) - -## 练习 - -1. **对比实验**:选一个你熟悉的代码仓库和一项非平凡的修改任务。先不给任何 harness 支持,让 agent 跑一次,记录失败。然后加一个 `AGENTS.md` 和显式的验证命令,让同一个 agent 再跑一次。对比两次的结果,把失败归因到五层防御中的某一层。 - -2. **验证缺口测量**:选 5 个编码任务,在每个任务完成后记录 agent 是否声称完成,然后用独立测试验证实际正确性。计算 agent 在实际没完成时声称完成的比例——这就是你的验证缺口。然后想想:加什么验证命令能把这个比例降下来? - -3. **诊断循环实践**:找一个 agent 在你的项目中反复失败的任务。跑一次,记录失败。归因到五层中的某一层。修那一层。再跑。重复三到五轮,记录每一轮的改善。 diff --git a/courses/learn-harness-engineering/docs/zh/lectures/lecture-02-what-a-harness-actually-is/code/harness-components.md b/courses/learn-harness-engineering/docs/zh/lectures/lecture-02-what-a-harness-actually-is/code/harness-components.md deleted file mode 100644 index 149babc6..00000000 --- a/courses/learn-harness-engineering/docs/zh/lectures/lecture-02-what-a-harness-actually-is/code/harness-components.md +++ /dev/null @@ -1,21 +0,0 @@ -# Harness 组件示例 - -对于在本地仓库中工作的编码 Agent: - -- 模型: - LLM 本身 - -- Harness: - - 系统提示词 - - AGENTS.md - - bash 工具 - - 文件读写工具 - - git 访问 - - 本地文件系统 - - 启动脚本 - - 测试命令 - - 停止钩子 - - 代码检查 - - 评估循环 - -如果你更改了上述任何一个 harness 组件,你就改变了实际运行的 Agent。 diff --git a/courses/learn-harness-engineering/docs/zh/lectures/lecture-02-what-a-harness-actually-is/code/harness-vs-no-harness.ts b/courses/learn-harness-engineering/docs/zh/lectures/lecture-02-what-a-harness-actually-is/code/harness-vs-no-harness.ts deleted file mode 100644 index d856ba36..00000000 --- a/courses/learn-harness-engineering/docs/zh/lectures/lecture-02-what-a-harness-actually-is/code/harness-vs-no-harness.ts +++ /dev/null @@ -1,180 +0,0 @@ -/** - * harness-vs-no-harness.ts - * - * Side-by-side comparison of the same task executor running with and without - * a harness. The harness version adds explicit rules, verification steps, - * and stop conditions. - * - * Run: npx tsx docs/lectures/lecture-02-what-a-harness-actually-is/code/harness-vs-no-harness.ts - */ - -// --------------------------------------------------------------------------- -// Types & helpers -// --------------------------------------------------------------------------- - -interface TaskResult { - name: string; - passed: boolean; - durationMs: number; - issues: string[]; -} - -function pad(s: string, len: number): string { - return s.length >= len ? s : s + " ".repeat(len - s.length); -} - -// --------------------------------------------------------------------------- -// Simulated task executor -// --------------------------------------------------------------------------- - -/** A simple task that can succeed or fail depending on rules. */ -type Task = { - name: string; - requiresAuth: boolean; - hasTests: boolean; - withinScope: boolean; -}; - -const tasks: Task[] = [ - { name: "Add search endpoint", requiresAuth: true, hasTests: false, withinScope: true }, - { name: "Add delete endpoint", requiresAuth: true, hasTests: true, withinScope: true }, - { name: "Refactor auth middleware", requiresAuth: true, hasTests: true, withinScope: false }, - { name: "Add health check", requiresAuth: false, hasTests: true, withinScope: true }, - { name: "Add rate limiter", requiresAuth: true, hasTests: false, withinScope: true }, -]; - -// --------------------------------------------------------------------------- -// Run WITHOUT harness -- just execute every task, no checks -// --------------------------------------------------------------------------- - -function runWithoutHarness(taskList: Task[]): TaskResult[] { - const results: TaskResult[] = []; - - for (const t of taskList) { - const start = performance.now(); - const issues: string[] = []; - - // The "agent" does the work and calls it done. - let passed = true; - - // Problems that go undetected without a harness - if (t.requiresAuth && !t.hasTests) { - issues.push("No tests for auth-protected endpoint"); - // Agent doesn't notice -- marks as pass anyway - } - if (!t.withinScope) { - issues.push("Task is outside current scope"); - // Agent doesn't notice - } - - const duration = performance.now() - start; - results.push({ name: t.name, passed, durationMs: duration, issues }); - } - - return results; -} - -// --------------------------------------------------------------------------- -// Run WITH harness -- rules, verification, stop conditions -// --------------------------------------------------------------------------- - -function runWithHarness(taskList: Task[]): TaskResult[] { - const rules = { - requireTestsForAuth: true, - enforceScope: true, - }; - - const results: TaskResult[] = []; - - for (const t of taskList) { - const start = performance.now(); - const issues: string[] = []; - let passed = true; - - // Rule: auth endpoints must have tests - if (rules.requireTestsForAuth && t.requiresAuth && !t.hasTests) { - issues.push("BLOCKED: Auth-protected endpoint missing tests"); - passed = false; - } - - // Rule: stay in scope - if (rules.enforceScope && !t.withinScope) { - issues.push("BLOCKED: Task outside active scope -- skipped"); - passed = false; - } - - // Verification: re-check after execution - if (passed && !t.hasTests) { - issues.push("WARNING: No tests exist for this change"); - } - - const duration = performance.now() - start; - results.push({ name: t.name, passed, durationMs: duration, issues }); - } - - return results; -} - -// --------------------------------------------------------------------------- -// Reporting -// --------------------------------------------------------------------------- - -function printComparison( - noHarness: TaskResult[], - withHarness: TaskResult[] -): void { - console.log("\n" + "=".repeat(80)); - console.log(" HARNESS vs NO-HARNESS COMPARISON"); - console.log("=".repeat(80)); - - const header = `| ${pad("Task", 28)}| ${pad("No-Harness", 12)}| ${pad("Issues(NH)", 35)}| ${pad("Harness", 12)}| ${pad("Issues(H)", 35)}|`; - const sep = `|${"-".repeat(30)}|${"-".repeat(14)}|${"-".repeat(37)}|${"-".repeat(14)}|${"-".repeat(37)}|`; - - console.log("\n" + header); - console.log(sep); - - for (let i = 0; i < noHarness.length; i++) { - const nh = noHarness[i]; - const wh = withHarness[i]; - const nhPass = nh.passed ? "PASS" : "FAIL"; - const whPass = wh.passed ? "PASS" : "FAIL"; - const nhIssue = nh.issues[0] ?? "(none)"; - const whIssue = wh.issues[0] ?? "(none)"; - - console.log( - `| ${pad(nh.name, 28)}| ${pad(nhPass, 12)}| ${pad(nhIssue, 35)}| ${pad(whPass, 12)}| ${pad(whIssue, 35)}|` - ); - } - - // Metrics - const nhPassed = noHarness.filter((r) => r.passed).length; - const whPassed = withHarness.filter((r) => r.passed).length; - const nhIssues = noHarness.reduce((sum, r) => sum + r.issues.length, 0); - const whIssues = withHarness.reduce((sum, r) => sum + r.issues.length, 0); - - console.log("\n" + "=".repeat(80)); - console.log(" SUMMARY METRICS"); - console.log("=".repeat(80)); - - const metricsHeader = `| ${pad("Metric", 30)}| ${pad("No Harness", 15)}| ${pad("With Harness", 15)}|`; - const metricsSep = `|${"-".repeat(32)}|${"-".repeat(17)}|${"-".repeat(17)}|`; - console.log("\n" + metricsHeader); - console.log(metricsSep); - console.log(`| ${pad("Tasks passed", 30)}| ${pad(String(nhPassed) + "/" + noHarness.length, 15)}| ${pad(String(whPassed) + "/" + withHarness.length, 15)}|`); - console.log(`| ${pad("Issues detected", 30)}| ${pad(String(nhIssues), 15)}| ${pad(String(whIssues), 15)}|`); - console.log(`| ${pad("False positives (passed but flawed)", 30)}| ${pad(String(nhPassed - truePassCount(noHarness)), 15)}| ${pad(String(whPassed - truePassCount(withHarness)), 15)}|`); - - console.log("\n The harness catches problems the no-harness run silently ignores."); - console.log(" Without a harness, every task 'passes' even when it shouldn't.\n"); -} - -/** Count tasks that actually pass (have tests and are in scope). */ -function truePassCount(results: TaskResult[]): number { - return results.filter((r) => r.passed && r.issues.length === 0).length; -} - -// --------------------------------------------------------------------------- -// Run -// --------------------------------------------------------------------------- - -printComparison(runWithoutHarness(tasks), runWithHarness(tasks)); diff --git a/courses/learn-harness-engineering/docs/zh/lectures/lecture-02-what-a-harness-actually-is/code/index.md b/courses/learn-harness-engineering/docs/zh/lectures/lecture-02-what-a-harness-actually-is/code/index.md deleted file mode 100644 index fee7c4c2..00000000 --- a/courses/learn-harness-engineering/docs/zh/lectures/lecture-02-what-a-harness-actually-is/code/index.md +++ /dev/null @@ -1,8 +0,0 @@ -# 第 02 讲代码 - -使用此文件夹存放小型示例,用于区分: - -- 模型行为 -- harness 行为 -- 纯提示词设置 -- 有环境支撑的 Agent 设置 diff --git a/courses/learn-harness-engineering/docs/zh/lectures/lecture-02-what-a-harness-actually-is/code/minimal-harness-loop.ts b/courses/learn-harness-engineering/docs/zh/lectures/lecture-02-what-a-harness-actually-is/code/minimal-harness-loop.ts deleted file mode 100644 index e56cddd8..00000000 --- a/courses/learn-harness-engineering/docs/zh/lectures/lecture-02-what-a-harness-actually-is/code/minimal-harness-loop.ts +++ /dev/null @@ -1,36 +0,0 @@ -type Message = { - role: "user" | "assistant"; - content: string; -}; - -type ToolResult = { - ok: boolean; - output: string; -}; - -function runTool(name: string, input: string): ToolResult { - if (name === "read_file") { - return { ok: true, output: `contents of ${input}` }; - } - - return { ok: false, output: `unknown tool: ${name}` }; -} - -export function minimalHarness(messages: Message[]) { - const nextAction = { - tool: "read_file", - input: "README.md" - }; - - const result = runTool(nextAction.tool, nextAction.input); - - return { - messages: [ - ...messages, - { - role: "assistant", - content: `Tool ${nextAction.tool} returned: ${result.output}` - } - ] - }; -} diff --git a/courses/learn-harness-engineering/docs/zh/lectures/lecture-02-what-a-harness-actually-is/index.md b/courses/learn-harness-engineering/docs/zh/lectures/lecture-02-what-a-harness-actually-is/index.md deleted file mode 100644 index de9c5287..00000000 --- a/courses/learn-harness-engineering/docs/zh/lectures/lecture-02-what-a-harness-actually-is/index.md +++ /dev/null @@ -1,109 +0,0 @@ -[English Version →](../../../en/lectures/lecture-02-what-a-harness-actually-is/) - -> 本篇代码示例:[code/](https://github.com/walkinglabs/learn-harness-engineering/blob/main/docs/zh/lectures/lecture-02-what-a-harness-actually-is/code/) -> 实战练习:[Project 01. 只写提示词让 agent 做,和定好规则再让它做,差多少](./../../projects/project-01-baseline-vs-minimal-harness/index.md) - -# 第二讲. Harness 到底是什么 - -"harness"这个词在 AI coding agent 的圈子里被用得越来越多了,但说实话,大部分人说的 harness 其实就只是"一个 prompt 文件"。这不是 harness。就像你开了一家餐厅,只有食材——没有灶台、没有刀具、没有菜谱、没有出菜流程——那不叫餐厅,那叫冰箱。 - -这节课要给你一个精确的、可操作的 harness 定义。不是学术论文里的抽象概念,而是你今天就能拿去用的框架:harness 由五个子系统组成,就像一个完整厨房里的五个功能区——菜谱架、刀具架、灶台、备菜台、和出菜检查口。每个子系统都有明确的职责和评判标准。 - -## 先讲一个类比 - -想象你是一个刚入职的工程师,被丢进一个没有任何文档的项目里。没有 README,代码里没有注释,没有人告诉你怎么跑测试,CI 配置文件藏在某个角落里。你能写出好代码吗?也许能——如果你足够聪明又足够有耐心。但你会花大量时间在"搞清楚这个项目是怎么回事"上,而不是在"解决问题"上。 - -AI agent 面对的困境一模一样。而且更糟——你至少可以问同事,agent 只能看到你放在它面前的文件和它能执行的命令。它不能拍拍同事的肩膀问"哎,这个项目的 ORM 用的是哪个版本?" - -OpenAI 在他们的 harness engineering 文章里把 harness 的核心原则表述为"仓库即规范"——所有必要的上下文都应该在仓库里,通过结构化的指令文件、明确的验证命令和清晰的目录组织来呈现。Anthropic 的 long-running agents 文档则更侧重状态持久化、显式恢复路径和结构化的进度跟踪。两家公司的侧重点不同,但都在说同一件事:**模型之外的一切工程基础设施,决定了模型能力能被发挥多少。** - -看看几个你熟悉的工具: - -**Claude Code** 的设计就体现了 harness 思想。它会读你仓库里的 `CLAUDE.md`(菜谱架),能用 shell 跑命令(刀具架),在你的本地环境里执行(灶台),有会话历史(备菜台),能跑测试看结果(出菜检查口)。但如果你不告诉它怎么跑测试,出菜检查口就是断的——菜做没做熟谁也不知道。 - -**Cursor** 也是类似的逻辑。它的 `.cursorrules` 文件是菜谱架,终端是刀具架,它能读你的项目结构和 lint 配置是灶台。但 Cursor 的状态管理相对弱——你关掉 IDE 再打开,上次的上下文就没了。 - -**Codex**(OpenAI 的 coding agent)用 git worktree 隔离每个任务的运行环境,配合本地的可观测性栈(日志、指标、追踪),让每个变更都在独立的环境中验证。它在有 `AGENTS.md` 和清晰验证命令的仓库里,表现远超在"裸"仓库里。 - -**AutoGPT** 则是反面教材——缺乏结构化的状态管理导致长任务中上下文不断累积,缺乏精确的反馈机制导致 agent 陷入循环。很多人说 AutoGPT "不行",但其实是它的 harness 不行——你给它一个破灶台,再好的食材也做不出菜来。 - -## 核心概念 - -- **什么是 harness**:模型权重之外的一切工程基础设施。OpenAI 把工程师的核心工作概括为三件事:设计环境、表达意图、构建反馈循环。Anthropic 直接把 Claude Agent SDK 称为"通用 agent harness"。 -- **仓库是唯一事实来源**:agent 看不到的东西,对它来说就不存在。OpenAI 把仓库当作"记录系统"——所有必要的上下文都必须在仓库里,通过结构化的文件和清晰的目录组织来呈现。 -- **给地图,不给说明书**:OpenAI 的经验——`AGENTS.md` 应该是目录页,不是百科全书。100 行左右就够了,放不下就拆分到 `docs/` 目录里,让 agent 按需去读。 -- **约束而非微操**:好的 harness 用可执行的规则来约束 agent,而不是在指令里逐条叮嘱。OpenAI 说"执行不变量,不要微管实现";Anthropic 发现 agent 会自信地夸赞自己的工作,解决方案是把"干活的人"和"检查的人"分开。 -- **逐个组件拆除法**:想量化 harness 各组件的边际贡献,就逐个移除,看哪个移除后性能下降最多。它能告诉你哪些组件当前最有价值,也能暴露哪些组件暂时贡献不明显。Anthropic 用这个方法发现:随着模型变强,某些组件不再关键,但总会有新的关键组件出现。 - -## Harness 五子系统模型 - -回到厨房的类比。一个完整的厨房有五个功能区,harness 也有五个子系统: - -```mermaid -flowchart LR - Rules["项目规则
    AGENTS.md / CLAUDE.md"] --> Agent["AI Agent"] - State["进度和 git
    PROGRESS.md / commits"] --> Agent - Agent --> Tools["工具
    shell / 文件 / 测试"] - Tools --> Env["运行环境
    依赖 / 服务 / 版本"] - Env --> Checks["检查结果
    test / lint / build"] - Checks --> Agent -``` - -**指令子系统(菜谱架)**:创建 `AGENTS.md`(或 `CLAUDE.md`),内容包括项目概览和目的(一句话说清楚这是什么)、技术栈和版本(Python 3.11、FastAPI 0.100+、PostgreSQL 15)、首次运行命令(`make setup`、`make test`)、不可违反的硬约束("所有 API 必须走 OAuth 2.0")、指向更详细文档的链接。 - -**工具子系统(刀具架)**:确保 agent 有足够的工具访问权限。不要因为"安全考虑"把 shell 给禁了——agent 连 `pip install` 都跑不了,还怎么干活?但也别什么都开放,按最小权限原则来。 - -**环境子系统(灶台)**:让环境状态自描述。用 `pyproject.toml` 或 `package.json` 锁定依赖,用 `.nvmrc` 或 `.python-version` 指定运行时版本,用 Docker 或 devcontainer 让环境可重现。 - -**状态子系统(备菜台)**:长任务必须有进度跟踪。用一个简单的 `PROGRESS.md` 文件记录:哪些做完了,哪些在做,哪些被阻塞。每个会话结束前更新,下一个会话开始时读取。 - -**反馈子系统(出菜检查口)**:这是投入产出比最高的子系统。在 `AGENTS.md` 里显式列出验证命令: -``` -验证命令: -- 测试:pytest tests/ -x -- 类型检查:mypy src/ --strict -- Lint:ruff check src/ -- 完整验证:make check(包含以上全部) -``` - -五个子系统缺一个,就像厨房里少了一个功能区——菜还是能做,但总是别扭。 - -**量化 harness 组件价值的方法**:用"等模型对照实验"。保持模型不变,逐个移除五个子系统,看哪个子系统缺失时性能下降最多。下降最多的组件,说明它在当前任务里边际贡献最大,应该优先保护和加强;几乎没有影响的组件,则可能是冗余、设计失效,或还没有被任务充分触发。它不能单独证明"瓶颈在哪里"。真正定位瓶颈,还要结合失败记录和归因:任务没说清楚、上下文不足、环境不可复现、验证反馈缺失,还是状态管理断裂。 - -## 一个团队的真实经历 - -一个团队用 GPT-4o 开发一个 TypeScript + React 前端应用(约 20,000 行代码)。他们经历了四个阶段——其实就是在一件一件地添置厨具: - -**阶段 1——空厨房**:只有 README 里的基本项目描述。5 次运行成功 1 次(20%)。主要失败:选错了包管理器(npm vs yarn)、没遵循组件命名约定、跑不了测试。 - -**阶段 2——装上菜谱架**:添加 `AGENTS.md`,写明技术栈版本、命名约定、关键架构决策。成功率升到 60%。剩余失败主要来自环境问题和验证缺失。 - -**阶段 3——开起出菜检查口**:在 `AGENTS.md` 里列出验证命令 `yarn test && yarn lint && yarn build`。成功率升到 80%。 - -**阶段 4——备菜台就位**:引入进度文件模板,agent 在每次运行中记录完成和未完成的工作。成功率稳定在 80-100%。 - -四次迭代,模型一个字没改,成功率从 20% 到接近 100%。这就是 harness 工程的力量。你没有换更贵的食材,你只是把厨房收拾利索了。 - -## 关键要点 - -- Harness = 指令 + 工具 + 环境 + 状态 + 反馈。五个子系统,就像厨房的五个功能区,缺一不可。 -- 不是模型权重的部分全是 harness。你的 harness 决定了模型能力能被发挥多少。 -- 五个子系统中,反馈子系统通常是投入最少、回报最高的。先把验证命令写清楚——出菜检查口是最值得先装的。 -- 用"等模型对照实验"量化各子系统的边际贡献,别凭感觉;用失败记录和归因定位真正的瓶颈。 -- Harness 和代码一样会腐化。定期审计,像还技术债一样还 harness 债。 - -## 延伸阅读 - -- [OpenAI: Harness Engineering](https://openai.com/index/harness-engineering/) -- [Anthropic: Effective Harnesses for Long-Running Agents](https://www.anthropic.com/engineering/effective-harnesses-for-long-running-agents) -- [HumanLayer: Harness Engineering for Coding Agents](https://humanlayer.dev/articles/harness-engineering-for-coding-agents/) -- [SWE-agent: Agent-Computer Interfaces](https://github.com/princeton-nlp/SWE-agent) -- [Thoughtworks: Harness Engineering on Technology Radar](https://www.thoughtworks.com/radar) - -## 练习 - -1. **Harness 五元组审计**:拿你正在用 AI agent 的项目,按五元组框架做一个完整审计。每个子系统打 1-5 分。找出最低分的那个子系统,花 30 分钟改进它,然后观察 agent 的表现变化。 - -2. **等模型对照实验**:选一个模型和一个有挑战性的任务。依次移除指令(删掉 AGENTS.md)、移除反馈(不给验证命令)、移除状态(不提供进度文件),每次只移除一个,测量性能下降幅度。基于结果,排出各子系统对你项目的重要性排名。 - -3. **可供性分析**:找一个 agent 在你的项目中"想做但做不了"的场景(比如知道要用参数化查询但不知道你项目的 ORM 怎么写)。分析这是执行鸿沟(不知道怎么操作)还是评估鸿沟(不知道做得对不对),然后设计 harness 改进来弥补。 diff --git a/courses/learn-harness-engineering/docs/zh/lectures/lecture-03-why-the-repository-must-become-the-system-of-record/code/index.md b/courses/learn-harness-engineering/docs/zh/lectures/lecture-03-why-the-repository-must-become-the-system-of-record/code/index.md deleted file mode 100644 index 3792d332..00000000 --- a/courses/learn-harness-engineering/docs/zh/lectures/lecture-03-why-the-repository-must-become-the-system-of-record/code/index.md +++ /dev/null @@ -1,7 +0,0 @@ -# 第 03 讲代码 - -使用此文件夹存放以下方面的示例: - -- Agent 可读的仓库结构 -- 作为系统记录的文档 -- 糟糕与良好的知识放置方式对比 diff --git a/courses/learn-harness-engineering/docs/zh/lectures/lecture-03-why-the-repository-must-become-the-system-of-record/code/repo-knowledge-layout.txt b/courses/learn-harness-engineering/docs/zh/lectures/lecture-03-why-the-repository-must-become-the-system-of-record/code/repo-knowledge-layout.txt deleted file mode 100644 index 45315dac..00000000 --- a/courses/learn-harness-engineering/docs/zh/lectures/lecture-03-why-the-repository-must-become-the-system-of-record/code/repo-knowledge-layout.txt +++ /dev/null @@ -1,13 +0,0 @@ -AGENTS.md -docs/ - ARCHITECTURE.md - PRODUCT.md - RELIABILITY.md - references/ - electron.md - sqlite.md - plans/ - active/ - completed/ -src/ -scripts/ diff --git a/courses/learn-harness-engineering/docs/zh/lectures/lecture-03-why-the-repository-must-become-the-system-of-record/code/repo-reader.ts b/courses/learn-harness-engineering/docs/zh/lectures/lecture-03-why-the-repository-must-become-the-system-of-record/code/repo-reader.ts deleted file mode 100644 index 46f6a62a..00000000 --- a/courses/learn-harness-engineering/docs/zh/lectures/lecture-03-why-the-repository-must-become-the-system-of-record/code/repo-reader.ts +++ /dev/null @@ -1,189 +0,0 @@ -/** - * repo-reader.ts - * - * Reads a directory structure and scores it on discoverability. - * Checks for: AGENTS.md, docs/, architecture docs, feature tracking, - * handoff files, and other signals of a repository that serves as the - * system of record. - * - * Usage: - * npx tsx docs/lectures/lecture-03.../code/repo-reader.ts [path] - * (defaults to current working directory if no path given) - * - * Run: npx tsx docs/lectures/lecture-03-why-the-repository-must-become-the-system-of-record/code/repo-reader.ts - */ - -import * as fs from "node:fs"; -import * as path from "node:path"; - -// --------------------------------------------------------------------------- -// Scoring criteria -// --------------------------------------------------------------------------- - -interface Check { - name: string; - description: string; - maxPoints: number; - check: (dir: string) => { points: number; found: string[]; missing: string[] }; -} - -const checks: Check[] = [ - { - name: "AGENTS.md / CLAUDE.md", - description: "Agent-readable instruction file at repo root", - maxPoints: 15, - check: (dir) => { - const candidates = ["AGENTS.md", "CLAUDE.md", ".claude/CLAUDE.md"]; - const found = candidates.filter((f) => fs.existsSync(path.join(dir, f))); - return { points: found.length > 0 ? 15 : 0, found, missing: found.length > 0 ? [] : candidates }; - }, - }, - { - name: "Documentation directory", - description: "Dedicated docs/ or documentation/ directory", - maxPoints: 10, - check: (dir) => { - const candidates = ["docs", "documentation", "doc"]; - const found = candidates.filter((f) => { - const p = path.join(dir, f); - return fs.existsSync(p) && fs.statSync(p).isDirectory(); - }); - return { points: found.length > 0 ? 10 : 0, found, missing: found.length > 0 ? [] : ["docs/"] }; - }, - }, - { - name: "Architecture documentation", - description: "Files describing system architecture", - maxPoints: 15, - check: (dir) => { - const patterns = [ - "architecture.md", "ARCHITECTURE.md", "docs/architecture.md", - "docs/architecture/", "design.md", "DESIGN.md", - ]; - const found = patterns.filter((f) => fs.existsSync(path.join(dir, f))); - return { points: found.length > 0 ? 15 : 0, found, missing: found.length > 0 ? [] : ["architecture.md or docs/architecture/"] }; - }, - }, - { - name: "Feature tracking", - description: "Feature list or task tracking file", - maxPoints: 15, - check: (dir) => { - const patterns = [ - "feature_list.json", "features.md", "FEATURES.md", - "docs/features.md", "tasks.json", "TODO.md", - ]; - const found = patterns.filter((f) => fs.existsSync(path.join(dir, f))); - return { points: found.length > 0 ? 15 : 0, found, missing: found.length > 0 ? [] : ["feature_list.json or features.md"] }; - }, - }, - { - name: "Handoff / session continuity", - description: "Files for multi-session continuity", - maxPoints: 15, - check: (dir) => { - const patterns = [ - "HANDOFF.md", "handoff.md", "SESSION_NOTES.md", - "docs/handoff.md", ".handoff", "PROGRESS.md", - ]; - const found = patterns.filter((f) => fs.existsSync(path.join(dir, f))); - return { points: found.length > 0 ? 15 : 0, found, missing: found.length > 0 ? [] : ["HANDOFF.md or PROGRESS.md"] }; - }, - }, - { - name: "Testing structure", - description: "Test directory or test configuration", - maxPoints: 10, - check: (dir) => { - const patterns = ["test", "tests", "__tests__", "spec"]; - const found = patterns.filter((f) => { - const p = path.join(dir, f); - return fs.existsSync(p) && fs.statSync(p).isDirectory(); - }); - return { points: found.length > 0 ? 10 : 0, found, missing: found.length > 0 ? [] : ["test/ or tests/"] }; - }, - }, - { - name: "Configuration files", - description: "Project config (package.json, tsconfig, etc.)", - maxPoints: 10, - check: (dir) => { - const patterns = ["package.json", "tsconfig.json", "pyproject.toml", "Cargo.toml", "go.mod"]; - const found = patterns.filter((f) => fs.existsSync(path.join(dir, f))); - return { points: found.length > 0 ? 10 : 0, found, missing: found.length > 0 ? [] : ["package.json or equivalent"] }; - }, - }, - { - name: "README", - description: "Root README file", - maxPoints: 10, - check: (dir) => { - const patterns = ["README.md", "README.rst", "README.txt", "README"]; - const found = patterns.filter((f) => fs.existsSync(path.join(dir, f))); - return { points: found.length > 0 ? 10 : 0, found, missing: found.length > 0 ? [] : ["README.md"] }; - }, - }, -]; - -// --------------------------------------------------------------------------- -// Report generation -// --------------------------------------------------------------------------- - -function pad(s: string, len: number): string { - return s.length >= len ? s : s + " ".repeat(len - s.length); -} - -function scoreRepo(targetDir: string): void { - const resolved = path.resolve(targetDir); - - if (!fs.existsSync(resolved)) { - console.error(` Error: Directory not found: ${resolved}`); - process.exit(1); - } - - console.log("\n" + "=".repeat(80)); - console.log(" REPOSITORY DISCOVERABILITY SCORE"); - console.log("=".repeat(80)); - console.log(` Target: ${resolved}\n`); - - const header = `| ${pad("Criterion", 30)}| ${pad("Points", 8)}| ${pad("Status", 10)}| Details`; - const sep = `|${"-".repeat(32)}|${"-".repeat(10)}|${"-".repeat(12)}|${"-".repeat(30)}`; - console.log(header); - console.log(sep); - - let totalScore = 0; - let maxScore = 0; - - for (const check of checks) { - const result = check.check(resolved); - totalScore += result.points; - maxScore += check.maxPoints; - const status = result.points > 0 ? "PASS" : "FAIL"; - const detail = result.found.length > 0 ? result.found.join(", ") : result.missing[0]; - - console.log(`| ${pad(check.name, 30)}| ${pad(result.points + "/" + check.maxPoints, 8)}| ${pad(status, 10)}| ${detail}`); - } - - // Final score - console.log("\n" + "-".repeat(80)); - console.log(` TOTAL SCORE: ${totalScore} / ${maxScore} (${Math.round((totalScore / maxScore) * 100)}%)`); - - // Grade - const pct = totalScore / maxScore; - let grade: string; - if (pct >= 0.9) grade = "A -- Repository is a strong system of record"; - else if (pct >= 0.7) grade = "B -- Good foundation, minor gaps"; - else if (pct >= 0.5) grade = "C -- Partial structure, significant gaps"; - else if (pct >= 0.3) grade = "D -- Minimal structure, hard for agents to navigate"; - else grade = "F -- Repository lacks discoverability signals"; - - console.log(` GRADE: ${grade}`); - console.log("=".repeat(80) + "\n"); -} - -// --------------------------------------------------------------------------- -// Main -// --------------------------------------------------------------------------- - -const target = process.argv[2] || process.cwd(); -scoreRepo(target); diff --git a/courses/learn-harness-engineering/docs/zh/lectures/lecture-03-why-the-repository-must-become-the-system-of-record/code/system-of-record-checklist.md b/courses/learn-harness-engineering/docs/zh/lectures/lecture-03-why-the-repository-must-become-the-system-of-record/code/system-of-record-checklist.md deleted file mode 100644 index 7f37de5c..00000000 --- a/courses/learn-harness-engineering/docs/zh/lectures/lecture-03-why-the-repository-must-become-the-system-of-record/code/system-of-record-checklist.md +++ /dev/null @@ -1,11 +0,0 @@ -# 系统记录检查清单 - -一个全新的 Agent 能否仅从仓库中发现以下信息? - -- 正在构建什么产品? -- 应用应该为用户做什么? -- 代码库是如何组织的? -- 应用如何启动? -- 如何检查健康状态? -- 当前正在进行什么工作? -- 哪些质量标准很重要? diff --git a/courses/learn-harness-engineering/docs/zh/lectures/lecture-03-why-the-repository-must-become-the-system-of-record/index.md b/courses/learn-harness-engineering/docs/zh/lectures/lecture-03-why-the-repository-must-become-the-system-of-record/index.md deleted file mode 100644 index 76281777..00000000 --- a/courses/learn-harness-engineering/docs/zh/lectures/lecture-03-why-the-repository-must-become-the-system-of-record/index.md +++ /dev/null @@ -1,137 +0,0 @@ -[English Version →](../../../en/lectures/lecture-03-why-the-repository-must-become-the-system-of-record/) - -> 本篇代码示例:[code/](https://github.com/walkinglabs/learn-harness-engineering/blob/main/docs/zh/lectures/lecture-03-why-the-repository-must-become-the-system-of-record/code/) -> 实战练习:[Project 02. 让 agent 看懂项目、接住上次的工作](./../../projects/project-02-agent-readable-workspace/index.md) - -# 第三讲. 让代码仓库成为唯一的事实来源 - -你团队的架构决策散落在 Confluence、Slack、Jira、和几个资深工程师的脑子里。对人类来说这勉强够用——你可以问同事、搜聊天记录、翻文档。实在不行还能去茶水间堵人。但对 AI agent 来说,不在仓库里的信息等于不存在。 - -这不是夸张。想想 agent 的输入都有什么:系统提示和任务描述、仓库里的文件内容、以及工具执行的输出。就这三样。你的 Slack 历史、Jira 工单、Confluence 页面、和周五下午跟同事在茶水间聊的架构决定——agent 全都看不到。它不能"去问一下",也不能"搜一下聊天记录"。它就是一个被关在仓库里的工程师——仓库外面的事,它一概不知。 - -所以问题变成了:你给不给这个工程师一张好地图? - -## 地图上该画什么 - -OpenAI 在他们的 harness engineering 文章里把这个问题说得非常直白:**仓库里不存在的信息,对 agent 来说等于不存在。** 他们把这称为"仓库即规范"原则——仓库本身就是最高权威的规范文档。 - -Anthropic 的 long-running agents 文档也强调了类似的观点:持久化状态是长任务连续性的必要条件。跨会话的知识可恢复性直接决定了任务成功率。而这些状态必须存在于仓库中——因为那是 agent 唯一稳定可访问的存储。 - -你可能会想:"我们团队人少,知识都在大家脑子里,这不也工作得好好的吗?"没错,对人类来说确实可以。但你要用 agent,就得接受一个事实:agent 不能问人。所有它需要知道的东西,都必须写下来,放在它能找到的地方。 - -这不是"写更多文档"的问题。这是"把决策信息放到正确的位置"的问题。一份在 `src/api/` 目录下、50 行的 `ARCHITECTURE.md`,比一份在 Confluence 里、500 页但没人维护的设计文档有用一万倍。就好比一张贴在你工位上的、手画的办公室走法图,比一份精美的、放在档案室里的建筑蓝图实用得多——因为前者在你需要的时候就在手边。 - -## 知识可见性 - -```mermaid -flowchart LR - Slack["Slack 里的规则"] --> Write["写进仓库文件
    AGENTS.md / ARCHITECTURE.md / PROGRESS.md"] - Confluence["Confluence 里的规则"] --> Write - Heads["工程师脑子里的规则"] --> Write - Jira["Jira 里的规则"] --> Write - Write --> Repo["仓库文件"] - Repo --> Agent["新的 agent 会话
    直接读仓库"] - Warning["规则不写进仓库
    agent 就看不见"] --> Agent -``` - -怎么检验你的地图画得够不够好?做一个"冷启动测试":开一个全新的 agent 会话,只看仓库内容,看它能不能回答五个基本问题: - -```mermaid -flowchart TB - Q1["这是什么系统?"] --> A1["AGENTS.md / README"] - Q2["怎么组织的?"] --> A2["ARCHITECTURE.md / 模块文档"] - Q3["怎么跑?"] --> A3["Makefile / init.sh / package scripts"] - Q4["怎么验证?"] --> A4["测试、lint、check 命令"] - Q5["现在做到哪了?"] --> A5["PROGRESS.md / 功能清单 / git 历史"] - - A1 --> Ready["新的会话不用问人
    就能开始工作"] - A2 --> Ready - A3 --> Ready - A4 --> Ready - A5 --> Ready -``` - -如果它答不上来,说明地图上有空白。空白的地方,agent 就得自己猜——猜错了就是 bug,猜多了就浪费上下文。每个新会话都要猜一遍,猜的成本远高于一开始就把地图画好的成本。 - -## 核心概念 - -- **知识可见性缺口**:项目总知识中不在仓库里的比例。缺口越大,agent 失败的概率越高。你脑子里有多少关于这个项目的隐性知识?把它们全算上,再看有多少写进了仓库——两者的差距就是你的可见性缺口。 -- **系统记录(System of Record)**:代码仓库作为项目决策、架构约束、执行状态和验证标准的权威信息源。仓库说了算,别的地方说了不算。就像地图上标注了"此路不通",你就不会再往那条路走——但如果这个信息只在老张的脑子里,你每次都得问老张。 -- **冷启动测试**:上一节说的五个问题。能回答几个,你的地图就画了几分。 -- **发现成本**:agent 为了在仓库里找到一条关键信息需要消耗多少上下文。信息放得越隐蔽,发现成本越高,留给实际任务的预算越少。把关键信息藏在十层目录深处的 README 里,就像把灭火器锁在地下室的保险柜里——不是没有,是用的时候找不到。 -- **知识衰减率**:仓库中单位时间内变得过时的知识条目比例。文档和代码脱节是最大的敌人——比没有文档更危险的是过时的文档。 -- **ACID 类比**:把数据库的事务管理原则(原子性、一致性、隔离性、持久性)用到 agent 的状态管理上。后面会展开讲。 - -## 怎么画好这张地图 - -**原则 1:知识靠近代码。** 一条关于 API 端点认证的规则,应该放在 API 代码旁边,而不是藏在一个巨大的全局文档里。每个模块目录下放一个简短的文档,说清楚这个模块的职责、接口和特殊约束。就像图书馆的书架标签——你想找历史类书籍,直接去标着"历史"的书架,不用把整个图书馆翻一遍。 - -**原则 2:用标准化的入口文件。** `AGENTS.md`(或 `CLAUDE.md`)是 agent 的"着陆页"。它不需要包含所有信息,但必须能让 agent 快速回答"这是什么项目"、"怎么跑"、"怎么验证"这三个问题。50-100 行就够了。 - -**原则 3:最小但完备。** 每条知识都应该有明确的使用场景。如果你删掉某条规则不影响 agent 的决策质量,那这条规则就不应该存在。但冷启动测试中的每个问题都必须有答案。这是一个精妙的平衡——不多不少,刚好够用。 - -**原则 4:和代码一起更新。** 把知识更新跟代码变更绑定在一起。最简单的方法:把架构文档放在对应的模块目录里。改代码的时候自然会看到文档,改代码之后 CI 提醒你检查文档是否需要更新。 - -**具体的仓库结构**: - -``` -project/ -├── AGENTS.md # 入口:项目概览、运行命令、硬约束 -├── src/ -│ ├── api/ -│ │ ├── ARCHITECTURE.md # API 层的架构决策 -│ │ └── ... -│ ├── db/ -│ │ ├── CONSTRAINTS.md # 数据库操作的硬约束 -│ │ └── ... -│ └── ... -├── PROGRESS.md # 当前进度:做了什么、在做什么、被什么阻塞 -└── Makefile # 标准化的操作命令:setup、test、lint、check -``` - -## 用 ACID 原则管理 agent 状态 - -这个类比来自数据库的事务管理——你可能觉得这是在把简单的事情搞复杂,但实际上它给了你一个非常实用的框架: - -- **原子性**:每次"逻辑操作"(比如"添加新端点并更新测试")用一个 git commit 原子化。中途挂了就 `git stash` 回滚。要么全做,要么不做,没有"做了一半"。 -- **一致性**:定义"一致状态"的验证谓词——所有测试通过、lint 无报错。Agent 每次操作后跑验证,不一致的中间状态不要 commit。就像银行转账——你不能只扣款不入账。 -- **隔离性**:多个 agent 并发工作时,状态文件要避免竞争条件。简单方案:每个 agent 用独立的进度文件,或者用 git 分支隔离。两个厨师不能同时往同一口锅里放盐——放重了谁负责? -- **持久性**:关键的项目知识用 git 跟踪的文件持久化。临时状态可以只在会话内存里,但跨会话必须的知识必须写到文件里。脑子里的不算,写在纸上的才算。 - -## 一个真实的改造故事 - -一个团队维护一个包含约 30 个微服务的电商平台。架构决策(服务间通信协议、数据一致性策略、API 版本化规则)散落在:Confluence(部分过时)、Slack(难以搜索)、几个资深工程师的脑子里(不可扩展)、以及零星的代码注释(不系统)。 - -引入 AI agent 后,70% 的任务需要人工干预。几乎每次失败都涉及 agent 违反了某个"所有人都知道但从未写入仓库"的隐性约束。这就像一个新来的员工,没人告诉他"中午点外卖要在群里接龙"——他自己猜,猜错了被骂,但骂完了还是没人告诉他规则。 - -团队执行了改造: -1. 仓库根目录创建 `AGENTS.md`,写明项目概览、技术栈版本、全局硬约束 -2. 每个微服务目录下添加 `ARCHITECTURE.md`,描述该服务的职责、接口和依赖 -3. 创建集中的 `CONSTRAINTS.md`,用"禁止/必须"的明确语言记录硬约束 -4. 每个服务目录添加 `PROGRESS.md`,记录当前工作状态 - -改造后:同一 agent 能在冷启动时回答所有关键项目问题,任务完成质量显著提升。 - -## 关键要点 - -- 不在仓库里的知识对 agent 来说等于不存在。把关键决策信息放进仓库是最基本的 harness 投资——画好地图,才不会迷路。 -- 用"冷启动测试"检验仓库质量:全新会话能不能只看仓库回答五个基本问题。 -- 知识要靠近代码、最小但完备、跟代码一起更新。不是写更多文档,是把信息放到正确的位置。 -- 用 ACID 原则管理 agent 状态:原子提交、一致性验证、隔离并发、持久化关键知识。 -- 知识衰减是最大敌人。过时的文档比没有文档更危险——它会让 agent 走错方向还以为自己是对的。 - -## 延伸阅读 - -- [OpenAI: Harness Engineering](https://openai.com/index/harness-engineering/) -- [Anthropic: Effective Harnesses for Long-Running Agents](https://www.anthropic.com/engineering/effective-harnesses-for-long-running-agents) -- [Infrastructure as Code — Martin Fowler](https://martinfowler.com/bliki/InfrastructureAsCode.html) -- [ADR: Architecture Decision Records](https://adr.github.io/) -- [The Twelve-Factor App](https://12factor.net/) - -## 练习 - -1. **冷启动测试**:在你的项目里开一个全新的 agent 会话(不提供任何口头上下文),只让它看仓库内容,然后问它五个问题:这是什么系统?怎么组织的?怎么运行?怎么验证?现在进度如何?记录它答不上来的问题,然后改进仓库让它能答上来。 - -2. **知识外置化量化**:列出你的项目中所有对开发工作重要的决策和约束。标注每个条目是在仓库内还是仓库外。算一下你的知识可见性缺口有多大(不在仓库里的占总数的比例)。制定计划把缺口降到 10% 以下。 - -3. **ACID 准则评估**:用本讲的 ACID 类比评估你的项目状态管理。原子性——agent 的操作能不能干净地回滚?一致性——仓库有没有"一致状态"的验证?隔离性——多 agent 并发时会不会互相踩脚?持久性——跨会话的知识是不是都持久化了? diff --git a/courses/learn-harness-engineering/docs/zh/lectures/lecture-04-why-one-giant-instruction-file-fails/code/AGENTS-short.md b/courses/learn-harness-engineering/docs/zh/lectures/lecture-04-why-one-giant-instruction-file-fails/code/AGENTS-short.md deleted file mode 100644 index 53ca934c..00000000 --- a/courses/learn-harness-engineering/docs/zh/lectures/lecture-04-why-one-giant-instruction-file-fails/code/AGENTS-short.md +++ /dev/null @@ -1,14 +0,0 @@ -# AGENTS.md - -## 从这里开始 - -- 阅读 `docs/ARCHITECTURE.md` -- 阅读 `docs/PRODUCT.md` -- 使用 `npm run dev` 启动应用 -- 在标记工作完成之前使用 `npm run check` - -## 严格规则 - -- 在阅读 `docs/ARCHITECTURE.md` 之前,不要更改 Electron 主进程/预加载/渲染器的边界 -- 未经验证不要将功能标记为已完成 -- 为下一个会话留下干净的状态 diff --git a/courses/learn-harness-engineering/docs/zh/lectures/lecture-04-why-one-giant-instruction-file-fails/code/anti-patterns.md b/courses/learn-harness-engineering/docs/zh/lectures/lecture-04-why-one-giant-instruction-file-fails/code/anti-patterns.md deleted file mode 100644 index 5eec9f29..00000000 --- a/courses/learn-harness-engineering/docs/zh/lectures/lecture-04-why-one-giant-instruction-file-fails/code/anti-patterns.md +++ /dev/null @@ -1,7 +0,0 @@ -# 指令文件反模式 - -- 将所有仓库知识放入一个文件中 -- 在多个地方重复同一条规则 -- 编写从未被审查的过时规则 -- 编写过于具体的条件指令,以至于很少适用 -- 在启动上下文中嵌入冗长的工具手册 diff --git a/courses/learn-harness-engineering/docs/zh/lectures/lecture-04-why-one-giant-instruction-file-fails/code/index.md b/courses/learn-harness-engineering/docs/zh/lectures/lecture-04-why-one-giant-instruction-file-fails/code/index.md deleted file mode 100644 index 3d4ce1b4..00000000 --- a/courses/learn-harness-engineering/docs/zh/lectures/lecture-04-why-one-giant-instruction-file-fails/code/index.md +++ /dev/null @@ -1,7 +0,0 @@ -# 第 04 讲代码 - -使用此文件夹存放以下方面的示例: - -- 单体式指令文件 -- 短小入口文件 -- 渐进式信息披露模式 diff --git a/courses/learn-harness-engineering/docs/zh/lectures/lecture-04-why-one-giant-instruction-file-fails/code/split-vs-monolithic.ts b/courses/learn-harness-engineering/docs/zh/lectures/lecture-04-why-one-giant-instruction-file-fails/code/split-vs-monolithic.ts deleted file mode 100644 index 59a96328..00000000 --- a/courses/learn-harness-engineering/docs/zh/lectures/lecture-04-why-one-giant-instruction-file-fails/code/split-vs-monolithic.ts +++ /dev/null @@ -1,209 +0,0 @@ -/** - * split-vs-monolithic.ts - * - * Creates a monolithic instruction file (~200 lines) and then shows how - * splitting into 4 focused files dramatically reduces the context needed - * for any single query. Simulates an "agent" searching for a specific rule - * and measures how many lines it must read in each approach. - * - * Run: npx tsx docs/lectures/lecture-04-why-one-giant-instruction-file-fails/code/split-vs-monolithic.ts - */ - -// --------------------------------------------------------------------------- -// Simulated monolithic instruction file (200 lines of rules) -// --------------------------------------------------------------------------- - -const monolithicInstructions: { lineNumber: number; section: string; content: string }[] = []; - -// Section 1: Project Overview (lines 1-50) -for (let i = 1; i <= 50; i++) { - monolithicInstructions.push({ - lineNumber: i, - section: "Project Overview", - content: i === 25 ? "This project uses React 18 with TypeScript strict mode." : `Overview detail line ${i}`, - }); -} - -// Section 2: Code Style Rules (lines 51-100) -for (let i = 51; i <= 100; i++) { - monolithicInstructions.push({ - lineNumber: i, - section: "Code Style", - content: - i === 72 - ? "RULE: Always use explicit return types on exported functions." - : i === 78 - ? "RULE: Use const assertions for immutable arrays." - : `Style rule detail line ${i}`, - }); -} - -// Section 3: Testing Standards (lines 101-150) -for (let i = 101; i <= 150; i++) { - monolithicInstructions.push({ - lineNumber: i, - section: "Testing", - content: - i === 120 - ? "RULE: Every new endpoint must have integration tests." - : i === 135 - ? "RULE: Test files must mirror the source file structure." - : `Testing detail line ${i}`, - }); -} - -// Section 4: Deployment Rules (lines 151-200) -for (let i = 151; i <= 200; i++) { - monolithicInstructions.push({ - lineNumber: i, - section: "Deployment", - content: - i === 175 - ? "RULE: Never deploy on Fridays. Deploy window is Tue-Thu 10am-3pm." - : `Deployment detail line ${i}`, - }); -} - -// --------------------------------------------------------------------------- -// Split instruction files (4 focused files) -// --------------------------------------------------------------------------- - -const splitInstructions: Record = { - "01-project-overview.md": monolithicInstructions.filter((l) => l.section === "Project Overview"), - "02-code-style.md": monolithicInstructions.filter((l) => l.section === "Code Style"), - "03-testing.md": monolithicInstructions.filter((l) => l.section === "Testing"), - "04-deployment.md": monolithicInstructions.filter((l) => l.section === "Deployment"), -}; - -// --------------------------------------------------------------------------- -// Simulated queries -- the agent needs to find specific rules -// --------------------------------------------------------------------------- - -interface Query { - description: string; - targetRule: string; - relevantSection: string; -} - -const queries: Query[] = [ - { - description: "Find the rule about return types", - targetRule: "explicit return types", - relevantSection: "Code Style", - }, - { - description: "Find the deployment window rule", - targetRule: "deploy on Fridays", - relevantSection: "Deployment", - }, - { - description: "Find the integration test rule", - targetRule: "integration tests", - relevantSection: "Testing", - }, - { - description: "Find the test file structure rule", - targetRule: "mirror the source file", - relevantSection: "Testing", - }, -]; - -// --------------------------------------------------------------------------- -// Search simulation -// --------------------------------------------------------------------------- - -function searchMonolithic(query: Query): { linesRead: number; found: boolean } { - // Agent must scan from the top, reading each line until it finds the rule. - // In the worst case it reads all lines. - let linesRead = 0; - let found = false; - - for (const line of monolithicInstructions) { - linesRead++; - if (line.content.toLowerCase().includes(query.targetRule.toLowerCase())) { - found = true; - break; - } - } - - return { linesRead, found }; -} - -function searchSplit(query: Query): { linesRead: number; found: boolean; fileAccessed: string } { - // Agent knows which file to look in based on the section. - // It only reads lines from that one file. - const fileMap: Record = { - "Project Overview": "01-project-overview.md", - "Code Style": "02-code-style.md", - Testing: "03-testing.md", - Deployment: "04-deployment.md", - }; - - const targetFile = fileMap[query.relevantSection]; - const lines = splitInstructions[targetFile]; - let linesRead = 0; - let found = false; - - for (const line of lines) { - linesRead++; - if (line.content.toLowerCase().includes(query.targetRule.toLowerCase())) { - found = true; - break; - } - } - - return { linesRead, found, fileAccessed: targetFile }; -} - -// --------------------------------------------------------------------------- -// Reporting -// --------------------------------------------------------------------------- - -function pad(s: string, len: number): string { - return s.length >= len ? s : s + " ".repeat(len - s.length); -} - -function run(): void { - console.log("\n" + "=".repeat(90)); - console.log(" MONOLITHIC vs SPLIT INSTRUCTION FILES"); - console.log("=".repeat(90)); - - console.log("\n Monolithic file: 1 file, " + monolithicInstructions.length + " lines total"); - console.log(" Split files: 4 files, ~" + Math.round(monolithicInstructions.length / 4) + " lines each\n"); - - const header = `| ${pad("Query", 42)}| ${pad("Monolithic (lines)", 20)}| ${pad("Split (lines)", 15)}| ${pad("File Accessed", 22)}| Savings`; - const sep = `|${"-".repeat(44)}|${"-".repeat(22)}|${"-".repeat(17)}|${"-".repeat(24)}|${"-".repeat(10)}`; - console.log(header); - console.log(sep); - - let totalMono = 0; - let totalSplit = 0; - - for (const q of queries) { - const mono = searchMonolithic(q); - const split = searchSplit(q); - totalMono += mono.linesRead; - totalSplit += split.linesRead; - - const savings = Math.round(((mono.linesRead - split.linesRead) / mono.linesRead) * 100); - console.log( - `| ${pad(q.description, 42)}| ${pad(String(mono.linesRead), 20)}| ${pad(String(split.linesRead), 15)}| ${pad(split.fileAccessed, 22)}| ${savings}%` - ); - } - - console.log(sep); - const avgMono = Math.round(totalMono / queries.length); - const avgSplit = Math.round(totalSplit / queries.length); - console.log( - `| ${pad("AVERAGE", 42)}| ${pad(String(avgMono), 20)}| ${pad(String(avgSplit), 15)}| ${pad("(targeted file)", 22)}| ${Math.round(((avgMono - avgSplit) / avgMono) * 100)}%` - ); - - console.log("\n" + "=".repeat(90)); - console.log(" KEY INSIGHT"); - console.log("=".repeat(90)); - console.log(" With a monolithic file, the agent must scan up to " + monolithicInstructions.length + " lines for every query."); - console.log(" With split files, it reads only the relevant " + Math.round(monolithicInstructions.length / 4) + "-line file."); - console.log(" This means less context window usage, fewer hallucinations, and faster execution.\n"); -} - -run(); diff --git a/courses/learn-harness-engineering/docs/zh/lectures/lecture-04-why-one-giant-instruction-file-fails/index.md b/courses/learn-harness-engineering/docs/zh/lectures/lecture-04-why-one-giant-instruction-file-fails/index.md deleted file mode 100644 index 18392872..00000000 --- a/courses/learn-harness-engineering/docs/zh/lectures/lecture-04-why-one-giant-instruction-file-fails/index.md +++ /dev/null @@ -1,132 +0,0 @@ -[English Version →](../../../en/lectures/lecture-04-why-one-giant-instruction-file-fails/) - -> 本篇代码示例:[code/](https://github.com/walkinglabs/learn-harness-engineering/blob/main/docs/zh/lectures/lecture-04-why-one-giant-instruction-file-fails/code/) -> 实战练习:[Project 02. 让 agent 看懂项目、接住上次的工作](./../../projects/project-02-agent-readable-workspace/index.md) - -# 第四讲. 把指令拆分到不同文件里 - -你开始认真对待 harness 了——好事。你建了个 `AGENTS.md`,把你能想到的所有规则、约束、历史教训都塞了进去。一个月后这个文件膨胀到了 300 行,两个月 450 行,三个月 600 行。然后你发现 agent 的表现反而变差了——改一个小 bug,agent 花大量上下文处理无关的部署指令;关键的安全约束埋在第 300 行,被直接忽略了;文件里有三条互相矛盾的代码风格规则,agent 每次随机选一条。 - -这就是"巨型指令文件"陷阱。就像你往行李箱里塞东西——觉得什么都有用,什么都往里装,结果拉链快崩了,想找换洗内衣得把整个箱子翻一遍。带了一箱子东西,但真正用上的可能只有三分之一。 - -## 问题的根源:一个恶性循环 - -最常见的恶性循环是这样的:agent 犯了个错 → 你说"加条规则防止这个" → 加到 AGENTS.md → 暂时管用 → agent 又犯了另一个错 → 再加一条 → 重复 → 文件膨胀到不可控。 - -这不是你的错。这是一个非常自然的反应——每次出问题就"加条规则"感觉很合理,就像每次出门忘带东西就往包里多塞一样。但累积效应是灾难性的。让我们看看具体出了什么问题。 - -**上下文预算被吃掉了。** Agent 的上下文窗口是有限的。假设你的 agent 有 200K tokens 的窗口(Claude 的标准),一个膨胀的指令文件可能占掉 10-20K。看起来还有不少余量?但一个复杂的任务可能需要读几十个源文件、工具执行的输出也占上下文、对话历史也在累积。到真正需要理解代码的时候,预算已经不够了——就像你的行李箱塞满了"万一用得着"的东西,结果放不下真正需要带的电脑了。 - -**中间迷失。** "Lost in the Middle"这篇论文(Liu et al., 2023)清楚地证明了:LLM 对长文本中间部分的信息利用效率显著低于两端。你的 AGENTS.md 有 600 行,第 300 行写的是"所有数据库查询必须用参数化查询"——这是安全硬约束。但它被埋在中间,agent 几乎一定会忽略它。就像你的行李箱里那瓶防晒霜——明明在最底层,但你翻三遍也找不到,最后又去买了一瓶。 - -**优先级冲突。** 文件里混合了不可违反的硬约束("不得使用 eval()")、重要的设计指导("优先使用函数式风格")、和某个特定场景的历史教训("上周修了一个 WebSocket 内存泄漏,注意类似的模式")。这三条规则的重要性完全不同,但在文件里看起来一模一样。Agent 没有可靠的信号来区分——就像行李箱里护照和充电线混在一起,看不出哪个更紧急。 - -**维护衰减。** 大文件天生难维护。指令过时了没人删——因为删除的后果不确定("也许别的地方依赖这条规则?"),但加新指令是无成本的。结果文件只增不减,信噪比持续下降。这和软件里的技术债务积累一模一样。 - -**矛盾累积。** 不同时期加的指令之间开始出现矛盾——一条说"用 TypeScript 严格模式",另一条说"某些遗留文件允许用 any"。Agent 每次随机选一条遵循。就像你妈说"穿厚点",你爸说"别穿太多",你站在门口不知道听谁的。 - -## 核心概念 - -- **指令膨胀**:当指令文件占用了超过上下文窗口 10-15% 的时候,它就开始挤占代码阅读和任务推理的预算。600 行的 `AGENTS.md` 可能占用 10,000-20,000 tokens——对 128K 的窗口来说,这就吃掉了 8-15%。 -- **中间迷失效应**:Liu 等人 2023 年的研究证明,LLM 对长文本中间部分的信息利用效率显著低于两端。埋在 600 行文件中间第 300 行的关键约束,被有效忽略的概率非常高。 -- **指令信噪比(SNR)**:文件中与当前任务相关的指令占总指令的比例。做 bug 修复时被要求读 50 行部署指令——SNR 很低。 -- **路由文件**:短小的入口文件,核心功能是引导 agent 去找更详细的文档,而不是自己包含所有内容。50-200 行就够了。 -- **渐进式披露**:先给概要信息,需要的时候再给详细信息。好的 harness 设计和好的 UI 设计一样,不把所有选项一次性砸到用户脸上。 -- **优先级模糊度**:当所有指令以相同格式和位置呈现时,agent 分不清哪些是不可违反的硬约束,哪些是建议性的软约束。 - -## 指令文件架构 - -```mermaid -flowchart LR - Mono["一个超长 AGENTS.md"] --> MonoLoad["改一个小 bug
    也得把部署说明和历史备注全读一遍"] - MonoLoad --> MonoRisk["关键规则埋在中间
    很容易漏掉"] - - Router["短 AGENTS.md"] --> Topics["按任务去读 API / 数据库 / 测试文档"] - Topics --> RoutedResult["把更多上下文留给代码阅读
    和验证"] -``` - -```mermaid -flowchart TB - File["600 行指令文件"] --> Top["顶部
    快速开始 + 硬约束"] - File --> Mid["中部
    第 300 行的安全规则"] - File --> Bot["底部
    明确的结束检查清单"] - Top --> Seen["高概率被记住"] - Bot --> Seen - Mid --> Missed["高概率被稀释或忽略"] -``` - -## 拆分思路 - -核心原则:常用信息放手边,偶尔用的收起来,用不上的别带。 - -入口文件 `AGENTS.md` 控制在 50-200 行,只放最常用的东西——项目概览(一两句话说清楚这是什么)、首次运行命令(`make setup && make test`)、全局硬约束(不超过 15 条不可违反的规则)、指向专题文档的链接(一行描述 + 适用条件)。 - -```markdown -# AGENTS.md - -## 项目概览 -Python 3.11 FastAPI 后端,PostgreSQL 15 数据库。 - -## 快速开始 -- 安装:`make setup` -- 测试:`make test` -- 完整验证:`make check` - -## 硬约束 -- 所有 API 必须走 OAuth 2.0 认证 -- 所有数据库查询必须用 SQLAlchemy 2.0 语法 -- 所有 PR 必须通过 pytest + mypy --strict + ruff check - -## 专题文档 -- [API 设计规范](docs/api-patterns.md) — 添加新端点时必读 -- [数据库操作约束](docs/database-rules.md) — 涉及数据库修改时必读 -- [测试标准](docs/testing-standards.md) — 编写测试时参考 -``` - -每个专题文档 50-150 行,按主题放在 `docs/` 目录下或对应模块目录旁。Agent 只在需要时才去读。就像行李箱里的收纳袋——内衣一个袋,洗漱一个袋,充电器一个袋。找东西不用翻整个箱子。 - -还有些信息直接放在代码里更合适——类型定义、接口注释、配置文件里的说明。Agent 读代码的时候自然能看到,不用再在指令里重复一遍。 - -每条指令都应该标明来源("为什么加这条规则?")、适用条件("这条规则在什么时候需要?")、过期条件("什么情况下可以删掉这条规则?")。定期审计,删掉过时的、冗余的、矛盾的条目。像管理代码依赖一样管理你的指令——用不上的依赖就该删掉,不然它们只会拖慢系统。 - -如果某条指令必须在入口文件里,放顶部或底部——不要放中间。"中间迷失"效应告诉我们,LLM 对长文本中间部分的信息利用效率显著低于两端。但更好的做法是把指令放到专题文档里,让 agent 按需加载。 - -OpenAI 和 Anthropic 都隐性支持拆分的做法。OpenAI 说入口文件应"短小且以路由为导向",Anthropic 说长运行 agent 的控制信息应"简洁且高优先级"。两家都在说同一件事:别把什么都塞进一个文件里。行李箱得整理,不能只靠蛮力塞。 - -## 实际案例 - -一个 SaaS 团队的 `AGENTS.md` 从最初的 50 行膨胀到 600 行。内容混合了技术栈版本、编码规范、历史 bug 修复笔记、API 使用说明、部署流程、和团队成员的个人偏好——整个行李箱塞得满满当当,拉链都快崩了。 - -Agent 表现开始明显下降:简单 bug 修复任务中 agent 花大量上下文处理无关的部署指令;安全约束"所有数据库查询必须用参数化查询"埋在第 300 行,经常被忽略;三条矛盾的代码风格规则导致 agent 随机选择。 - -团队执行了"行李箱整理": -1. `AGENTS.md` 裁剪到 80 行:只保留项目概览、运行命令、15 条全局硬约束 -2. 创建专题文档:`docs/api-patterns.md`(120 行)、`docs/database-rules.md`(60 行)、`docs/testing-standards.md`(80 行) -3. 路由文件添加指向专题文档的链接 -4. 历史笔记要么转成测试用例,要么删除 - -重构后:同一任务集的成功率从 45% 提升到 72%。安全约束遵循率从 60% 提升到 95%——因为从文件中间移到了路由文件顶部,不再被"中间迷失"了。 - -## 关键要点 - -- "加条规则"是短期的止痛药,长期的毒药。每次加规则前想想:这条规则放专题文档是不是更合适?——别什么都往行李箱里塞。 -- 入口文件是路由器,不是百科全书。50-200 行,只放概览、硬约束、和链接。 -- 利用"中间迷失"效应:重要信息放文件顶部或底部,不重要的移到专题文档。 -- 像管理技术债一样管理指令膨胀。定期审计,每条指令要有来源、适用条件、和过期条件。 -- 拆分之后信噪比提升,agent 把更多上下文预算花在实际任务上,而不是处理无关指令。 - -## 延伸阅读 - -- [OpenAI: Harness Engineering](https://openai.com/index/harness-engineering/) -- [Anthropic: Effective Harnesses for Long-Running Agents](https://www.anthropic.com/engineering/effective-harnesses-for-long-running-agents) -- [Lost in the Middle: How Language Models Use Long Contexts](https://arxiv.org/abs/2307.03172) -- [HumanLayer: Harness Engineering for Coding Agents](https://humanlayer.dev/articles/harness-engineering-for-coding-agents/) -- [Nielsen Norman Group: Progressive Disclosure](https://www.nngroup.com/articles/progressive-disclosure/) - -## 练习 - -1. **信噪比审计**:拿你当前的入口指令文件,列出所有指令条目。选 5 个不同的常见任务类型,标注每条指令是否跟该任务相关。计算每个任务类型的 SNR。那些对大多数任务都是噪声的指令,移到专题文档里。 - -2. **渐进式披露重构**:如果你有一个超过 300 行的指令文件,把它拆成:(a) 不超过 100 行的路由文件,(b) 3-5 个专题文档。重构前后各跑同一组任务(至少 5 个),对比成功率。 - -3. **中间迷失验证**:在一个长指令文件里,把一条关键约束分别放在顶部、中间、底部各跑一组任务(每组至少 5 次),看遵循率有没有差别。你可能会惊讶于位置效应有多大。 diff --git a/courses/learn-harness-engineering/docs/zh/lectures/lecture-05-why-long-running-tasks-lose-continuity/code/continuity-checklist.md b/courses/learn-harness-engineering/docs/zh/lectures/lecture-05-why-long-running-tasks-lose-continuity/code/continuity-checklist.md deleted file mode 100644 index 41c345f2..00000000 --- a/courses/learn-harness-engineering/docs/zh/lectures/lecture-05-why-long-running-tasks-lose-continuity/code/continuity-checklist.md +++ /dev/null @@ -1,6 +0,0 @@ -# 连续性检查清单 - -- 一个全新的 Agent 能否在五分钟内识别最近的工作? -- 当前稳定的启动路径是否有文档记录? -- 未完成的工作是否被清楚标识? -- 下一个最佳任务是否在不阅读旧聊天记录的情况下可见? diff --git a/courses/learn-harness-engineering/docs/zh/lectures/lecture-05-why-long-running-tasks-lose-continuity/code/index.md b/courses/learn-harness-engineering/docs/zh/lectures/lecture-05-why-long-running-tasks-lose-continuity/code/index.md deleted file mode 100644 index 1707a8c2..00000000 --- a/courses/learn-harness-engineering/docs/zh/lectures/lecture-05-why-long-running-tasks-lose-continuity/code/index.md +++ /dev/null @@ -1,7 +0,0 @@ -# 第 05 讲代码 - -使用此文件夹存放以下方面的示例: - -- 中断的多会话任务 -- 缺失的连续性产物 -- 连续性恢复模式 diff --git a/courses/learn-harness-engineering/docs/zh/lectures/lecture-05-why-long-running-tasks-lose-continuity/code/session-handoff.md b/courses/learn-harness-engineering/docs/zh/lectures/lecture-05-why-long-running-tasks-lose-continuity/code/session-handoff.md deleted file mode 100644 index 816aa75e..00000000 --- a/courses/learn-harness-engineering/docs/zh/lectures/lecture-05-why-long-running-tasks-lose-continuity/code/session-handoff.md +++ /dev/null @@ -1,17 +0,0 @@ -# 会话交接示例 - -## 已完成 - -- 添加了 markdown 导入支持 -- 在渲染器中添加了基本的文档列表 - -## 有问题或未验证 - -- `.md` 导入成功,但大型 `.txt` 文件导入失败 -- 应用可以启动,但详情视图尚未连接 - -## 下一步最佳操作 - -- 修复 `.txt` 导入路径 -- 端到端验证导入功能 -- 然后添加文档详情面板 diff --git a/courses/learn-harness-engineering/docs/zh/lectures/lecture-05-why-long-running-tasks-lose-continuity/code/session-simulator.ts b/courses/learn-harness-engineering/docs/zh/lectures/lecture-05-why-long-running-tasks-lose-continuity/code/session-simulator.ts deleted file mode 100644 index 69f9c3e2..00000000 --- a/courses/learn-harness-engineering/docs/zh/lectures/lecture-05-why-long-running-tasks-lose-continuity/code/session-simulator.ts +++ /dev/null @@ -1,162 +0,0 @@ -/** - * session-simulator.ts - * - * Simulates two sessions working on a multi-step task. - * Run 1: No handoff file -- Session B starts from scratch and duplicates work. - * Run 2: With handoff file -- Session B picks up where Session A left off. - * - * Run: npx tsx docs/lectures/lecture-05-why-long-running-tasks-lose-continuity/code/session-simulator.ts - */ - -// --------------------------------------------------------------------------- -// Types -// --------------------------------------------------------------------------- - -interface TaskStep { - id: number; - name: string; - durationMs: number; -} - -interface SessionResult { - session: string; - stepsCompleted: number; - totalDurationMs: number; - duplicatedSteps: number; - output: string[]; -} - -// --------------------------------------------------------------------------- -// Task definition -// --------------------------------------------------------------------------- - -const taskSteps: TaskStep[] = [ - { id: 1, name: "Read project structure", durationMs: 50 }, - { id: 2, name: "Understand existing auth module", durationMs: 80 }, - { id: 3, name: "Design new search endpoint", durationMs: 60 }, - { id: 4, name: "Implement search endpoint", durationMs: 100 }, - { id: 5, name: "Write integration tests", durationMs: 70 }, - { id: 6, name: "Update documentation", durationMs: 40 }, -]; - -// --------------------------------------------------------------------------- -// Simulated session executor -// --------------------------------------------------------------------------- - -/** Simulate a session doing work. */ -function runSession( - sessionName: string, - startStep: number, - endStep: number -): SessionResult { - const output: string[] = []; - let totalDuration = 0; - - for (let i = startStep; i <= endStep; i++) { - const step = taskSteps.find((s) => s.id === i)!; - totalDuration += step.durationMs; - output.push(` [${sessionName}] Step ${step.id}: ${step.name} (${step.durationMs}ms)`); - } - - return { - session: sessionName, - stepsCompleted: endStep - startStep + 1, - totalDurationMs: totalDuration, - duplicatedSteps: 0, - output, - }; -} - -// --------------------------------------------------------------------------- -// Run 1: No handoff file -// --------------------------------------------------------------------------- - -function simulateNoHandoff(): { sessionA: SessionResult; sessionB: SessionResult } { - // Session A does steps 1-3 before timing out - const sessionA = runSession("Session A", 1, 3); - - // Session B has no context, starts from scratch - const sessionB = runSession("Session B", 1, 6); - sessionB.duplicatedSteps = 3; // Redoes steps 1-3 that A already did - - return { sessionA, sessionB }; -} - -// --------------------------------------------------------------------------- -// Run 2: With handoff file -// --------------------------------------------------------------------------- - -function simulateWithHandoff(): { sessionA: SessionResult; sessionB: SessionResult } { - // Session A does steps 1-3 and writes a handoff file - const sessionA = runSession("Session A", 1, 3); - - // Session B reads handoff, starts from step 4 - const sessionB = runSession("Session B", 4, 6); - sessionB.duplicatedSteps = 0; - - return { sessionA, sessionB }; -} - -// --------------------------------------------------------------------------- -// Reporting -// --------------------------------------------------------------------------- - -function pad(s: string, len: number): string { - return s.length >= len ? s : s + " ".repeat(len - s.length); -} - -function printRun(title: string, result: { sessionA: SessionResult; sessionB: SessionResult }): void { - console.log("\n" + "=".repeat(80)); - console.log(` ${title}`); - console.log("=".repeat(80)); - - console.log("\n Session A output:"); - for (const line of result.sessionA.output) { - console.log(line); - } - - console.log("\n Session B output:"); - for (const line of result.sessionB.output) { - console.log(line); - } - - console.log("\n Session B notes:"); - if (result.sessionB.duplicatedSteps > 0) { - console.log(` - Redid ${result.sessionB.duplicatedSteps} steps that Session A already completed`); - console.log(` - Wasted ${result.sessionB.totalDurationMs - (taskSteps.slice(3).reduce((s, t) => s + t.durationMs, 0))}ms on duplicate work`); - } else { - console.log(" - Read handoff file, continued from step 4"); - console.log(" - No duplicate work performed"); - } -} - -function printComparison(): void { - const noHandoff = simulateNoHandoff(); - const withHandoff = simulateWithHandoff(); - - printRun("RUN 1: NO HANDOFF FILE", noHandoff); - printRun("RUN 2: WITH HANDOFF FILE", withHandoff); - - // Comparison table - console.log("\n" + "=".repeat(80)); - console.log(" COMPARISON TABLE"); - console.log("=".repeat(80) + "\n"); - - const header = `| ${pad("Metric", 35)}| ${pad("No Handoff", 15)}| ${pad("With Handoff", 15)}|`; - const sep = `|${"-".repeat(37)}|${"-".repeat(17)}|${"-".repeat(17)}|`; - console.log(header); - console.log(sep); - - const noTotal = noHandoff.sessionA.totalDurationMs + noHandoff.sessionB.totalDurationMs; - const withTotal = withHandoff.sessionA.totalDurationMs + withHandoff.sessionB.totalDurationMs; - - console.log(`| ${pad("Session A steps completed", 35)}| ${pad(String(noHandoff.sessionA.stepsCompleted), 15)}| ${pad(String(withHandoff.sessionA.stepsCompleted), 15)}|`); - console.log(`| ${pad("Session B steps completed", 35)}| ${pad(String(noHandoff.sessionB.stepsCompleted), 15)}| ${pad(String(withHandoff.sessionB.stepsCompleted), 15)}|`); - console.log(`| ${pad("Duplicated steps", 35)}| ${pad(String(noHandoff.sessionB.duplicatedSteps), 15)}| ${pad(String(withHandoff.sessionB.duplicatedSteps), 15)}|`); - console.log(`| ${pad("Total work (ms)", 35)}| ${pad(String(noTotal), 15)}| ${pad(String(withTotal), 15)}|`); - console.log(`| ${pad("Time saved by handoff", 35)}| ${pad("-", 15)}| ${pad(String(noTotal - withTotal) + "ms", 15)}|`); - - console.log("\n A handoff file eliminates duplicate work and ensures continuity across sessions.\n"); -} - -printComparison(); diff --git a/courses/learn-harness-engineering/docs/zh/lectures/lecture-05-why-long-running-tasks-lose-continuity/index.md b/courses/learn-harness-engineering/docs/zh/lectures/lecture-05-why-long-running-tasks-lose-continuity/index.md deleted file mode 100644 index 537d7e42..00000000 --- a/courses/learn-harness-engineering/docs/zh/lectures/lecture-05-why-long-running-tasks-lose-continuity/index.md +++ /dev/null @@ -1,180 +0,0 @@ -[English Version →](../../../en/lectures/lecture-05-why-long-running-tasks-lose-continuity/) - -> 本篇代码示例:[code/](https://github.com/walkinglabs/learn-harness-engineering/blob/main/docs/zh/lectures/lecture-05-why-long-running-tasks-lose-continuity/code/) -> 实战练习:[Project 03. 让 agent 关掉再打开还能接着干](./../../projects/project-03-multi-session-continuity/index.md) - -# 第五讲. 让跨会话的任务保持上下文连续 - -你让 Claude Code 帮你实现一个完整的功能,它跑了 30 分钟,做了大部分工作,但上下文快满了。你开个新会话继续,然后发现:它不记得上次做了什么决策、为什么选了方案 A 而不是方案 B、哪些文件已经改过、测试跑到什么状态了。它得花 15 分钟重新探索一遍项目,而且可能跟上次的做法不一致。 - -想象一下,如果你是一个工匠,每天早上醒来都不记得昨天干了什么。你得重新认识整个工地——哪面墙砌了一半、为什么用的是红砖而不是青砖、水电管道走到哪了。更糟的是,你可能会把昨天已经装好的窗户拆了重来,因为你不记得它已经装过了。 - -这就是 AI coding agent 在跨会话任务中面对的困境。本节课讲为什么 agent 会"断片",以及如何通过结构化的状态持久化让它像一个每天坚持写日记的工匠——虽然还是会失忆,但日记本里记着一切。 - -## 上下文窗口:不是无限的 - -上下文窗口是有限的。这不是一个可以通过模型升级解决的问题——即使窗口大小增长到 1M tokens,复杂任务依然会用完。因为 agent 不只是在生成代码,它还要理解代码库、跟踪自己的决策历史、处理工具输出、维护对话上下文。这些信息加起来增长得比窗口扩容快得多。 - -更深层的问题:agent 产生的信息不是均匀重要的。中间推理步骤包含决策的"为什么"——为什么选了方案 A 而不是方案 B,为什么用了这个库而不是那个库,为什么跳过了某个优化。最终输出只包含"是什么"——代码本身。压缩策略通常保留后者但丢了前者。下一个会话看到代码但不知道为什么这么写,可能会"优化"掉一个有意为之的设计决策。 - -Anthropic 在他们的长运行 agent 研究中发现了一个很有意思的现象:当 agent 感觉上下文快满了,它们会表现出一种"过早收敛"的行为——匆忙结束当前工作,跳过验证步骤,或者选一个简单的方案而不是最优方案。这就像你考试时发现时间快到了,赶紧随便选几个选择题一样。Anthropic 把这叫"上下文焦虑"。 - -## 会话连续性流程 - -没有连续性工件的时候,每个新会话都是一场灾难: - -```mermaid -flowchart LR - S1["会话 1
    功能做到一半"] --> End1["上下文快满了
    会话结束"] - End1 --> S2["会话 2 重新开始"] - S2 --> Guess["重新读目录、重跑测试、
    猜上次为什么这么写"] - Guess --> Drift["代码会重复改
    恢复速度也很慢"] -``` - -有连续性工件的时候,新会话能快速接上: - -```mermaid -flowchart LR - Work["会话 1 的工作"] --> Progress["PROGRESS.md
    已完成 / 进行中 / 下一步"] - Work --> Decisions["DECISIONS.md
    为什么这样做"] - Work --> Verify["验证记录
    哪些测试通过或失败"] - Work --> Commit["Git 检查点
    当前仓库状态"] - - Progress --> Rebuild["会话 2 重建"] - Decisions --> Rebuild - Verify --> Rebuild - Commit --> Rebuild - - Rebuild --> Resume["新会话能很快接上"] -``` - -## 核心概念 - -- **上下文窗口是有限的**:不管模型吹多大的窗口(128K、200K、1M),长任务总会用完。用完之后要么压缩(丢信息),要么重置(开新会话)。两种方式都会丢东西。 -- **连续性工件**:持久化的状态文件,让新会话能无歧义地恢复到上次离开的地方。最基本的形式:进度日志 + 验证记录 + 下一步行动。就是那个工匠的日记本。 -- **重建成本**:新会话恢复到可执行状态所需的时间。好的 harness 能把重建成本从 15 分钟压到 3 分钟。 -- **漂移(Drift)**:agent 的理解跟代码仓库实际状态之间的偏差。每次会话边界都会引入漂移,不加控制会越漂越远。 -- **上下文焦虑**:Anthropic 观察到的现象——agent 在接近上下文限制时表现异常,过早结束任务以避免信息丢失。是一种非理性的资源焦虑。 -- **压缩 vs 重置**:压缩是在同一个会话里把上下文摘要化(保留"是什么",可能丢了"为什么");重置是开新会话从持久化状态重建(干净但依赖工件完备性)。 - -## 连续性断了以后会发生什么 - -上个会话花了很多上下文预算分析了三种方案的优劣,最终选了方案 B。这个会话的 agent 不知道这个分析过程,可能基于不完整的信息重新做了决策——而且可能选了方案 A。就像那个失忆的工匠不记得为什么选了红砖,今天看着觉得青砖更好看,就把昨天的墙拆了重砌。 - -更要命的是重复劳动。Agent 不确定某项工作是否已完成,重新做了一遍。或者更糟——做了一半发现跟已有的实现冲突,需要返工。工地上不能两拨人同时砌同一面墙——但在没有进度记录的情况下,新来的人完全不知道这面墙已经有人在砌了。 - -几个会话累积下来,实现方向可能已经悄悄偏离了原始需求。每个新会话对项目目标的理解都略有偏差,就像传话游戏——经过十个人传话,"帮我买杯咖啡"可能变成了"帮我买个咖啡机"。 - -还有验证缺口。上个会话的验证结果(哪些测试通过、哪些失败、为什么失败)没有记录,新会话得重新跑一遍验证才能了解当前状态。每次都重新诊断,每次都浪费宝贵的上下文。 - -OpenAI 和 Anthropic 都在他们的文档里强调了结构化状态持久化的重要性。OpenAI 的 harness engineering 文章把仓库当作"操作记录"——每次操作的结果都应该在仓库里留下可追溯的痕迹。Anthropic 的 long-running agents 文档则更具体地建议使用"交接文件"——包含当前状态、已知问题和下一步行动的结构化文档。 - -## 给失忆工匠的日记本 - -核心思路:**把 agent 当成一个会失忆的超级工程师来管理。** 每次它要"下班"之前,必须把关键信息写下来,让下一个"接班"的 agent 能快速上手。 - -**工具 1:进度文件(PROGRESS.md)**。这是最基本的连续性工件——日记本的核心部分: - -```markdown -# 项目进度 - -## 当前状态 -- 最新 commit: abc1234 (feat: add user preferences endpoint) -- 测试状态: 42/43 通过 (test_pagination_edge_case 失败) -- Lint: 通过 - -## 已完成 -- [x] 用户模型和数据库迁移 -- [x] 基础 CRUD 端点 -- [x] 认证中间件集成 - -## 进行中 -- [ ] 分页功能 (90% - 边界条件测试失败) - -## 已知问题 -- test_pagination_edge_case 在空结果集时返回 500 -- 需要确认是否要在列表中包含已删除用户 - -## 下一步 -1. 修复分页边界条件 bug -2. 添加"是否包含已删除用户"的查询参数 -3. 更新 API 文档 -``` - -**工具 2:决策日志(DECISIONS.md)**。记录重要的设计决策和原因。不需要详细的设计文档,只需要"什么决策、为什么、什么时候做的"——这是日记本里的备忘: - -```markdown -# 设计决策 - -## 2024-01-15: 使用 Redis 缓存用户偏好 -- 原因: 读取频率高(每次 API 调用都需要),数据量小 -- 否决方案: 用 PostgreSQL 物化视图(变更频率高,物化视图维护成本不划算) -- 约束: 缓存 TTL 设为 5 分钟,写入时主动失效 -``` - -**工具 3:git 提交作为检查点。** 每完成一个原子工作单元就提交。commit message 要说清楚做了什么和为什么。这是免费的、自动版本化的状态快照。 - -**工具 4:init.sh 或 harness 的初始化流程。** 在 `AGENTS.md` 里写明每天"上班"和"下班"的流程: - -```markdown -## 每次会话开始时(上班打卡) -1. 读 PROGRESS.md 了解当前状态 -2. 读 DECISIONS.md 了解重要决策 -3. 跑 make check 确认仓库处于一致状态 -4. 从 PROGRESS.md 的"下一步"部分继续工作 - -## 每次会话结束前(下班打卡) -1. 更新 PROGRESS.md -2. 跑 make check 确认一致状态 -3. 提交所有已完成的工作 -``` - -**混合策略**:不需要每次都重置上下文。短任务(30 分钟以内)可以在同一个会话里完成。长任务(跨会话)必须用进度文件和决策日志来维持连续性。判断标准:如果任务需要的上下文超过窗口的 60%,就开始准备交接。 - -### 上下文焦虑的深层分析 - -Anthropic 在 2026 年 3 月发布的研究进一步揭示了上下文焦虑的具体表现:在 Sonnet 4.5 上,当上下文接近窗口限制时,agent 会表现出强烈的"过早收敛"行为。这就像考试时发现时间快到了,赶紧随便填选择题。 - -针对这个现象,有两种策略: - -**压缩(Compaction)**:在同一个会话里把早期对话摘要化。优点是保留连续性,agent 能看到"是什么"。缺点是"为什么"经常在摘要中丢失——为什么选了方案 B 而非 A,为什么跳过了某个优化。更关键的是,压缩并不能消除上下文焦虑——agent 知道上下文曾经很大,心理上仍然倾向于加速收尾。 - -**重置(Context Reset)**:完全清空上下文,开一个新会话,从持久化工件重建。优点是干净的心理状态——新会话没有"我快没时间了"的焦虑。缺点是依赖交接工件的完备性。如果日记本里漏了关键信息,新会话可能在错误方向上浪费时间。 - -Anthropic 的实际数据:对于 Sonnet 4.5,上下文焦虑足够严重,以至于压缩单独不够用,上下文重置成为 harness 设计的关键组件。但对于 Opus 4.5,这种行为大幅减弱,可以不依赖重置而靠压缩管理上下文。这意味着:**harness 设计需要对目标模型有具体的理解,而不是套用通用模板。** - -> 来源:[Anthropic: Harness design for long-running application development](https://www.anthropic.com/engineering/harness-design-long-running-apps) - -## 实际案例 - -一个 agent 被要求实现一个带用户认证的博客系统,12 个功能点,预计需要 5 个会话。 - -**没有日记本的基线**:会话 1 实现了用户模型和基础路由。会话 2 开始时,agent 不记得认证中间件的接口约定,花了约 15 分钟推断上次的设计意图。到会话 3,累积漂移导致 agent 开始重复已实现的功能。到会话 5,仓库有大量冗余代码,但核心认证功能仍未通过端到端测试。12 个功能点只完成了 7 个,其中 3 个有隐含的正确性问题。就像那个没写日记的工匠——到了第五天,工地上一团乱,有些墙砌了两遍,有些该砌的根本没砌。 - -**有日记本的对照**:使用进度文件、决策日志、验证记录和 git 检查点。每个会话结束时自动更新状态报告。会话 2 的重建成本降到约 3 分钟。到会话 5,所有 12 个功能点完成且通过验证。 - -定量对比:重建时间减少约 78%,功能完成率从 58% 提升到 100%,隐含缺陷率从 43% 降到 8%。工匠还是那个会失忆的工匠,但有了日记本,他的每一天都从昨天停下的地方开始,而不是从零开始。 - -## 关键要点 - -- 上下文窗口是有限的资源。长任务一定会跨会话,跨会话一定会丢信息——这就像工匠每天都会失忆一样,是客观现实。 -- 解决方案不是更大的窗口,而是更好的状态持久化。进度文件 + 决策日志 + git 检查点——给失忆的工匠一个靠谱的日记本。 -- 把 agent 当成会失忆的工程师来管理:每次"下班"前写清楚做了什么、为什么、下一步做什么。 -- 重建成本是关键指标。好的 harness 应该让新会话在 3 分钟内恢复到可执行状态。 -- 混合策略:短任务在会话内完成,长任务用结构化工件维持连续性。 - -## 延伸阅读 - -- [Anthropic: Effective Harnesses for Long-Running Agents](https://www.anthropic.com/engineering/effective-harnesses-for-long-running-agents) -- [OpenAI: Harness Engineering](https://openai.com/index/harness-engineering/) -- [Lost in the Middle: How Language Models Use Long Contexts](https://arxiv.org/abs/2307.03172) -- [Claude Code Documentation](https://docs.anthropic.com/en/docs/claude-code) -- [HumanLayer: Harness Engineering for Coding Agents](https://humanlayer.dev/articles/harness-engineering-for-coding-agents/) - -## 练习 - -1. **连续性损耗度量**:选一个需要至少 3 个会话的开发任务。不提供任何连续性工件,在每个会话开始时记录 agent 花了多少上下文来"搞清楚上次做了什么"。会话结束后,创建进度文件,让下一个会话从进度文件开始。对比有进度文件和没有时的重建成本。 - -2. **交接模板设计**:设计一个最小化的交接模板,包含四个字段:仓库状态(commit hash)、运行时状态(测试通过率)、阻塞项、下一步行动。让一个全新的 agent 会话只凭这个模板恢复项目状态,记录恢复过程中出现的歧义点,迭代改进模板。 - -3. **混合策略实验**:在一个包含 5 个会话的开发任务中,对比三种策略:(a) 每次都开全新会话 + 进度文件,(b) 在同一个会话里尽可能多做(上下文压缩),(c) 混合策略(短任务在会话内,长任务跨会话 + 进度文件)。对比重建时间、功能完成率和决策一致性。 diff --git a/courses/learn-harness-engineering/docs/zh/lectures/lecture-06-why-initialization-needs-its-own-phase/code/index.md b/courses/learn-harness-engineering/docs/zh/lectures/lecture-06-why-initialization-needs-its-own-phase/code/index.md deleted file mode 100644 index 2cb65d86..00000000 --- a/courses/learn-harness-engineering/docs/zh/lectures/lecture-06-why-initialization-needs-its-own-phase/code/index.md +++ /dev/null @@ -1,8 +0,0 @@ -# 第 06 讲代码 - -使用此文件夹存放以下方面的示例: - -- 初始化器输出 -- 初始化脚本 -- 进度文件 -- 首次运行脚手架 diff --git a/courses/learn-harness-engineering/docs/zh/lectures/lecture-06-why-initialization-needs-its-own-phase/code/init-check.ts b/courses/learn-harness-engineering/docs/zh/lectures/lecture-06-why-initialization-needs-its-own-phase/code/init-check.ts deleted file mode 100644 index 52dcd985..00000000 --- a/courses/learn-harness-engineering/docs/zh/lectures/lecture-06-why-initialization-needs-its-own-phase/code/init-check.ts +++ /dev/null @@ -1,296 +0,0 @@ -/** - * init-check.ts - * - * Programmatically checks initialization prerequisites: - * - Node.js version - * - Dependencies installed (node_modules exists) - * - Data directory exists - * - Config files present - * - * Simulates running with and without an explicit init phase, - * showing how missing prerequisites cause silent failures later. - * - * Run: npx tsx docs/lectures/lecture-06-why-initialization-needs-its-own-phase/code/init-check.ts - */ - -import * as fs from "node:fs"; -import * as path from "node:path"; -import * as childProcess from "node:child_process"; - -// --------------------------------------------------------------------------- -// Types -// --------------------------------------------------------------------------- - -interface CheckItem { - name: string; - category: string; - check: () => { pass: boolean; detail: string }; - impactIfMissing: string; -} - -interface CheckResult { - name: string; - category: string; - passed: boolean; - detail: string; - impactIfMissing: string; -} - -// --------------------------------------------------------------------------- -// Checks -// --------------------------------------------------------------------------- - -function createChecks(targetDir: string): CheckItem[] { - return [ - { - name: "Node.js version >= 18", - category: "Runtime", - check: () => { - try { - const version = process.version; // e.g. "v20.11.0" - const major = parseInt(version.replace("v", "").split(".")[0], 10); - return { - pass: major >= 18, - detail: `Detected: ${version} (need >= v18)`, - }; - } catch { - return { pass: false, detail: "Could not detect Node.js version" }; - } - }, - impactIfMissing: "TypeScript features and built-in APIs unavailable", - }, - { - name: "package.json exists", - category: "Config", - check: () => { - const p = path.join(targetDir, "package.json"); - const exists = fs.existsSync(p); - return { - pass: exists, - detail: exists ? `Found: ${p}` : "Not found in target directory", - }; - }, - impactIfMissing: "Cannot install dependencies or run scripts", - }, - { - name: "Dependencies installed (node_modules)", - category: "Dependencies", - check: () => { - const p = path.join(targetDir, "node_modules"); - const exists = fs.existsSync(p); - return { - pass: exists, - detail: exists ? `Found: ${p}` : "node_modules/ not found -- run npm install", - }; - }, - impactIfMissing: "All imports fail at runtime", - }, - { - name: "TypeScript available", - category: "Toolchain", - check: () => { - try { - const result = childProcess.execSync("npx tsc --version", { - encoding: "utf-8", - timeout: 10000, - stdio: ["pipe", "pipe", "pipe"], - }).trim(); - return { pass: true, detail: `Detected: ${result}` }; - } catch { - return { pass: false, detail: "TypeScript not available via npx" }; - } - }, - impactIfMissing: "Cannot compile TypeScript files", - }, - { - name: "tsconfig.json exists", - category: "Config", - check: () => { - const p = path.join(targetDir, "tsconfig.json"); - const exists = fs.existsSync(p); - return { - pass: exists, - detail: exists ? `Found: ${p}` : "Not found", - }; - }, - impactIfMissing: "TypeScript compiler uses defaults, may not match project needs", - }, - { - name: "Source directory exists", - category: "Structure", - check: () => { - const candidates = ["src", "lib", "app"]; - const found = candidates.filter((d) => { - const p = path.join(targetDir, d); - return fs.existsSync(p) && fs.statSync(p).isDirectory(); - }); - return { - pass: found.length > 0, - detail: found.length > 0 ? `Found: ${found.join(", ")}` : "No src/ lib/ or app/ directory found", - }; - }, - impactIfMissing: "Agent cannot locate source files to modify", - }, - { - name: "Test directory exists", - category: "Structure", - check: () => { - const candidates = ["test", "tests", "__tests__", "spec"]; - const found = candidates.filter((d) => { - const p = path.join(targetDir, d); - return fs.existsSync(p) && fs.statSync(p).isDirectory(); - }); - return { - pass: found.length > 0, - detail: found.length > 0 ? `Found: ${found.join(", ")}` : "No test directory found", - }; - }, - impactIfMissing: "Agent cannot find or run existing tests", - }, - { - name: "Git repository initialized", - category: "Version Control", - check: () => { - const gitDir = path.join(targetDir, ".git"); - const exists = fs.existsSync(gitDir); - return { - pass: exists, - detail: exists ? "Git repository detected" : "No .git directory found", - }; - }, - impactIfMissing: "No rollback capability, no change history", - }, - ]; -} - -// --------------------------------------------------------------------------- -// Simulation: with and without init phase -// --------------------------------------------------------------------------- - -interface SimResult { - scenario: string; - checksRun: boolean; - failuresBeforeWork: number; - workAttempted: boolean; - workSucceeded: boolean; - timeWastedMs: number; -} - -function simulateWithoutInit(): SimResult { - // Agent skips init, goes straight to work. - // Encounters failures one at a time as it hits missing prerequisites. - const checks = createChecks(process.cwd()); - let failures = 0; - let timeWasted = 0; - - for (const check of checks) { - const result = check.check(); - if (!result.pass) { - failures++; - timeWasted += 200; // Agent spends time discovering each missing piece - } - } - - return { - scenario: "NO INIT PHASE", - checksRun: false, - failuresBeforeWork: 0, // Didn't check upfront - workAttempted: true, - workSucceeded: failures === 0, - timeWastedMs: timeWasted, - }; -} - -function simulateWithInit(): SimResult { - // Agent runs init phase first, discovers all issues upfront. - const checks = createChecks(process.cwd()); - let failures = 0; - - for (const check of checks) { - if (!check.check().pass) { - failures++; - } - } - - return { - scenario: "WITH INIT PHASE", - checksRun: true, - failuresBeforeWork: failures, - workAttempted: failures === 0, - workSucceeded: failures === 0, - timeWastedMs: 0, - }; -} - -// --------------------------------------------------------------------------- -// Reporting -// --------------------------------------------------------------------------- - -function pad(s: string, len: number): string { - return s.length >= len ? s : s + " ".repeat(len - s.length); -} - -function run(): void { - const targetDir = process.cwd(); - console.log("\n" + "=".repeat(80)); - console.log(" INITIALIZATION PREREQUISITE CHECK"); - console.log("=".repeat(80)); - console.log(` Target: ${targetDir}\n`); - - const checks = createChecks(targetDir); - const results: CheckResult[] = checks.map((c) => { - const r = c.check(); - return { - name: c.name, - category: c.category, - passed: r.pass, - detail: r.detail, - impactIfMissing: c.impactIfMissing, - }; - }); - - // Detailed report - const header = `| ${pad("Check", 35)}| ${pad("Category", 12)}| ${pad("Status", 6)}| Detail`; - const sep = `|${"-".repeat(37)}|${"-".repeat(14)}|${"-".repeat(8)}|${"-".repeat(40)}`; - console.log(header); - console.log(sep); - - for (const r of results) { - const status = r.passed ? "PASS" : "FAIL"; - console.log(`| ${pad(r.name, 35)}| ${pad(r.category, 12)}| ${pad(status, 6)}| ${r.detail}`); - } - - const passCount = results.filter((r) => r.passed).length; - const failCount = results.filter((r) => !r.passed).length; - - console.log("\n" + "-".repeat(80)); - console.log(` Results: ${passCount} passed, ${failCount} failed out of ${results.length} checks`); - - if (failCount > 0) { - console.log("\n FAILED CHECKS -- Impact if not resolved:"); - for (const r of results.filter((r) => !r.passed)) { - console.log(` - ${r.name}: ${r.impactIfMissing}`); - } - } - - // Comparison: with vs without init - const noInit = simulateWithoutInit(); - const withInit = simulateWithInit(); - - console.log("\n" + "=".repeat(80)); - console.log(" INIT PHASE COMPARISON"); - console.log("=".repeat(80) + "\n"); - - const cHeader = `| ${pad("Metric", 35)}| ${pad("No Init Phase", 18)}| ${pad("With Init Phase", 18)}|`; - const cSep = `|${"-".repeat(37)}|${"-".repeat(20)}|${"-".repeat(20)}|`; - console.log(cHeader); - console.log(cSep); - console.log(`| ${pad("Prerequisites checked upfront", 35)}| ${pad("No", 18)}| ${pad("Yes", 18)}|`); - console.log(`| ${pad("Work attempted despite issues", 35)}| ${pad(String(noInit.workAttempted), 18)}| ${pad(String(withInit.workAttempted), 18)}|`); - console.log(`| ${pad("Time wasted discovering issues late", 35)}| ${pad(noInit.timeWastedMs + "ms", 18)}| ${pad(withInit.timeWastedMs + "ms", 18)}|`); - console.log(`| ${pad("Work succeeded", 35)}| ${pad(String(noInit.workSucceeded), 18)}| ${pad(String(withInit.workSucceeded), 18)}|`); - - console.log("\n An explicit init phase catches problems before they waste time.\n"); -} - -run(); diff --git a/courses/learn-harness-engineering/docs/zh/lectures/lecture-06-why-initialization-needs-its-own-phase/code/init.sh b/courses/learn-harness-engineering/docs/zh/lectures/lecture-06-why-initialization-needs-its-own-phase/code/init.sh deleted file mode 100644 index 2c6e1b0c..00000000 --- a/courses/learn-harness-engineering/docs/zh/lectures/lecture-06-why-initialization-needs-its-own-phase/code/init.sh +++ /dev/null @@ -1,10 +0,0 @@ -#!/usr/bin/env bash -set -euo pipefail - -echo "[init] installing dependencies" -npm install - -echo "[init] starting the docs site is optional" -echo "[init] use npm run docs:dev for course docs" - -echo "[init] project-specific startup would go here" diff --git a/courses/learn-harness-engineering/docs/zh/lectures/lecture-06-why-initialization-needs-its-own-phase/code/initializer-output-checklist.md b/courses/learn-harness-engineering/docs/zh/lectures/lecture-06-why-initialization-needs-its-own-phase/code/initializer-output-checklist.md deleted file mode 100644 index 5fd4e496..00000000 --- a/courses/learn-harness-engineering/docs/zh/lectures/lecture-06-why-initialization-needs-its-own-phase/code/initializer-output-checklist.md +++ /dev/null @@ -1,7 +0,0 @@ -# 初始化器输出检查清单 - -- 是否有标准的启动命令? -- 是否有标准的验证命令? -- 是否有第一个进度产物? -- 是否有稳定的首次提交? -- 是否有供后续运行使用的可见功能面? diff --git a/courses/learn-harness-engineering/docs/zh/lectures/lecture-06-why-initialization-needs-its-own-phase/index.md b/courses/learn-harness-engineering/docs/zh/lectures/lecture-06-why-initialization-needs-its-own-phase/index.md deleted file mode 100644 index a253a6ba..00000000 --- a/courses/learn-harness-engineering/docs/zh/lectures/lecture-06-why-initialization-needs-its-own-phase/index.md +++ /dev/null @@ -1,157 +0,0 @@ -[English Version →](../../../en/lectures/lecture-06-why-initialization-needs-its-own-phase/) - -> 本篇代码示例:[code/](https://github.com/walkinglabs/learn-harness-engineering/blob/main/docs/zh/lectures/lecture-06-why-initialization-needs-its-own-phase/code/) -> 实战练习:[Project 03. 让 agent 关掉再打开还能接着干](./../../projects/project-03-multi-session-continuity/index.md) - -# 第六讲. 让 agent 每次工作前先初始化 - -你开了一个新的 agent 会话,让它"帮我加个搜索功能"。它上来就开始改代码——精神可嘉。改了 20 分钟发现测试框架没配好,又花 10 分钟搞测试框架,然后发现数据库迁移脚本格式不对,又折腾了一会儿。最后搜索功能倒是加了,但整个会话的效率很低——大部分时间花在了"搞清楚这个项目怎么运作"上,而不是写搜索功能。 - -更好的做法是:在让 agent 开始干活之前,先用一个独立的阶段把基础环境搭好、验证命令跑通、项目结构搞清楚。就像盖房子——你不能边打地基边砌墙,不然墙砌到一半地基还没干,整栋楼都得推倒重来。先打地基,地基干了,再砌墙,一气呵成。 - -这节课讲的就是为什么初始化必须是独立的阶段,不能跟实现混在一起。 - -## 地基和墙:两种完全不同的工作 - -初始化和实现的优化目标完全不同。实现阶段的目标是:最大化已验证功能的数量和质量。初始化阶段的目标是:最大化后续所有实现的可靠性和效率。 - -当你把初始化和实现混在一起的时候,agent 面临一个多目标优化问题——它要同时搭基础设施和写功能代码。在没有显式优先级设定的情况下,agent 自然倾向于写代码(因为那是直接可见的产出),而牺牲基础设施(因为它的价值只能在后续会话中体现)。这就像让施工队同时打地基和砌墙——他们大概率会急着砌墙,因为墙看得见、能交差。但地基没打好的房子,后面出的问题是系统性的。 - -## 初始化生命周期 - -```mermaid -flowchart TB - subgraph Wrong["混在一起的一次会话(错误)"] - W1["一上来就开始做功能"] --> W2["做到一半才发现环境和测试缺口"] - W2 --> W3["累积未经验证的代码"] - W3 --> W4["下个会话还得重新摸项目状态"] - end - - subgraph Right["独立初始化阶段(正确)"] - R1["会话 1:环境可运行"] --> R2["示例测试通过"] - R2 --> R3["写出启动契约 + 任务清单"] - R3 --> R4["提交干净检查点"] - R4 --> R5["后续会话直接开始做已准备好的任务"] - end -``` - -## 混在一起做会怎样 - -最直接的问题:地基打不牢。Agent 花了 80% 的精力写功能代码,剩下 20% 随便搭了点基础设施。测试框架配了但没验证过,lint 规则设了但太宽松,进度文件没创建。这些缺陷在第一个会话里不明显(因为 agent 还记得它做了什么),但到第二个会话就暴露了——新 agent 不知道项目怎么跑、怎么测、做到哪了。地基不牢,地动山摇。 - -更隐蔽的代价是"未验证的累积"——在测试框架配好之前写的功能代码,等回头补测试的时候可能发现设计上就有问题,早知道的话应该用不同的方式实现。就像在地基没干的时候就开始贴瓷砖,等发现地面不平时,瓷砖全得撬掉重来。 - -上下文预算也在被浪费。初始化工作(配环境、配测试、理解项目结构)消耗了大量预算,留给实际功能实现的反而不够了。结果第一个会话只完成了一半的功能,第二个会话还得从头理解项目。预算花在了打地基上,但地基也没打好——两头都没占着。 - -最容易被忽略的是隐式假设埋下的雷。Agent 在初始化过程中做的决策(用什么测试框架、目录怎么组织、依赖怎么管理)如果不显式记录下来,后续会话就可能做出矛盾的选择。第一个施工队用的是水泥地基,第二个施工队不知道,往上面打了木桩——地基直接裂了。 - -Anthropic 在他们的长运行应用开发研究中明确建议把初始化和实现分离。他们的实验数据:使用独立初始化阶段的项目,多会话场景中的功能完成率比混合方式高 31%。关键是——初始化阶段投入的时间在后续 3-4 个会话中就能完全收回。地基打得越扎实,上面的楼盖得越快。 - -OpenAI 的 Codex harness engineering 指南也强调"仓库作为操作记录"的原则——第一次运行就要建立清晰的操作结构,否则每次新会话都得重新推断项目约定。 - -## 核心概念 - -- **初始化阶段**:agent 生命周期中的第一个阶段,不做功能实现,只建立后续所有实现阶段的执行前提。输出不是代码,而是基础设施。 -- **自举契约**:一个项目能被全新 agent 会话无歧义操作的条件——能启动、能测试、能看进度、能接手下一步。四个条件缺一不可。 -- **冷启动 vs 热启动**:冷启动是从空目录开始,agent 要猜项目结构;热启动是从模板或已有项目开始,基础设施已经就位。热启动的效果远好于冷启动——就像在有水电的工地上开工,比在荒郊野岭从头搞起快得多。 -- **交接就绪性**:项目在任何时刻都处于"可以被全新 agent 接手"的状态。不需要口头解释,只看仓库内容就能接着干。 -- **首次验证时间**:从项目开始到第一个功能点通过验证的时间。这是衡量初始化效率的核心指标。 -- **下游可用性**:初始化质量的最佳衡量标准——后续会话不需要依赖隐式知识就能成功执行任务的比例。 - -## 怎么做好初始化 - -**把初始化当作一个独立的阶段来执行。** 第一个会话只做初始化,不写任何业务功能代码。初始化的产出是: - -**1. 可运行的环境。** 项目能启动、依赖都装好、没有环境问题。地基浇好了,没裂缝。 - -**2. 可验证的测试框架。** 至少有一个示例测试能通过。这证明测试框架本身是配对的——就像地基上立了一根柱子,证明地基能承重。 - -**3. 自举契约文档。** 一个明确的文档告诉后续会话: -```markdown -# 初始化契约 - -## 启动命令 -- 安装依赖:`make setup` -- 启动开发服务器:`make dev` -- 运行测试:`make test` -- 完整验证:`make check` - -## 当前状态 -- 所有依赖已安装并锁定 -- 测试框架已配置(Vitest + React Testing Library) -- 示例测试通过(1/1) -- Lint 规则已配置(ESLint + Prettier) - -## 项目结构 -- src/ — 源代码 -- src/components/ — React 组件 -- src/api/ — API 客户端 -- tests/ — 测试文件 -``` - -**4. 任务分解。** 把整个项目拆成有序的任务列表,每个任务有明确的验收标准: -```markdown -# 任务分解 - -## Task 1: 用户认证基础 -- 实现 JWT 认证中间件 -- 添加登录/注册端点 -- 验收标准:pytest tests/test_auth.py 全部通过 - -## Task 2: 用户资料页面 -- 实现用户资料 CRUD -- 添加资料编辑表单 -- 验收标准:pytest tests/test_profile.py 全部通过 - -## Task 3: 搜索功能 -- ... -``` - -**5. Git 提交作为检查点。** 初始化完成后提交一个干净的 checkpoint。后续所有工作都从这个 checkpoint 开始。 - -**热启动策略**:不要从空目录开始。用一个项目模板(create-react-app、fastapi-template 等)预置好标准的目录结构、依赖配置和测试框架。把通用的初始化步骤预置到模板里,只留下项目特有的初始化工作。就像在有通水通电的工地上开工,比从荒郊野岭开始强一万倍。 - -**初始化的完成条件**:不是"写了多少代码",而是自举契约的四个条件都满足了——能启动、能测试、能看进度、能接手下一步。用这个检查清单验收初始化: - -```markdown -## 初始化验收清单 -- [ ] `make setup` 从零开始能成功 -- [ ] `make test` 至少有一个测试通过 -- [ ] 新的 agent 会话能只看仓库回答"怎么跑"和"怎么测" -- [ ] 任务分解文件存在且有至少 3 个任务 -- [ ] 所有内容已提交到 git -``` - -## 实际案例 - -一个 React 前端项目的两种初始化方式对比: - -**混合方式(边打地基边砌墙)**:agent 在第一个会话中同时做了项目脚手架创建和首个功能实现。会话结束时,仓库有可运行的代码,但:没有显式的启动/测试命令文档、没有进度跟踪文件、没有任务分解。第二个会话花了约 20 分钟推断项目结构、测试框架和构建流程——就像新来的施工队看着一片工地,不知道地基打到什么程度、水电管道走到哪了,只能一个个挖开来看。 - -**独立初始化(先打地基)**:第一个会话只做初始化——用项目模板创建目录结构、配置测试框架(Vitest + React Testing Library)、写一个示例测试并验证通过、创建自举契约文档和任务分解文件、提交初始检查点。第二个会话的重建时间不到 3 分钟,直接从任务列表开始工作——施工队来了,看了一眼施工图就知道从哪里接着干。 - -整个项目周期对比:混合方式的总重建时间(跨所有会话)比独立初始化多约 60%。独立初始化多花的那 20 分钟在后续会话中被成倍收回。就像地基打得扎实,上面盖楼的效率反而更高——慢即是快。 - -## 关键要点 - -- 初始化和实现的优化目标不同,混在一起只会互相拖后腿。先打地基,再砌墙。 -- 初始化的产出不是代码,是基础设施:可运行的环境、可验证的测试、自举契约、任务分解。 -- 用"自举契约"的四个条件验收初始化:能启动、能测试、能看进度、能接手下一步。 -- 热启动优于冷启动。用项目模板预置标准化的基础设施。 -- 初始化投入的时间会在后续 3-4 个会话中完全收回。这不是额外的成本,是前期投资——地基打得越扎实,楼盖得越快。 - -## 延伸阅读 - -- [Anthropic: Effective Harnesses for Long-Running Agents](https://www.anthropic.com/engineering/effective-harnesses-for-long-running-agents) -- [OpenAI: Harness Engineering](https://openai.com/index/harness-engineering/) -- [HumanLayer: Harness Engineering for Coding Agents](https://humanlayer.dev/articles/harness-engineering-for-coding-agents/) -- [Infrastructure as Code — Martin Fowler](https://martinfowler.com/bliki/InfrastructureAsCode.html) -- [SWE-agent: Agent-Computer Interfaces](https://github.com/princeton-nlp/SWE-agent) - -## 练习 - -1. **自举契约设计**:为一个你正在开发的项目写一个完整的自举契约。然后开一个全新的 agent 会话,只给它看仓库内容(不给任何口头上下文),让它尝试启动项目、跑测试、了解当前进度。记录它遇到的问题——每个问题都对应自举契约中缺失的一个条款。 - -2. **对比实验**:选一个中等复杂度的新项目。方式 A:让 agent 初始化和首次实现同时做。方式 B:先花一个会话做独立初始化,第二个会话开始实现。在 4 个会话后对比:首次验证时间、重建成本、功能完成率。 - -3. **初始化验收清单**:为你的项目设计一个初始化验收清单。让一个全新的 agent 会话执行清单上的每一项,记录哪些项通过了、哪些没通过。没通过的项就是你的 harness 需要补强的地方。 diff --git a/courses/learn-harness-engineering/docs/zh/lectures/lecture-07-why-agents-overreach-and-under-finish/code/index.md b/courses/learn-harness-engineering/docs/zh/lectures/lecture-07-why-agents-overreach-and-under-finish/code/index.md deleted file mode 100644 index f681cddb..00000000 --- a/courses/learn-harness-engineering/docs/zh/lectures/lecture-07-why-agents-overreach-and-under-finish/code/index.md +++ /dev/null @@ -1,8 +0,0 @@ -# 第 07 讲代码 - -使用此文件夹存放以下方面的示例: - -- 一次性失败 -- 过大的任务提示 -- 增量式任务塑造 -- 结构化的功能面 diff --git a/courses/learn-harness-engineering/docs/zh/lectures/lecture-07-why-agents-overreach-and-under-finish/code/next-task-template.md b/courses/learn-harness-engineering/docs/zh/lectures/lecture-07-why-agents-overreach-and-under-finish/code/next-task-template.md deleted file mode 100644 index cf6463e2..00000000 --- a/courses/learn-harness-engineering/docs/zh/lectures/lecture-07-why-agents-overreach-and-under-finish/code/next-task-template.md +++ /dev/null @@ -1,6 +0,0 @@ -# 下一个任务模板 - -- 当前最高优先级功能: -- 为什么这个功能是下一个: -- 什么算作通过: -- 在此步骤中不能更改什么: diff --git a/courses/learn-harness-engineering/docs/zh/lectures/lecture-07-why-agents-overreach-and-under-finish/code/scope-surface-example.md b/courses/learn-harness-engineering/docs/zh/lectures/lecture-07-why-agents-overreach-and-under-finish/code/scope-surface-example.md deleted file mode 100644 index a84b39ed..00000000 --- a/courses/learn-harness-engineering/docs/zh/lectures/lecture-07-why-agents-overreach-and-under-finish/code/scope-surface-example.md +++ /dev/null @@ -1,17 +0,0 @@ -# 范围界定示例 - -任务: - -- 为 Electron 知识库应用添加索引功能 - -糟糕的范围定义: - -- "实现索引" - -更好的范围定义: - -- 解析导入的文档 -- 将文档拆分为块 -- 持久化块元数据 -- 在 UI 中暴露索引状态 -- 添加重新索引操作 diff --git a/courses/learn-harness-engineering/docs/zh/lectures/lecture-07-why-agents-overreach-and-under-finish/code/scope-tracker.ts b/courses/learn-harness-engineering/docs/zh/lectures/lecture-07-why-agents-overreach-and-under-finish/code/scope-tracker.ts deleted file mode 100644 index c587a1dc..00000000 --- a/courses/learn-harness-engineering/docs/zh/lectures/lecture-07-why-agents-overreach-and-under-finish/code/scope-tracker.ts +++ /dev/null @@ -1,151 +0,0 @@ -/** - * scope-tracker.ts - * - * Reads a feature list and a change log. Enforces single-active-feature - * policy. Given a log of changes, flags any changes outside the active - * feature scope. Demonstrates how scope drift happens and how the tracker - * catches it. - * - * Run: npx tsx docs/lectures/lecture-07-why-agents-overreach-and-under-finish/code/scope-tracker.ts - */ - -// --------------------------------------------------------------------------- -// Types -// --------------------------------------------------------------------------- - -interface Feature { - id: string; - name: string; - status: "active" | "pending" | "done"; -} - -interface ChangeLogEntry { - step: number; - file: string; - description: string; - featureId: string; // The feature this change claims to belong to -} - -// --------------------------------------------------------------------------- -// Sample data -// --------------------------------------------------------------------------- - -const features: Feature[] = [ - { id: "F-001", name: "Search endpoint", status: "active" }, - { id: "F-002", name: "Delete endpoint", status: "pending" }, - { id: "F-003", name: "Rate limiting", status: "pending" }, - { id: "F-004", name: "User dashboard", status: "pending" }, -]; - -// A realistic change log where the agent gradually drifts from the active feature -const changeLog: ChangeLogEntry[] = [ - { step: 1, file: "src/routes/search.ts", description: "Add search route handler", featureId: "F-001" }, - { step: 2, file: "src/routes/search.ts", description: "Add query parameter validation", featureId: "F-001" }, - { step: 3, file: "src/routes/search.ts", description: "Add search results pagination", featureId: "F-001" }, - { step: 4, file: "src/routes/delete.ts", description: "Add delete route handler", featureId: "F-002" }, // DRIFT - { step: 5, file: "src/middleware/rate-limit.ts", description: "Add rate limiter middleware", featureId: "F-003" }, // DRIFT - { step: 6, file: "src/routes/search.ts", description: "Integrate rate limiter into search", featureId: "F-001" }, - { step: 7, file: "src/dashboard/ui.tsx", description: "Create dashboard layout component", featureId: "F-004" }, // DRIFT - { step: 8, file: "src/routes/search.ts", description: "Add search response formatting", featureId: "F-001" }, - { step: 9, file: "src/routes/delete.ts", description: "Add delete confirmation logic", featureId: "F-002" }, // DRIFT - { step: 10, file: "src/routes/search.ts", description: "Add search tests", featureId: "F-001" }, -]; - -// --------------------------------------------------------------------------- -// Scope tracker -// --------------------------------------------------------------------------- - -interface ScopeCheckResult { - step: number; - file: string; - description: string; - featureId: string; - inScope: boolean; - activeFeature: string; -} - -function trackScope( - featureList: Feature[], - changes: ChangeLogEntry[] -): ScopeCheckResult[] { - const activeFeatures = featureList.filter((f) => f.status === "active"); - const activeIds = new Set(activeFeatures.map((f) => f.id)); - const activeNames = activeFeatures.map((f) => f.name).join(", "); - - return changes.map((change) => ({ - step: change.step, - file: change.file, - description: change.description, - featureId: change.featureId, - inScope: activeIds.has(change.featureId), - activeFeature: activeNames, - })); -} - -// --------------------------------------------------------------------------- -// Reporting -// --------------------------------------------------------------------------- - -function pad(s: string, len: number): string { - return s.length >= len ? s : s + " ".repeat(len - s.length); -} - -function run(): void { - const results = trackScope(features, changeLog); - - console.log("\n" + "=".repeat(100)); - console.log(" SCOPE TRACKER -- Single Active Feature Enforcement"); - console.log("=".repeat(100)); - - console.log("\n Active feature: " + features.filter((f) => f.status === "active").map((f) => `${f.id} (${f.name})`).join(", ")); - console.log(" Pending features: " + features.filter((f) => f.status === "pending").map((f) => `${f.id} (${f.name})`).join(", ")); - - // Detailed change log - console.log("\n" + "-".repeat(100)); - const header = `| ${pad("Step", 5)}| ${pad("File", 35)}| ${pad("Description", 40)}| ${pad("Feature", 8)}| ${pad("In Scope", 10)}|`; - const sep = `|${"-".repeat(7)}|${"-".repeat(37)}|${"-".repeat(42)}|${"-".repeat(10)}|${"-".repeat(12)}|`; - console.log(header); - console.log(sep); - - let inScopeCount = 0; - let driftCount = 0; - - for (const r of results) { - const scopeLabel = r.inScope ? "OK" : "DRIFT"; - if (r.inScope) inScopeCount++; - else driftCount++; - - const marker = r.inScope ? " " : ">>"; - console.log(`${marker}| ${pad(String(r.step), 5)}| ${pad(r.file, 35)}| ${pad(r.description, 40)}| ${pad(r.featureId, 8)}| ${pad(scopeLabel, 10)}|`); - } - - // Summary - console.log("\n" + "=".repeat(100)); - console.log(" SCOPE DRIFT SUMMARY"); - console.log("=".repeat(100) + "\n"); - - const sHeader = `| ${pad("Metric", 40)}| ${pad("Value", 15)}|`; - const sSep = `|${"-".repeat(42)}|${"-".repeat(17)}|`; - console.log(sHeader); - console.log(sSep); - console.log(`| ${pad("Total changes", 40)}| ${pad(String(results.length), 15)}|`); - console.log(`| ${pad("Changes within active scope (F-001)", 40)}| ${pad(String(inScopeCount), 15)}|`); - console.log(`| ${pad("Changes outside active scope (DRIFT)", 40)}| ${pad(String(driftCount), 15)}|`); - console.log(`| ${pad("Features touched (total)", 40)}| ${pad(String(new Set(results.map((r) => r.featureId)).size), 15)}|`); - - // Drift detail - const driftFeatures = [...new Set(results.filter((r) => !r.inScope).map((r) => r.featureId))]; - if (driftFeatures.length > 0) { - console.log("\n DRIFTED FEATURES:"); - for (const fid of driftFeatures) { - const feat = features.find((f) => f.id === fid); - const driftChanges = results.filter((r) => r.featureId === fid); - console.log(` ${fid} (${feat?.name}): ${driftChanges.length} unauthorized changes`); - } - } - - console.log("\n Without a scope tracker, the agent silently worked on " + driftFeatures.length + " unrelated features."); - console.log(" The tracker catches this drift and enforces the single-active-feature policy.\n"); -} - -run(); diff --git a/courses/learn-harness-engineering/docs/zh/lectures/lecture-07-why-agents-overreach-and-under-finish/index.md b/courses/learn-harness-engineering/docs/zh/lectures/lecture-07-why-agents-overreach-and-under-finish/index.md deleted file mode 100644 index 3fe2d032..00000000 --- a/courses/learn-harness-engineering/docs/zh/lectures/lecture-07-why-agents-overreach-and-under-finish/index.md +++ /dev/null @@ -1,134 +0,0 @@ -[English Version →](../../../en/lectures/lecture-07-why-agents-overreach-and-under-finish/) - -> 本篇代码示例:[code/](https://github.com/walkinglabs/learn-harness-engineering/blob/main/docs/zh/lectures/lecture-07-why-agents-overreach-and-under-finish/code/) -> 实战练习:[Project 04. 用运行反馈修正 agent 的行为](./../../projects/project-04-incremental-indexing/index.md) - -# 第七讲. 给 agent 划清每次任务的边界 - -你让 Claude Code "给这个项目加上用户认证功能",结果它同时开始改数据库 schema、写路由、改前端组件、还顺手重构了错误处理中间件。两个小时后你一看——12 个文件被修改,800 行新代码,但没有一个功能是端到端跑通的。 - -贪多嚼不烂,这句话放到 AI agent 身上格外贴切。Agent 天生就有"多做一点"的冲动——看到相关的事情就顺手一起做了,和那种在超市本来只打算买瓶酱油、结果推着满满一车出来的人一个德行。问题是,人类买了太多东西最多浪费钱,agent 同时做太多事情则是每一件都做不好。 - -Anthropic 在 "Effective harnesses for long-running agents" 工程博客中明确指出:当提示太宽泛时,agent 倾向于"同时启动多件事"而非"先做完一件事"。OpenAI 在 Codex 工程实践中也发现,没有显式范围控制的任务,完成率会暴跌。这不是模型的问题——是你没有在 harness 里给它划清边界。 - -## 注意力是有限的资源 - -这不是比喻,是数学。假设 agent 的上下文容量为 C,同时激活 k 个任务,每个任务平均获得 C/k 的推理资源。当 C/k 低于完成单个任务所需的最小阈值时,所有任务都做不完。这就像你的胃就那么大——同时塞十个包子进去,不是一个都消化不了,而是十个都消化不良。 - -Claude Code 的真实行为很说明问题。你让它"添加用户注册功能",它很可能这样做: - -1. 创建 User model -2. 写注册路由 -3. 发现需要邮箱验证,于是加邮件服务 -4. 看到密码需要加密,于是引入 bcrypt -5. 注意到错误处理不统一,于是重构全局错误中间件 -6. 看到测试文件结构不清晰,于是重组目录结构 - -6 步之后,每一个都是半成品。没有端到端验证,代码之间耦合复杂,下一个会话来接手时会一脸懵。就像一个人同时炒六道菜,每道菜都下了锅但没有一道出锅——全糊了。 - -Anthropic 的实验数据直接支持这一点:使用"小下一步"策略(等价于 WIP=1)的 agent,任务完成率比使用宽泛提示的 agent 高 37%。更有意思的是,agent 生成的代码行数和实际完成的功能数量呈弱负相关——写得越多,完成得越少。贪多嚼不烂,数据为证。 - -## WIP=1 工作流 - -```mermaid -flowchart LR - Queue["功能队列"] --> Pick["只选一个任务"] - Pick --> Active["仅允许一个 active"] - Active --> Verify["跑端到端验证"] - Verify -->|通过| Commit["提交并解锁下一个任务"] - Verify -->|失败| Active - Commit --> Queue -``` - -```mermaid -flowchart TB - Budget["可用推理预算 = C"] --> One["WIP = 1
    每个任务拿到 C / 1"] - Budget --> Many["WIP = 5
    每个任务只有 C / 5"] - - One --> Finish["一个功能进入 passing"] - Many --> Partial["五个功能都只做了一半"] - Partial --> VCR["已验证完成率低
    下一会话返工高"] -``` - -## 核心概念 - -- **过度延伸(Overreach)**:agent 在一次会话中激活的任务数量超过最优值。它不是主观判断,而是可量化的——同时做 5 个功能但 0 个跑通,就是 overreach。 -- **不足完成(Under-finish)**:已启动的任务中,通过端到端验证的比例低于阈值。写了代码但没跑通测试,就是 under-finish。 -- **WIP 限制(Work-in-Progress Limit)**:来自 Kanban 方法论。核心思想:限制同时在进行的任务数量。对于 agent,WIP=1 是最安全的默认值——做完一个再做下一个。就像自助餐厅不要一次拿太多盘子,吃完一盘再去拿下一盘。 -- **完成证据(Completion Evidence)**:一个任务从"进行中"变成"已完成"必须满足的可验证条件。没有这个,agent 会用"代码看起来没问题"代替"行为通过测试"。 -- **范围表面(Scope Surface)**:一个 DAG 结构,每个节点是一个工作单元,边是依赖关系。状态只有四种:未开始、进行中、阻塞、已通过。 -- **完成压力(Completion Pressure)**:harness 通过 WIP 限制和完成证据要求共同产生的约束力,迫使 agent 先完成当前任务再开始新任务。 - -## Overreach 和 Under-finish 是一对难兄难弟 - -这两个问题不是独立的,而是互相加剧。overreach 导致注意力分散,注意力分散导致 under-finish,under-finish 留下的半成品代码又增加了系统复杂度,进一步导致下一个任务的 overreach。恶性循环。 - -用 Kanban 的语言说:Little 法则告诉我们 L = lambda * W。如果在制品数量 L 过大(同时做太多事),每个任务的前置时间 W 必然增加。对于 agent 来说,这意味着每个功能从开始到验证通过的时间被拉长,失败概率被放大。 - -这在人类世界也是老问题了——Steve McConnell 在《Rapid Development》中记录,范围蔓延是项目失败的首要原因。但人类至少有"我已经做得够多了"的直觉,agent 完全没有。生成下一个想法的成本对模型来说太低了——写一行"顺便把这个也改了"几乎不消耗额外 token,但每个额外的修改都会稀释 agent 的注意力。就像在自助餐厅里,每多拿一个盘子的边际成本几乎为零,但你的胃只有那么大。 - -## 怎么做才对 - -### 1. 强制 WIP=1 - -这是最直接有效的方法。在你的 harness 里,明确告诉 agent:**任何时刻只允许一个任务处于"进行中"状态。** 在 Claude Code 的 CLAUDE.md 或 Codex 的 AGENTS.md 里写: - -``` -## 工作规则 -- 每次只做一个功能点 -- 当前功能点端到端验证通过后,才能开始下一个 -- 不要在实现功能 A 时"顺便"重构功能 B -``` - -就像吃自助餐——一次只拿一盘,吃完再去拿。 - -### 2. 给每个任务定义显式的完成证据 - -完成不是"代码写完了",而是"行为验证通过了"。在你的功能列表里,每个条目都要有验证命令: - -``` -F01: 用户注册 - 验证: curl -X POST /api/register -d '{"email":"test@example.com","password":"123456"}' | jq .status == 201 - 状态: passing -``` - -### 3. 把范围表面外部化 - -用一个机器可读的文件(JSON 或 Markdown)记录所有任务的状态。任何新会话都能直接读这个文件,知道:哪个任务在做?什么行为算完成?已经通过了什么验证? - -### 4. 监控验证完成率 - -harness 应该持续跟踪 VCR(Verified Completion Rate)= 已通过验证的任务数 / 已启动的任务数。VCR < 1.0 时,阻止新任务启动。 - -## 实际案例 - -一个 8 个功能点的 REST API 项目,两种策略对比: - -**自助餐模式(无约束)**:agent 在第一个会话同时启动 5 个功能。产出约 800 行代码,涉及 12 个文件。端到端测试通过率只有 20%——只有用户注册跑通了。其余 4 个功能:数据库 schema 建了但缺验证逻辑,路由定义了但返回格式错误。就像一个人同时炒六道菜,只有一道勉强能吃。到第 3 个会话结束,8 个功能只完成 3 个。 - -**单盘模式(WIP=1)**:agent 在第一个会话只做用户注册。产出约 200 行代码,涉及 4 个文件。端到端测试 100% 通过。提交干净的、已验证的实现。到第 4 个会话结束,8 个功能完成 7 个(第 8 个因外部依赖被阻塞)。 - -结果:总代码量更少(800 行 vs 1200 行),但有效代码更多。完成率 87.5% vs 37.5%。一口一口吃,反而吃得最多。 - -## 关键要点 - -- **WIP=1 是 agent harness 的默认安全设置**——做完一个再做下一个,不要试图并行。一口吃不成胖子。 -- **完成证据必须是可执行的**——"代码看起来没问题"不算完成,"curl 返回 201"才算。 -- **范围表面必须外部化为文件**——不能只在对话里说,必须在仓库里有机器可读的记录。 -- **overreach 和 under-finish 是共生问题**——解决一个就解决了另一个。 -- **"少做但做完"永远优于"多做但做半"**——agent 代码行数和功能完成率呈负相关。质量永远比数量重要。 - -## 延伸阅读 - -- [Effective harnesses for long-running agents - Anthropic](https://www.anthropic.com/engineering/effective-harnesses-for-long-running-agents) — Anthropic 工程博客,详细论述了"小下一步"策略 -- [Harness Engineering - OpenAI](https://openai.com/index/harness-engineering/) — OpenAI 对 harness 工程的完整论述 -- [Kanban: Successful Evolutionary Change - David Anderson](https://www.goodreads.com/book/show/1070822.Kanban) — WIP 限制的经典来源 -- [Rapid Development - Steve McConnell](https://www.goodreads.com/book/show/125171.Rapid_Development) — 范围蔓延作为项目失败首要原因的实证数据 - -## 练习 - -1. **任务原子化练习**:选一个宽泛需求(如"实现用户管理系统"),把它拆成至少 5 个原子工作单元。每个单元写清楚:(a) 单一行为描述,(b) 可执行的验证命令,(c) 依赖关系。检查是否满足 WIP=1 的约束。 - -2. **对比实验**:在同一个项目上跑两次——一次不给约束,一次强制 WIP=1。比较:验证完成率、总代码行数、有效代码比例。 - -3. **完成证据审计**:回顾一个最近的 agent 运行结果,把每个代码变更分类为"已完成行为"、"未完成行为"或"脚手架"。给每个未完成行为补充缺失的验证命令。 diff --git a/courses/learn-harness-engineering/docs/zh/lectures/lecture-08-why-feature-lists-are-harness-primitives/code/feature-list-validator.ts b/courses/learn-harness-engineering/docs/zh/lectures/lecture-08-why-feature-lists-are-harness-primitives/code/feature-list-validator.ts deleted file mode 100644 index 52d6a2b9..00000000 --- a/courses/learn-harness-engineering/docs/zh/lectures/lecture-08-why-feature-lists-are-harness-primitives/code/feature-list-validator.ts +++ /dev/null @@ -1,218 +0,0 @@ -/** - * feature-list-validator.ts - * - * Reads a feature_list.json, validates its schema, and checks for features - * marked "pass" without verification evidence. Outputs a structured report. - * Can run against any project directory that has a feature_list.json. - * - * Usage: - * npx tsx docs/lectures/lecture-08.../code/feature-list-validator.ts [path-to-dir] - * (defaults to the directory containing this script) - * - * Run: npx tsx docs/lectures/lecture-08-why-feature-lists-are-harness-primitives/code/feature-list-validator.ts - */ - -import * as fs from "node:fs"; -import * as path from "node:path"; - -// --------------------------------------------------------------------------- -// Types -// --------------------------------------------------------------------------- - -interface FeatureEntry { - id?: string; - category?: string; - description?: string; - verification?: string[]; - passes?: boolean; - // Allow unknown fields for flexibility - [key: string]: unknown; -} - -interface ValidationResult { - featureId: string; - schemaValid: boolean; - schemaErrors: string[]; - hasVerification: boolean; - markedPassWithoutEvidence: boolean; - passes: boolean; - verificationCount: number; -} - -// --------------------------------------------------------------------------- -// Schema validation -// --------------------------------------------------------------------------- - -function validateSchema(entry: FeatureEntry, index: number): string[] { - const errors: string[] = []; - const label = entry.id ?? `entry ${index + 1}`; - - if (!entry.id || typeof entry.id !== "string") { - errors.push(`[${label}] Missing or invalid 'id' field`); - } - if (!entry.category || typeof entry.category !== "string") { - errors.push(`[${label}] Missing or invalid 'category' field`); - } - if (!entry.description || typeof entry.description !== "string") { - errors.push(`[${label}] Missing or invalid 'description' field`); - } - if (entry.verification !== undefined && !Array.isArray(entry.verification)) { - errors.push(`[${label}] 'verification' must be an array if present`); - } - if (entry.passes !== undefined && typeof entry.passes !== "boolean") { - errors.push(`[${label}] 'passes' must be a boolean if present`); - } - - return errors; -} - -// --------------------------------------------------------------------------- -// Evidence validation -// --------------------------------------------------------------------------- - -function checkEvidence(entry: FeatureEntry): { - hasVerification: boolean; - markedPassWithoutEvidence: boolean; -} { - const hasVerification = Array.isArray(entry.verification) && entry.verification.length > 0; - const markedPassWithoutEvidence = entry.passes === true && !hasVerification; - - return { hasVerification, markedPassWithoutEvidence }; -} - -// --------------------------------------------------------------------------- -// Process feature list -// --------------------------------------------------------------------------- - -function processFeatureList(entries: FeatureEntry[]): ValidationResult[] { - return entries.map((entry, index) => { - const schemaErrors = validateSchema(entry, index); - const evidence = checkEvidence(entry); - - return { - featureId: (entry.id as string) ?? `entry-${index + 1}`, - schemaValid: schemaErrors.length === 0, - schemaErrors, - hasVerification: evidence.hasVerification, - markedPassWithoutEvidence: evidence.markedPassWithoutEvidence, - passes: entry.passes === true, - verificationCount: Array.isArray(entry.verification) ? entry.verification.length : 0, - }; - }); -} - -// --------------------------------------------------------------------------- -// Reporting -// --------------------------------------------------------------------------- - -function pad(s: string, len: number): string { - return s.length >= len ? s : s + " ".repeat(len - s.length); -} - -function run(): void { - // Resolve target directory - const scriptDir = path.dirname(new URL(import.meta.url).pathname); - const targetDir = process.argv[2] - ? path.resolve(process.argv[2]) - : scriptDir; - - const filePath = path.join(targetDir, "feature_list.json"); - - console.log("\n" + "=".repeat(90)); - console.log(" FEATURE LIST VALIDATOR"); - console.log("=".repeat(90)); - console.log(` Reading: ${filePath}\n`); - - if (!fs.existsSync(filePath)) { - console.error(` ERROR: feature_list.json not found at ${filePath}`); - console.error(" Usage: npx tsx feature-list-validator.ts [path-to-directory-containing-feature_list.json]\n"); - process.exit(1); - } - - let entries: FeatureEntry[]; - try { - const raw = fs.readFileSync(filePath, "utf-8"); - entries = JSON.parse(raw); - } catch (err) { - console.error(` ERROR: Could not parse feature_list.json: ${err}`); - process.exit(1); - } - - if (!Array.isArray(entries)) { - console.error(" ERROR: feature_list.json must contain a JSON array at the top level."); - process.exit(1); - } - - // For demo purposes, also validate an extended test set - const demoEntries: FeatureEntry[] = [ - ...entries, - { - id: "qna-002", - category: "import", - description: "User can import a PDF document.", - verification: ["Upload a PDF file", "Verify it appears in the document list"], - passes: true, - }, - { - id: "qna-003", - category: "grounded_qa", - description: "System hallucination rate is below 5%.", - verification: [], // Empty -- no evidence - passes: true, // Marked as pass WITHOUT evidence - }, - { - id: "missing-fields", - // Missing 'category' and 'description' - passes: true, - } as FeatureEntry, - ]; - - const results = processFeatureList(demoEntries); - - // Print report - const header = `| ${pad("Feature ID", 14)}| ${pad("Schema", 8)}| ${pad("Passes", 7)}| ${pad("Verifications", 14)}| ${pad("Evidence?", 12)}| Notes`; - const sep = `|${"-".repeat(16)}|${"-".repeat(10)}|${"-".repeat(9)}|${"-".repeat(16)}|${"-".repeat(14)}|${"-".repeat(30)}`; - console.log(header); - console.log(sep); - - for (const r of results) { - const schemaLabel = r.schemaValid ? "OK" : "INVALID"; - const passesLabel = r.passes ? "PASS" : "FAIL"; - const evidenceLabel = r.hasVerification ? "Present" : "MISSING"; - const notes = r.markedPassWithoutEvidence - ? "FLAGGED: passes without evidence!" - : r.schemaErrors.length > 0 - ? r.schemaErrors[0] - : ""; - - const marker = r.markedPassWithoutEvidence ? ">>" : r.schemaValid ? " " : "!!"; - console.log( - `${marker}| ${pad(r.featureId, 14)}| ${pad(schemaLabel, 8)}| ${pad(passesLabel, 7)}| ${pad(String(r.verificationCount), 14)}| ${pad(evidenceLabel, 12)}| ${notes}` - ); - } - - // Summary - const total = results.length; - const schemaOk = results.filter((r) => r.schemaValid).length; - const passing = results.filter((r) => r.passes).length; - const flagged = results.filter((r) => r.markedPassWithoutEvidence).length; - const withEvidence = results.filter((r) => r.hasVerification).length; - - console.log("\n" + "-".repeat(90)); - console.log(" SUMMARY"); - console.log("-".repeat(90)); - console.log(` Total features: ${total}`); - console.log(` Schema valid: ${schemaOk}/${total}`); - console.log(` Marked as passing: ${passing}/${total}`); - console.log(` With verification evidence: ${withEvidence}/${total}`); - console.log(` Flagged (pass without evidence): ${flagged}`); - - if (flagged > 0) { - console.log(`\n WARNING: ${flagged} feature(s) marked as "pass" without any verification evidence.`); - console.log(" These features need verification before they can be trusted.\n"); - } else { - console.log("\n All passing features have verification evidence. Feature list is healthy.\n"); - } -} - -run(); diff --git a/courses/learn-harness-engineering/docs/zh/lectures/lecture-08-why-feature-lists-are-harness-primitives/code/feature_list.json b/courses/learn-harness-engineering/docs/zh/lectures/lecture-08-why-feature-lists-are-harness-primitives/code/feature_list.json deleted file mode 100644 index 8b973e31..00000000 --- a/courses/learn-harness-engineering/docs/zh/lectures/lecture-08-why-feature-lists-are-harness-primitives/code/feature_list.json +++ /dev/null @@ -1,15 +0,0 @@ -[ - { - "id": "qna-001", - "category": "grounded_qa", - "description": "User can ask a question about an imported document and receive an answer with visible citations.", - "verification": [ - "Import a markdown document", - "Open the Q&A panel", - "Ask a question about known content", - "Verify the answer is returned", - "Verify one or more citations are displayed" - ], - "passes": false - } -] diff --git a/courses/learn-harness-engineering/docs/zh/lectures/lecture-08-why-feature-lists-are-harness-primitives/code/index.md b/courses/learn-harness-engineering/docs/zh/lectures/lecture-08-why-feature-lists-are-harness-primitives/code/index.md deleted file mode 100644 index 19a2e671..00000000 --- a/courses/learn-harness-engineering/docs/zh/lectures/lecture-08-why-feature-lists-are-harness-primitives/code/index.md +++ /dev/null @@ -1,8 +0,0 @@ -# 第 08 讲代码 - -使用此文件夹存放以下方面的示例: - -- 通过状态门控 -- 端到端验证 -- 弱与强的完成标准对比 -- 评估循环示例 diff --git a/courses/learn-harness-engineering/docs/zh/lectures/lecture-08-why-feature-lists-are-harness-primitives/code/pass-gate-policy.md b/courses/learn-harness-engineering/docs/zh/lectures/lecture-08-why-feature-lists-are-harness-primitives/code/pass-gate-policy.md deleted file mode 100644 index 54851a3b..00000000 --- a/courses/learn-harness-engineering/docs/zh/lectures/lecture-08-why-feature-lists-are-harness-primitives/code/pass-gate-policy.md +++ /dev/null @@ -1,8 +0,0 @@ -# 通过门控策略 - -一个功能只有在满足以下条件时,才能从 `passes: false` 变为 `passes: true`: - -- 预期的工作流已被执行 -- 成功的证据已被记录 -- 被测试的路径中不存在阻塞性错误 -- 实现不会使应用处于损坏或模糊的状态 diff --git a/courses/learn-harness-engineering/docs/zh/lectures/lecture-08-why-feature-lists-are-harness-primitives/index.md b/courses/learn-harness-engineering/docs/zh/lectures/lecture-08-why-feature-lists-are-harness-primitives/index.md deleted file mode 100644 index c0397474..00000000 --- a/courses/learn-harness-engineering/docs/zh/lectures/lecture-08-why-feature-lists-are-harness-primitives/index.md +++ /dev/null @@ -1,142 +0,0 @@ -[English Version →](../../../en/lectures/lecture-08-why-feature-lists-are-harness-primitives/) - -> 本篇代码示例:[code/](https://github.com/walkinglabs/learn-harness-engineering/blob/main/docs/zh/lectures/lecture-08-why-feature-lists-are-harness-primitives/code/) -> 实战练习:[Project 04. 用运行反馈修正 agent 的行为](./../../projects/project-04-incremental-indexing/index.md) - -# 第八讲. 用功能清单约束 agent 该做什么 - -你让 agent 做一个电商网站,跑完之后它告诉你"做完了"。你打开代码一看——用户认证有了,但购物车的结算按钮点了没反应,支付流程根本没接上。问题是:你从来没告诉它"做完"的标准是什么,所以它用自己的标准——"代码写了不少,看起来挺完整"。 - -功能清单(feature list)在很多人眼里就是个备忘录——写下来怕忘了,写完扔在一边。但在 harness 的世界里,功能清单不是给人看的备忘录,而是整个 harness 的脊梁骨。调度器靠它选任务,验证器靠它判完成,交接器靠它生成报告。脊梁骨断了,全身都瘫。 - -Anthropic 和 OpenAI 都强调:**工件必须外部化**。功能状态必须是仓库里机器可读的文件,不能是对话里的非结构化描述。 - -## Agent 不知道"做完"是什么意思 - -Claude Code 和 Codex 都不会自动知道你心目中的"做完"是什么意思。你说"加一个购物车功能",模型的理解可能是"写一个 Cart 组件和 addToCart 方法"。而你的意思是"用户能从浏览商品到下单支付完整走通"。 - -这个理解鸿沟在没有功能清单的情况下会持续存在。agent 用自己的隐式标准判断完成——通常是"代码没有明显的语法错误"。而你需要的是端到端的行为验证。就像你让朋友帮你买菜,说"买点水果",他拎了一袋柠檬回来——他要的水果,你要的水果,不是一个水果。 - -看看这种常见的进度记录: - -``` -做了用户认证、购物车基本完成了、还需要做支付 -``` - -新的 agent 会话看到这个记录,能回答以下问题吗?"基本完成"意味着什么?购物车通过了哪些测试?支付的阻塞条件是什么?答案都是"不知道"。就像你看病时跟医生说"我肚子疼,最近还行",医生能开出什么药来? - -结果是:新会话花 20 分钟推断项目状态,最终可能重复实现已完成的功能。Anthropic 的工程实践数据表明,好的进度记录可以减少 60-80% 的会话启动诊断时间。 - -## 功能状态机 - -```mermaid -flowchart LR - Feature["一行功能项"] --> Behavior["行为
    例如:POST /cart/items 返回 201"] - Feature --> Check["验证命令
    具体要跑什么检查"] - Feature --> State["状态
    not_started / active / blocked / passing"] - - Behavior --> Complete["三列都齐了
    这行功能项才能用"] - Check --> Complete - State --> Complete -``` - -```mermaid -flowchart LR - List["feature_list.json / features.md"] --> Scheduler["选下一个 not_started"] - Scheduler --> Agent["agent 只做这一项"] - Agent --> Verifier["跑这一项自己的验证命令"] - Verifier -->|通过| Passing["写成 passing
    并补上验证证据"] - Verifier -->|失败| Active["继续保持 active"] - Verifier -->|依赖问题| Blocked["标成 blocked"] - Passing --> Handoff["更新交接说明
    和当前进度"] - Active --> Agent -``` - -## 核心概念 - -- **功能清单是 harness 原语**:它不是"可选的规划工具",而是其他所有 harness 组件依赖的基础数据结构。就像数据库里的表结构——你不能说"要不我们省掉主键吧",省掉了整个系统就散架了。 -- **三元组结构**:每个功能项是 `(行为描述, 验证命令, 当前状态)` 的三元组。缺了任何一项,这个功能项就不完整。行为描述告诉 agent 做什么,验证命令告诉它怎么算做完,状态告诉它现在到哪了。 -- **状态机模型**:每个功能项有四种状态——`not_started`、`active`、`blocked`、`passing`。状态转移由 harness 控制,不是 agent 想改就能改。 -- **通过状态门控**:功能从 `active` 变成 `passing` 的唯一方式是验证命令执行成功。这是不可逆的——`passing` 了就不能退回去。就像考试及格了就是及格了,不能事后改成分数。 -- **单一权威来源**:项目里关于"该做什么"的所有信息,必须从一个功能清单派生。不能出现功能清单和对话记录矛盾的情况。 -- **反向压力**:还没通过的功能项数量就是 harness 对 agent 施加的压力。压力归零 = 项目完成。 - -## 为什么功能清单必须是"原语" - -文档是给人看的,原语是给系统用的。文档可以被忽略,原语不能被绕过。 - -类比数据库的触发器约束和应用层的检查逻辑:前者由数据库引擎强制执行,任何 SQL 都无法跳过;后者依赖于应用代码的正确性,可能被意外绕过。功能清单作为 harness 原语,就是数据库级别的约束——agent 不能绕过它。 - -具体来说,功能清单服务四个 harness 组件: - -1. **调度器**:读状态,选下一个 `not_started` 的功能。就像工厂的排产系统——看完订单才知道下一步做什么。 -2. **验证器**:执行验证命令,判断是否允许状态转移。就像质检——不是你说合格就合格,得通过检验。 -3. **交接报告器**:从功能清单自动生成会话交接摘要。就像换班时自动生成的交接表——不用手写,系统自己出。 -4. **进度追踪器**:统计各状态分布,提供项目健康度指标。就像仪表盘——一眼看出项目走到哪了。 - -## 怎么做 - -### 1. 定义一个最小化的功能清单格式 - -不需要复杂的系统,一个结构化的 Markdown 或 JSON 文件就够了。关键是每个条目必须有三元组: - -```json -{ - "id": "F03", - "behavior": "POST /cart/items with {product_id, quantity} returns 201", - "verification": "curl -X POST http://localhost:3000/api/cart/items -H 'Content-Type: application/json' -d '{\"product_id\":1,\"quantity\":2}' | jq .status == 201", - "state": "passing", - "evidence": "commit abc123, test output log" -} -``` - -### 2. 让 harness 控制状态转移 - -agent 不能直接把状态改成 `passing`。它只能提交验证请求,harness 执行验证命令,根据结果决定是否允许状态转移。这就是"通过状态门控"——不是你说考过了就考过了,得看成绩单。 - -### 3. 在 CLAUDE.md 里写清楚规则 - -``` -## 功能清单规则 -- 功能清单文件: /docs/features.md -- 每次只激活一个功能项 -- 功能项验证命令必须通过才能标为 passing -- 不要修改功能清单的状态——由验证脚本自动更新 -``` - -### 4. 粒度校准 - -每个功能项应该是"一次会话能完成"的范围。太粗了做不完,太细了管理开销大。"用户可以添加商品到购物车"是一个好粒度,"实现购物车"太粗了,"创建 Cart 模型的 name 字段"太细了。就像切牛排——不能整块啃,也不能切成肉沫。 - -## 实际案例 - -一个电商平台的开发任务,10 个功能项。对比两种追踪方式: - -**备忘录模式**:agent 用非结构化笔记记录进度。3 个会话后,笔记变成了"做了用户认证和商品列表、购物车基本完成但还有 bug、支付没开始"。新会话需要 20 分钟推断状态,最终重复实现了已完成的功能。就像你的购物清单上写着"牛奶、面包、还有那个什么来着"——到超市了你还是不知道要买什么。 - -**脊梁骨模式**:每个功能项有明确的状态和验证命令。新会话读取功能清单,3 分钟内知道:F01-F05 是 `passing`,F06 是 `active`(正在做),F07-F10 是 `not_started`。直接从 F06 继续,零重复。 - -定量结果:使用结构化功能清单的项目,功能完成率比自由形式高 45%,零重复实现。 - -## 关键要点 - -- **功能清单是 harness 的脊梁骨**,不是给人看的备忘录。调度器、验证器、交接器都依赖它。 -- **每个功能项必须有三元组**:行为描述 + 验证命令 + 当前状态。缺一项就不完整——就像三条腿的凳子少一条腿。 -- **状态转移由 harness 控制**,agent 不能自己改状态。通过验证 = 唯一的升级路径。 -- **功能清单是项目的单一权威来源**——任何关于"该做什么"的信息都从这里派生。 -- **粒度控制在"一次会话能完成"的范围**。太粗做不完,太细管不过来。 - -## 延伸阅读 - -- [Building Effective Agents - Anthropic](https://www.anthropic.com/research/building-effective-agents) — 明确指出功能列表是控制 agent 执行范围的"核心数据结构" -- [Harness Engineering - OpenAI](https://openai.com/index/harness-engineering/) — 强调"将工件外部化"的原则 -- [Design by Contract - Bertrand Meyer](https://www.goodreads.com/book/show/130439.Object_Oriented_Software_Construction) — 契约式设计原则,功能列表的理论基础 -- [How Google Tests Software](https://www.goodreads.com/book/show/13563030-how-google-tests-software) — 测试金字塔和行为规格的工程实践 - -## 练习 - -1. **功能清单设计**:定义一个最小化的功能清单 JSON schema。包含:id、行为描述、验证命令、当前状态、证据引用。用它描述一个包含 5 个功能的真实项目。 - -2. **验证严格性对比**:选 3 个功能,分别设计"宽松"验证(如"代码无语法错误")和"严格"验证(如"端到端测试通过")。对比两种验证下的假阳性率。 - -3. **单一来源原则审查**:审查一个已有的 agent 项目,检查是否存在与功能清单矛盾的范围信息(对话里的隐式需求、代码里的 TODO 注释等)。设计一个方案,把所有信息统一到功能清单中。 diff --git a/courses/learn-harness-engineering/docs/zh/lectures/lecture-09-why-agents-declare-victory-too-early/code/clean-state-checklist.md b/courses/learn-harness-engineering/docs/zh/lectures/lecture-09-why-agents-declare-victory-too-early/code/clean-state-checklist.md deleted file mode 100644 index 58c022f1..00000000 --- a/courses/learn-harness-engineering/docs/zh/lectures/lecture-09-why-agents-declare-victory-too-early/code/clean-state-checklist.md +++ /dev/null @@ -1,6 +0,0 @@ -# 干净状态检查清单 - -- 应用无需手动修复即可启动 -- 当前进度已记录 -- 没有未记录的半完成导入或索引步骤 -- 下一个 Agent 可以立即运行标准的启动和验证路径 diff --git a/courses/learn-harness-engineering/docs/zh/lectures/lecture-09-why-agents-declare-victory-too-early/code/index.md b/courses/learn-harness-engineering/docs/zh/lectures/lecture-09-why-agents-declare-victory-too-early/code/index.md deleted file mode 100644 index 47651091..00000000 --- a/courses/learn-harness-engineering/docs/zh/lectures/lecture-09-why-agents-declare-victory-too-early/code/index.md +++ /dev/null @@ -1,8 +0,0 @@ -# 第 09 讲代码 - -使用此文件夹存放以下方面的示例: - -- 日志作为反馈 -- 运行时状态可见性 -- 干净状态检查 -- 恢复示例 diff --git a/courses/learn-harness-engineering/docs/zh/lectures/lecture-09-why-agents-declare-victory-too-early/code/victory-detector.ts b/courses/learn-harness-engineering/docs/zh/lectures/lecture-09-why-agents-declare-victory-too-early/code/victory-detector.ts deleted file mode 100644 index fad1a3ba..00000000 --- a/courses/learn-harness-engineering/docs/zh/lectures/lecture-09-why-agents-declare-victory-too-early/code/victory-detector.ts +++ /dev/null @@ -1,241 +0,0 @@ -/** - * victory-detector.ts - * - * Simulates an agent that claims task completion, then checks the claimed - * state against actual verification. Outputs: claimed vs actual, highlighting - * gaps between what the agent said and what's really true. - * - * Run: npx tsx docs/lectures/lecture-09-why-agents-declare-victory-too-early/code/victory-detector.ts - */ - -// --------------------------------------------------------------------------- -// Types -// --------------------------------------------------------------------------- - -interface Task { - name: string; - claimedComplete: boolean; - checks: VerificationCheck[]; -} - -interface VerificationCheck { - description: string; - claimed: boolean; // What the agent says - actual: boolean; // What is actually true - severity: "critical" | "warning"; -} - -// --------------------------------------------------------------------------- -// Simulated tasks with claimed vs actual states -// --------------------------------------------------------------------------- - -const tasks: Task[] = [ - { - name: "Add search endpoint", - claimedComplete: true, - checks: [ - { - description: "Route handler exists", - claimed: true, - actual: true, - severity: "critical", - }, - { - description: "Authentication middleware applied", - claimed: true, - actual: false, // Agent forgot auth - severity: "critical", - }, - { - description: "Input validation added", - claimed: true, - actual: true, - severity: "critical", - }, - { - description: "Unit tests written", - claimed: true, - actual: false, // Agent skipped tests - severity: "critical", - }, - { - description: "Integration tests pass", - claimed: true, - actual: false, // No tests to run - severity: "critical", - }, - { - description: "Documentation updated", - claimed: true, - actual: false, // Agent skipped docs - severity: "warning", - }, - ], - }, - { - name: "Fix pagination bug", - claimedComplete: true, - checks: [ - { - description: "Bug reproduction case confirmed", - claimed: true, - actual: true, - severity: "critical", - }, - { - description: "Root cause identified", - claimed: true, - actual: true, - severity: "critical", - }, - { - description: "Fix applied", - claimed: true, - actual: true, - severity: "critical", - }, - { - description: "Edge cases tested", - claimed: true, - actual: false, // Only happy path tested - severity: "warning", - }, - { - description: "Regression tests added", - claimed: true, - actual: false, - severity: "critical", - }, - ], - }, - { - name: "Refactor auth module", - claimedComplete: true, - checks: [ - { - description: "Old code removed", - claimed: true, - actual: false, // Old code still present - severity: "warning", - }, - { - description: "All existing tests still pass", - claimed: true, - actual: false, // Two tests broke - severity: "critical", - }, - { - description: "New auth flow works", - claimed: true, - actual: true, - severity: "critical", - }, - { - description: "Migration guide written", - claimed: true, - actual: false, - severity: "warning", - }, - ], - }, -]; - -// --------------------------------------------------------------------------- -// Verification -// --------------------------------------------------------------------------- - -interface TaskVerification { - taskName: string; - claimedComplete: boolean; - actuallyComplete: boolean; - totalChecks: number; - claimedPassing: number; - actualPassing: number; - gaps: { description: string; severity: string }[]; -} - -function verifyTask(task: Task): TaskVerification { - const claimedPassing = task.checks.filter((c) => c.claimed).length; - const actualPassing = task.checks.filter((c) => c.actual).length; - const gaps = task.checks.filter((c) => c.claimed && !c.actual); - - return { - taskName: task.name, - claimedComplete: task.claimedComplete, - actuallyComplete: gaps.length === 0, - totalChecks: task.checks.length, - claimedPassing, - actualPassing, - gaps: gaps.map((g) => ({ description: g.description, severity: g.severity })), - }; -} - -// --------------------------------------------------------------------------- -// Reporting -// --------------------------------------------------------------------------- - -function pad(s: string, len: number): string { - return s.length >= len ? s : s + " ".repeat(len - s.length); -} - -function run(): void { - console.log("\n" + "=".repeat(90)); - console.log(" VICTORY DETECTOR -- Claimed vs Actual Verification"); - console.log("=".repeat(90)); - - const verifications = tasks.map(verifyTask); - - for (const v of verifications) { - console.log("\n Task: " + v.taskName); - console.log(" " + "-".repeat(70)); - console.log(` Agent claimed: ${v.claimedComplete ? "COMPLETE" : "NOT COMPLETE"}`); - console.log(` Actually is: ${v.actuallyComplete ? "COMPLETE" : "INCOMPLETE"}`); - - if (!v.actuallyComplete) { - console.log(` MISMATCH: Agent declared victory but ${v.gaps.length} check(s) failed!`); - } - - // Detailed checks for this task - const task = tasks.find((t) => t.name === v.taskName)!; - console.log("\n Check detail:"); - const header = ` | ${pad("Check", 40)}| ${pad("Claimed", 9)}| ${pad("Actual", 9)}| ${pad("Gap?", 6)}|`; - const sep = ` |${"-".repeat(42)}|${"-".repeat(11)}|${"-".repeat(11)}|${"-".repeat(8)}|`; - console.log(header); - console.log(sep); - - for (const check of task.checks) { - const claimedLabel = check.claimed ? "PASS" : "FAIL"; - const actualLabel = check.actual ? "PASS" : "FAIL"; - const gapLabel = check.claimed && !check.actual ? "GAP" : ""; - const marker = gapLabel ? ">>" : " "; - console.log(`${marker}| ${pad(check.description, 40)}| ${pad(claimedLabel, 9)}| ${pad(actualLabel, 9)}| ${pad(gapLabel, 6)}|`); - } - } - - // Overall summary - console.log("\n" + "=".repeat(90)); - console.log(" OVERALL SUMMARY"); - console.log("=".repeat(90) + "\n"); - - const sHeader = `| ${pad("Task", 28)}| ${pad("Claimed", 10)}| ${pad("Actual", 10)}| ${pad("Gaps", 6)}| ${pad("Accuracy", 10)}|`; - const sSep = `|${"-".repeat(30)}|${"-".repeat(12)}|${"-".repeat(12)}|${"-".repeat(8)}|${"-".repeat(12)}|`; - console.log(sHeader); - console.log(sSep); - - for (const v of verifications) { - const accuracy = Math.round((v.actualPassing / v.totalChecks) * 100); - const cLabel = v.claimedComplete ? "Done" : "Pending"; - const aLabel = v.actuallyComplete ? "Done" : "Incomplete"; - console.log(`| ${pad(v.taskName, 28)}| ${pad(cLabel, 10)}| ${pad(aLabel, 10)}| ${pad(String(v.gaps.length), 6)}| ${pad(accuracy + "%", 10)}|`); - } - - const totalGaps = verifications.reduce((s, v) => s + v.gaps.length, 0); - const falseVictories = verifications.filter((v) => v.claimedComplete && !v.actuallyComplete).length; - - console.log("\n False victories: " + falseVictories + " of " + verifications.length + " tasks"); - console.log(" Total undetected gaps: " + totalGaps); - console.log("\n The agent declared 'done' on every task, but verification reveals " + totalGaps + " unmet criteria."); - console.log(" Without explicit verification, premature victory declarations go unchecked.\n"); -} - -run(); diff --git a/courses/learn-harness-engineering/docs/zh/lectures/lecture-09-why-agents-declare-victory-too-early/index.md b/courses/learn-harness-engineering/docs/zh/lectures/lecture-09-why-agents-declare-victory-too-early/index.md deleted file mode 100644 index d9ab4db5..00000000 --- a/courses/learn-harness-engineering/docs/zh/lectures/lecture-09-why-agents-declare-victory-too-early/index.md +++ /dev/null @@ -1,145 +0,0 @@ -[English Version →](../../../en/lectures/lecture-09-why-agents-declare-victory-too-early/) - -> 本篇代码示例:[code/](https://github.com/walkinglabs/learn-harness-engineering/blob/main/docs/zh/lectures/lecture-09-why-agents-declare-victory-too-early/code/) -> 实战练习:[Project 05. 让 agent 自己检查自己做的对不对](./../../projects/project-05-grounded-qa-verification/index.md) - -# 第九讲. 防止 agent 提前宣告完成 - -你让 agent 实现"密码重置"功能。它改了数据库 schema、写了 API 端点、加了邮件模板,跑了单元测试(全部通过),然后自信地告诉你"做完了"。你实际一跑——密码重置链接发不出去(邮件服务配置缺失)、数据库迁移半途失败(schema 不一致)、端到端流程根本没走过一遍。 - -这种感觉你一定不陌生——考试时把卷子写得满满当当,信心满满地第一个交卷,结果成绩出来不及格。卷子写满了,不代表做对了。 - -这不是偶然事件。Guo 等人 2017 年在 ICML 上的经典论文证明:**现代神经网络系统性地过度自信**——模型自报的置信度显著高于实际准确率。AI 编码 agent 也一样:它"觉得"做完了,但实际上差得远。你的 harness 必须用外部化的、基于执行的验证来替代 agent 的"感觉"。 - -## 滑坡效应 - -过早完成声明几乎总是一样的套路:代码看着还行——语法正确、逻辑似乎合理,静态检查没有明显错误。但 harness 没有强制要求全面执行验证,agent 跳过了实际运行或只跑了部分测试。跑了单元测试但跳过集成测试,跑了测试但没检查覆盖率。最后"代码看起来没问题"就被当作了"功能已完成"的证据。交卷了。 - -每一步都在丢失信息。从任务规范到代码实现到运行时行为,每次转换都可能引入偏差,而每次跳过的验证都加剧了信息不对称。 - -## 三层终止检查 - -```mermaid -flowchart LR - Claim["agent 说:做完了"] --> L1["先跑
    lint / typecheck"] - L1 --> L2["再跑
    测试和启动检查"] - L2 --> L3["最后跑
    完整用户流程"] - L3 --> Done["三层都过才算完成"] -``` - -```mermaid -flowchart LR - A["代码写完了
    单测也绿了"] --> B["但应用没真正启动
    完整流程也没跑"] - B --> C["配置、数据库、外部服务问题
    还都藏着"] - C --> D["于是 agent 太早宣告完成"] -``` - -## 核心概念 - -- **过早完成声明**:agent 断言任务完成,但实际上存在未满足的正确性规范。核心问题:agent 依据代码层面的局部信心做判断,系统级正确性需要全局验证。 -- **置信度校准偏差**:agent 自报的完成信心与实际完成质量之间的系统性差距。对复杂多文件任务,这个偏差显著为正——agent 总是比实际做得更自信。就像学生考完试估分总是偏高。 -- **终止标准**:一组明确的、可执行的判定条件,定义在 harness 里。agent 必须满足所有条件才能声明完成。"完成"从主观判断变成了客观判定。 -- **验证-确认双闸门**:第一层验证检查"代码是否正确实现了指定行为";第二层确认检查"系统级行为是否满足端到端需求"。两层都通过才算完成。 -- **运行时反馈信号**:来自程序执行的日志、进程状态、健康检查。这是 harness 判定完成质量的客观基础。 -- **完成优先级约束**:先验证功能正确性,再处理性能,最后管风格。核心功能没验证通过之前,不许做重构。 - -## 单元测试通过 ≠ 任务完成 - -这是最常见的陷阱,也是最危险的一个。agent 写了代码,跑了单元测试,全部绿色,然后说"做完了"。但单元测试的设计哲学——隔离被测单元、模拟依赖——恰好使其无法检测跨组件问题: - -**接口不匹配**:渲染进程传给预加载脚本的文件路径是相对路径,但预加载脚本期望绝对路径。各自的单元测试都用了 mock,都通过了。只有端到端跑通时才发现问题。就像乐队里每个乐手各自练习都完美,但合在一起才发现定调不一样。 - -**状态传播错误**:数据库迁移改了表结构,但 ORM 的缓存层还持有旧结构的缓存条目。单元测试每次都是全新的 mock 环境,不会暴露这种跨层状态不一致。 - -**环境依赖性**:代码在测试环境(一切 mock)行为正确,在真实环境因配置差异、网络延迟、服务不可用而失败。就像在排练室唱得很好,上台演出时音响设备出了问题。 - -### "顺便重构"是完成判定的毒药 - -Claude Code 有一个常见行为模式:在核心功能还没验证通过时就开始重构代码、优化性能、改进风格。Knuth 说的"过早优化是万恶之源"在 agent 场景中有了新含义——重构会改变已完成验证和未完成验证之间的边界,可能破坏之前隐式正确的代码路径。就像你数学大题还没做完,就跑去把前面选择题的答案重新抄一遍格式——浪费时间不说,还可能抄错了。 - -### 自我评价的系统性偏差 - -Anthropic 在 2026 年的研究中发现了一个更深层的失败模式:**当 agent 被要求评估自己的工作时,它系统性地过度正面评价——即使人类观察者认为质量明显不达标。** 这就像让学生自己给自己判卷——对自己的答案总是特别宽容。 - -这个问题在主观任务(如设计审美)上尤其严重——"布局是否精致"是一个判断题,agent 可靠地偏向正面。即使在有可验证结果的任务上,agent 也会因为判断失误而影响表现。 - -解决方案不是让 agent "更客观"——同一个模型既生成又评估,内在地倾向对自己慷慨。**解决方案是把"干活的人"和"检查的人"分开。** 就像考试不能让学生自己批改自己的卷子——得有个独立的阅卷老师。 - -一个独立的评估 agent,经过专门调校为"挑剔"之后,比让生成 agent 自我评估有效得多。Anthropic 的实验数据: - -| 架构 | 运行时长 | 成本 | 核心功能是否可用 | -|------|---------|------|---------------| -| 单 agent 裸跑 | 20 分钟 | $9 | 否(游戏实体无法响应输入) | -| 三 agent(planner + generator + evaluator) | 6 小时 | $200 | 是(游戏可以正常游玩) | - -这是同一个模型(Opus 4.5),同一段提示词("做一个 2D 复古游戏编辑器")。区别只在 harness——从"裸奔"到"planner 扩展需求 → generator 逐功能实现 → evaluator 用 Playwright 实际点击测试"。 - -> 来源:[Anthropic: Harness design for long-running application development](https://www.anthropic.com/engineering/harness-design-long-running-apps) - -## 怎么防止提前交卷 - -### 1. 外部化终止判定 - -完成判定不应该由 agent 自己做。harness 独立执行终止校验,输入是运行时信号,不是 agent 的置信度。在 CLAUDE.md 里写清楚: - -``` -## 完成定义 -- 功能完成 = 端到端验证通过,不是"代码写完了" -- 必须运行的验证层级: - 1. 单元测试通过 - 2. 集成测试通过 - 3. 端到端流程验证通过 -- 在第 1 层没通过时,不许进入第 2 层 -- 在第 2 层没通过时,不许进入第 3 层 -``` - -### 2. 构建三层终止校验 - -- **第一层:语法与静态分析**。成本最低,信息量最小,但必须通过。这是最低限度的检查——字都没写错才能往下看。 -- **第二层:运行时行为验证**。测试执行、应用启动检查、关键路径验证。这是核心完成证据。不仅要写了,还要能跑。 -- **第三层:系统级确认**。端到端测试、集成验证、用户场景模拟。防止过早声明的最后一道防线。不仅要能跑,还要跑对。 - -### 3. 为 agent 设计好的"红笔批注" - -OpenAI 在 Codex 实践中提出了一个特别有效的模式:**给 agent 写的错误消息要包含修复指导**。不要像阅卷老师只画个大红叉,要像好老师一样在旁边写上"这里应该怎么改"。不要用 `"Test failed"`,而用 `"Test failed: POST /api/reset-password returned 500. Check that the email service config exists in environment variables. The template file should be at templates/reset-email.html."` 这种具体的、可操作的反馈让 agent 能自我修正,而不需要人类介入。 - -### 4. 捕获运行时信号 - -有效的运行时信号包括: -- 应用是否成功启动并达到就绪状态? -- 关键功能路径在运行时是否执行成功? -- 数据库写入、文件操作等副作用是否正确? -- 临时资源是否被清理? - -## 实际案例 - -**任务**:实现用户密码重置功能。涉及数据库操作、邮件发送和 API 端点修改。 - -**提前交卷路径**:agent 修改数据库 schema、编写 API 端点、添加邮件模板、跑单元测试(通过)、声明完成。卷子写得满满当当。 - -**实际扣分项**:(1) 端到端流程未测试——重置链接的实际发送和验证未确认。(2) 数据库迁移在部分执行后失败,导致 schema 不一致。(3) 邮件服务配置在目标环境中缺失。 - -**harness 介入**:终止校验强制执行——(1) 启动完整应用验证重置端点可访问;(2) 执行完整重置流程;(3) 验证数据库状态一致性。所有缺陷在会话内被发现,节省了 5-10 倍的后续修复成本。独立阅卷老师批出了真正的问题。 - -## 关键要点 - -- **agent 系统性地过度自信**——置信度校准偏差是客观存在的。卷子写满了不代表做对了。 -- **完成判定必须外部化**——harness 独立验证,不信任 agent 的"感觉"。不能让学生自己批自己的卷子。 -- **三层校验缺一不可**——语法通过、行为通过、系统通过,层层递进。 -- **错误消息要像好老师的红笔批注**——包含具体修复步骤,让 agent 能自我修正。 -- **核心功能验证通过之前不许重构**——完成优先级约束是防止过早优化的关键。 - -## 延伸阅读 - -- [On Calibration of Modern Neural Networks - Guo et al.](https://arxiv.org/abs/1706.04599) — 证明现代深度网络系统性地过度自信 -- [Building Effective Agents - Anthropic](https://www.anthropic.com/research/building-effective-agents) — 运行时证据在完成判定中的关键作用 -- [Harness Engineering - OpenAI](https://openai.com/index/harness-engineering/) — 过早完成声明是 agent 的主要失败模式之一 -- [The Art of Software Testing - Myers](https://www.goodreads.com/book/show/137543.The_Art_of_Software_Testing) — 测试方法层次和有效性的经典参考 - -## 练习 - -1. **终止校验函数设计**:为一个涉及数据库迁移和 API 修改的任务设计完整的终止校验。列出需要的运行时信号和每个信号的通过/失败标准。在一个实际任务上运行,记录它发现了哪些隐藏问题。 - -2. **校准偏差测量**:选 10 个不同类型的编码任务,记录 agent 的自报完成信心和实际完成质量。计算偏差值,分析它和任务复杂度的关系。 - -3. **多层防御实验**:对同一组任务跑三种配置——(a) 仅静态分析,(b) 加单元测试,(c) 完整三层校验。比较过早完成声明的比例和未捕获缺陷的数量。 diff --git a/courses/learn-harness-engineering/docs/zh/lectures/lecture-10-why-end-to-end-testing-changes-results/code/architecture-rules.md b/courses/learn-harness-engineering/docs/zh/lectures/lecture-10-why-end-to-end-testing-changes-results/code/architecture-rules.md deleted file mode 100644 index 85e0e48d..00000000 --- a/courses/learn-harness-engineering/docs/zh/lectures/lecture-10-why-end-to-end-testing-changes-results/code/architecture-rules.md +++ /dev/null @@ -1,6 +0,0 @@ -# Electron 架构规则 - -- 渲染器代码不能直接访问文件系统。 -- 预加载是渲染器与 Electron 主进程之间的唯一桥梁。 -- 检索和索引逻辑位于服务模块中,而非 UI 组件中。 -- 日志应该是结构化的,并从服务边界发出。 diff --git a/courses/learn-harness-engineering/docs/zh/lectures/lecture-10-why-end-to-end-testing-changes-results/code/e2e-runner.ts b/courses/learn-harness-engineering/docs/zh/lectures/lecture-10-why-end-to-end-testing-changes-results/code/e2e-runner.ts deleted file mode 100644 index 24284165..00000000 --- a/courses/learn-harness-engineering/docs/zh/lectures/lecture-10-why-end-to-end-testing-changes-results/code/e2e-runner.ts +++ /dev/null @@ -1,253 +0,0 @@ -/** - * e2e-runner.ts - * - * A minimal E2E test harness. Defines test cases as user action sequences - * (import doc -> index -> ask question -> verify citation). Simulates - * running them and shows the difference between "unit tests pass" and - * "full pipeline works". - * - * Run: npx tsx docs/lectures/lecture-10-why-end-to-end-testing-changes-results/code/e2e-runner.ts - */ - -// --------------------------------------------------------------------------- -// Types -// --------------------------------------------------------------------------- - -interface PipelineStep { - name: string; - unitTestPasses: boolean; - // Simulated actual behavior in the pipeline - actualBehavior: "works" | "fails" | "partial"; - failureReason?: string; -} - -interface TestCase { - name: string; - steps: PipelineStep[]; -} - -interface TestResult { - testCase: string; - unitTestsPassed: number; - unitTestsTotal: number; - unitTestResult: "PASS" | "FAIL"; - e2eResult: "PASS" | "FAIL"; - e2eFailureStep?: string; - e2eFailureReason?: string; -} - -// --------------------------------------------------------------------------- -// Test cases -- realistic scenarios where unit tests pass but E2E fails -// --------------------------------------------------------------------------- - -const testCases: TestCase[] = [ - { - name: "Import document and ask question", - steps: [ - { - name: "Parse uploaded document", - unitTestPasses: true, - actualBehavior: "works", - }, - { - name: "Store document chunks", - unitTestPasses: true, - actualBehavior: "works", - }, - { - name: "Index chunks for retrieval", - unitTestPasses: true, - actualBehavior: "partial", // Indexes but with wrong embedding dimensions - failureReason: "Embedding dimension mismatch between indexer and retriever", - }, - { - name: "Retrieve relevant chunks", - unitTestPasses: true, // Unit test uses mock data with correct dimensions - actualBehavior: "fails", - failureReason: "Empty results due to dimension mismatch from previous step", - }, - { - name: "Generate answer with citations", - unitTestPasses: true, // Unit test provides pre-retrieved chunks - actualBehavior: "fails", - failureReason: "No chunks retrieved, so answer has no citations", - }, - ], - }, - { - name: "Delete document and verify removal", - steps: [ - { - name: "Find document by ID", - unitTestPasses: true, - actualBehavior: "works", - }, - { - name: "Delete document record", - unitTestPasses: true, - actualBehavior: "works", - }, - { - name: "Remove indexed chunks", - unitTestPasses: true, - actualBehavior: "fails", // Orphaned chunks remain in the index - failureReason: "Index cleanup query timed out, chunks remain orphaned", - }, - { - name: "Verify document not in search results", - unitTestPasses: true, // Unit test mocks the search - actualBehavior: "fails", - failureReason: "Orphaned chunks from previous step still appear in results", - }, - ], - }, - { - name: "Multi-user concurrent access", - steps: [ - { - name: "User A imports document", - unitTestPasses: true, - actualBehavior: "works", - }, - { - name: "User B imports document", - unitTestPasses: true, - actualBehavior: "works", - }, - { - name: "User A queries their document", - unitTestPasses: true, - actualBehavior: "partial", // Cross-contamination of results - failureReason: "No user-scoping on retrieval, returns chunks from User B's doc", - }, - { - name: "Verify only User A's results returned", - unitTestPasses: true, - actualBehavior: "fails", - failureReason: "Results include documents from other users", - }, - ], - }, -]; - -// --------------------------------------------------------------------------- -// Run tests -// --------------------------------------------------------------------------- - -function runUnitTests(tc: TestCase): { passed: number; total: number } { - const passed = tc.steps.filter((s) => s.unitTestPasses).length; - return { passed, total: tc.steps.length }; -} - -function runE2ETest(tc: TestCase): { pass: boolean; failureStep?: string; failureReason?: string } { - // Pipeline: if any step actually fails, the whole E2E fails - for (const step of tc.steps) { - if (step.actualBehavior === "fails") { - return { - pass: false, - failureStep: step.name, - failureReason: step.failureReason ?? "Unknown failure", - }; - } - // "partial" means the step technically completes but creates problems downstream - // We let it continue but track it - } - // Check if any step was "partial" (which may cause downstream issues) - const partialSteps = tc.steps.filter((s) => s.actualBehavior === "partial"); - if (partialSteps.length > 0) { - // The partial steps may or may not cause overall failure - // In our simulation, partial steps always lead to failure downstream - // unless there's an explicit "fails" step that already caught it - // This case means all steps were either "works" or "partial" - return { - pass: false, - failureStep: partialSteps[partialSteps.length - 1].name, - failureReason: partialSteps[partialSteps.length - 1].failureReason ?? "Partial completion", - }; - } - - return { pass: true }; -} - -// --------------------------------------------------------------------------- -// Reporting -// --------------------------------------------------------------------------- - -function pad(s: string, len: number): string { - return s.length >= len ? s : s + " ".repeat(len - s.length); -} - -function run(): void { - console.log("\n" + "=".repeat(95)); - console.log(" E2E TEST RUNNER -- Unit Tests vs Full Pipeline"); - console.log("=".repeat(95)); - - const results: TestResult[] = testCases.map((tc) => { - const unit = runUnitTests(tc); - const e2e = runE2ETest(tc); - - return { - testCase: tc.name, - unitTestsPassed: unit.passed, - unitTestsTotal: unit.total, - unitTestResult: unit.passed === unit.total ? "PASS" : "FAIL", - e2eResult: e2e.pass ? "PASS" : "FAIL", - e2eFailureStep: e2e.failureStep, - e2eFailureReason: e2e.failureReason, - }; - }); - - // Per-test-case detail - for (let i = 0; i < testCases.length; i++) { - const tc = testCases[i]; - const r = results[i]; - - console.log("\n Test Case: " + tc.name); - console.log(" " + "-".repeat(70)); - - const stepHeader = ` | ${pad("Step", 40)}| ${pad("Unit Test", 11)}| ${pad("E2E Actual", 12)}|`; - const stepSep = ` |${"-".repeat(42)}|${"-".repeat(13)}|${"-".repeat(14)}|`; - console.log(stepHeader); - console.log(stepSep); - - for (const step of tc.steps) { - const utLabel = step.unitTestPasses ? "PASS" : "FAIL"; - let e2eLabel: string; - if (step.actualBehavior === "works") e2eLabel = "PASS"; - else if (step.actualBehavior === "partial") e2eLabel = "PARTIAL*"; - else e2eLabel = "FAIL"; - - const marker = step.actualBehavior !== "works" ? ">>" : " "; - console.log(`${marker}| ${pad(step.name, 40)}| ${pad(utLabel, 11)}| ${pad(e2eLabel, 12)}|`); - } - - if (r.e2eFailureStep) { - console.log(`\n E2E Failure at: ${r.e2eFailureStep}`); - console.log(` Reason: ${r.e2eFailureReason}`); - } - } - - // Summary comparison - console.log("\n" + "=".repeat(95)); - console.log(" COMPARISON: Unit Tests vs E2E Tests"); - console.log("=".repeat(95) + "\n"); - - const header = `| ${pad("Test Case", 35)}| ${pad("Unit Tests", 15)}| ${pad("E2E Result", 12)}| Discrepancy`; - const sep = `|${"-".repeat(37)}|${"-".repeat(17)}|${"-".repeat(14)}|${"-".repeat(30)}`; - console.log(header); - console.log(sep); - - for (const r of results) { - const utLabel = `${r.unitTestsPassed}/${r.unitTestsTotal} ${r.unitTestResult}`; - const discrepancy = r.unitTestResult === "PASS" && r.e2eResult === "FAIL"; - const discLabel = discrepancy ? "UNIT PASS BUT E2E FAIL" : "Consistent"; - - console.log(`| ${pad(r.testCase, 35)}| ${pad(utLabel, 15)}| ${pad(r.e2eResult, 12)}| ${discLabel}`); - } - - const falseConfidence = results.filter((r) => r.unitTestResult === "PASS" && r.e2eResult === "FAIL").length; - console.log("\n False confidence count: " + falseConfidence + " of " + results.length); - console.log(" Unit tests pass but E2E reveals integration failures that units cannot catch.\n"); -} - -run(); diff --git a/courses/learn-harness-engineering/docs/zh/lectures/lecture-10-why-end-to-end-testing-changes-results/code/index.md b/courses/learn-harness-engineering/docs/zh/lectures/lecture-10-why-end-to-end-testing-changes-results/code/index.md deleted file mode 100644 index 448bdb70..00000000 --- a/courses/learn-harness-engineering/docs/zh/lectures/lecture-10-why-end-to-end-testing-changes-results/code/index.md +++ /dev/null @@ -1,8 +0,0 @@ -# 第 10 讲代码 - -使用此文件夹存放以下方面的示例: - -- 架构约束 -- 结构测试 -- 品味不变量 -- 以修复为导向的代码检查消息 diff --git a/courses/learn-harness-engineering/docs/zh/lectures/lecture-10-why-end-to-end-testing-changes-results/code/review-feedback-to-rule.md b/courses/learn-harness-engineering/docs/zh/lectures/lecture-10-why-end-to-end-testing-changes-results/code/review-feedback-to-rule.md deleted file mode 100644 index 9d63b237..00000000 --- a/courses/learn-harness-engineering/docs/zh/lectures/lecture-10-why-end-to-end-testing-changes-results/code/review-feedback-to-rule.md +++ /dev/null @@ -1,10 +0,0 @@ -# 示例:将审查反馈转化为规则 - -反复出现的审查意见: - -> 不要从渲染器调用文件系统工具。使用预加载桥接。 - -提升为 harness 规则: - -- 添加一个 lint 或 import 规则,阻止在渲染器代码中使用 `fs` -- 添加修复文本,解释预加载边界 diff --git a/courses/learn-harness-engineering/docs/zh/lectures/lecture-10-why-end-to-end-testing-changes-results/index.md b/courses/learn-harness-engineering/docs/zh/lectures/lecture-10-why-end-to-end-testing-changes-results/index.md deleted file mode 100644 index d68568a3..00000000 --- a/courses/learn-harness-engineering/docs/zh/lectures/lecture-10-why-end-to-end-testing-changes-results/index.md +++ /dev/null @@ -1,158 +0,0 @@ -[English Version →](../../../en/lectures/lecture-10-why-end-to-end-testing-changes-results/) - -> 本篇代码示例:[code/](https://github.com/walkinglabs/learn-harness-engineering/blob/main/docs/zh/lectures/lecture-10-why-end-to-end-testing-changes-results/code/) -> 实战练习:[Project 05. 让 agent 自己检查自己做的对不对](./../../projects/project-05-grounded-qa-verification/index.md) - -# 第十讲. 跑通完整流程才算真正验证 - -你让 agent 给 Electron 应用加一个文件导出功能。它写了渲染进程组件、预加载脚本、服务层逻辑,每个组件的单元测试都通过了。agent 说"做完了"。你实际一点击导出按钮——文件路径格式不对、进度条没反应、大文件导出时内存泄漏。5 个组件边界缺陷,单元测试一个都没发现。 - -这就像一个合唱团排练——每个声部单独唱的时候都完美,但合在一起的时候,女高音比男低音快了半拍,伴奏的调子和主旋律差了半个音。每个部分都"对"了,但整体跑调了。 - -Google 的测试金字塔告诉我们:大量单元测试是基础,但如果你止步于此,就会系统性地漏掉组件交互问题。对于 AI 编码 agent 来说,这个问题更严重——agent 倾向于只跑最快的测试然后宣告完成。**只有端到端测试能证明系统级缺陷不存在**。 - -## 单元测试的盲区 - -单元测试的设计哲学是隔离——模拟依赖,专注被测单元。这个哲学使单元测试快速且精确,但也制造了系统性的盲区。就像合唱排练时每个声部戴着耳机对着伴奏唱——听着都挺好,但真正合在一起才发现问题: - -**接口不匹配**:渲染进程传给预加载脚本的文件路径是相对路径,但预加载脚本期望绝对路径。各自的单元测试都用了 mock,都通过了。只有端到端跑通时才发现问题——就像两个声部各自练的时候都觉得节奏没问题,一合才发现一个用 4/4 拍一个用 3/4 拍。 - -**状态传播错误**:数据库迁移改了表结构,但 ORM 的缓存层还持有旧结构的缓存条目。单元测试每次都是全新的 mock 环境,不会暴露这种跨层状态不一致。就像换了一首歌的歌词,但有人还在唱旧版本。 - -**资源生命周期问题**:文件句柄、数据库连接、网络套接字的获取和释放跨越多个组件。单元测试为每个测试创建和销毁独立资源,不会暴露资源竞争或泄漏。就像排练时每个声部轮流用麦克风,但演出时所有声部同时上台——话筒不够用了。 - -**环境依赖性**:代码在测试环境(一切 mock)行为正确,在真实环境因配置差异、网络延迟、服务不可用而失败。就像排练厅里唱得好好的,到了户外音乐节风一吹话筒一啸叫就全乱了。 - -## 端到端测试不仅改变结果,还改变行为 - -这是很多人没意识到的一点:当 agent 知道它的工作要过端到端测试时,它的编码行为会改变。 - -1. **考虑组件交互**:写代码时会想"这个接口和上游怎么对接",而不是只关注单个函数。就像知道最终要合在一起唱,练习的时候就会注意听其他声部。 -2. **尊重架构边界**:有架构约束的系统里,端到端测试迫使 agent 遵守边界规则。就像乐谱上标注了"此处渐强",你得跟着来。 -3. **处理错误路径**:端到端测试通常包含故障场景,迫使 agent 考虑异常处理。就像排练时模拟了"话筒突然没声了"的情况,你知道该怎么做。 - -## 测试金字塔与审查反馈提升 - -```mermaid -flowchart TB - subgraph Unit["单元测试只看孤立部件"] - U1["渲染层测试"] - U2["Preload 测试"] - U3["服务层测试"] - end - - subgraph E2E["端到端运行会穿过真实系统"] - R["点击渲染层按钮"] --> P["Preload 桥"] - P --> S["服务层"] - S --> F["文件系统 / 操作系统"] - F --> Result["真实导出文件"] - end -``` - -```mermaid -flowchart LR - Review["审查意见:
    renderer 不能直接 import fs"] --> Rule["加一条 direct fs import 检查"] - Rule --> Message["报错里直接告诉 agent
    把文件访问移到 preload"] - Message --> Harness["把这条检查加入 harness"] - Harness --> Stronger["以后再犯会第一时间报错"] -``` - -OpenAI 在 Codex 工程实践中强调:**为 agent 写的错误消息必须包含修复指导**。不写 `"Direct filesystem access in renderer"`,而写 `"Direct filesystem access in renderer. All file operations must go through the preload bridge. Move this call to preload/file-ops.ts and invoke it via window.api."` 这把架构规则变成了自动修正的闭环。就像合唱排练时指挥不只说"你唱错了",而是说"这里你快了半拍,听一下女低音的节奏,在第 32 小节进入"。 - -## 核心概念 - -- **组件边界缺陷**:组件 A 和 B 各自单元测试通过,但它们的交互产生了不正确的行为。这是端到端测试最擅长捕获的问题类型——合唱里各声部单独都对但合起来跑调的那种。 -- **测试充分性梯度**:单元测试能检测的缺陷 <= 集成测试能检测的缺陷 <= 端到端测试能检测的缺陷。每往上一层,检测能力增强。 -- **架构边界执行规则**:把架构文档里的规则(如"渲染进程不能直接访问文件系统")变成可执行的自动化检查。从"写在纸上"变成"跑在 CI 里"。 -- **审查反馈提升**:把重复出现的代码审查意见转化为自动化测试。每次发现重复问题就加一条规则,harness 会自动变强。就像合唱排练时指挥把常见的错误编成练习曲——下次再犯同样的错误,不用指挥说,练习曲自己就暴露了。 -- **面向 agent 的错误消息**:失败信息不只是说"出了什么问题",还要告诉 agent 具体怎么修。这把测试失败变成自我修正的反馈循环。 - -## 怎么做 - -### 0. 先定好架构边界,再写端到端测试 - -端到端测试的前提是系统有清晰的边界。如果架构是一团面条,端到端测试只会证明"这团面条整体能跑",不会告诉你哪里违反了设计意图。就像合唱团如果连分声部都没分好,排练再多也是乱唱。 - -OpenAI 的经验:**对 agent 生成的代码库,架构约束必须是第一天就建立的早期前置条件,不是等团队规模大了再考虑的事。** 原因很直接——agent 会复制仓库中已有的模式,即使那些模式是不均匀的或次优的。没有架构约束,agent 会在每次会话中引入更多偏差。 - -OpenAI 采用了"分层领域架构"——每个业务领域被分成固定的层:Types → Config → Repo → Service → Runtime → UI。依赖方向严格向前,跨领域关注点通过显式的 Providers 接口进入。任何其他依赖都是禁止的,并且通过自定义 lint 机械执行。 - -关键原则:**执行不变量,不微管实现。** 比如要求"数据在边界解析",但不规定用哪个库。错误消息要包含修复指导——不只是说"违规了",而是告诉 agent 具体怎么改。 - -> 来源:[OpenAI: Harness engineering: leveraging Codex in an agent-first world](https://openai.com/index/harness-engineering/) - -### 1. harness 必须包含端到端层 - -在你的验证流程里明确:对于涉及跨组件修改的任务,端到端测试通过是完成的前置条件: - -``` -## 验证层级 -- 层级 1: 单元测试 (必须通过) -- 层级 2: 集成测试 (必须通过) -- 层级 3: 端到端测试 (涉及跨组件修改时必须通过) -- 跳过任何必须层级的任务 = 未完成 -``` - -### 2. 把架构规则变成可执行检查 - -每条架构约束都应该有对应的测试或 lint 规则: - -```bash -# 检查渲染进程是否直接调用 Node.js API -grep -r "require('fs')" src/renderer/ && exit 1 || echo "OK: no direct fs access in renderer" -``` - -### 3. 设计面向 agent 的错误消息 - -失败信息要包含三要素:什么出了问题、为什么、怎么修: - -``` -ERROR: Found direct import of 'fs' in src/renderer/App.tsx:12 -WHY: Renderer process has no access to Node.js APIs for security -FIX: Move file operations to src/preload/file-ops.ts and call via window.api.readFile() -``` - -### 4. 建立审查反馈提升流程 - -每次在代码审查中发现新类型的 agent 错误,就把它变成自动化检查。一个月后你的 harness 会比月初强得多。就像合唱团的排练笔记——每次排练发现的问题都记下来,下次排练前先检查这些点。久而久之,常见错误越来越少,音乐越来越和谐。 - -## 实际案例 - -**任务**:在 Electron 应用中实现文件导出功能。涉及渲染进程 UI、预加载脚本文件系统代理、服务层数据转换。 - -**各声部单独唱(单元测试通过)**:渲染组件测试(通过,mock 文件操作)、预加载脚本测试(通过,mock 文件系统)、服务层测试(通过,mock 数据源)。agent 声明完成。 - -**合唱合在一起(端到端测试揭示的缺陷)**: - -| 缺陷 | 描述 | 单元测试 | 端到端 | -|------|------|---------|--------| -| 接口不匹配 | 文件路径格式不一致 | 未检测 | 检测 | -| 状态传播 | 导出进度未通过 IPC 传回 UI | 未检测 | 检测 | -| 资源泄漏 | 大文件导出句柄未释放 | 未检测 | 检测 | -| 权限问题 | 打包环境权限不同 | 未检测 | 检测 | -| 错误传播 | 服务层异常未到 UI 层 | 未检测 | 检测 | - -5 个缺陷全部被端到端测试捕获,单元测试一个都没发现。代价是测试时间从 2 秒增加到 15 秒——在 agent 工作流里完全可以接受。每个声部单独唱得再好,也比不上一次完整的合唱排练。 - -## 关键要点 - -- **单元测试对组件边界缺陷系统性盲视**——它们的隔离设计恰好使其无法检测交互问题。每个人唱得都对,不代表合唱不跑调。 -- **端到端测试不仅检测缺陷,还改变 agent 的编码行为**——让它更关注集成和边界。 -- **架构规则必须可执行**——不是写在文档里等人来看,而是每次提交自动检查。 -- **错误消息要面向 agent 设计**——包含"怎么修"的具体步骤,形成自我修正闭环。 -- **审查反馈提升让 harness 自动变强**——每个被捕获的缺陷类别都变成永久防线。 - -## 延伸阅读 - -- [How Google Tests Software - Whittaker et al.](https://www.goodreads.com/book/show/13563030-how-google-tests-software) — 测试金字塔模型的经典来源 -- [Harness Engineering - OpenAI](https://openai.com/index/harness-engineering/) — 架构约束自动化执行的工程实践 -- [Chaos Engineering - Netflix (Basiri et al.)](https://ieeexplore.ieee.org/document/7466237) — 主动注入故障验证系统弹性 -- [QuickCheck - Claessen & Hughes](https://www.cs.tufts.edu/~nr/cs257/archive/john-hughes/quick.pdf) — 属性测试方法,介于示例测试和形式化验证之间 - -## 练习 - -1. **跨组件缺陷检测**:选一个涉及至少三个组件的修改任务。先只跑单元测试记录结果,再跑端到端测试。分析每个额外发现的缺陷属于哪种跨层交互问题。 - -2. **架构规则自动化**:选项目里的一条架构约束,把它变成可执行检查(含面向 agent 的错误消息)。集成到 harness 里,用基准任务验证效果。 - -3. **审查反馈提升**:从代码审查历史中找一个重复出现的意见类型,按五步流程转化为自动化检查。比较提升前后该类问题的出现频率。 diff --git a/courses/learn-harness-engineering/docs/zh/lectures/lecture-11-why-observability-belongs-inside-the-harness/code/evaluator-rubric.md b/courses/learn-harness-engineering/docs/zh/lectures/lecture-11-why-observability-belongs-inside-the-harness/code/evaluator-rubric.md deleted file mode 100644 index 7373e26c..00000000 --- a/courses/learn-harness-engineering/docs/zh/lectures/lecture-11-why-observability-belongs-inside-the-harness/code/evaluator-rubric.md +++ /dev/null @@ -1,8 +0,0 @@ -# 评估者评分标准示例 - -对每个维度使用 1-5 评分: - -- 基础性:答案是否清晰关联到导入的来源? -- 引用质量:来源引用是否可见且具体? -- 功能性:用户能否完成问答流程? -- 产品一致性:工作流是否感觉一体化? diff --git a/courses/learn-harness-engineering/docs/zh/lectures/lecture-11-why-observability-belongs-inside-the-harness/code/index.md b/courses/learn-harness-engineering/docs/zh/lectures/lecture-11-why-observability-belongs-inside-the-harness/code/index.md deleted file mode 100644 index 5f66cd38..00000000 --- a/courses/learn-harness-engineering/docs/zh/lectures/lecture-11-why-observability-belongs-inside-the-harness/code/index.md +++ /dev/null @@ -1,8 +0,0 @@ -# 第 11 讲代码 - -使用此文件夹存放以下方面的示例: - -- 规划器输出 -- 评估者评分标准 -- 生成器/评估者循环 -- 单 Agent 与多角色对比 diff --git a/courses/learn-harness-engineering/docs/zh/lectures/lecture-11-why-observability-belongs-inside-the-harness/code/runtime-logger.ts b/courses/learn-harness-engineering/docs/zh/lectures/lecture-11-why-observability-belongs-inside-the-harness/code/runtime-logger.ts deleted file mode 100644 index 213dc198..00000000 --- a/courses/learn-harness-engineering/docs/zh/lectures/lecture-11-why-observability-belongs-inside-the-harness/code/runtime-logger.ts +++ /dev/null @@ -1,260 +0,0 @@ -/** - * runtime-logger.ts - * - * A structured logging module demo. Shows ad-hoc console.log output vs - * structured JSON log output when diagnosing a failure. Includes a seeded - * failure scenario and demonstrates how structured logs pinpoint the issue - * faster. - * - * Run: npx tsx docs/lectures/lecture-11-why-observability-belongs-inside-the-harness/code/runtime-logger.ts - */ - -// --------------------------------------------------------------------------- -// Types -// --------------------------------------------------------------------------- - -interface StructuredLogEntry { - timestamp: string; - level: "info" | "warn" | "error" | "debug"; - component: string; - action: string; - durationMs?: number; - input?: unknown; - output?: unknown; - error?: string; - correlationId: string; -} - -// --------------------------------------------------------------------------- -// Simulated pipeline with a seeded failure -// --------------------------------------------------------------------------- - -const CORRELATION_ID = "req-" + Math.random().toString(36).slice(2, 8); - -// Simulated stages of a document Q&A pipeline -interface PipelineStage { - component: string; - action: string; - durationMs: number; - success: boolean; - errorMessage?: string; - input?: unknown; - output?: unknown; -} - -function runPipeline(): PipelineStage[] { - return [ - { - component: "DocumentLoader", - action: "parse_upload", - durationMs: 45, - success: true, - input: { filename: "report.pdf", size: "2.3MB" }, - output: { chunks: 47 }, - }, - { - component: "ChunkIndexer", - action: "embed_and_store", - durationMs: 230, - success: true, - input: { chunks: 47 }, - output: { indexed: 47, embeddingDim: 1536 }, - }, - { - component: "QueryRouter", - action: "route_query", - durationMs: 12, - success: true, - input: { query: "What is the revenue target?" }, - output: { routedTo: "RetrievalEngine" }, - }, - { - // SEEDED FAILURE: Retrieval returns 0 results due to dimension mismatch - component: "RetrievalEngine", - action: "semantic_search", - durationMs: 180, - success: false, - errorMessage: "Vector dimension mismatch: query embedding dim=768, index embedding dim=1536", - input: { query: "What is the revenue target?", topK: 5 }, - output: { results: 0 }, - }, - { - component: "AnswerGenerator", - action: "generate_with_citations", - durationMs: 1500, - success: true, // Doesn't crash, but produces a bad answer - input: { context: [], query: "What is the revenue target?" }, - output: { answer: "I could not find relevant information.", citations: 0 }, - }, - ]; -} - -// --------------------------------------------------------------------------- -// Ad-hoc logging (console.log style) -// --------------------------------------------------------------------------- - -function printAdHocLog(stages: PipelineStage[]): void { - console.log("Starting document Q&A pipeline..."); - console.log("User uploaded report.pdf"); - - for (const stage of stages) { - if (stage.success) { - console.log(`${stage.component}: ${stage.action} done (${stage.durationMs}ms)`); - } else { - console.log(`${stage.component}: something went wrong`); - } - } - - console.log("Pipeline finished. Answer: I could not find relevant information."); -} - -// --------------------------------------------------------------------------- -// Structured logging (JSON) -// --------------------------------------------------------------------------- - -function printStructuredLog(stages: PipelineStage[]): StructuredLogEntry[] { - const entries: StructuredLogEntry[] = []; - - for (const stage of stages) { - entries.push({ - timestamp: new Date().toISOString(), - level: stage.success ? "info" : "error", - component: stage.component, - action: stage.action, - durationMs: stage.durationMs, - input: stage.input, - output: stage.output, - error: stage.errorMessage, - correlationId: CORRELATION_ID, - }); - } - - return entries; -} - -// --------------------------------------------------------------------------- -// Diagnose failure from structured logs -// --------------------------------------------------------------------------- - -function diagnoseFromStructured(entries: StructuredLogEntry[]): string[] { - const diagnosis: string[] = []; - - // Find errors - const errors = entries.filter((e) => e.level === "error"); - if (errors.length > 0) { - diagnosis.push("ERRORS FOUND:"); - for (const err of errors) { - diagnosis.push(` - ${err.component}.${err.action}: ${err.error}`); - } - } - - // Check for latency spikes - const slowStages = entries.filter((e) => (e.durationMs ?? 0) > 1000); - if (slowStages.length > 0) { - diagnosis.push("LATENCY SPIKES:"); - for (const s of slowStages) { - diagnosis.push(` - ${s.component}.${s.action}: ${s.durationMs}ms`); - } - } - - // Check for cascading failures - const emptyOutputs = entries.filter((e) => { - const out = e.output as Record | undefined; - return out && typeof out === "object" && "results" in out && (out.results as number) === 0; - }); - if (emptyOutputs.length > 0) { - diagnosis.push("EMPTY OUTPUTS (possible cascade):"); - for (const e of emptyOutputs) { - diagnosis.push(` - ${e.component}.${e.action}: returned 0 results`); - } - } - - return diagnosis; -} - -// --------------------------------------------------------------------------- -// Reporting -// --------------------------------------------------------------------------- - -function pad(s: string, len: number): string { - return s.length >= len ? s : s + " ".repeat(len - s.length); -} - -function run(): void { - const pipeline = runPipeline(); - - console.log("\n" + "=".repeat(90)); - console.log(" OBSERVABILITY DEMO -- Ad-hoc vs Structured Logging"); - console.log("=".repeat(90)); - - // --- Ad-hoc output --- - console.log("\n" + "-".repeat(90)); - console.log(" SCENARIO A: Ad-hoc console.log output"); - console.log("-".repeat(90) + "\n"); - printAdHocLog(pipeline); - console.log("\n Diagnosis from ad-hoc logs: ??? Hard to tell what went wrong."); - console.log(" The failure message is vague: 'something went wrong'."); - console.log(" No dimensions, no input/output data, no correlation ID."); - - // --- Structured output --- - console.log("\n" + "-".repeat(90)); - console.log(" SCENARIO B: Structured JSON log output"); - console.log("-".repeat(90) + "\n"); - - const structuredEntries = printStructuredLog(pipeline); - - const header = `| ${pad("Timestamp", 26)}| ${pad("Level", 6)}| ${pad("Component", 20)}| ${pad("Action", 25)}| ${pad("Duration", 9)}| Error?`; - const sep = `|${"-".repeat(28)}|${"-".repeat(8)}|${"-".repeat(22)}|${"-".repeat(27)}|${"-".repeat(11)}|${"-".repeat(30)}`; - console.log(header); - console.log(sep); - - for (const entry of structuredEntries) { - const hasError = entry.error ? entry.error.slice(0, 30) : ""; - const marker = entry.level === "error" ? ">>" : " "; - console.log( - `${marker}| ${pad(entry.timestamp, 26)}| ${pad(entry.level, 6)}| ${pad(entry.component, 20)}| ${pad(entry.action, 25)}| ${pad(String(entry.durationMs) + "ms", 9)}| ${hasError}` - ); - } - - // --- Diagnosis --- - console.log("\n" + "-".repeat(90)); - console.log(" AUTOMATED DIAGNOSIS FROM STRUCTURED LOGS"); - console.log("-".repeat(90) + "\n"); - - const errors = structuredEntries.filter((e) => e.level === "error"); - for (const err of errors) { - console.log(` ROOT CAUSE: ${err.component}.${err.action}`); - console.log(` Error: ${err.error}`); - console.log(` Input: ${JSON.stringify(err.input)}`); - console.log(` Output: ${JSON.stringify(err.output)}`); - console.log(` Correlation ID: ${err.correlationId}`); - } - - // Downstream impact - console.log("\n DOWNSTREAM IMPACT:"); - const answerGen = structuredEntries.find((e) => e.component === "AnswerGenerator"); - if (answerGen) { - const out = answerGen.output as Record; - console.log(` AnswerGenerator received empty context (${JSON.stringify(answerGen.input)})`); - console.log(` Produced answer: "${out.answer}" with ${out.citations} citations`); - } - - // Comparison summary - console.log("\n" + "=".repeat(90)); - console.log(" COMPARISON"); - console.log("=".repeat(90) + "\n"); - - const cHeader = `| ${pad("Metric", 35)}| ${pad("Ad-hoc Logs", 18)}| ${pad("Structured Logs", 18)}|`; - const cSep = `|${"-".repeat(37)}|${"-".repeat(20)}|${"-".repeat(20)}|`; - console.log(cHeader); - console.log(cSep); - console.log(`| ${pad("Root cause identifiable", 35)}| ${pad("No", 18)}| ${pad("Yes", 18)}|`); - console.log(`| ${pad("Input/output traceable", 35)}| ${pad("No", 18)}| ${pad("Yes", 18)}|`); - console.log(`| ${pad("Correlation across steps", 35)}| ${pad("No", 18)}| ${pad("Yes", 18)}|`); - console.log(`| ${pad("Machine-parseable", 35)}| ${pad("No", 18)}| ${pad("Yes", 18)}|`); - console.log(`| ${pad("Time to diagnose", 35)}| ${pad("Minutes (manual)", 18)}| ${pad("Seconds (auto)", 18)}|`); - - console.log("\n Structured logging transforms debugging from guesswork into a deterministic lookup.\n"); -} - -run(); diff --git a/courses/learn-harness-engineering/docs/zh/lectures/lecture-11-why-observability-belongs-inside-the-harness/code/sprint-contract.md b/courses/learn-harness-engineering/docs/zh/lectures/lecture-11-why-observability-belongs-inside-the-harness/code/sprint-contract.md deleted file mode 100644 index c3e4b4e8..00000000 --- a/courses/learn-harness-engineering/docs/zh/lectures/lecture-11-why-observability-belongs-inside-the-harness/code/sprint-contract.md +++ /dev/null @@ -1,12 +0,0 @@ -# Sprint 契约示例 - -Sprint 目标: - -- 为有据可依的问答结果添加可见引用 - -完成意味着: - -- 用户提出一个问题 -- 应用返回一个答案 -- 显示至少一个引用 -- 点击引用在文档视图中打开来源位置 diff --git a/courses/learn-harness-engineering/docs/zh/lectures/lecture-11-why-observability-belongs-inside-the-harness/index.md b/courses/learn-harness-engineering/docs/zh/lectures/lecture-11-why-observability-belongs-inside-the-harness/index.md deleted file mode 100644 index 7667573b..00000000 --- a/courses/learn-harness-engineering/docs/zh/lectures/lecture-11-why-observability-belongs-inside-the-harness/index.md +++ /dev/null @@ -1,173 +0,0 @@ -[English Version →](../../../en/lectures/lecture-11-why-observability-belongs-inside-the-harness/) - -> 本篇代码示例:[code/](https://github.com/walkinglabs/learn-harness-engineering/blob/main/docs/zh/lectures/lecture-11-why-observability-belongs-inside-the-harness/code/) -> 实战练习:[Project 06. 搭建一套完整的 agent 工作环境](./../../projects/project-06-runtime-observability-and-debugging/index.md) - -# 第十一讲. 让 agent 的运行过程可观测 - -你让 agent 做一个功能,它跑了 20 分钟,改了一堆文件,然后告诉你"做完了但有两个测试失败"。你问它为什么失败,它说"不太确定,可能是时序问题"。你问它改了哪些关键路径,它说"让我看看代码……"。 - -这不是 agent 能力不够,是你的 harness 没有给它装仪表盘。想象你在开一辆没有仪表盘的车——没有速度表、没有油量表、没有发动机故障灯。你能开,但你不知道开多快、还剩多少油、发动机是不是快爆了。技术再好的司机,蒙着眼也得出事。 - -**没有可观测性,agent 在不确定状态中做决策,评估变成主观判断,重试变成盲目摸索。** OpenAI 和 Anthropic 都将可靠性定义为证据问题——harness 必须以可指导下一步决策的形式暴露运行时行为和评估信号。 - -## 可观测性缺失的真实代价 - -当 harness 缺乏可观测性时,四类问题系统性出现: - -**无法区分"正确"和"看似正确"**:一个函数在代码审查时看起来完全正确——语法对、逻辑通。但运行时因为边界条件处理错误,在特定输入下产生了不正确结果。只有运行时追踪能揭示实际执行路径偏离了预期。就像一个演员排练时台词全对了,但上台演出时灯光一打,表情和走位全都变了——你不看现场是发现不了的。 - -**评估变成玄学**:没有评分标准和验收条件时,评估者(人或 agent)依赖隐式假设。同一个输出,不同评估者可能给出截然不同的评价。质量评估不可复现。就像体操比赛没有评分标准——这个裁判觉得你的动作优雅,那个裁判觉得你落地不稳,谁说了算? - -**重试变成盲猜**:agent 不知道为什么失败时,重试方向是随机的。它可能在错误的方向上反复尝试——修复了不相关的代码路径而忽略真正的故障根源。就像你开车发现车跑偏了,但你没有仪表盘——你猜是轮胎的问题换了轮胎,实际是方向盘的 alignment 出了问题。每次盲重试都消耗 token 和时间。 - -**会话交接信息断崖**:当未完成的工作移交给下一个会话时,缺乏可观测性意味着新会话必须从零诊断系统状态。Anthropic 的长期运行 agent 观察表明,这种重复诊断可能占会话总时间的 30-50%。就像换班司机上车发现没有交接记录——他得花半小时检查油量、胎压、发动机状态才能出发。 - -## Claude Code 的真实场景 - -想象一个使用"计划者-生成者-评估者"三角色工作流的 harness,执行"为应用添加暗色模式"任务。 - -**没有仪表盘**:计划者输出模糊描述。生成者根据模糊描述实现暗色模式,但和计划者的隐式预期不一致。评估者基于自己的隐式标准拒绝,但说不出具体哪里不对——"感觉不太对"。生成者基于模糊拒绝理由盲重试。循环 3-4 次,总耗时约 45 分钟,最终勉强产出。 - -**有完整仪表盘**:计划者输出冲刺合同——列明要改哪些组件、每个组件的验证标准、排除项(不处理打印样式)。生成者按合同实现。运行时可观测性记录每个组件的样式加载和应用过程。评估者用评分标准逐维度评估,附具体证据引用——"按钮颜色对比度不足(WCAG AA 标准 4.5:1,实测 2.1:1)"。一次迭代出高质量结果,总耗时约 15 分钟。 - -效率差 3 倍。区别只在可观测性——给车装上了仪表盘。 - -## 双层可观测性 - -可观测性不是"多打点日志"那么简单。它分两层,缺一不可: - -```mermaid -flowchart LR - Contract["先把这次任务写清楚
    改哪些文件 / 不改哪些部分 / 怎么算通过"] --> Generator["生成器"] - Generator --> Signals["运行时收集
    日志 / 追踪 / 健康检查"] - Contract --> Review["按检查表逐项看
    功能 / 测试 / 边界"] - Signals --> Review - Review --> Verdict["指出哪一项没过
    以及应该去改哪里"] - Verdict --> Generator -``` - -**运行时可观测性**:系统层的信号——日志、追踪、进程事件、健康检查。回答"系统做了什么"。这是你车上的仪表盘——速度、油量、发动机温度。 - -**过程可观测性**:harness 决策工件的可见性——计划、评分标准、验收条件。回答"为什么这个变更应该被接受"。这是你的导航系统——不光知道现在在哪,还知道为什么走这条路。 - -## 核心概念 - -- **运行时可观测性**:系统层的信号——日志、追踪、进程事件、健康检查。回答"系统做了什么"。 -- **过程可观测性**:harness 决策工件的可见性——计划、评分标准、验收条件。回答"为什么这个变更应该被接受"。 -- **任务轨迹**:一个任务从开始到完成的完整决策路径记录,类似分布式系统中的请求追踪。agent 的每一步操作及其上下文都被记录。就像黑匣子——出了问题可以回放完整过程。 -- **冲刺合同**:编码开始前协商的短期协议——明确任务范围、验证标准、排除项。是过程可观测性的核心工具。 -- **评估评分标准**:把质量评估从主观判断变成基于证据的结构化评分。使不同评估者对同一输出产生相似结论。就像体操比赛的评分标准——有了它,十个裁判的分才不会差太远。 -- **双层可观测性**:系统层和过程层同时设计、相互增强。运行时信号解释行为,过程工件解释意图。 - -## 为什么 agent 自己解决不了这个问题 - -你可能在想:"agent 不能自己打日志吗?" 问题在于: - -1. **agent 不知道它不知道什么**——它不会主动记录自己没意识到需要的信号。就像你不知道油箱快漏了的时候,你不会去看油量表——因为你压根不知道该看。 -2. **日志格式不统一**——不同会话用不同的日志格式,无法做系统化分析。就像十个司机各写各的交接记录,格式都不一样,下一个司机看不懂。 -3. **过程可观测性不是日志能解决的**——冲刺合同和评分标准是结构化的工件,需要 harness 层面的支持。不是多 print 几行就能搞定的。 - -## 怎么装仪表盘 - -### 1. 在 harness 里内置运行时信号采集 - -不要依赖 agent 自己打日志。harness 应该自动采集以下信号: - -- **应用生命周期**:启动、就绪、运行、关闭各阶段状态 -- **功能路径执行**:关键路径的执行记录,包括入口、检查点和出口 -- **数据流**:数据在组件间的流转记录 -- **资源利用**:异常的资源使用模式(如内存持续增长) -- **错误和异常**:完整的错误上下文,不只是错误消息 - -### 2. 实施冲刺合同 - -在每个任务开始前,生成者和评估者(可能是同一个 agent 的不同调用)协商一份合同——就像施工队开工前签的施工协议: - -```markdown -# 冲刺合同: 暗色模式支持 - -## 范围 -- 修改主题切换组件 -- 更新全局 CSS 变量 -- 添加暗色模式测试 - -## 验证标准 -- 每个组件的视觉回归测试通过 -- 主流程端到端测试通过 -- 无样式闪烁 (FOUC) - -## 排除项 -- 不处理打印样式 -- 不处理第三方组件暗色模式 -``` - -### 3. 建立评估评分标准 - -把"好不好"变成可量化的评分——就像给体操比赛定评分标准: - -```markdown -# 评分标准 - -| 维度 | A | B | C | D | -|------|---|---|---|---| -| 代码正确性 | 所有测试通过 | 主流程通过 | 部分通过 | 编译失败 | -| 架构合规 | 完全合规 | 轻微偏离 | 明显偏离 | 严重违反 | -| 测试覆盖 | 主流程+边缘 | 仅主流程 | 仅有骨架 | 无测试 | -``` - -### 4. 用 OpenTelemetry 标准化 - -为每个 harness 会话创建一个 trace,每个任务创建一个 span,每个验证步骤创建子 span。使用标准属性标注关键信息。这样可观测性数据可以和标准工具链(Jaeger、Zipkin)集成。 - -## Anthropic 的三 agent 架构实验 - -Anthropic 在 2026 年 3 月发布了一项系统性的 harness 实验。他们用三种架构跑同一个任务("用 Web Audio API 做一个浏览器端 DAW"),记录了详细的阶段数据: - -| Agent 和阶段 | 时长 | 成本 | -|------------|------|------| -| Planner(规划者) | 4.7 分钟 | $0.46 | -| Build 第 1 轮 | 2 小时 7 分钟 | $71.08 | -| QA 第 1 轮 | 8.8 分钟 | $3.24 | -| Build 第 2 轮 | 1 小时 2 分钟 | $36.89 | -| QA 第 2 轮 | 6.8 分钟 | $3.09 | -| Build 第 3 轮 | 10.9 分钟 | $5.88 | -| QA 第 3 轮 | 9.6 分钟 | $4.06 | -| **总计** | **3 小时 50 分钟** | **$124.70** | - -三个 agent 各司其职,每个都有明确的可观测性角色: - -**Planner(规划者)**:接收一段 1-4 句话的用户需求,扩展成完整产品规格。被要求"大胆设定范围"并且"专注于产品上下文和高层技术设计,而不是详细的技术实现"。原因是:如果 planner 过早指定了粒度技术细节且搞错了,错误会级联到下游实现。更好的做法是约束交付物,让 agent 在执行中自己找到路径。就像建筑设计师只画效果图和结构图,不规定每块砖怎么砌。 - -**Generator(生成者)**:按 sprint 逐个功能实现。每个 sprint 前和 evaluator 协商一份 sprint 合同——约定这个功能块"做完"的标准。然后按合同实现,自评后交给 QA。按合同施工,不按感觉施工。 - -**Evaluator(评估者)**:用 Playwright MCP 像用户一样点击运行中的应用,测试 UI 功能、API 端点和数据库状态。对每个 sprint 按四个维度评分——产品深度、功能性、视觉设计和代码质量。每个维度有硬性阈值,任一不达标则 sprint 失败,generator 收到详细反馈后修复。就像验收工程师拿着验收标准逐项检查——不达标就打回去重做。 - -QA 第 1 轮反馈的示例——"这是一个视觉上令人印象深刻的应用,AI 集成工作良好,但核心 DAW 功能有几个是展示性的,没有交互深度:剪辑不能拖拽/移动,没有乐器 UI 面板(合成器旋钮、鼓垫),没有视觉效果编辑器(EQ 曲线、压缩器仪表)"。这些不是边缘情况——它们是让 DAW 可用的核心交互。具体的、有证据的反馈,不是"感觉不对"。 - -Evaluator 不是一开始就这么强。早期版本会识别出合理的问题,然后说服自己这些问题不严重,最终批准工作。调校方式是:读 evaluator 的日志,找到它的判断和人类判断分叉的地方,更新 QA 的 prompt 解决那些问题。经过几轮这种开发循环,evaluator 的评分才变得合理。就像训练一个新验收工程师——一开始他太宽容,出了几次事故后学会了严格。 - -> 来源:[Anthropic: Harness design for long-running application development](https://www.anthropic.com/engineering/harness-design-long-running-apps) - -## 关键要点 - -- **可观测性是 harness 的架构属性**——不是事后添加的功能,而是设计时必须考虑的核心能力。仪表盘不是可选配件,是出厂标配。 -- **双层可观测性缺一不可**——运行时信号解释"发生了什么",过程工件解释"为什么这样做"。速度表和导航系统各有各的用处。 -- **冲刺合同前置对齐工作**——防止"生成者做了评估者因可预见原因立即拒绝的东西"。施工协议要在开工前签,不是完工后补。 -- **评分标准让评估可复现**——不同评估者对同一输出产生相似评分。有了评分标准,十个裁判的分才不会差太远。 -- **可观测性缺失导致 30-50% 的会话时间浪费在重复诊断上**。 - -## 延伸阅读 - -- [Observability Engineering - Charity Majors](https://www.honeycomb.io/blog/observability-engineering-book) — 现代可观测性工程的理论和实践框架 -- [Dapper - Google (Sigelman et al.)](https://research.google/pubs/pub36356/) — 大规模分布式追踪的开创性实践 -- [Harness Design - Anthropic](https://www.anthropic.com/engineering/harness-design-long-running-apps) — 引入冲刺合同和评估评分标准 -- [Site Reliability Engineering - Google](https://sre.google/sre-book/table-of-contents/) — 可观测性在生产系统中的系统化应用 - -## 练习 - -1. **可观测性差距分析**:审查你当前的 harness,评估系统层和过程层可观测性。找出无法从现有信号区分的系统状态,提出补充方案。 - -2. **冲刺合同实践**:为一个真实任务写冲刺合同。让 agent 按合同执行,对比没有合同时的效率和质量差异。 - -3. **任务轨迹构建**:记录一个完整编码任务中 agent 的每一步操作。用 OpenTelemetry 语义约定标注。分析轨迹中的信息瓶颈——哪些步骤的决策缺乏足够的信号支持。 diff --git a/courses/learn-harness-engineering/docs/zh/lectures/lecture-12-why-every-session-must-leave-a-clean-state/code/benchmark-comparison-template.md b/courses/learn-harness-engineering/docs/zh/lectures/lecture-12-why-every-session-must-leave-a-clean-state/code/benchmark-comparison-template.md deleted file mode 100644 index d603f6f2..00000000 --- a/courses/learn-harness-engineering/docs/zh/lectures/lecture-12-why-every-session-must-leave-a-clean-state/code/benchmark-comparison-template.md +++ /dev/null @@ -1,18 +0,0 @@ -# 基准对比模板 - -Harness A: - -- 完成率 -- 平均重试次数 -- 人工审查前捕获的缺陷 - -Harness B: - -- 完成率 -- 平均重试次数 -- 人工审查前捕获的缺陷 - -解读: - -- 哪个 harness 改变了结果? -- 哪个 harness 改变了获得结果的成本? diff --git a/courses/learn-harness-engineering/docs/zh/lectures/lecture-12-why-every-session-must-leave-a-clean-state/code/benchmark-runner.ts b/courses/learn-harness-engineering/docs/zh/lectures/lecture-12-why-every-session-must-leave-a-clean-state/code/benchmark-runner.ts deleted file mode 100644 index 0388cad7..00000000 --- a/courses/learn-harness-engineering/docs/zh/lectures/lecture-12-why-every-session-must-leave-a-clean-state/code/benchmark-runner.ts +++ /dev/null @@ -1,216 +0,0 @@ -/** - * benchmark-runner.ts - * - * Reads a benchmark task definition (JSON array of tasks with pass criteria), - * "executes" each task, records timing and pass/fail, outputs a comparison - * report showing which tasks pass and which fail. - * - * Run: npx tsx docs/lectures/lecture-12-why-every-session-must-leave-a-clean-state/code/benchmark-runner.ts - */ - -// --------------------------------------------------------------------------- -// Types -// --------------------------------------------------------------------------- - -interface BenchmarkTask { - id: string; - name: string; - category: string; - passCriteria: string[]; - expectedDurationMs: number; - // Simulated actual results - actualDurationMs: number; - actualPass: boolean; - failureReason?: string; -} - -interface BenchmarkResult { - id: string; - name: string; - category: string; - criteriaTotal: number; - criteriaPassed: number; - pass: boolean; - expectedMs: number; - actualMs: number; - durationDelta: number; - failureReason?: string; -} - -// --------------------------------------------------------------------------- -// Benchmark task definitions -// --------------------------------------------------------------------------- - -const benchmarkTasks: BenchmarkTask[] = [ - { - id: "bench-001", - name: "Import markdown document", - category: "Document Pipeline", - passCriteria: ["File accepted", "Chunks created", "Metadata stored"], - expectedDurationMs: 100, - actualDurationMs: 95, - actualPass: true, - }, - { - id: "bench-002", - name: "Import PDF document", - category: "Document Pipeline", - passCriteria: ["File accepted", "Text extracted", "Chunks created", "Metadata stored"], - expectedDurationMs: 200, - actualDurationMs: 310, - actualPass: false, - failureReason: "PDF text extraction failed on page 3 -- encoding issue", - }, - { - id: "bench-003", - name: "Ask grounded question", - category: "Q&A Pipeline", - passCriteria: ["Query received", "Relevant chunks retrieved", "Answer generated", "Citations present"], - expectedDurationMs: 500, - actualDurationMs: 480, - actualPass: true, - }, - { - id: "bench-004", - name: "Ask question with no relevant docs", - category: "Q&A Pipeline", - passCriteria: ["Query received", "Graceful 'no results' message", "No hallucinated citations"], - expectedDurationMs: 400, - actualDurationMs: 420, - actualPass: false, - failureReason: "Model hallucinated a citation instead of saying 'no results'", - }, - { - id: "bench-005", - name: "Delete imported document", - category: "Document Pipeline", - passCriteria: ["Document removed from list", "Chunks removed from index", "No orphan data"], - expectedDurationMs: 150, - actualDurationMs: 145, - actualPass: true, - }, - { - id: "bench-006", - name: "Concurrent user access", - category: "Security", - passCriteria: ["Users isolated", "No cross-user data leak", "Performance within SLA"], - expectedDurationMs: 300, - actualDurationMs: 850, - actualPass: false, - failureReason: "Cross-user data leak detected + response time exceeded SLA (850ms > 500ms)", - }, - { - id: "bench-007", - name: "Session continuity after restart", - category: "Reliability", - passCriteria: ["State persisted", "Session recovers", "No data loss"], - expectedDurationMs: 200, - actualDurationMs: 180, - actualPass: true, - }, - { - id: "bench-008", - name: "API rate limiting", - category: "Security", - passCriteria: ["Rate limit enforced", "429 response after limit", "Legitimate traffic unaffected"], - expectedDurationMs: 100, - actualDurationMs: 100, - actualPass: true, - }, -]; - -// --------------------------------------------------------------------------- -// Execution simulation -// --------------------------------------------------------------------------- - -function executeBenchmark(tasks: BenchmarkTask[]): BenchmarkResult[] { - return tasks.map((task) => ({ - id: task.id, - name: task.name, - category: task.category, - criteriaTotal: task.passCriteria.length, - criteriaPassed: task.actualPass ? task.passCriteria.length : Math.floor(task.passCriteria.length * 0.5), - pass: task.actualPass, - expectedMs: task.expectedDurationMs, - actualMs: task.actualDurationMs, - durationDelta: task.actualDurationMs - task.expectedDurationMs, - failureReason: task.failureReason, - })); -} - -// --------------------------------------------------------------------------- -// Reporting -// --------------------------------------------------------------------------- - -function pad(s: string, len: number): string { - return s.length >= len ? s : s + " ".repeat(len - s.length); -} - -function run(): void { - console.log("\n" + "=".repeat(100)); - console.log(" BENCHMARK RUNNER -- Task Execution Report"); - console.log("=".repeat(100)); - - const results = executeBenchmark(benchmarkTasks); - - // Detailed results - const header = `| ${pad("ID", 10)}| ${pad("Task", 35)}| ${pad("Category", 18)}| ${pad("Pass?", 6)}| ${pad("Criteria", 10)}| ${pad("Expected", 10)}| ${pad("Actual", 10)}| ${pad("Delta", 8)}|`; - const sep = `|${"-".repeat(12)}|${"-".repeat(37)}|${"-".repeat(20)}|${"-".repeat(8)}|${"-".repeat(12)}|${"-".repeat(12)}|${"-".repeat(12)}|${"-".repeat(10)}|`; - console.log("\n" + header); - console.log(sep); - - for (const r of results) { - const passLabel = r.pass ? "PASS" : "FAIL"; - const criteriaLabel = `${r.criteriaPassed}/${r.criteriaTotal}`; - const deltaLabel = r.durationDelta >= 0 ? `+${r.durationDelta}ms` : `${r.durationDelta}ms`; - const marker = r.pass ? " " : ">>"; - - console.log( - `${marker}| ${pad(r.id, 10)}| ${pad(r.name, 35)}| ${pad(r.category, 18)}| ${pad(passLabel, 6)}| ${pad(criteriaLabel, 10)}| ${pad(r.expectedMs + "ms", 10)}| ${pad(r.actualMs + "ms", 10)}| ${pad(deltaLabel, 8)}|` - ); - } - - // Failure details - const failures = results.filter((r) => !r.pass); - if (failures.length > 0) { - console.log("\n" + "-".repeat(100)); - console.log(" FAILURE DETAILS"); - console.log("-".repeat(100)); - for (const f of failures) { - console.log(`\n [${f.id}] ${f.name}`); - console.log(` Category: ${f.category}`); - console.log(` Criteria: ${f.criteriaPassed}/${f.criteriaTotal} passed`); - console.log(` Reason: ${f.failureReason ?? "Unknown"}`); - console.log(` Timing: Expected ${f.expectedMs}ms, actual ${f.actualMs}ms (delta: ${f.durationDelta >= 0 ? "+" : ""}${f.durationDelta}ms)`); - } - } - - // Summary - console.log("\n" + "=".repeat(100)); - console.log(" SUMMARY BY CATEGORY"); - console.log("=".repeat(100) + "\n"); - - const categories = [...new Set(results.map((r) => r.category))]; - const catHeader = `| ${pad("Category", 20)}| ${pad("Total", 8)}| ${pad("Passed", 8)}| ${pad("Failed", 8)}| ${pad("Pass Rate", 12)}|`; - const catSep = `|${"-".repeat(22)}|${"-".repeat(10)}|${"-".repeat(10)}|${"-".repeat(10)}|${"-".repeat(14)}|`; - console.log(catHeader); - console.log(catSep); - - for (const cat of categories) { - const catResults = results.filter((r) => r.category === cat); - const catPassed = catResults.filter((r) => r.pass).length; - const catFailed = catResults.filter((r) => !r.pass).length; - const rate = Math.round((catPassed / catResults.length) * 100); - - console.log(`| ${pad(cat, 20)}| ${pad(String(catResults.length), 8)}| ${pad(String(catPassed), 8)}| ${pad(String(catFailed), 8)}| ${pad(rate + "%", 12)}|`); - } - - // Overall - const totalPassed = results.filter((r) => r.pass).length; - const totalFailed = results.filter((r) => !r.pass).length; - console.log("\n" + "-".repeat(100)); - console.log(` OVERALL: ${totalPassed}/${results.length} passed (${Math.round((totalPassed / results.length) * 100)}%), ${totalFailed} failures`); - console.log("=".repeat(100) + "\n"); -} - -run(); diff --git a/courses/learn-harness-engineering/docs/zh/lectures/lecture-12-why-every-session-must-leave-a-clean-state/code/cleanup-loop.md b/courses/learn-harness-engineering/docs/zh/lectures/lecture-12-why-every-session-must-leave-a-clean-state/code/cleanup-loop.md deleted file mode 100644 index fe98d0a6..00000000 --- a/courses/learn-harness-engineering/docs/zh/lectures/lecture-12-why-every-session-must-leave-a-clean-state/code/cleanup-loop.md +++ /dev/null @@ -1,9 +0,0 @@ -# 清理循环示例 - -周期性清理任务: - -- 扫描过期文档 -- 扫描结构性违规 -- 更新质量评级 -- 创建针对性的清理 PR -- 清理后重新运行固定的基准切片 diff --git a/courses/learn-harness-engineering/docs/zh/lectures/lecture-12-why-every-session-must-leave-a-clean-state/code/cleanup-scanner.ts b/courses/learn-harness-engineering/docs/zh/lectures/lecture-12-why-every-session-must-leave-a-clean-state/code/cleanup-scanner.ts deleted file mode 100644 index 6b35905c..00000000 --- a/courses/learn-harness-engineering/docs/zh/lectures/lecture-12-why-every-session-must-leave-a-clean-state/code/cleanup-scanner.ts +++ /dev/null @@ -1,336 +0,0 @@ -/** - * cleanup-scanner.ts - * - * Scans a project directory for stale artifacts, dead code, structural - * violations, and outputs a cleanup report. Helps enforce the "clean state - * at end of every session" principle. - * - * Usage: - * npx tsx docs/lectures/lecture-12.../code/cleanup-scanner.ts [path] - * (defaults to current working directory) - * - * Run: npx tsx docs/lectures/lecture-12-why-every-session-must-leave-a-clean-state/code/cleanup-scanner.ts - */ - -import * as fs from "node:fs"; -import * as path from "node:path"; - -// --------------------------------------------------------------------------- -// Types -// --------------------------------------------------------------------------- - -interface ScanResult { - category: string; - check: string; - severity: "critical" | "warning" | "info"; - found: string[]; - description: string; -} - -// --------------------------------------------------------------------------- -// Scanner checks -// --------------------------------------------------------------------------- - -interface ScannerCheck { - category: string; - name: string; - severity: "critical" | "warning" | "info"; - description: string; - scan: (dir: string) => string[]; -} - -function createChecks(): ScannerCheck[] { - return [ - { - category: "Stale Artifacts", - name: "Temporary files (*.tmp, *.bak, *.swp)", - severity: "warning", - description: "Leftover temp files from editing or processing", - scan: (dir) => findFiles(dir, [".tmp", ".bak", ".swp", "~"]), - }, - { - category: "Stale Artifacts", - name: "Debug/log files in source", - severity: "warning", - description: "Log files that should not be in the source tree", - scan: (dir) => { - const found: string[] = []; - const logPatterns = [".log"]; - for (const p of findFiles(dir, logPatterns)) { - // Only flag log files in source directories - if (p.includes("/src/") || p.includes("/lib/") || p.includes("/app/")) { - found.push(p); - } - } - return found; - }, - }, - { - category: "Dead Code", - name: "Unused imports (placeholder detection)", - severity: "info", - description: "Files that may contain unused code (heuristic scan)", - scan: (dir) => { - const found: string[] = []; - const srcDirs = ["src", "lib", "app"]; - for (const srcDir of srcDirs) { - const srcPath = path.join(dir, srcDir); - if (fs.existsSync(srcPath)) { - scanForPatterns(srcPath, ["TODO:", "FIXME:", "HACK:", "XXX:"], found, dir); - } - } - return found; - }, - }, - { - category: "Structural Violations", - name: "Missing .gitignore", - severity: "warning", - description: "No .gitignore file found -- risk of committing build artifacts", - scan: (dir) => fs.existsSync(path.join(dir, ".gitignore")) ? [] : [".gitignore (MISSING)"], - }, - { - category: "Structural Violations", - name: "Build artifacts in source", - severity: "critical", - description: "Compiled output (dist/, build/) mixed with source", - scan: (dir) => { - const found: string[] = []; - // Check if dist/ or build/ exists inside src/ - const dirs = ["src/dist", "src/build", "lib/dist", "lib/build"]; - for (const d of dirs) { - if (fs.existsSync(path.join(dir, d))) { - found.push(d + "/"); - } - } - return found; - }, - }, - { - category: "Structural Violations", - name: "node_modules in source tree", - severity: "critical", - description: "node_modules directory found outside root (nested dependency)", - scan: (dir) => { - const found: string[] = []; - const srcDirs = ["src", "lib", "app", "test", "tests"]; - for (const srcDir of srcDirs) { - const nmPath = path.join(dir, srcDir, "node_modules"); - if (fs.existsSync(nmPath)) { - found.push(srcDir + "/node_modules/"); - } - } - return found; - }, - }, - { - category: "Session Cleanliness", - name: "Uncommitted changes indicator", - severity: "info", - description: "Checks for common artifacts of incomplete sessions", - scan: (dir) => { - const found: string[] = []; - const indicators = [ - "WIP.md", "IN_PROGRESS.md", "scratch.ts", "scratch.js", - "debug.ts", "test-manual.ts", "temp.ts", - ]; - for (const f of indicators) { - if (fs.existsSync(path.join(dir, f))) { - found.push(f); - } - } - return found; - }, - }, - { - category: "Session Cleanliness", - name: "Empty directories", - severity: "info", - description: "Empty directories that may be leftover from incomplete work", - scan: (dir) => { - const found: string[] = []; - const commonDirs = ["src", "lib", "app", "test", "tests", "docs"]; - for (const d of commonDirs) { - const dp = path.join(dir, d); - if (fs.existsSync(dp) && fs.statSync(dp).isDirectory()) { - try { - const contents = fs.readdirSync(dp); - if (contents.length === 0) { - found.push(d + "/"); - } - } catch { - // Skip directories we can't read - } - } - } - return found; - }, - }, - { - category: "Configuration", - name: "Environment files in source", - severity: "critical", - description: ".env files that may contain secrets", - scan: (dir) => { - const found: string[] = []; - const envFiles = [".env", ".env.local", ".env.production", ".env.staging"]; - for (const f of envFiles) { - if (fs.existsSync(path.join(dir, f))) { - found.push(f); - } - } - return found; - }, - }, - ]; -} - -// --------------------------------------------------------------------------- -// Helper functions -// --------------------------------------------------------------------------- - -function findFiles(dir: string, extensions: string[]): string[] { - const found: string[] = []; - - function walk(current: string, depth: number): void { - if (depth > 4) return; // Limit recursion depth - try { - const entries = fs.readdirSync(current, { withFileTypes: true }); - for (const entry of entries) { - // Skip common non-source directories - if (entry.isDirectory() && ["node_modules", ".git", "dist", "build", ".next", "coverage"].includes(entry.name)) { - continue; - } - - const fullPath = path.join(current, entry.name); - const relativePath = path.relative(dir, fullPath); - - if (entry.isDirectory()) { - walk(fullPath, depth + 1); - } else if (entry.isFile()) { - if (extensions.some((ext) => entry.name.endsWith(ext))) { - found.push(relativePath); - } - } - } - } catch { - // Skip directories we can't read - } - } - - walk(dir, 0); - return found; -} - -function scanForPatterns(dir: string, patterns: string[], results: string[], baseDir: string): void { - try { - const entries = fs.readdirSync(dir, { withFileTypes: true }); - for (const entry of entries) { - if (entry.isDirectory()) { - if (!["node_modules", ".git", "dist", "build"].includes(entry.name)) { - scanForPatterns(path.join(dir, entry.name), patterns, results, baseDir); - } - continue; - } - - const fullPath = path.join(dir, entry.name); - if (!entry.name.endsWith(".ts") && !entry.name.endsWith(".tsx") && !entry.name.endsWith(".js")) { - continue; - } - - try { - const content = fs.readFileSync(fullPath, "utf-8"); - const lines = content.split("\n"); - for (let i = 0; i < Math.min(lines.length, 50); i++) { - for (const pattern of patterns) { - if (lines[i].includes(pattern)) { - const relative = path.relative(baseDir, fullPath); - results.push(`${relative}:${i + 1} contains ${pattern}`); - } - } - } - } catch { - // Skip files we can't read - } - } - } catch { - // Skip directories we can't read - } -} - -// --------------------------------------------------------------------------- -// Reporting -// --------------------------------------------------------------------------- - -function pad(s: string, len: number): string { - return s.length >= len ? s : s + " ".repeat(len - s.length); -} - -function run(): void { - const targetDir = process.argv[2] ? path.resolve(process.argv[2]) : process.cwd(); - - console.log("\n" + "=".repeat(90)); - console.log(" CLEANUP SCANNER -- Stale Artifact & Violation Report"); - console.log("=".repeat(90)); - console.log(` Scanning: ${targetDir}\n`); - - const checks = createChecks(); - const results: ScanResult[] = []; - - for (const check of checks) { - const found = check.scan(targetDir); - results.push({ - category: check.category, - check: check.name, - severity: check.severity, - found, - description: check.description, - }); - } - - // Report - const header = `| ${pad("Category", 20)}| ${pad("Check", 40)}| ${pad("Severity", 10)}| ${pad("Count", 6)}| Details`; - const sep = `|${"-".repeat(22)}|${"-".repeat(42)}|${"-".repeat(12)}|${"-".repeat(8)}|${"-".repeat(30)}`; - console.log(header); - console.log(sep); - - for (const r of results) { - const count = r.found.length; - const detail = count > 0 ? r.found.slice(0, 2).join(", ") : "(clean)"; - const marker = count > 0 && r.severity === "critical" ? ">>" : count > 0 ? " * " : " "; - - console.log(`${marker}| ${pad(r.category, 20)}| ${pad(r.check, 40)}| ${pad(r.severity, 10)}| ${pad(String(count), 6)}| ${detail}`); - } - - // Summary - const criticals = results.filter((r) => r.severity === "critical" && r.found.length > 0); - const warnings = results.filter((r) => r.severity === "warning" && r.found.length > 0); - const infos = results.filter((r) => r.severity === "info" && r.found.length > 0); - const clean = results.filter((r) => r.found.length === 0); - - console.log("\n" + "=".repeat(90)); - console.log(" CLEANUP SUMMARY"); - console.log("=".repeat(90) + "\n"); - - console.log(` Critical issues: ${criticals.length}`); - console.log(` Warnings: ${warnings.length}`); - console.log(` Info items: ${infos.length}`); - console.log(` Clean checks: ${clean.length}/${results.length}`); - - if (criticals.length > 0) { - console.log("\n ACTION REQUIRED (Critical):"); - for (const c of criticals) { - console.log(` - ${c.check}: ${c.found.length} item(s) found`); - console.log(` ${c.description}`); - } - } - - const totalIssues = results.reduce((s, r) => s + r.found.length, 0); - if (totalIssues === 0) { - console.log("\n Project is in a clean state. No issues found.\n"); - } else { - console.log(`\n Total issues found: ${totalIssues}. Run this scanner at the end of every session to maintain clean state.\n`); - } -} - -run(); diff --git a/courses/learn-harness-engineering/docs/zh/lectures/lecture-12-why-every-session-must-leave-a-clean-state/code/index.md b/courses/learn-harness-engineering/docs/zh/lectures/lecture-12-why-every-session-must-leave-a-clean-state/code/index.md deleted file mode 100644 index ad7c2080..00000000 --- a/courses/learn-harness-engineering/docs/zh/lectures/lecture-12-why-every-session-must-leave-a-clean-state/code/index.md +++ /dev/null @@ -1,8 +0,0 @@ -# 第 12 讲代码 - -使用此文件夹存放以下方面的示例: - -- 基准切片 -- 清理任务 -- 熵减示例 -- 可重复的 harness 运行 diff --git a/courses/learn-harness-engineering/docs/zh/lectures/lecture-12-why-every-session-must-leave-a-clean-state/index.md b/courses/learn-harness-engineering/docs/zh/lectures/lecture-12-why-every-session-must-leave-a-clean-state/index.md deleted file mode 100644 index 0483de8c..00000000 --- a/courses/learn-harness-engineering/docs/zh/lectures/lecture-12-why-every-session-must-leave-a-clean-state/index.md +++ /dev/null @@ -1,198 +0,0 @@ -[English Version →](../../../en/lectures/lecture-12-why-every-session-must-leave-a-clean-state/) - -> 本篇代码示例:[code/](https://github.com/walkinglabs/learn-harness-engineering/blob/main/docs/zh/lectures/lecture-12-why-every-session-must-leave-a-clean-state/code/) -> 实战练习:[Project 06. 搭建一套完整的 agent 工作环境](./../../projects/project-06-runtime-observability-and-debugging/index.md) - -# 第十二讲. 每次会话结束前都做好交接 - -你的 agent 跑了一下午,改了 20 个文件,提交了代码,会话结束。下一个 agent 会话开始,一上来就发现:构建失败了、测试红了、临时调试文件到处都是、功能清单没更新、进度完全不清楚。新会话的前 30 分钟全花在"搞清楚上一个会话到底干了什么"上。 - -这就像大学宿舍——你不倒垃圾,下一个室友进来就得替你收拾。他本来的计划是复习考试,结果先花半小时收拾你留下的烂摊子。更要命的是,收拾完了他也不想复习了——环境太差,心情也差。 - -OpenAI 和 Anthropic 都明确指出:**长期可靠性取决于操作纪律,不仅是单次运行的成功。** 每个会话结束时的状态质量,直接决定下一个会话的效率。 - -## 熵增是默认状态 - -Lehman 的软件演化定律告诉我们:持续变更的系统,除非主动管理,否则复杂性必然增加。这对 AI 编码 agent 尤其成立——agent 每次会话都会引入变更,如果不在退出时清理,技术债务会指数级累积。宿舍不打扫,脏衣服和外卖盒只会越堆越多,不会自己消失。 - -OpenAI 在 5 个月的 Codex 实验中观察到:**agent 会复制仓库中已有的模式——即使那些模式是不均匀的或次优的。** 随着时间的推移,这种复制必然导致漂移。就像宿舍里的公共区域——第一个人在桌上放了一个杯子,第二个人觉得"反正已经乱了"又放了一个,一周后桌上堆满了。 - -OpenAI 团队最初花每周五(20% 的工作时间)手动清理 "AI slop"。不出所料,这种方式不可扩展。就像宿舍每周大扫除一次——其他六天的脏乱差还是得忍。他们的解决方案是: - -1. **把"黄金原则"编码进仓库**:比如"优先使用共享工具包而非手写的 ad-hoc 辅助函数"(保持不变量集中)、"不要 YOLO 式地猜数据结构"(验证边界或依赖类型化 SDK)。这些原则是具体的、机械的、可自动检查的。 -2. **建立周期性的清理流程**:一组后台 Codex 任务定期扫描偏差,更新质量评分,开针对性的重构 PR。大多数可以在一分钟内审查并自动合并。就像宿舍安装了自动扫地机器人——不用你动手,定期清理。 -3. **人类品味捕获一次,持续执行**:审查意见、重构 PR、用户侧 bug 都被转化为文档更新或直接编码到工具中。当文档不够用时,把规则提升为代码。就像把宿舍公约从"口头约定"变成"贴在门上的检查表"。 - -这个机制就像垃圾回收——技术债是高息贷款,持续小额还清几乎总是比攒到一次性爆发好得多。 - -> 来源:[OpenAI: Harness engineering: leveraging Codex in an agent-first world](https://openai.com/index/harness-engineering/) - -## 清洁状态:不只看地上有没有垃圾 - -清洁状态不是单一的"代码能编译"。代码能无错构建——这是最基本的,下一个会话不应该先修构建错误。就像你搬出去的时候水电不能是断的。所有测试也得通过,包括会话之前就存在的测试——会话有责任不破坏已有功能。而且要在 CI 环境验证,不是"在我机器上通过"。 - -```mermaid -flowchart LR - Work["功能工作已完成"] --> Build{"构建通过?"} - Build -->|是| Test{"测试通过?"} - Build -->|否| Fix["先修好再退出"] - Test -->|是| Record["更新功能清单 + 进度"] - Test -->|否| Fix - Record --> Cleanup["清理临时工件 / 调试代码"] - Cleanup --> Startup{"标准启动路径可用?"} - Startup -->|是| Clean["干净交接"] - Startup -->|否| Fix - Fix --> Build -``` - -但这还不够。当前进度必须记录在机器可读的工件中——已完成的子任务和通过标准、进行中但未完成的子任务和当前状态、尚未开始的子任务。好的进度记录减少 60-80% 的会话启动诊断时间。调试日志、临时文件、注释掉的代码、TODO 标记这些临时工件得清理干净,它们增加下一个会话的认知负担。标准启动路径也必须可用——下一个会话能不能不人工干预就开始工作?环境初始化、代码库加载、上下文获取、任务选择,这些路径不能被破坏。 - -```mermaid -flowchart LR - Dirty["这次退出时
    测试是红的、临时文件没删、进度也没写"] --> Diagnose["下个会话先花时间
    搞清楚发生了什么"] - Diagnose --> Fragile["接着在一个很乱的仓库上继续改"] - Fragile --> More["更多调试文件、更多坏检查、
    更多说不清的进度"] - More --> Dirty - - Clean["这次退出前
    测试是绿的、进度写了、临时文件删了"] --> Fast["下个会话打开仓库就能继续写代码"] - Fast --> Stable["不用先救火"] - Stable --> Clean -``` - -## 核心概念 - -- **清洁状态**:会话结束时系统满足五个条件——构建通过、测试通过、进度已记录、无过时工件、启动路径可用。缺一个都不算"做完"。 -- **会话完整性**:类比数据库事务——要么全部提交并留下清洁状态,要么回滚到上一致状态。没有中间地带。 -- **质量文档**:对每个模块的质量等级做持续记录的活跃工件。不是一次性评估,而是跟踪代码库是变强了还是变弱了。 -- **清理循环**:定期的维护会话,目标是系统性减少代码库中的熵。不是紧急修复,而是常规运维——就像宿舍的每周值日表。 -- **harness 简化**:随着模型能力提升,定期移除不再必要的 harness 组件。今天必要的约束,三个月后可能是多余的开销。 -- **幂等清理**:清理操作无论执行多少次都产生相同结果。确保清理在失败重试场景中仍然安全。 - -## "以后再清理"是永远不清理 - -最常见的心理陷阱是"这次来不及清理了,下次再弄"。但下次的 agent 不知道你上次留下了什么——它看到的是一堆混乱的代码和不确定的状态。它会花大量时间推断"这堆代码里哪些是有意的,哪些是临时的"。 - -更糟的是,每个会话都有自己的任务目标。新会话来的时候是要做新功能的,不是来清理上一个会话的烂摊子的。它会忽略混乱直接开始新工作,然后在混乱的基础上引入更多混乱。这是熵增的正反馈循环——就像宿舍越乱你越不想收拾,越不收拾越乱,最后谁都不想回宿舍了。 - -数据为证。一个使用 agent 持续开发 12 周的项目,没有清洁策略的情况下: - -- 第 1 周:构建通过率 100%,测试通过率 100%,新会话启动 5 分钟 -- 第 4 周:构建 95%,测试 92%,启动 15 分钟 -- 第 8 周:构建 82%,测试 78%,启动 35 分钟 -- 第 12 周:构建 68%,测试 61%,启动 60+ 分钟 - -同样的项目,有清洁策略的情况下: - -- 第 1 周:100%,100%,5 分钟 -- 第 12 周:97%,95%,9 分钟 - -12 周后,构建通过率差 29 个百分点,新会话启动时间差 85%。这不是理论推导,是实际可观测到的差异。每周不倒垃圾的宿舍和每周倒垃圾的宿舍,12 周后的差距是惊人的。 - -## 怎么做 - -### 1. 清洁状态是完成的必要条件 - -在 harness 里明确定义:**会话完成 = 任务通过验证 AND 清洁状态检查通过。** 缺任何一个,会话不算完成。在 CLAUDE.md 里写: - -``` -## 会话退出检查清单 -- [ ] 构建通过 (npm run build) -- [ ] 所有测试通过 (npm test) -- [ ] 功能清单已更新 -- [ ] 无调试代码残留 (console.log, debugger, TODO) -- [ ] 标准启动路径可用 (npm run dev) -``` - -### 2. 双模式清理策略 - -结合两种清理模式: - -**即时清理(每个会话结束时)**:清理本次会话创建的临时工件、更新功能清单状态、确保构建和测试通过。这是"引用计数式"清理——用完就清。就像吃完饭马上洗碗,不留到第二天。 - -**定期清理(每周一次)**:全系统扫描——处理累积的结构性问题、更新质量文档、运行基准测试检测漂移。这是"追踪式"清理——定期做一次大扫除。 - -### 3. 维护质量文档 - -质量文档是对每个模块持续评分的活跃工件——就像宿舍的卫生检查评分表: - -```markdown -# 质量文档 - -## 用户认证模块 (质量: A) -- 验证通过: 是 -- agent 可理解: 是 -- 测试稳定性: 稳定 -- 架构边界: 合规 -- 代码规范: 遵循 - -## 支付模块 (质量: C) -- 验证通过: 部分(支付回调未测试) -- agent 可理解: 困难(逻辑分散在 3 个文件) -- 测试稳定性: 不稳定(2 个 flaky 测试) -- 架构边界: 有违规 -- 代码规范: 部分遵循 -``` - -新会话读这个文档就知道优先处理哪里。质量评分最低的模块先修。就像卫生检查表上标记了"需要重点打扫的区域"——下一个值日生知道重点在哪里。 - -### 4. 定期简化 harness - -harness 里的每个组件之所以存在,是因为模型无法独立做好某件事。但随着模型改进,这些假设会过时。就像你大一的时候需要学长带着选课,到了大三你自己就知道怎么选了——学长的作用变小了,但有些事情你还是需要问。 - -Anthropic 的实验直接展示了这一点。他们最初的 harness 包含 sprint 拆分机制——把工作分成小块让 Sonnet 4.5 逐个完成。当 Opus 4.6 发布后,模型的原生能力已经可以自主处理工作分解,sprint 构造变成了不必要的开销。移除后,builder agent 能连续工作超过两小时而不会跑偏,反而更流畅。 - -但 evaluator 的情况不同。即使 Opus 4.6 能力更强,在任务接近模型能力边界时,evaluator 仍然提供了实际价值——捕获 generator 的遗漏功能和存根实现。这意味着 evaluator 不是一个固定的是/否决策,而是取决于任务难度相对于模型能力的位置。 - -**推荐做法**:每月挑一个 harness 组件,暂时禁用它,跑基准任务。如果结果没退化,永久移除。如果退化了,恢复或用更轻量的替代。就像定期检查宿舍公约——大四了还在执行"每天 10 点熄灯"就不太合理了。 - -一个更具体的原则:**随着模型改进,harness 的有趣组合不是变少了,而是移动了。** 以前必须解决的问题被模型能力覆盖了,但新的能力边界打开了以前不可能的 harness 设计。AI 工程师的工作是持续找到下一个有价值的组合。 - -### 5. 清理操作必须幂等 - -清理脚本要能安全地重复执行——就像打扫卫生,扫两遍比扫一遍更干净,但不会更脏: - -```bash -# 幂等的清理操作 -rm -f /tmp/debug-*.log # -f 确保文件不存在时不报错 -git checkout -- .env.local # 恢复到已知状态 -npm run test # 验证清理未破坏功能 -``` - -### 6. 高吞吐量改变了 merge 哲学 - -当 agent 的产出远超人类审查能力时,传统的 merge 哲学需要调整。OpenAI 团队的经验:在一个 agent 每天开 3.5 个 PR(且后来增加到更多)的环境里,最小化阻塞式 merge gate 是正确的。PR 应该短命。测试 flake 通常用后续运行解决,而不是无限期阻塞进度。在一个修正成本很低、等待成本很高的系统里,快速前进 + 快速修正是比缓慢确认更好的策略。 - -**注意**:这在低产出环境里是不负责任的。但在 agent 产出远超人类注意力的环境里,这往往是正确的权衡。关键判断标准:**修正一个 bug 的平均成本 vs 等待人类审查一个 PR 的平均成本。** 前者低于后者时,快速合并是对的。 - -## 实际案例 - -一个使用 agent 持续开发的 Electron 应用,12 周演化过程的对比数据: - -**无清洁策略(对照组)**——从不倒垃圾的宿舍:第 12 周,构建通过率 68%,测试通过率 61%,新会话启动 60+ 分钟,过时工件 103 个。 - -**有清洁策略(实验组)**——每天值日的宿舍:每个会话结束时执行完整清洁检查 + 每周清理循环。第 12 周,构建通过率 97%,测试通过率 95%,新会话启动 9 分钟,过时工件 11 个。 - -到第 12 周,实验组的构建通过率比对照组高 29 个百分点,测试通过率高 34 个百分点,新会话启动时间减少 85%。每天多花 5 分钟打扫卫生,12 周后省下了几十个小时的混乱时间。 - -## 关键要点 - -- **清洁状态是会话完成的必要条件**——不是可选的善后工作,是"完成定义"的一部分。你不倒垃圾,下一个室友就得替你倒。 -- **五个维度缺一不可**——构建、测试、进度、工件、启动,每个都要显式检查。 -- **质量文档让代码库健康可追踪**——知道哪里在退化才能主动修复。卫生检查表不是形式主义,是让你知道哪块地还没拖。 -- **定期简化 harness**——随着模型能力提升,移除不再必要的约束。大四了就别执行大一的宿舍公约了。 -- **"以后再清理"等于永远不清理**——熵增是默认状态,只有主动的清洁操作才能对抗它。 - -## 延伸阅读 - -- [Clean Code - Robert C. Martin](https://www.goodreads.com/book/show/3735293-clean-code) — 代码清洁性的系统化原则 -- [Harness Engineering - OpenAI](https://openai.com/index/harness-engineering/) — 可重复性作为 harness 设计的核心要求 -- [Effective Harnesses - Anthropic](https://www.anthropic.com/engineering/effective-harnesses-for-long-running-agents) — 清洁会话退出对长期可靠性的关键作用 -- [Programs, Life Cycles, and Laws of Software Evolution - Lehman](https://ieeexplore.ieee.org/document/1702314) — 软件演化定律,证明系统复杂性在无主动维护时必然增长 - -## 练习 - -1. **清洁状态检查表**:为你的代码库设计一个会话退出检查表,涵盖五个维度。在 5 个连续会话中应用,记录每个维度上的违反次数。 - -2. **基准对比实验**:固定任务集,两种 harness 变体(有/无清洁状态要求)各跑一遍。比较完成率、重试次数和缺陷逃逸率。 - -3. **harness 简化实践**:选一个 harness 组件,暂时禁用,跑基准任务。比较有无该组件的结果。决定保留、移除还是替换。 diff --git a/courses/learn-harness-engineering/docs/zh/projects/index.md b/courses/learn-harness-engineering/docs/zh/projects/index.md deleted file mode 100644 index 7134d5f1..00000000 --- a/courses/learn-harness-engineering/docs/zh/projects/index.md +++ /dev/null @@ -1,23 +0,0 @@ -# 欢迎来到项目实战 - -这里是 Learn Harness Engineering 的动手实践部分。仅仅阅读讲义是不够的,你需要亲自搭建环境,观察 Codex 或 Claude Code 等 Agent 在不同规则下的真实表现。 - -## 项目概览 - -本课程包含 6 个渐进式的实战项目,带你从零开始构建一个完整的 Agent 工作环境: - -1. **提示词驱动 vs 规则驱动**:对比一个纯 Prompt 驱动的 Agent 和带有基础 Harness 的 Agent 在表现上的差异。 -2. **让项目可读并接住上次工作**:学习如何重构代码仓库结构,使其对 AI 更加友好,并建立工作交接机制。 -3. **跨会话工作连续性**:设计状态文件和初始化脚本,让 Agent 可以在多次对话中断后无缝恢复工作。 -4. **运行反馈与行为修正**:引入运行时反馈机制,让 Agent 能够自己检查代码是否能跑通,并修正错误。 -5. **工作评审与自我验证**:建立独立的评审与验证环节,防止 Agent 产生“幻觉”或提前宣告胜利。 -6. **综合 Agent 工作环境**:搭建一套完整的、带有可观测性的最终 Agent 工作环境(综合演练)。 - -## 如何进行 - -每个项目文件夹中通常包含: -- `starter/`:你的起始工作区。 -- `solution/`:参考的最终实现(如果你卡住了可以参考)。 -- 相关的指导文档,说明本次任务的具体背景和目标。 - -请使用你习惯的 AI Coding Agent(如 Claude Code、Cursor、Trae 等)在每个项目的 `starter/` 目录下完成任务。 diff --git a/courses/learn-harness-engineering/docs/zh/projects/project-01-baseline-vs-minimal-harness/index.md b/courses/learn-harness-engineering/docs/zh/projects/project-01-baseline-vs-minimal-harness/index.md deleted file mode 100644 index a9d2ad4e..00000000 --- a/courses/learn-harness-engineering/docs/zh/projects/project-01-baseline-vs-minimal-harness/index.md +++ /dev/null @@ -1,70 +0,0 @@ -# Project 01. 只写提示词让 agent 做,和定好规则再让它做,差多少 - -> 相关讲义:[L01. 模型能力强,不等于执行可靠](./../../lectures/lecture-01-why-capable-agents-still-fail/index.md) · [L02. Harness 到底是什么](./../../lectures/lecture-02-what-a-harness-actually-is/index.md) -> 本篇模板文件:[templates/](https://github.com/walkinglabs/learn-harness-engineering/blob/main/docs/zh/resources/templates/) - -## 你要做什么 - -用 Electron 搭一个最简的知识库应用壳子——能启动窗口、左边显示文档列表、右边显示问答面板、本地有一个数据目录。任务本身不复杂,复杂的是你怎么让 agent 完成它。 - -你需要跑两次。第一次只给一段提示词,什么都不准备,看 agent 能做到什么程度。第二次提前在仓库里放好 `AGENTS.md`、`init.sh`、`feature_list.json`,用结构化的方式告诉 agent 该干什么、怎么验证、什么时候算做完。然后对比两次结果。 - -这个项目的核心不是写代码,是搞清楚"提前花 15 分钟准备规则"和"上来就让 agent 干"之间到底差多少。 - -## 用什么工具 - -- Claude Code 或 Codex(选一个,两次都用同一个) -- Git(管理分支和对比) -- Node.js + Electron(项目技术栈) -- 一个计时器(记录每次运行时间) - -## 具体步骤 - -### 准备工作 - -1. 从一个干净的 commit 出发,记录 commit hash。 -2. 创建两个分支:`p01-baseline` 和 `p01-improved`。 -3. 准备同一段任务提示词,内容是:"用 Electron 做一个知识库应用,窗口左边是文档列表区域,右边是问答面板区域,应用需要创建并使用本地数据目录。" - -### 第一次运行(弱 harness) - -切到 `p01-baseline` 分支。 - -1. 只用上面那段提示词启动 agent。 -2. 不提供 `AGENTS.md`,不提供启动脚本,不提供验收标准。 -3. 设定相同的时间上限和轮次上限(建议 30 分钟 / 20 轮)。 -4. agent 停下后,运行 `npm start`(或对应启动命令),看应用能不能跑起来。 -5. 记录:终端输出、关键 diff、agent 的最终总结。 -6. **不要手动修代码**。跑不起来就是跑不起来,如实记录。 - -### 第二次运行(强 harness) - -切到 `p01-improved` 分支。在启动 agent 之前,先在仓库里准备好: - -- `AGENTS.md`:写明项目结构、启动命令、Electron 层边界规则 -- `init.sh`:一键恢复可运行状态(`npm install && npm start`) -- `feature_list.json`:列出四个功能点及其完成状态 - -然后用和第一次相同的提示词启动 agent,同样的时间上限和轮次上限。agent 停下后,跑 `./init.sh`,记录结果。 - -## 怎么衡量结果 - -| 指标 | 说明 | -|------|------| -| 完成状态 | 完全完成 / 部分完成 / 失败 | -| 首次成功启动时间 | 从开始到 `npm start` 第一次成功运行 | -| 重试次数 | 中间需要人工干预几次才能跑起来 | -| 遗漏项 | agent 宣布完成时还有哪些功能没做 | -| 过早停止 | agent 是否在不可运行状态就宣布完成 | - -## 要交什么 - -- 弱 harness 运行记录:提示词、日志/对话记录、最终 diff、启动证据 -- 强 harness 运行记录:同上,加上你准备的 harness 文件 -- 一份对比笔记(1-2 页):两次运行的差异、数据、结论 - -## 对应讲义 - -- [Lecture 01 — 为什么强 agent 仍然失败](../../lectures/lecture-01-why-capable-agents-still-fail/index.md) -- [Lecture 02 — harness 到底是什么](../../lectures/lecture-02-what-a-harness-actually-is/index.md) -- [Lecture 06 — 为什么初始化需要单独一个阶段](../../lectures/lecture-06-why-initialization-needs-its-own-phase/index.md) diff --git a/courses/learn-harness-engineering/docs/zh/projects/project-02-agent-readable-workspace/index.md b/courses/learn-harness-engineering/docs/zh/projects/project-02-agent-readable-workspace/index.md deleted file mode 100644 index 9ed60e56..00000000 --- a/courses/learn-harness-engineering/docs/zh/projects/project-02-agent-readable-workspace/index.md +++ /dev/null @@ -1,88 +0,0 @@ -# Project 02. 让 agent 看懂项目、接住上次的工作 - -> 相关讲义:[L03. 让代码仓库成为唯一的事实来源](./../../lectures/lecture-03-why-the-repository-must-become-the-system-of-record/index.md) · [L04. 为什么一个巨大的指令文件会失败](./../../lectures/lecture-04-why-one-giant-instruction-file-fails/index.md) -> 本篇模板文件:[templates/](https://github.com/walkinglabs/learn-harness-engineering/blob/main/docs/zh/resources/templates/) - -## 你要做什么 - -P01 证明了准备规则有用。但 P01 的任务一次会话就干完了。真实开发不是这样的——你昨天干了一半,今天开新会话,agent 得从仓库状态里搞清楚"做了什么、没做什么、接下来干什么"。 - -这个项目要求你给仓库加上"可读性":让一个全新的 agent 打开仓库后,能快速理解项目结构、知道当前进度、接手上次的工作。具体任务是给知识库应用加上三个功能:文档导入、文档详情页、导入后的本地持久化。这些功能必须跨至少两个 agent 会话完成。 - -你需要跑两次。第一次不给 agent 任何帮助,看它在第二个会话里要花多久才能"接上"。第二次提前放好 `ARCHITECTURE.md`、`PRODUCT.md`、`session-handoff.md`,让它快速对齐上下文。 - -## 用什么工具 - -- Claude Code 或 Codex(和 P01 保持一致) -- Git -- Node.js + Electron -- 文本编辑器(写文档用) - -## 具体步骤 - -### 准备工作 - -1. 基于 P01 完成后的代码,从同一个 commit 出发。 -2. 创建两个分支:`p02-baseline` 和 `p02-improved`。 -3. 列出要实现的三个功能:文档导入流程、文档详情视图、文档持久化。两条分支任务范围完全一致。 - -### 第一次运行(弱 harness) - -切到 `p02-baseline` 分支。 - -**会话 A:** - -1. 启动 agent,只给任务描述,不给架构文档,不给进度文件。 -2. 故意在功能完成之前停止会话(比如只完成文档导入)。 -3. 不写任何交接文件。直接结束。 - -**会话 B:** - -1. 开一个全新的 agent 会话。 -2. 只说"继续开发",不给额外上下文。 -3. 记录 agent 花了多久才做出第一个有意义的代码修改。 -4. 记录 agent 哪些时候在"重新发现"本来已经知道的东西。 - -### 第二次运行(强 harness) - -切到 `p02-improved` 分支。在第一个会话之前,先在仓库里准备好: - -- `ARCHITECTURE.md`:描述项目结构、Electron 各层职责、数据流 -- `PRODUCT.md`:描述产品功能范围和当前阶段目标 -- `AGENTS.md`:启动命令、工作规则、验证方式 -- `init.sh`:一键恢复可运行状态 - -**会话 A:** - -1. 启动 agent,让它开始工作。 -2. 同样在功能完成之前停止。 -3. **这次要求 agent 更新 `session-handoff.md`**:记录做了什么、没做什么、下一步是什么。 - -**会话 B:** - -1. 开一个全新的 agent 会话。 -2. 让 agent 读 `session-handoff.md` 和 `feature_list.json`,然后继续。 -3. 同样记录接手速度和重复工作比例。 - -## 怎么衡量结果 - -| 指标 | 说明 | -|------|------| -| 会话 B 接手时间 | 从开始到第一个有效代码修改的时间 | -| 重新发现次数 | agent 重新了解架构、命令、状态等已有信息的次数 | -| 交接文件质量 | 交接记录是否完整、准确、可操作 | -| 重复工作比例 | 会话 B 中有多少工作量是在重复会话 A 已做过的事 | -| 最终完成状态 | 三个功能是否全部完成 | - -## 要交什么 - -- 弱 harness 的会话 A + B 日志/对话记录 -- 强 harness 的会话 A + B 日志/对话记录 -- 两次运行中产生的交接文件 -- 一份对比笔记:重点比较接手速度和上下文恢复质量 - -## 对应讲义 - -- [Lecture 03 — 为什么仓库必须成为唯一事实来源](../../lectures/lecture-03-why-the-repository-must-become-the-system-of-record/index.md) -- [Lecture 04 — 为什么一个大而全的指令文件不行](../../lectures/lecture-04-why-one-giant-instruction-file-fails/index.md) -- [Lecture 05 — 为什么长任务会丢失连续性](../../lectures/lecture-05-why-long-running-tasks-lose-continuity/index.md) diff --git a/courses/learn-harness-engineering/docs/zh/projects/project-03-multi-session-continuity/index.md b/courses/learn-harness-engineering/docs/zh/projects/project-03-multi-session-continuity/index.md deleted file mode 100644 index 720a5099..00000000 --- a/courses/learn-harness-engineering/docs/zh/projects/project-03-multi-session-continuity/index.md +++ /dev/null @@ -1,77 +0,0 @@ -# Project 03. 让 agent 关掉再打开还能接着干 - -> 相关讲义:[L05. 为什么长时任务会丢失上下文](./../../lectures/lecture-05-why-long-running-tasks-lose-continuity/index.md) · [L06. 为什么初始化需要单独一个阶段](./../../lectures/lecture-06-why-initialization-needs-its-own-phase/index.md) -> 本篇模板文件:[templates/](https://github.com/walkinglabs/learn-harness-engineering/blob/main/docs/zh/resources/templates/) - -## 你要做什么 - -P02 解决了"接手"的问题,但 agent 接手之后能不能把活干完、干对了,又是另一回事。这个项目要你给 agent 加上范围控制和验证关卡。 - -你要实现的知识库功能是:文档分块、元数据提取、索引进度显示、带引用的问答流程。这些功能比前两个项目复杂,agent 更容易跑偏——要么多做了不该做的,要么说做完了但其实没通过验证。 - -你需要一个 `feature_list.json`,每个功能有明确的状态(failing / passing)。规则很简单:一次只做一个功能,没有可运行的验证证据就不能标成 passing。跑两次,一次不给这些约束,一次严格执行,看结果差多少。 - -## 用什么工具 - -- Claude Code 或 Codex -- Git -- Node.js + Electron -- `feature_list.json`(模板参考 `docs/zh/resources/templates/feature_list.json`) - -## 具体步骤 - -### 准备工作 - -1. 基于 P02 完成后的代码,从同一个 commit 出发。 -2. 创建两个分支:`p03-baseline` 和 `p03-improved`。 -3. 定义四个功能:文档分块、元数据提取、索引进度 UI 显示、带引用的问答。两条分支的功能定义完全一致。 - -### 第一次运行(弱 harness) - -切到 `p03-baseline` 分支。 - -1. 启动 agent,给一段模糊的任务提示词。 -2. 不提供 `feature_list.json`,没有状态追踪。 -3. 不限制 agent 一次做几个功能。 -4. 没有明确的验证标准——agent 自己说"完成了"就算。 -5. 运行结束后,手动检查每个功能是否真的能用。 -6. 记录哪些功能 agent 声称完成但实际没通过验证。 - -### 第二次运行(强 harness) - -切到 `p03-improved` 分支。在启动 agent 之前: - -- 在仓库根目录放好 `feature_list.json`,四个功能全部标为 `failing`。 -- 在 `AGENTS.md` 里写明规则:一次只做一个功能;状态只能从 `failing` 切到 `passing`,且必须有验证证据。 -- 准备好 `init.sh`。 - -然后启动 agent: - -1. agent 开始工作,每完成一个功能必须更新 `feature_list.json` 并附上验证证据(截图、测试输出等)。 -2. 至少要有一个功能展示从 `failing` 到 `passing` 的完整转换过程。 -3. 问答功能的验证必须检查引用是否存在、引用是否相关,不能只看有没有输出。 -4. 运行结束后归档所有验证证据。 - -## 怎么衡量结果 - -| 指标 | 说明 | -|------|------| -| 范围漂移次数 | agent 做了功能清单之外的事的次数 | -| 虚假完成率 | agent 声称完成但验证不通过的功能比例 | -| 验证覆盖率 | 有明确验证证据的功能占总功能的百分比 | -| 问答质量 | 引用是否存在、引用是否相关 | -| 重试次数 | 从开始到所有功能 passing 总共重试了几次 | - -## 要交什么 - -- 弱 harness 运行记录:提示词、日志、验证结果 -- 强 harness 运行记录:`feature_list.json` 变更历史、日志、验证证据 -- 至少一个功能从 `failing` 到 `passing` 的转换证据 -- 一份对比笔记:重点看范围纪律和完成准确度 - -## 对应讲义 - -- [Lecture 07 — 为什么 agent 总是做过头、做不完](../../lectures/lecture-07-why-agents-overreach-and-under-finish/index.md) -- [Lecture 08 — 为什么 feature list 是 harness 的基础原语](../../lectures/lecture-08-why-feature-lists-are-harness-primitives/index.md) -- [Lecture 09 — 为什么 agent 总是过早宣布胜利](../../lectures/lecture-09-why-agents-declare-victory-too-early/index.md) -- [Lecture 10 — 为什么端到端测试能改变结果](../../lectures/lecture-10-why-end-to-end-testing-changes-results/index.md) diff --git a/courses/learn-harness-engineering/docs/zh/projects/project-04-incremental-indexing/index.md b/courses/learn-harness-engineering/docs/zh/projects/project-04-incremental-indexing/index.md deleted file mode 100644 index 9d7d44c4..00000000 --- a/courses/learn-harness-engineering/docs/zh/projects/project-04-incremental-indexing/index.md +++ /dev/null @@ -1,76 +0,0 @@ -# Project 04. 用运行反馈修正 agent 的行为 - -> 相关讲义:[L07. 为什么 agent 会多做或少做](./../../lectures/lecture-07-why-agents-overreach-and-under-finish/index.md) · [L08. 为什么功能清单是 harness 原语](./../../lectures/lecture-08-why-feature-lists-are-harness-primitives/index.md) -> 本篇模板文件:[templates/](https://github.com/walkinglabs/learn-harness-engineering/blob/main/docs/zh/resources/templates/) - -## 你要做什么 - -前三个项目关注的是"让 agent 把活干完"。这个项目关注的是"出了问题怎么修"——而且不是你修,是让 agent 自己通过运行时信号来修。 - -你要做两件事。第一,给知识库应用加上运行时可观测性:启动日志、导入和索引的日志、问答失败时的用户可见错误状态。第二,在仓库里编码架构约束,让 agent 不可能静默地跨层违规(main / preload / renderer / services 之间的边界)。 - -然后你会在代码里埋一个运行时 bug,让 agent 去修。跑两次:一次不给日志和约束,看 agent 怎么修;一次给好日志和架构规则,看差别。 - -## 用什么工具 - -- Claude Code 或 Codex -- Git -- Node.js + Electron -- 日志库(如 `electron-log` 或简单的 console 封装) -- 结构化检查工具(ESLint 自定义规则、guard 脚本、或测试) - -## 具体步骤 - -### 准备工作 - -1. 基于 P03 完成后的代码,从同一个 commit 出发。 -2. 创建两个分支:`p04-baseline` 和 `p04-improved`。 -3. 在两个分支中埋入同一个运行时 bug(比如:索引时 chunk 大小读取错误导致问答返回空结果)。 -4. 记录 bug 的位置和表现,但不要告诉 agent bug 在哪。 - -### 第一次运行(弱 harness) - -切到 `p04-baseline` 分支。 - -1. 启动 agent,告诉它"问答功能返回空结果,请修复"。 -2. 仓库里没有运行时日志,没有架构约束文件。 -3. 记录 agent 花了多久找到根因、怎么确认修复的、修复过程中是否引入了新的层边界违规。 -4. 修复后重启应用,确认是否能干净启动。 - -### 第二次运行(强 harness) - -切到 `p04-improved` 分支。在启动 agent 之前,先在仓库里准备好: - -- **运行时日志**:启动时打印初始化步骤,导入时打印文件数量和 chunk 结果,索引时打印进度,问答失败时打印错误原因。 -- **架构约束**:在 `AGENTS.md` 里明确写 Electron 四层边界(main / preload / renderer / services),说明哪些调用路径是允许的。用 ESLint 规则或 guard 脚本检查违规。 -- **干净状态要求**:最终交付前必须能干净重启。 - -然后启动 agent: - -1. 先让 agent 加上日志和结构化检查(这是任务的一部分)。 -2. 然后告诉它"问答功能返回空结果,请修复"。 -3. 这次 agent 有日志可以看,有架构规则可以参照。 -4. 记录诊断速度、修复质量、是否引入了边界违规。 -5. 修复后重启应用,确认干净启动。 - -## 怎么衡量结果 - -| 指标 | 说明 | -|------|------| -| 根因定位时间 | 从开始到 agent 准确描述 bug 原因 | -| 修复确认时间 | 从定位到验证修复成功 | -| 边界违规数 | 修复过程中引入的跨层违规次数 | -| 日志有用性 | 日志是否直接指向了失败原因 | -| 干净重启 | 修复后是否能干净重启、无报错 | - -## 要交什么 - -- 弱 harness 的调试记录:诊断过程、修复 diff、启动证据 -- 强 harness 的调试记录:同上,加上日志和架构约束文件 -- 结构化检查产物(lint 规则、guard 脚本、或测试文件) -- 一份对比笔记:重点比较诊断速度和修复健壮性 - -## 对应讲义 - -- [Lecture 11 — 为什么可观测性属于 harness 的一部分](../../lectures/lecture-11-why-observability-belongs-inside-the-harness/index.md) -- [Lecture 12 — 为什么每个会话都必须留下干净状态](../../lectures/lecture-12-why-every-session-must-leave-a-clean-state/index.md) diff --git a/courses/learn-harness-engineering/docs/zh/projects/project-05-grounded-qa-verification/index.md b/courses/learn-harness-engineering/docs/zh/projects/project-05-grounded-qa-verification/index.md deleted file mode 100644 index 5285c089..00000000 --- a/courses/learn-harness-engineering/docs/zh/projects/project-05-grounded-qa-verification/index.md +++ /dev/null @@ -1,93 +0,0 @@ -# Project 05. 让 agent 自己检查自己做的对不对 - -> 相关讲义:[L09. 为什么 agent 会提前宣告完成](./../../lectures/lecture-09-why-agents-declare-victory-too-early/index.md) · [L10. 为什么端到端测试会改变结果](./../../lectures/lecture-10-why-end-to-end-testing-changes-results/index.md) -> 本篇模板文件:[templates/](https://github.com/walkinglabs/learn-harness-engineering/blob/main/docs/zh/resources/templates/) - -## 你要做什么 - -前四个项目里,"检查做得对不对"这件事要么是你手动做的,要么是靠文件规则强制执行的。这个项目要让 agent 自己来检查。 - -方法是角色分离。以前是同一个 agent 既写代码又判断质量。现在拆开:一个 agent 负责实现(生成者),另一个 agent 负责审查(评估者)。更进一步,还可以加一个负责规划的角色(规划者)。你要跑三次,看每加一层角色分离,结果好多少。 - -选一个实质性的功能升级(比如多轮对话历史、引用面板重设计、文档集合与筛选),三次都做同一个升级,唯一变量是 harness 的角色分工。 - -## 用什么工具 - -- Claude Code 或 Codex(用于生成和规划角色) -- 同一个或另一个 agent 实例(用于评估角色) -- 评估量表(参考 `docs/zh/resources/templates/evaluator-rubric.md`) -- Sprint contract 模板(参考 `docs/lectures/lecture-11-why-observability-belongs-inside-the-harness/code/sprint-contract.md`) - -## 具体步骤 - -### 准备工作 - -1. 基于 P04 完成后的代码,从同一个 commit 出发。 -2. 创建三个分支:`p05-single`、`p05-gen-eval`、`p05-plan-gen-eval`。 -3. 选定一个功能升级,写清楚验收标准。三次运行的功能范围和验收标准完全一致。 -4. 写好评估量表,打分维度至少包括:正确性、可靠性、可维护性、用户体验。 - -### 第一次运行(弱 harness — 单角色) - -切到 `p05-single` 分支。 - -1. 一个 agent 包揽规划、实现、自查。 -2. 没有外部评估量表,agent 自己判断做得好不好。 -3. 记录最终产出和未解决的缺陷。 - -### 第二次运行(强 harness — 生成者 + 评估者) - -切到 `p05-gen-eval` 分支。 - -1. 生成者和评估者先用 sprint contract 对齐:做什么、怎么验证、什么不算在范围内。 -2. 生成者实现功能。 -3. 评估者用评估量表打分,反馈问题。 -4. 生成者根据反馈修改。至少经过一轮修订。 -5. 记录评估者抓住了多少缺陷、修订了多少内容。 - -### 第三次运行(更强 harness — 规划者 + 生成者 + 评估者) - -切到 `p05-plan-gen-eval` 分支。 - -1. 规划者先拆解任务、定义步骤和依赖关系。 -2. 生成者按规划实现。 -3. 评估者用同一份评估量表打分。 -4. 至少经过一轮修订。 -5. 记录规划是否减少了返工。 - -### 评估者调优 - -评估者不是一次就能用好的。初始版本的评估者往往会"发现问题然后自己说服自己通过"。你需要: - -1. 让评估者给一个已完成的 sprint 打分。 -2. 和你自己的判断对比。 -3. 哪里有分歧,就把量表写得更具体。 -4. 重新跑评估者,检查对齐程度。 -5. 重复 3-5 轮,直到评估者判断和你的判断基本一致。记录每一轮调优的内容。 - -## 怎么衡量结果 - -| 指标 | 说明 | -|------|------| -| 范围定义质量 | 编码前的任务拆解清晰度 | -| 缺陷检出数 | 交付前被评估者抓到的缺陷数量和严重程度 | -| 量表评分 | 各维度的得分(正确性、可靠性、可维护性、UX) | -| 返工量 | 评估反馈后需要重做的比例 | -| 最终健壮性 | 升级后功能在实际运行中的稳定程度 | -| 评估者调优轮数 | 评估者判断与你对齐需要的迭代次数 | -| Sprint contract 效果 | 合同是否减少了评估中的模糊地带 | - -## 要交什么 - -- Sprint contract 文档 -- 评估者调优日志(每轮改了什么、为什么改) -- 单角色运行记录:提示词、日志、可运行证据 -- 生成者+评估者运行记录:含量表评分和修订记录 -- 规划者+生成者+评估者运行记录:含规划文档、量表评分、修订记录 -- 一份对比笔记:质量提升幅度和协调成本 - -## 对应讲义 - -- [Lecture 09 — 为什么 agent 总是过早宣布胜利](../../lectures/lecture-09-why-agents-declare-victory-too-early/index.md) -- [Lecture 10 — 为什么端到端测试能改变结果](../../lectures/lecture-10-why-end-to-end-testing-changes-results/index.md) -- [Lecture 11 — 为什么可观测性属于 harness 的一部分](../../lectures/lecture-11-why-observability-belongs-inside-the-harness/index.md) diff --git a/courses/learn-harness-engineering/docs/zh/projects/project-06-runtime-observability-and-debugging/index.md b/courses/learn-harness-engineering/docs/zh/projects/project-06-runtime-observability-and-debugging/index.md deleted file mode 100644 index 2e777dbe..00000000 --- a/courses/learn-harness-engineering/docs/zh/projects/project-06-runtime-observability-and-debugging/index.md +++ /dev/null @@ -1,101 +0,0 @@ -# Project 06. 搭建一套完整的 agent 工作环境 - -> 相关讲义:[L11. 为什么可观测性属于 harness](./../../lectures/lecture-11-why-observability-belongs-inside-the-harness/index.md) · [L12. 为什么每次会话都要留干净状态](./../../lectures/lecture-12-why-every-session-must-leave-a-clean-state/index.md) -> 本篇模板文件:[templates/](https://github.com/walkinglabs/learn-harness-engineering/blob/main/docs/zh/resources/templates/) - -## 你要做什么 - -这是结业项目。把前五个项目学到的所有东西组装起来,跑一次完整的基准测试,然后做一轮清理,验证质量是可以持续维护的。 - -你要用一套固定的多功能任务集,覆盖知识库应用的完整产品切片:导入文档、构建索引、带引用的问答、运行时可观测性、可读可重启的仓库状态。先跑一次弱 harness 基线,再跑一次你组装的最强 harness,然后做一轮清理和重跑。最后还要做一次 harness 精简实验——删掉一个组件看看结果会不会变差,判断哪些组件是真正有用的、哪些是多余的开销。 - -## 用什么工具 - -- Claude Code 或 Codex -- Git -- Node.js + Electron -- 质量文档模板(`docs/zh/resources/templates/quality-document.md`) -- 评估量表(`docs/zh/resources/templates/evaluator-rubric.md`) -- 前五个项目积累的所有 harness 组件 - -## 具体步骤 - -### 准备工作 - -1. 基于 P05 完成后的代码,从同一个 commit 出发。 -2. 创建两个分支:`p06-baseline` 和 `p06-improved`。 -3. 用质量文档模板给当前代码打一次初始评分(每个产品领域和架构层的等级)。 -4. 定义一套固定的基准任务集和评分表——在跑任何 agent 之前就定好,跑的过程中不改。 - -基准任务集至少包括: - -- 导入一篇文档 -- 构建或刷新索引 -- 回答一个带引用的问题 -- 查看运行时日志确认可观测性 -- 关掉重开后状态仍在 - -### 第一次运行(弱 harness) - -切到 `p06-baseline` 分支。 - -1. 用课程早期阶段的弱 harness(没有完整交接文件、没有严格验证、可观测性不足)。 -2. 用 agent 跑完整个基准任务集。 -3. 立刻评分。记录每个任务的完成状态、重试次数、缺陷数。 -4. 更新质量文档,记录每个领域和层的等级变化。 - -### 第二次运行(强 harness) - -切到 `p06-improved` 分支。 - -1. 用你在这门课里组装的最强 harness:交接文件和启动脚本、明确的范围和验证关卡、运行时信号和架构约束、评估者或多角色审查、质量文档追踪。 -2. 同样的基准任务集,同样的模型和预算。 -3. 立刻评分。记录结果。 -4. 更新质量文档。 - -### 清理和重跑 - -在 `p06-improved` 分支上: - -1. 做一轮清理:删死代码、修不清楚的文档、理顺不稳定的运行路径。 -2. 清理后重跑同样的基准任务集,重新评分。 -3. 更新质量文档。 - -对比三个快照的质量文档:基线、强 harness、清理后。 - -### Harness 精简实验 - -1. 从 `p06-improved` 分支中删掉一个 harness 组件(比如删掉 sprint contract,或者删掉显式范围关卡)。 -2. 重跑基准任务集。 -3. 如果结果没变差——说明这个组件是多余的开销,可以去掉。 -4. 如果结果变差了——说明这个组件是承重的,必须保留。 -5. 记录实验结果。可以多试几个组件。 - -## 怎么衡量结果 - -| 指标 | 说明 | -|------|------| -| 基准完成率 | 基准任务集中成功完成的比例 | -| 重试次数 | 每个任务需要重试几次 | -| 缺陷数 | 人工干预前发现的缺陷数量 | -| 清理工作量 | 清理花了多长时间、改了多少文件 | -| 清理后可读性和重启成功率 | 清理后仓库的可维护程度 | -| 质量文档等级变化 | 三个快照的等级对比 | -| Harness 精简结果 | 哪些组件可以删、哪些是承重的 | - -## 要交什么 - -- 质量文档的三个快照(基线、强 harness、清理后) -- 基线基准测试记录:评分和证据 -- 强 harness 基准测试记录:评分和证据 -- 清理运行记录:清理前后评分变化 -- Harness 精简日志:删了什么组件、基准结果、决定保留还是删 -- 最终结业总结:关键经验教训 - -## 对应讲义 - -- [Lecture 01 — 为什么强 agent 仍然失败](../../lectures/lecture-01-why-capable-agents-still-fail/index.md) -- [Lecture 03 — 为什么仓库必须成为唯一事实来源](../../lectures/lecture-03-why-the-repository-must-become-the-system-of-record/index.md) -- [Lecture 08 — 为什么 feature list 是 harness 的基础原语](../../lectures/lecture-08-why-feature-lists-are-harness-primitives/index.md) -- [Lecture 11 — 为什么可观测性属于 harness 的一部分](../../lectures/lecture-11-why-observability-belongs-inside-the-harness/index.md) -- [Lecture 12 — 为什么每个会话都必须留下干净状态](../../lectures/lecture-12-why-every-session-must-leave-a-clean-state/index.md) diff --git a/courses/learn-harness-engineering/docs/zh/resources/index.md b/courses/learn-harness-engineering/docs/zh/resources/index.md deleted file mode 100644 index 865de482..00000000 --- a/courses/learn-harness-engineering/docs/zh/resources/index.md +++ /dev/null @@ -1,52 +0,0 @@ -# 中文资料库 - -这个文件夹把课程里的方法整理成可以直接参考和复用的材料,不是再讲一遍概念,而是尽量回答两个问题: - -- 我现在应该先抄哪些文件 -- 这些文件分别解决什么问题 - -## 适合什么时候用 - -当你准备让 Codex、Claude Code 或其他 coding agent 在一个仓库里持续工作时,就可以从这里开始。它特别适合这些场景: - -- 多轮会话开发,担心上下文断裂 -- 功能多,容易出现做一半就停的半成品 -- 常常提前宣布完成,但实际没测透 -- 每次开工都要重新摸索启动方式 - -## 从这里开始 - -如果你想先搭一个最小可用版本,优先看这些文件: - -- 根指令文件:[`templates/AGENTS.md`](./templates/AGENTS.md) 或 [`templates/CLAUDE.md`](./templates/CLAUDE.md) -- 功能状态文件:[`templates/feature_list.json`](./templates/feature_list.json) -- 进度日志:[`templates/claude-progress.md`](./templates/claude-progress.md) -- 启动脚本参考:`docs/resources/templates/init.sh` - -然后按需要补上: - -- 会话交接模板:[`templates/session-handoff.md`](./templates/session-handoff.md) -- 收尾检查清单:[`templates/clean-state-checklist.md`](./templates/clean-state-checklist.md) -- 评审模板:[`templates/evaluator-rubric.md`](./templates/evaluator-rubric.md) - -如果你想直接采用 OpenAI 那篇 harness engineering 文章里更完整的仓库组织方式,可以继续看: - -- [`openai-advanced/index.md`](./openai-advanced/index.md) - -## 资料库结构 - -- [`templates/`](./templates/index.md):可以直接复制到真实仓库里的模板 -- [`reference/`](./reference/index.md):方法说明、启动流程和问题对照表 -- [`openai-advanced/`](./openai-advanced/index.md):更完整的 OpenAI 风格高级资源包,包括仓库骨架、质量文档、执行计划和系统级治理文件 - -## 推荐最小组合 - -- `AGENTS.md` 或 `CLAUDE.md` -- `feature_list.json` -- `claude-progress.md` -- `init.sh` - -先把这四样放进项目里,再开始让 agent 持续工作,通常就已经能明显降低返工和瞎猜。 - -如果你的仓库已经进入多模块、多阶段、多角色协作,可以直接升级到 -[`openai-advanced/`](./openai-advanced/index.md) 这一套高级结构,而不是继续把最小模板硬撑成一个大而乱的系统。 diff --git a/courses/learn-harness-engineering/docs/zh/resources/openai-advanced/index.md b/courses/learn-harness-engineering/docs/zh/resources/openai-advanced/index.md deleted file mode 100644 index 5d7981a0..00000000 --- a/courses/learn-harness-engineering/docs/zh/resources/openai-advanced/index.md +++ /dev/null @@ -1,76 +0,0 @@ -# OpenAI 高级资源包 - -这个文件夹把 OpenAI 那篇《Harness engineering: leveraging Codex in an -agent-first world》里更完整、更高阶的仓库组织方式整理成可以直接参考和复制的模板。 - -当最小 harness 已经不够用,而你的仓库开始需要下面这些能力时,就该看这一套: - -- 一个简短、负责路由的 `AGENTS.md` -- 放在仓库里的“唯一事实来源”文档体系 -- 活跃执行计划与已完成计划的分层管理 -- 明确的产品、可靠性、安全、前端治理文件 -- 按产品领域和架构层持续更新的质量评分 -- 面向模型阅读的参考材料目录 -- 针对架构、知识沉淀、运行验证的标准 SOP - -## 包含的目录骨架 - -[`repo-template/`](./repo-template/index.md) 里提供了一套可直接复制的起步 -结构,核心布局如下: - -```text -AGENTS.md -ARCHITECTURE.md -docs/ -├── design-docs/ -│ ├── index.md -│ └── core-beliefs.md -├── exec-plans/ -│ ├── active/ -│ ├── completed/ -│ └── tech-debt-tracker.md -├── generated/ -│ └── db-schema.md -├── product-specs/ -│ ├── index.md -│ └── new-user-onboarding.md -├── references/ -│ ├── design-system-reference-llms.txt -│ ├── nixpacks-llms.txt -│ └── uv-llms.txt -├── DESIGN.md -├── FRONTEND.md -├── PLANS.md -├── PRODUCT_SENSE.md -├── QUALITY_SCORE.md -├── RELIABILITY.md -└── SECURITY.md -``` - -## 怎么用 - -1. 如果仓库还小,先用最小资源包。 -2. 一旦进入多模块、多轮会话、长期演化阶段,就把 - [`repo-template/`](./repo-template/index.md) 里的骨架复制到你的仓库。 -3. 保持 `AGENTS.md` 很短,把深层规则拆到 `docs/` 里。 -4. 把质量文档、可靠性文档、执行计划当成日常开发的一部分,而不是事后补写。 -5. 把生成物和外部参考材料明确收进仓库,避免 agent 依赖聊天上下文或人的记忆。 - -## SOP 资料库 - -[`sops/`](./sops/index.md) 把文章里的几张关键图,整理成可以逐步执行的标准流程: - -- 分层领域架构搭建 SOP -- 把不可见知识编码进仓库的 SOP -- 本地可观测性栈与反馈回路 SOP -- 用 Chrome DevTools 做 UI 验证闭环的 SOP - -## 设计原则 - -- 短入口,深链接 -- 仓库就是唯一事实来源 -- 机械约束优先于口头约定 -- 计划、质量和技术债都和代码一起版本化 -- 清理与 harness 简化是常规工作,不是临时救火 - -这套资源包是有倾向性的模板,不是必须逐字照抄。最好的用法是把它当成一套高质量起点,再按你的项目改造。 diff --git a/courses/learn-harness-engineering/docs/zh/resources/openai-advanced/repo-template/AGENTS.md b/courses/learn-harness-engineering/docs/zh/resources/openai-advanced/repo-template/AGENTS.md deleted file mode 100644 index cc0a0e4d..00000000 --- a/courses/learn-harness-engineering/docs/zh/resources/openai-advanced/repo-template/AGENTS.md +++ /dev/null @@ -1,55 +0,0 @@ -# AGENTS.md - -这个仓库面向长时运行的 coding-agent 工作流。保持这个文件简短,把它当成“唯一事实来源”文档的入口和路由层,而不是一个不断膨胀的大说明书。 - -## 开工流程 - -改代码前先做这些事: - -1. 用 `pwd` 确认仓库根目录。 -2. 读取 `ARCHITECTURE.md`,理解当前系统地图和硬性依赖规则。 -3. 读取 `docs/QUALITY_SCORE.md`,先知道最弱的产品领域和架构层。 -4. 读取 `docs/PLANS.md`,再打开当前要执行的 active plan。 -5. 读取相关的 `docs/product-specs/` 规格文档。 -6. 跑这个仓库约定的 bootstrap 与验证路径。 -7. 如果基础验证先失败,先修 baseline,再加新范围。 - -## 路由地图 - -- `ARCHITECTURE.md`:领域地图、分层模型、依赖规则 -- `docs/design-docs/index.md`:设计决策与核心信念 -- `docs/product-specs/index.md`:当前产品行为与验收目标 -- `docs/PLANS.md`:计划生命周期与执行计划规则 -- `docs/QUALITY_SCORE.md`:产品领域与架构层健康度 -- `docs/RELIABILITY.md`:运行信号、benchmark、重启要求 -- `docs/SECURITY.md`:密钥、沙箱、数据和外部动作规则 -- `docs/FRONTEND.md`:UI 约束、设计系统规则、可访问性检查 - -## 工作约定 - -- 一次只围绕一个有边界的计划或功能切片工作。 -- 不能只靠读代码就宣布完成,必须有可运行证据。 -- 只要改了行为,就同步更新对应的产品、计划或可靠性文档。 -- 如果某类 review feedback 反复出现,把它升级成机械规则、检查或 linter,不要一直在聊天里重复解释。 -- 生成物放进 `docs/generated/`,外部 reference 放进 `docs/references/`。 -- 需要更多细节时,优先补小而新的文档,而不是继续把这个文件写长。 - -## 完成定义 - -一个改动只有在以下条件都满足时才算完成: - -- 目标行为已实现 -- 要求的验证真的跑过 -- 证据已经挂到相关 plan 或质量文档里 -- 受影响的文档仍然是最新的 -- 仓库能按标准启动路径干净重启 - -## 收尾 - -结束会话前: - -1. 更新当前 active execution plan。 -2. 如果产品领域或架构层有明显变化,更新 `docs/QUALITY_SCORE.md`。 -3. 如果有延期处理的债务,记到 `docs/exec-plans/tech-debt-tracker.md`。 -4. 已完成的计划及时移到 `docs/exec-plans/completed/`。 -5. 保证仓库可重启,并留下清晰的下一步动作。 diff --git a/courses/learn-harness-engineering/docs/zh/resources/openai-advanced/repo-template/ARCHITECTURE.md b/courses/learn-harness-engineering/docs/zh/resources/openai-advanced/repo-template/ARCHITECTURE.md deleted file mode 100644 index 914c13fe..00000000 --- a/courses/learn-harness-engineering/docs/zh/resources/openai-advanced/repo-template/ARCHITECTURE.md +++ /dev/null @@ -1,55 +0,0 @@ -# ARCHITECTURE.md - -这份文件是系统的顶层地图。它应该保持简短,只提供最关键的结构信息,并把更深的内容指向其他文档。 - -## 系统形态 - -- 产品:`[替换成产品名]` -- 主用户流程:`[替换成核心流程]` -- 运行面:`[desktop / web / cli / services / workers]` -- 产品行为真相来源:`docs/product-specs/` - -## 领域地图 - -| 领域 | 负责什么 | 主要入口 | 对应规格 | -|------|---------|---------|---------| -| `[domain-a]` | `[职责]` | `[模块 / 路由 / 命令]` | `[spec path]` | -| `[domain-b]` | `[职责]` | `[模块 / 路由 / 命令]` | `[spec path]` | - -## 分层模型 - -用固定方向的分层模型,避免 agent 临场发明架构: - -`Types -> Config -> Repo -> Service -> Runtime -> UI` - -跨领域关注点应该通过明确的 provider 或 adapter 边界进入,而不是直接跨层穿透。 - -## 硬性依赖规则 - -- 低层不能依赖高层。 -- UI 不能绕过 runtime 或 service 契约。 -- 数据访问必须通过 repo 或等价 adapter 进入。 -- 共享 util 必须保持通用,不能慢慢堆成领域逻辑垃圾桶。 -- 新依赖要在对应 plan 或 design doc 里说明理由。 - -## 横切接口 - -| 关注点 | 允许的边界 | 备注 | -|-------|-----------|------| -| 日志与 tracing | `[provider / utility path]` | `[只允许结构化日志,不允许临时 console]` | -| Auth | `[provider path]` | `[token/session 规则]` | -| 外部 API | `[client 或 provider path]` | `[限流 / 重试原则]` | -| Feature flags | `[flag boundary]` | `[归属]` | - -## 当前热点 - -- `[最难安全修改的区域]` -- `[边界最弱或测试最脆的区域]` - -## 变更检查 - -当你修改了会影响架构的代码: - -1. 如果领域地图或允许边界变了,就更新这份文件。 -2. 如果背后的设计理由变了,就更新 `docs/design-docs/` 里的相关文档。 -3. 如果规则应该机械执行,就补一个可执行检查。 diff --git a/courses/learn-harness-engineering/docs/zh/resources/openai-advanced/repo-template/docs/DESIGN.md b/courses/learn-harness-engineering/docs/zh/resources/openai-advanced/repo-template/docs/DESIGN.md deleted file mode 100644 index fd9f327d..00000000 --- a/courses/learn-harness-engineering/docs/zh/resources/openai-advanced/repo-template/docs/DESIGN.md +++ /dev/null @@ -1,26 +0,0 @@ -# DESIGN.md - -这份文件是设计文档入口。保持简短,把更细的内容路由到 -`docs/design-docs/` 里的具体文件。 - -## 目的 - -记录那些应该跨越单次聊天、单个 sprint、单个 reviewer 记忆而持续存在的产品与系统设计决策。 - -## 什么时候先看它 - -- 你需要理解当前的设计哲学 -- 你准备引入新模式 -- 你要判断哪些设计已经定了,哪些还在开放状态 - -## 核心设计文档 - -- `docs/design-docs/index.md`:accepted / proposed / deprecated 文档索引 -- `docs/design-docs/core-beliefs.md`:项目级 agent-first 核心信念 - -## 设计规则 - -- 设计文档要小而新。 -- 一个决策领域尽量对应一份文档。 -- 某个改动依赖设计文档时,要在 plan 和 spec 里显式链接它。 -- 如果某条设计规则已经变成运行上的硬要求,就把它升级成自动化检查或更新到 `ARCHITECTURE.md`。 diff --git a/courses/learn-harness-engineering/docs/zh/resources/openai-advanced/repo-template/docs/FRONTEND.md b/courses/learn-harness-engineering/docs/zh/resources/openai-advanced/repo-template/docs/FRONTEND.md deleted file mode 100644 index dea27405..00000000 --- a/courses/learn-harness-engineering/docs/zh/resources/openai-advanced/repo-template/docs/FRONTEND.md +++ /dev/null @@ -1,23 +0,0 @@ -# FRONTEND.md - -这份文件定义稳定的前端预期,避免 agent 每次都临场发明一套 UI 模式。 - -## UI 原则 - -- 先保证清晰,再追求新鲜感。 -- 交互流程要可发现、可重走、可重启。 -- 优先沉淀少量可复用组件,而不是到处长一次性变体。 -- 可访问性检查属于正常验证,不是最后的美化工作。 - -## 护栏 - -- 把设计系统或组件库参考材料放进 `docs/references/`。 -- 显式记录关键用户状态:empty、loading、success、error、retry。 -- 在不同流程里保持文案、键盘行为、视觉层级一致。 -- 修复 UI bug 后,顺手补上对应验证步骤。 - -## 验证要求 - -- 为关键用户旅程留下证据。 -- 把浏览器或运行时验证步骤写进相关 plan。 -- 如果视觉回归常见,就标准化截图或 DOM 检查。 diff --git a/courses/learn-harness-engineering/docs/zh/resources/openai-advanced/repo-template/docs/PLANS.md b/courses/learn-harness-engineering/docs/zh/resources/openai-advanced/repo-template/docs/PLANS.md deleted file mode 100644 index 92cfc125..00000000 --- a/courses/learn-harness-engineering/docs/zh/resources/openai-advanced/repo-template/docs/PLANS.md +++ /dev/null @@ -1,34 +0,0 @@ -# PLANS.md - -这份文件定义执行计划如何创建、更新、完成和归档。 - -## 什么时候必须有 plan - -当工作满足以下任一条件时,就创建 execution plan: - -- 会跨越多个会话 -- 会同时影响多个子系统 -- 验证或上线风险不小 -- 存在需要显式记录的开放决策 - -## 计划放哪 - -- `docs/exec-plans/active/`:当前正在驱动工作的计划 -- `docs/exec-plans/completed/`:已完成但仍然要保留上下文的计划 -- `docs/exec-plans/tech-debt-tracker.md`:延期处理的债务与 follow-up - -## 最少要包含的部分 - -- 目标 -- 范围与明确不做什么 -- 验证路径 -- 风险与 blocker -- 进度日志 -- 开放决策 - -## 运行规则 - -- 一份 active plan 同一时间应该只有一个清晰的当前步骤。 -- plan 要随着工作推进而更新,不要把它当成静态散文。 -- 只要某个决策改变了实现方向,就记到 plan 里。 -- 已完成的计划要移到 `completed/`,让后续 agent 仍然能发现历史上下文。 diff --git a/courses/learn-harness-engineering/docs/zh/resources/openai-advanced/repo-template/docs/PRODUCT_SENSE.md b/courses/learn-harness-engineering/docs/zh/resources/openai-advanced/repo-template/docs/PRODUCT_SENSE.md deleted file mode 100644 index bfb7db05..00000000 --- a/courses/learn-harness-engineering/docs/zh/resources/openai-advanced/repo-template/docs/PRODUCT_SENSE.md +++ /dev/null @@ -1,24 +0,0 @@ -# PRODUCT_SENSE.md - -这份文件记录那些代码本身并不能可靠表达出来的产品判断。 - -## 产品核心 - -- 主要用户:`[替换]` -- 要完成的任务:`[替换]` -- 最想解决的核心痛点:`[替换]` -- 可接受质量门槛:`[替换]` - -## 产品规则 - -- 优先保证用户可感知的可靠性,而不是一味加功能。 -- 行为有歧义时,把它当成 spec 缺口,不要默认允许猜。 -- 只要实现改变了用户看见或信任的东西,就更新对应 spec。 -- 具体流程写在 product spec 里,跨流程的产品优先级写在这里。 - -## 禁区模式 - -- 隐蔽的破坏性操作 -- 没有用户反馈的静默失败 -- 用户可见状态没有明确真相来源 -- 不能用一句话解释清楚的功能 diff --git a/courses/learn-harness-engineering/docs/zh/resources/openai-advanced/repo-template/docs/QUALITY_SCORE.md b/courses/learn-harness-engineering/docs/zh/resources/openai-advanced/repo-template/docs/QUALITY_SCORE.md deleted file mode 100644 index 63c9867c..00000000 --- a/courses/learn-harness-engineering/docs/zh/resources/openai-advanced/repo-template/docs/QUALITY_SCORE.md +++ /dev/null @@ -1,39 +0,0 @@ -# QUALITY_SCORE.md - -这份文档用来跟踪仓库是在变强还是变弱。 - -## 评级标准 - -- `A`:验证通过、可读、稳定、边界执行到位 -- `B`:可用,只有少量缺口 -- `C`:部分可用,存在明显混乱或不稳定 -- `D`:损坏、不安全,或结构上说不清楚 - -## 产品领域 - -| 领域 | 评级 | 验证状态 | Agent 可读性 | 测试稳定性 | 关键缺口 | 上次更新 | -|------|------|---------|-------------|-----------|---------|---------| -| `[domain-a]` | - | - | - | - | - | - | -| `[domain-b]` | - | - | - | - | - | - | -| `[domain-c]` | - | - | - | - | - | - | - -## 架构层 - -| 层级 | 评级 | 边界执行 | Agent 可读性 | 关键缺口 | 上次更新 | -|------|------|---------|-------------|---------|---------| -| Types | - | - | - | - | - | -| Services | - | - | - | - | - | -| Runtime | - | - | - | - | - | -| UI | - | - | - | - | - | - -## Benchmark 快照 - -| 日期 | Harness 变体 | 完成率 | 重试次数 | Review 前缺陷数 | 备注 | -|------|-------------|-------|---------|----------------|------| -| YYYY-MM-DD | `[baseline / improved / simplified]` | - | - | - | - | - -## 简化实验日志 - -| 日期 | 移除的组件 | 结果 | 决策 | -|------|-----------|------|------| -| YYYY-MM-DD | `[component]` | `[degraded / unchanged]` | `[restore / keep removed]` | diff --git a/courses/learn-harness-engineering/docs/zh/resources/openai-advanced/repo-template/docs/RELIABILITY.md b/courses/learn-harness-engineering/docs/zh/resources/openai-advanced/repo-template/docs/RELIABILITY.md deleted file mode 100644 index 0f8c5a56..00000000 --- a/courses/learn-harness-engineering/docs/zh/resources/openai-advanced/repo-template/docs/RELIABILITY.md +++ /dev/null @@ -1,32 +0,0 @@ -# RELIABILITY.md - -这份文件定义系统如何证明自己健康、可诊断、可重启。 - -## 标准路径 - -- Bootstrap:`[command]` -- Verification:`[command]` -- 启动应用或服务:`[command]` -- 调试或查看运行态:`[command]` - -## 必需运行信号 - -- 启动与关键流程的结构化日志 -- 关键服务的 health check -- 条件允许时为慢路径提供 trace 或 timing 数据 -- 对可恢复失败提供用户可见的错误状态 - -## 黄金旅程 - -- `[journey 1]` -- `[journey 2]` -- `[journey 3]` - -每条黄金旅程都应该有可重复的验证路径和清晰的失败信号。 - -## 可靠性规则 - -- 只要系统不能干净重启,功能就不能算完成。 -- 运行失败必须能从 repo-local 信号里定位。 -- 某类失败模式一旦反复出现,就给它补 benchmark 或 guardrail。 -- cleanup 属于可靠性的一部分,不是另外一件事。 diff --git a/courses/learn-harness-engineering/docs/zh/resources/openai-advanced/repo-template/docs/SECURITY.md b/courses/learn-harness-engineering/docs/zh/resources/openai-advanced/repo-template/docs/SECURITY.md deleted file mode 100644 index 147a4d1b..00000000 --- a/courses/learn-harness-engineering/docs/zh/resources/openai-advanced/repo-template/docs/SECURITY.md +++ /dev/null @@ -1,27 +0,0 @@ -# SECURITY.md - -这份文件定义那些 agent 绝不能靠猜来处理的安全规则。 - -## Secrets 与凭证 - -- 绝不把 secrets 硬编码进源码或文档。 -- 在这里记录允许的 secret 加载路径。 -- 从日志和截图里去掉 token、API key 与个人数据。 - -## 不可信输入 - -- 外部内容在验证前一律视为不可信。 -- 把允许的抓取或执行边界写在这里。 -- 如果存在 prompt injection 或 command injection 风险,要把 guardrail 记录清楚。 - -## 外部动作 - -- 列出哪些动作必须显式批准。 -- 记录哪些生产或破坏性命令默认不能跑。 -- 调试和验证优先走 sandbox-safe 路径。 - -## 依赖与评审规则 - -- 新依赖必须在 active plan 里说明理由。 -- 涉及安全的改动必须有显式验证步骤。 -- 反复出现的安全 review 评论要升级成检查,而不是变成口口相传的经验。 diff --git a/courses/learn-harness-engineering/docs/zh/resources/openai-advanced/repo-template/docs/design-docs/core-beliefs.md b/courses/learn-harness-engineering/docs/zh/resources/openai-advanced/repo-template/docs/design-docs/core-beliefs.md deleted file mode 100644 index 3d561136..00000000 --- a/courses/learn-harness-engineering/docs/zh/resources/openai-advanced/repo-template/docs/design-docs/core-beliefs.md +++ /dev/null @@ -1,9 +0,0 @@ -# 核心信念 - -- 仓库就是 agent 的唯一事实来源。 -- `AGENTS.md` 是路由器,不是百科全书。 -- 验证证据比自信更重要。 -- 一次做好一个有边界的任务,胜过同时开很多半成品。 -- 反复出现的人类反馈要升级成可复用的 harness 规则。 -- 清理与简化都是交付的一部分,不是事后补救。 -- 如果 agent 不能在仓库里发现某个事实,就把这个事实视为运行上不存在。 diff --git a/courses/learn-harness-engineering/docs/zh/resources/openai-advanced/repo-template/docs/design-docs/index.md b/courses/learn-harness-engineering/docs/zh/resources/openai-advanced/repo-template/docs/design-docs/index.md deleted file mode 100644 index dcad6340..00000000 --- a/courses/learn-harness-engineering/docs/zh/resources/openai-advanced/repo-template/docs/design-docs/index.md +++ /dev/null @@ -1,21 +0,0 @@ -# 设计文档索引 - -把这份索引当成设计历史的可发现地图。 - -## Accepted - -- `core-beliefs.md`:agent-first 的运行信念与持久项目规范 - -## Proposed - -- `[在这里添加新的设计文档路径]` - -## Deprecated - -- `[把被替代或过期的设计文档移到这里,并给出替代链接]` - -## 维护规则 - -- 每份设计文档都应该有 owner 或更新触发条件。 -- 过期文档要么删除,要么明确标成 deprecated,不要让它悄悄漂移。 -- active execution plan 要链接自己依赖的设计文档。 diff --git a/courses/learn-harness-engineering/docs/zh/resources/openai-advanced/repo-template/docs/exec-plans/active/index.md b/courses/learn-harness-engineering/docs/zh/resources/openai-advanced/repo-template/docs/exec-plans/active/index.md deleted file mode 100644 index 6a67efbc..00000000 --- a/courses/learn-harness-engineering/docs/zh/resources/openai-advanced/repo-template/docs/exec-plans/active/index.md +++ /dev/null @@ -1,9 +0,0 @@ -# 当前执行计划 - -这个文件夹里每一份 markdown 都代表一份当前仍在执行的计划。 - -建议文件名格式: - -- `YYYY-MM-DD-short-topic.md` - -每份 active plan 都应该足够新,让一个没有聊天上下文的新 agent 也能只靠仓库继续推进。 diff --git a/courses/learn-harness-engineering/docs/zh/resources/openai-advanced/repo-template/docs/exec-plans/completed/index.md b/courses/learn-harness-engineering/docs/zh/resources/openai-advanced/repo-template/docs/exec-plans/completed/index.md deleted file mode 100644 index 9e6315b8..00000000 --- a/courses/learn-harness-engineering/docs/zh/resources/openai-advanced/repo-template/docs/exec-plans/completed/index.md +++ /dev/null @@ -1,3 +0,0 @@ -# 已完成计划 - -已完成的计划移动到这里,不要直接删掉。完成计划也是仓库记忆面的一部分,后续 agent 需要靠它理解代码为什么会变成现在这样。 diff --git a/courses/learn-harness-engineering/docs/zh/resources/openai-advanced/repo-template/docs/exec-plans/tech-debt-tracker.md b/courses/learn-harness-engineering/docs/zh/resources/openai-advanced/repo-template/docs/exec-plans/tech-debt-tracker.md deleted file mode 100644 index e5b7c355..00000000 --- a/courses/learn-harness-engineering/docs/zh/resources/openai-advanced/repo-template/docs/exec-plans/tech-debt-tracker.md +++ /dev/null @@ -1,7 +0,0 @@ -# 技术债跟踪 - -这份文件只记录那些真实存在、已经确认、并且被有意识延后的技术债。 - -| 日期 | 区域 | 债务 | 为什么延后 | 风险 | 下次触发点 | -|------|------|------|-----------|------|-----------| -| YYYY-MM-DD | `[area]` | `[debt]` | `[reason]` | `[risk]` | `[when to revisit]` | diff --git a/courses/learn-harness-engineering/docs/zh/resources/openai-advanced/repo-template/docs/generated/db-schema.md b/courses/learn-harness-engineering/docs/zh/resources/openai-advanced/repo-template/docs/generated/db-schema.md deleted file mode 100644 index 8aaf66b7..00000000 --- a/courses/learn-harness-engineering/docs/zh/resources/openai-advanced/repo-template/docs/generated/db-schema.md +++ /dev/null @@ -1,13 +0,0 @@ -# 数据库结构 - -这个目录用来存放那些生成出来但又值得 agent 直接阅读的工件,避免它每次都去源码里反推。 - -## 来源 - -- 生成命令或来源:`[command or source path]` -- 最近刷新时间:`YYYY-MM-DD` - -## 备注 - -- 生成部分不要手改。 -- 底层 schema 一变,就重新生成这份文件。 diff --git a/courses/learn-harness-engineering/docs/zh/resources/openai-advanced/repo-template/docs/product-specs/index.md b/courses/learn-harness-engineering/docs/zh/resources/openai-advanced/repo-template/docs/product-specs/index.md deleted file mode 100644 index e012d00b..00000000 --- a/courses/learn-harness-engineering/docs/zh/resources/openai-advanced/repo-template/docs/product-specs/index.md +++ /dev/null @@ -1,13 +0,0 @@ -# 产品规格索引 - -这个目录用来放当前仍然有效的用户可见行为规格。 - -## 当前有效规格 - -- `new-user-onboarding.md` - -## 规则 - -- spec 重点描述用户可见行为和验收标准。 -- 如果实现和 spec 不一致,要在同一轮会话里更新其中一方。 -- 保持这份索引是最新的,让新 agent 能快速发现产品范围。 diff --git a/courses/learn-harness-engineering/docs/zh/resources/openai-advanced/repo-template/docs/product-specs/new-user-onboarding.md b/courses/learn-harness-engineering/docs/zh/resources/openai-advanced/repo-template/docs/product-specs/new-user-onboarding.md deleted file mode 100644 index 99c916e3..00000000 --- a/courses/learn-harness-engineering/docs/zh/resources/openai-advanced/repo-template/docs/product-specs/new-user-onboarding.md +++ /dev/null @@ -1,26 +0,0 @@ -# 新用户引导 - -## 目标 - -描述一个新用户第一次进入产品时应该经历的流程。 - -## 起始条件 - -- `[流程开始前的状态]` - -## 用户流程 - -1. `[步骤一]` -2. `[步骤二]` -3. `[步骤三]` - -## 验收标准 - -- `[用户可观察到的结果]` -- `[用户可观察到的结果]` -- `[用户可观察到的结果]` - -## 失败状态 - -- `[可恢复错误与用户反馈]` -- `[被阻塞状态与兜底方案]` diff --git a/courses/learn-harness-engineering/docs/zh/resources/openai-advanced/repo-template/docs/references/design-system-reference-llms.txt b/courses/learn-harness-engineering/docs/zh/resources/openai-advanced/repo-template/docs/references/design-system-reference-llms.txt deleted file mode 100644 index 415075c3..00000000 --- a/courses/learn-harness-engineering/docs/zh/resources/openai-advanced/repo-template/docs/references/design-system-reference-llms.txt +++ /dev/null @@ -1,9 +0,0 @@ -用途:存放给模型阅读的 design system 参考材料。 - -建议内容: -- 组件命名规则 -- 间距与字体 token -- 状态变体 -- 可访问性要求 - -保持文件简洁;上游设计系统变化时及时刷新。 diff --git a/courses/learn-harness-engineering/docs/zh/resources/openai-advanced/repo-template/docs/references/nixpacks-llms.txt b/courses/learn-harness-engineering/docs/zh/resources/openai-advanced/repo-template/docs/references/nixpacks-llms.txt deleted file mode 100644 index a8295b1b..00000000 --- a/courses/learn-harness-engineering/docs/zh/resources/openai-advanced/repo-template/docs/references/nixpacks-llms.txt +++ /dev/null @@ -1,7 +0,0 @@ -用途:存放仓库依赖的部署或打包规则的 agent-readable 摘要。 - -建议内容: -- build 入口 -- runtime 假设 -- 环境变量要求 -- 常见失败特征 diff --git a/courses/learn-harness-engineering/docs/zh/resources/openai-advanced/repo-template/docs/references/uv-llms.txt b/courses/learn-harness-engineering/docs/zh/resources/openai-advanced/repo-template/docs/references/uv-llms.txt deleted file mode 100644 index 0f33a52d..00000000 --- a/courses/learn-harness-engineering/docs/zh/resources/openai-advanced/repo-template/docs/references/uv-llms.txt +++ /dev/null @@ -1,7 +0,0 @@ -用途:当仓库依赖 `uv` 或类似工具时,存放一份紧凑的 Python 包与环境工作流参考。 - -建议内容: -- install 与 sync 命令 -- lockfile 策略 -- virtualenv 约定 -- verification 命令 diff --git a/courses/learn-harness-engineering/docs/zh/resources/openai-advanced/repo-template/index.md b/courses/learn-harness-engineering/docs/zh/resources/openai-advanced/repo-template/index.md deleted file mode 100644 index 4de4782d..00000000 --- a/courses/learn-harness-engineering/docs/zh/resources/openai-advanced/repo-template/index.md +++ /dev/null @@ -1,22 +0,0 @@ -# 高级仓库模板 - -当你希望把仓库升级成更接近 OpenAI 文中那种 agent-first 文档面,而不只是一个最小 harness 时,就复制这套起步模板。 - -## 推荐复制顺序 - -1. 先把 `AGENTS.md` 和 `ARCHITECTURE.md` 放到仓库根目录。 -2. 再复制整个 `docs/` 目录树。 -3. 优先把 `docs/PRODUCT_SENSE.md`、`docs/QUALITY_SCORE.md`、 - `docs/RELIABILITY.md` 填起来。 -4. 在 `docs/exec-plans/active/` 下创建你的第一份 active plan。 -5. 始终保持入口文件很短,详细规则拆到链接文档里。 - -## 这套模板主要优化什么 - -- 持久、repo-local 的上下文 -- 渐进披露,而不是一个超长 instruction 文件 -- 明确的计划生命周期 -- 随时间演化的质量追踪 -- 对 agent 和人都更可读的边界 - -这里的每个文件都只是起点。真正投入使用前,把占位文字、示例命令和样例规则替换成你自己的项目现实。 diff --git a/courses/learn-harness-engineering/docs/zh/resources/openai-advanced/sops/chrome-devtools-validation-loop.md b/courses/learn-harness-engineering/docs/zh/resources/openai-advanced/sops/chrome-devtools-validation-loop.md deleted file mode 100644 index e041a4e3..00000000 --- a/courses/learn-harness-engineering/docs/zh/resources/openai-advanced/sops/chrome-devtools-validation-loop.md +++ /dev/null @@ -1,48 +0,0 @@ -# SOP:Chrome DevTools 验证闭环 - -当 UI 改动必须经过真实交互、截图、DOM 状态和 console 输出来判断,而不是只靠读代码时,就用这份 SOP。 - -## 目标 - -把 UI 验证变成 agent 可以反复执行的交互闭环,直到这条用户旅程变干净。 - -## 核心闭环 - -1. 选定目标页面或 app 实例。 -2. 清掉旧的 console 噪声。 -3. 记录 BEFORE 状态。 -4. 触发 UI 路径。 -5. 观察交互过程中的 runtime events。 -6. 记录 AFTER 状态。 -7. 修复问题,必要时重启 app。 -8. 反复重跑验证,直到这条旅程 clean。 - -## 必需输入 - -- 稳定的启动命令 -- 可重复的 UI journey -- 能抓 DOM、console 或截图的方式 -- 明确“什么算 clean”的规则 - -## 执行 SOP - -1. 把目标旅程写进 active plan。 -2. 用可观察行为定义成功:文本出现、按钮可点、错误消失、console 干净、请求成功。 -3. 交互前先拍初始状态。 -4. 一次只触发一条路径。 -5. 记录 runtime event、DOM 变化和可见输出。 -6. 如果失败,只修最小负责层,然后重启。 -7. 重跑同一路径,对比 BEFORE/AFTER 证据。 - -## Clean 标准 - -- 目标可见状态已经出现 -- 没有意外错误 -- console 噪声已理解或清理 -- 重跑同一路径结果一致 - -## 需要同步更新的仓库工件 - -- 当前 active execution plan -- 如果它变成黄金旅程,更新 `docs/RELIABILITY.md` -- 如果可见行为改变了,更新 product spec diff --git a/courses/learn-harness-engineering/docs/zh/resources/openai-advanced/sops/encode-knowledge-into-repo.md b/courses/learn-harness-engineering/docs/zh/resources/openai-advanced/sops/encode-knowledge-into-repo.md deleted file mode 100644 index 190ae6ad..00000000 --- a/courses/learn-harness-engineering/docs/zh/resources/openai-advanced/sops/encode-knowledge-into-repo.md +++ /dev/null @@ -1,42 +0,0 @@ -# SOP:把不可见知识编码进仓库 - -当重要上下文还散落在 Google Docs、聊天记录、ticket 或人的脑子里时,就用这份 SOP。 - -## 目标 - -让那些 agent 以前看不见的知识,变成 codebase 里可发现、可读取、可执行的事实。 - -## 触发信号 - -- agent 总在追问系统到底怎么运作。 -- 人开始说“这个是 Slack 里定过的”或“按某某上周说的做”。 -- review 里经常引用仓库里根本没写下来的产品、安全或架构规则。 -- 新会话总在重复那些本该已经稳定下来的发现工作。 - -## 执行 SOP - -1. 先列出所有不可见知识来源:外部文档、聊天、默认团队规则、口头决策。 -2. 对每条知识判断:它属于架构、产品行为、安全策略、可靠性要求、执行上下文,还是参考材料? -3. 按类别编码到对应仓库工件: - - 架构 -> `ARCHITECTURE.md` - - 产品行为 -> `docs/product-specs/` - - 设计理由 -> `docs/design-docs/` - - 执行状态 -> `docs/exec-plans/` - - 外部参考材料 -> `docs/references/` - - 质量或可靠性要求 -> `docs/QUALITY_SCORE.md` 或 `docs/RELIABILITY.md` -4. 把模糊表达改写成运行上真正有用的表达。 -5. 删除或废弃旧副本,保证仓库里有一个可发现的真相来源。 - -## 好的编码规则 - -- 为“可发现”而写,不是为“写得很全”而写。 -- 文件尽量短,名字尽量明确。 -- 相关工件要互相链接。 -- 记录耐久规则,不要把会议流水账原封不动塞进来。 -- 决策形成的同一轮会话里,就把仓库更新掉。 - -## 完成定义 - -- 一个全新 agent 不问人也能找到相关规则。 -- 同一个事实不会散落在多个互相打架的文件里。 -- 新工件放在离它所治理的代码或流程最近的地方。 diff --git a/courses/learn-harness-engineering/docs/zh/resources/openai-advanced/sops/index.md b/courses/learn-harness-engineering/docs/zh/resources/openai-advanced/sops/index.md deleted file mode 100644 index 4741daee..00000000 --- a/courses/learn-harness-engineering/docs/zh/resources/openai-advanced/sops/index.md +++ /dev/null @@ -1,23 +0,0 @@ -# OpenAI 高级 SOP - -这些 SOP 把文章里的工作方式翻成可以直接参考、执行、改造的操作手册。 - -## 包含哪些 SOP - -- [`layered-domain-architecture.md`](./layered-domain-architecture.md): - 搭建显式分层与跨领域边界 -- [`encode-knowledge-into-repo.md`](./encode-knowledge-into-repo.md): - 把聊天、外部文档、脑内知识变成 repo-local 文档 -- [`observability-feedback-loop.md`](./observability-feedback-loop.md): - 给 agent 提供 logs、metrics、traces 与可重复的调试闭环 -- [`chrome-devtools-validation-loop.md`](./chrome-devtools-validation-loop.md): - 用浏览器自动化和快照反复验证 UI,直到 clean - -## 怎么用 - -1. 先选和你当前瓶颈最匹配的 SOP。 -2. 按检查清单补齐缺失工件或工具。 -3. 把得到的规则编码回 `repo-template/` 里的文档。 -4. 把反复出现的 review 评论升级成检查、脚本或 guardrail。 - -这些 SOP 不是让你机械照抄,而是为了让 harness 更可读、更可执行、更可复用。 diff --git a/courses/learn-harness-engineering/docs/zh/resources/openai-advanced/sops/layered-domain-architecture.md b/courses/learn-harness-engineering/docs/zh/resources/openai-advanced/sops/layered-domain-architecture.md deleted file mode 100644 index 09a77aaa..00000000 --- a/courses/learn-harness-engineering/docs/zh/resources/openai-advanced/sops/layered-domain-architecture.md +++ /dev/null @@ -1,47 +0,0 @@ -# SOP:分层领域架构 - -当 agent 反复跨层乱连、在不同层重复逻辑、或者几轮会话后代码越来越难审时,就用这份 SOP。 - -## 目标 - -把领域边界写清楚、立起来、能执行,让 agent 在高速度下也不至于悄悄把结构做烂。 - -## 目标模型 - -在一个业务领域内部,优先使用这条单向流: - -`Types -> Config -> Repo -> Service -> Runtime -> UI` - -跨领域关注点通过明确的 provider 或 adapter 进入。共享 utils 保持在领域之外,不能慢慢长成业务逻辑垃圾场。 - -## 建设检查清单 - -- 在 `ARCHITECTURE.md` 里列清当前 domains。 -- 在 `ARCHITECTURE.md` 里写清允许的依赖方向。 -- 记录 auth、telemetry、外部 API 等 cross-cutting interface。 -- 为当前最难处理的边界违规写一条短说明。 -- 决定哪些规则应该升级成 lint、test 或 script。 - -## 执行 SOP - -1. 先给代码库画 domain map,再谈实现风格。 -2. 对每个 domain,明确允许的 layer sequence。 -3. 找出所有横切关注点,并把它们改走 provider 或 adapter。 -4. 把含糊的 shared logic 重新放回所属 domain,或抽成真正通用的 utils。 -5. 把规则写进 `ARCHITECTURE.md`。 -6. 先为代价最高的一类违规补一个可执行 guardrail。 -7. 改完后更新质量评分。 - -## 完成定义 - -- 一个全新的 agent 能判断某个改动应该落在哪一层。 -- UI 不再直接连 repo 或外部副作用。 -- 横切能力都有明确入口。 -- 至少一条重要边界已经被机械执行。 - -## 需要同步更新的仓库工件 - -- `ARCHITECTURE.md` -- `docs/QUALITY_SCORE.md` -- 如果设计理由变了,更新 `docs/design-docs/` -- `docs/PLANS.md` 或当前 active execution plan diff --git a/courses/learn-harness-engineering/docs/zh/resources/openai-advanced/sops/observability-feedback-loop.md b/courses/learn-harness-engineering/docs/zh/resources/openai-advanced/sops/observability-feedback-loop.md deleted file mode 100644 index d9410141..00000000 --- a/courses/learn-harness-engineering/docs/zh/resources/openai-advanced/sops/observability-feedback-loop.md +++ /dev/null @@ -1,42 +0,0 @@ -# SOP:可观测性反馈闭环 - -当调试太慢、agent 总在没证据的情况下宣布成功、或者运行时行为比代码本身还难看懂时,就用这份 SOP。 - -## 目标 - -给 agent 一套本地闭环,让它可以基于 logs、metrics、traces 和可重复 workload 来判断系统,而不是只靠看代码猜。 - -## 最小可用栈 - -- 应用输出结构化日志 -- 条件允许时输出 metrics 和 traces -- 本地采集或 fan-out 层 -- 可查询 logs / metrics / traces 的接口 -- 每次改动后都能重跑的 workload 或 user journey - -## 执行 SOP - -1. 先定义最重要的黄金运行旅程。 -2. 给启动流程和关键路径补结构化日志。 -3. 在合适位置补 latency、failure count、queue depth 之类的 metrics。 -4. 为慢路径或多阶段流程补 traces 或 timing 标记。 -5. 让这些信号能从本地开发环境查询到。 -6. 给 agent 一条可以反复重跑的 workload 或场景。 -7. 强制执行这条闭环:query -> correlate -> reason -> implement -> restart - -> rerun -> verify。 - -## 调试会话检查清单 - -- 到底哪里失败了? -- 哪条信号能证明它失败? -- 失败归属哪一层? -- 修复后哪条信号发生了变化? -- App 是否能干净重启? -- 同一 workload 重跑后是否通过? - -## 完成定义 - -- agent 能用运行证据解释失败模式。 -- 每次改动后都能重跑同一 workload。 -- restart 与 rerun 已经成为正常任务循环的一部分。 -- 可靠性信号已经记录到 `docs/RELIABILITY.md`。 diff --git a/courses/learn-harness-engineering/docs/zh/resources/reference/coding-agent-startup-flow.md b/courses/learn-harness-engineering/docs/zh/resources/reference/coding-agent-startup-flow.md deleted file mode 100644 index 1d74379e..00000000 --- a/courses/learn-harness-engineering/docs/zh/resources/reference/coding-agent-startup-flow.md +++ /dev/null @@ -1,33 +0,0 @@ -# 编码代理开工流程 - -初始化完成后,每轮编码会话都按这个流程开始。 - -## 固定开工模板 - -1. 运行 `pwd`,确认仓库根目录 -2. 读取 `claude-progress.md` -3. 读取 `feature_list.json` -4. 用 `git log --oneline -5` 查看最近提交 -5. 运行 `./init.sh` -6. 跑一条基础 smoke test 或端到端路径 -7. 如果基础状态已坏,先修这个 -8. 选择最高优先级的未完成功能 -9. 只围绕这个功能工作,直到它被验证或明确 blocked - -## 为什么顺序不能乱 - -- `pwd` 能防止在错误目录里干活 -- progress 和 feature 文件先恢复持久状态 -- 最近提交能解释刚刚发生了什么 -- `init.sh` 让启动过程标准化,而不是靠记忆 -- 基础验证先跑,可以避免在坏状态上继续叠改动 - -## 对应的收尾流程 - -同一轮会话结束时,也应该镜像地做这些事: - -1. 记录进度 -2. 更新功能状态 -3. 必要时写交接摘要 -4. 提交安全状态的代码 -5. 留下可直接重启的干净环境 diff --git a/courses/learn-harness-engineering/docs/zh/resources/reference/index.md b/courses/learn-harness-engineering/docs/zh/resources/reference/index.md deleted file mode 100644 index 8eb48b97..00000000 --- a/courses/learn-harness-engineering/docs/zh/resources/reference/index.md +++ /dev/null @@ -1,71 +0,0 @@ -# 中文参考 - -这一部分解释这些模板该怎么配合使用,而不是把它们当成一堆孤立文件。 - -## 内部参考材料 - -- [`method-map.md`](./method-map.md):把常见长时任务翻车点映射到对应方法和工件 -- [`initializer-agent-playbook.md`](./initializer-agent-playbook.md):初始化代理在第一轮应该产出什么 -- [`coding-agent-startup-flow.md`](./coding-agent-startup-flow.md):后续编码代理每次开工的固定流程 -- [`prompt-calibration.md`](./prompt-calibration.md):根指令应该写到什么程度才合适 - -## 重点参考文章 - -这里的筛选标准很窄:只保留能直接解释 harness 机制的文章。Harness 在这里指模型外部的运行系统,包括 agent loop、工具执行、沙箱、状态、上下文、验证、终止条件、控制平面和观测反馈;不是泛泛的 prompt engineering 或 agent 框架介绍。 - -保留原始三篇作为课程主轴: - -- [OpenAI: Harness engineering: leveraging Codex in an agent-first world](https://openai.com/index/harness-engineering/)(2026-02-11):agent-first 仓库、repo-local context、custom lint、结构性 guardrail。 -- [Anthropic: Effective harnesses for long-running agents](https://www.anthropic.com/engineering/effective-harnesses-for-long-running-agents)(2025-11-26):initializer agent、coding agent、feature list、progress log、跨上下文窗口交接。 -- [Anthropic: Harness design for long-running application development](https://www.anthropic.com/engineering/harness-design-long-running-apps)(2026-03-24):planner / generator / evaluator 三角色、context reset、harness 简化和组件过期问题。 - -额外只加入几篇高相关、高含金量的 2026 文章: - -- [OpenAI: Unrolling the Codex agent loop](https://openai.com/index/unrolling-the-codex-agent-loop/)(2026-01-23):解释 Codex runtime harness 的核心循环、工具调用、上下文增长和终止状态。 -- [Anthropic: Demystifying evals for AI agents](https://www.anthropic.com/engineering/demystifying-evals-for-ai-agents)(2026-01-09):明确评估 agent 时评的是 model + harness,并区分 evaluation harness 与 agent harness。 -- [LangChain: Improving Deep Agents with harness engineering](https://www.langchain.com/blog/improving-deep-agents-with-harness-engineering)(2026-02-17):同一模型不变,只改 system prompt、tools、middleware、tracing 和 self-verification,让 coding agent 在 Terminal Bench 2.0 上从 Top 30 进到 Top 5。 -- [Thoughtworks / Martin Fowler: Harness engineering for coding agent users](https://martinfowler.com/articles/harness-engineering.html)(2026-04-02):把 coding-agent user harness 拆成 feedforward guides 和 feedback sensors,并区分 deterministic controls 与 inferential controls。 -- [Cursor: Continually improving our agent harness](https://cursor.com/blog/continually-improving-agent-harness)(2026-04-30):把 harness 当成持续迭代的产品系统,用离线评估、线上指标、工具错误分类、模型定制和 mid-chat model switching 改善 agent 行为。 - -## 2026 扩展参考 - -这些文章不作为课程主轴,但在设计特定 harness 模块时很有借鉴价值。只保留文章正文直接涉及 agent loop、工具执行、上下文管理、验证、沙箱、控制层、回归治理等机制的材料;纯 agent 产品、平台发布、团队实践或 benchmark 不放进这里。 - -- [OpenAI: Unlocking the Codex harness: how we built the App Server](https://openai.com/index/unlocking-the-codex-harness/)(2026-02-04):把 harness 抽象成 App Server 协议,覆盖 thread lifecycle、resume、fork、diff 和客户端集成。 -- [OpenAI Developers: Run long horizon tasks with Codex](https://developers.openai.com/blog/run-long-horizon-tasks-with-codex)(2026-02-23):长时任务中的 durable project memory、milestone validation 和 done-when 例子。 -- [OpenAI: The next evolution of the Agents SDK](https://openai.com/index/the-next-evolution-of-the-agents-sdk/)(2026-04-15):model-native harness、sandbox execution、文件与命令执行能力。 -- [OpenAI: An open-source spec for Codex orchestration: Symphony](https://openai.com/index/open-source-codex-orchestration-symphony/)(2026-04-27):把 issue tracker / Linear 变成多 agent 控制平面。 -- [Anthropic: Building a C compiler with a team of parallel Claudes](https://www.anthropic.com/engineering/building-c-compiler)(2026-02-05):并行 agent 团队、任务锁、git 同步、容器隔离和自主循环。 -- [Anthropic: Scaling Managed Agents: Decoupling the brain from the hands](https://www.anthropic.com/engineering/managed-agents)(2026-04-08):meta-harness 视角,把 session、harness、sandbox 拆成可替换接口。 -- [Anthropic: An update on recent Claude Code quality reports](https://www.anthropic.com/engineering/april-23-postmortem)(2026-04-23):reasoning effort、context pruning、system prompt 都属于 harness 变更,且需要回归治理。 -- [LangChain: Context Management for Deep Agents](https://www.langchain.com/blog/context-management-for-deepagents)(2026-01-28):filesystem offloading、tool-call truncation、summarization 和 targeted evals 组成的 context-management harness。 -- [LangChain: Tuning Deep Agents to Work Well with Different Models](https://www.langchain.com/blog/tuning-deep-agents-different-models)(2026-04-29):用 model-specific harness profiles 调整 prompt、tool names、middleware 和 subagent 配置。 -- [LangChain: Continual learning for AI agents](https://www.langchain.com/blog/continual-learning-for-ai-agents)(2026-04-05):把 agent 改进拆成 model、harness、context 三层,并把 traces 作为改进信号。 -- [Microsoft: Agent Harness in Agent Framework](https://devblogs.microsoft.com/agent-framework/agent-harness-in-agent-framework/)(2026-03-12):shell/filesystem harness、approval flow、hosted shell、context compaction。 -- [Google: Announcing ADK for Java 1.0.0](https://developers.googleblog.com/announcing-adk-for-java-100-building-the-future-of-ai-agents-in-java/)(2026-03-30):插件、event compaction、HITL、session/memory service、A2A 等可复用 harness primitives。 -- [GitHub: Automate repository tasks with GitHub Agentic Workflows](https://github.blog/ai-and-ml/automate-repository-tasks-with-github-agentic-workflows/)(2026-02-13):把 GitHub Actions 变成 agentic workflow runner,包含 safe outputs、sandboxing、permissions、review。 -- [AWS: AI agents in enterprises: Best practices with Amazon Bedrock AgentCore](https://aws.amazon.com/blogs/machine-learning/ai-agents-in-enterprises-best-practices-with-amazon-bedrock-agentcore/)(2026-02-03):Runtime、Memory、Gateway、Identity/Policy、Observability、Evaluations 的企业级 harness 分层。 -- [Stripe: Minions: Stripe's one-shot, end-to-end coding agents](https://stripe.dev/blog/minions-stripes-one-shot-end-to-end-coding-agents)(2026-02-09)和 [Part 2](https://stripe.dev/blog/minions-stripes-one-shot-end-to-end-coding-agents-part-2)(2026-02-19):devbox 隔离、custom agent harness、blueprints 状态机、规则文件、MCP tool curation、安全控制、pre-push/CI 反馈循环。 -- [Cognition: What We Learned Building Cloud Agents](https://cognition.ai/blog/what-we-learned-building-cloud-agents)(2026-04-23):云端 agent runtime 的 VM 隔离、session snapshot/resume、orchestration、governance、audit logging 和 integrations。 -- [Cognition: Multi-Agents: What's Actually Working](https://cognition.ai/blog/multi-agents-working)(2026-04-22):generator-verifier loop、clean-context reviewer、smart-friend routing、manager-child coordination 和跨 agent 通信边界。 -- [Replit: Decision-Time Guidance: Keeping Replit Agent Reliable](https://blog.replit.com/decision-time-guidance)(2026-01-20,2026-01-23 更新):用轻量分类器在关键决策点注入短指令,而不是把所有规则塞进系统提示词。 -- [Vercel: How we made v0 an effective coding agent](https://vercel.com/blog/how-we-made-v0-an-effective-coding-agent)(2026-01-07):动态系统提示、streaming rewrite layer 和 deterministic/model-driven autofixers。 -- [Vercel: Introducing deepsec](https://vercel.com/blog/introducing-deepsec-find-and-fix-vulnerabilities-in-your-code-base)(2026-05-04):面向安全扫描的 coding-agent harness,包含 scan、investigate、revalidate、enrich、export、plugin 和 refusal-checker。 -- [Sourcegraph: CodeScaleBench](https://sourcegraph.com/blog/codescalebench-testing-coding-agents-on-large-codebases-and-multi-repo-software-engineering-tasks)(2026-03-03):偏 eval/tooling harness,包含 MCP tool adoption、tool-use transcripts、benchmark QA、verifier/reproducibility gates 和 prompt/preamble 迭代。 - -严格按时间筛选时,2025-only 的泛参考不进入主列表。原始三篇中的 Anthropic 2025 文章保留,是因为它是本课程方法的基础来源。 - -## 推荐阅读顺序 - -1. `method-map.md` -2. `initializer-agent-playbook.md` -3. `coding-agent-startup-flow.md` -4. `prompt-calibration.md` -5. OpenAI Harness engineering -6. Anthropic Effective harnesses -7. Anthropic Harness design for long-running application development -8. OpenAI Codex agent loop -9. Anthropic agent evals -10. LangChain Improving Deep Agents -11. Thoughtworks / Martin Fowler Harness engineering for coding agent users -12. Cursor Continually improving our agent harness diff --git a/courses/learn-harness-engineering/docs/zh/resources/reference/initializer-agent-playbook.md b/courses/learn-harness-engineering/docs/zh/resources/reference/initializer-agent-playbook.md deleted file mode 100644 index 10c4ad6e..00000000 --- a/courses/learn-harness-engineering/docs/zh/resources/reference/initializer-agent-playbook.md +++ /dev/null @@ -1,35 +0,0 @@ -# 初始化代理操作手册 - -这个手册用于仓库里的第一轮重要会话,也就是正式开始增量开发之前的初始化阶段。 - -## 目标 - -先搭出稳定的工作面,让后续会话不必重新推导启动命令、当前状态和任务边界。 - -## 必需产出 - -初始化阶段至少应该留下这些工件: - -- 一个根指令文件,例如 `AGENTS.md` 或 `CLAUDE.md` -- 一个机器可读的功能面,例如 `feature_list.json` -- 一个持久进度工件,例如 `claude-progress.md` -- 一个标准启动辅助脚本,例如 `init.sh` -- 一个记录基础脚手架状态的初始安全提交 - -## 检查清单 - -1. 定义标准启动路径 -2. 定义标准验证路径 -3. 建立进度日志并写下初始状态 -4. 把工作拆成功能,并给出状态字段 -5. 建立第一个干净的 baseline commit - -## 成功标准 - -一个完全没有前文聊天上下文的新会话,应该能回答: - -- 这个仓库是做什么的 -- 怎么启动 -- 怎么验证 -- 还有什么没做完 -- 下一步最佳动作是什么 diff --git a/courses/learn-harness-engineering/docs/zh/resources/reference/method-map.md b/courses/learn-harness-engineering/docs/zh/resources/reference/method-map.md deleted file mode 100644 index b442f356..00000000 --- a/courses/learn-harness-engineering/docs/zh/resources/reference/method-map.md +++ /dev/null @@ -1,16 +0,0 @@ -# 方法对照表 - -这个表把最常见的长时 coding-agent 失败模式,对应到最先该补的工件或规则。 - -| 失败模式 | 实际表现 | 首要修复 | 主要工件 | -| --- | --- | --- | --- | -| 冷启动混乱 | 新会话花大量时间重新摸索状态和启动方式 | 让仓库成为唯一事实来源 | `claude-progress.md` | -| 范围蔓延 | 一次启动多个功能,最后没有一个完整收尾 | 限制当前活跃范围 | `feature_list.json` | -| 提前宣布完成 | 代码改了就说“完成了”,但没有可运行证据 | 把完成绑定到验证证据 | `clean-state-checklist.md` | -| 启动脆弱 | 每轮会话都要重新学怎么启动项目 | 统一启动与验证路径 | `init.sh` | -| 交接薄弱 | 下一轮看不出哪里可用、哪里坏了、接下来做什么 | 明确写交接摘要 | `session-handoff.md` | -| 评审主观 | 质量判断依赖个人记忆和感觉 | 用固定维度做评分 | `evaluator-rubric.md` | - -## 使用原则 - -优先补最能直接消除当前失败模式的那个工件,不要一出问题就把更多规则堆进一个超长 instruction 文件里。 diff --git a/courses/learn-harness-engineering/docs/zh/resources/reference/prompt-calibration.md b/courses/learn-harness-engineering/docs/zh/resources/reference/prompt-calibration.md deleted file mode 100644 index f89002c2..00000000 --- a/courses/learn-harness-engineering/docs/zh/resources/reference/prompt-calibration.md +++ /dev/null @@ -1,23 +0,0 @@ -# Prompt 校准 - -根指令文件应该定义工作框架,而不是把所有动作写死。 - -## 应该留在根文件里的内容 - -- 仓库用途和范围 -- 启动路径 -- 验证路径 -- 不可违反的约束 -- 必需的状态工件 -- 会话结束规则 - -## 应该移出根文件的内容 - -- 过长的历史边角案例 -- 只属于某个子系统的细节实现说明 -- 更适合贴在代码附近的局部架构笔记 -- 只对单一模块成立的示例 - -## 工作原则 - -根文件的职责是让新会话快速建立方向感。如果它开始变成“过去每次失败都往里加一句”的堆叠场,就应该把细节拆到更小、更明确的文档里,再从根文件跳转过去。 diff --git a/courses/learn-harness-engineering/docs/zh/resources/templates/AGENTS.md b/courses/learn-harness-engineering/docs/zh/resources/templates/AGENTS.md deleted file mode 100644 index 66d84dc8..00000000 --- a/courses/learn-harness-engineering/docs/zh/resources/templates/AGENTS.md +++ /dev/null @@ -1,50 +0,0 @@ -# AGENTS.md - -这个仓库面向长时运行的 coding agent 工作流。目标不是尽可能快地产出代码,而是让每一轮会话结束后,下一个会话仍然能无猜测地继续工作。 - -## 开工流程 - -写代码前先做这些事: - -1. 用 `pwd` 确认当前目录。 -2. 读取 `claude-progress.md`,了解最新已验证状态和下一步。 -3. 读取 `feature_list.json`,选择优先级最高的未完成功能。 -4. 用 `git log --oneline -5` 看最近提交。 -5. 运行 `./init.sh`。 -6. 在开始新功能前,先跑必需的 smoke test 或端到端验证。 - -如果基础验证一开始就失败,先修基础状态,不要在坏的起点上继续叠新功能。 - -## 工作规则 - -- 一次只做一个功能。 -- 不要因为“代码已经写了”就把功能标记为完成。 -- 除非为了消除当前 blocker 的窄范围修复,否则不要扩大到其他功能。 -- 实现过程中不要悄悄改弱验证规则。 -- 优先依赖仓库里的持久化文件,而不是聊天记录。 - -## 必需文件 - -- `feature_list.json`:功能状态的唯一事实来源 -- `claude-progress.md`:会话进度和当前已验证状态 -- `init.sh`:统一的启动与验证入口 -- `session-handoff.md`:较长会话可选的交接摘要 - -## 完成定义 - -一个功能只有在以下条件都满足时才算完成: - -- 目标行为已经实现 -- 要求的验证真的跑过 -- 证据记录在 `feature_list.json` 或 `claude-progress.md` -- 仓库仍然能按标准启动路径重新开始工作 - -## 收尾 - -结束会话前: - -1. 更新 `claude-progress.md` -2. 更新 `feature_list.json` -3. 记录仍未解决的风险或 blocker -4. 在工作处于安全状态后,用清晰的提交信息提交 -5. 保证下一轮会话可以直接运行 `./init.sh` diff --git a/courses/learn-harness-engineering/docs/zh/resources/templates/CLAUDE.md b/courses/learn-harness-engineering/docs/zh/resources/templates/CLAUDE.md deleted file mode 100644 index bc2bed3b..00000000 --- a/courses/learn-harness-engineering/docs/zh/resources/templates/CLAUDE.md +++ /dev/null @@ -1,43 +0,0 @@ -# CLAUDE.md - -你正在一个为长时实现工作设计的仓库中工作。优先保证可靠完成、跨会话连续性和显式验证,而不是表面上的速度。 - -## 固定工作循环 - -每轮会话开始时: - -1. 运行 `pwd`,确认当前在正确的仓库根目录 -2. 读取 `claude-progress.md` -3. 读取 `feature_list.json` -4. 用 `git log --oneline -5` 查看最近提交 -5. 运行 `./init.sh` -6. 检查基础 smoke test 或端到端路径是否已经损坏 - -然后只选择一个未完成功能,围绕它工作,直到它被验证通过,或者被明确记录为 blocked。 - -## 规则 - -- 同一时间只能有一个 active feature -- 没有可运行证据时,不要声称完成 -- 不要通过重写功能清单来隐藏未完成工作 -- 不要为了“看起来完成”而删除或削弱测试 -- 以仓库内文件作为唯一事实来源 - -## 必需文件 - -- `feature_list.json` -- `claude-progress.md` -- `init.sh` -- 需要简短交接时使用 `session-handoff.md` - -## 完成门槛 - -只有在要求的验证成功且结果被记录后,功能状态才可以切换到 `passing`。 - -## 结束前 - -1. 更新进度日志 -2. 更新功能状态 -3. 记录仍然损坏或未验证的内容 -4. 在仓库可安全恢复后提交 -5. 给下一轮会话留下干净的重启路径 diff --git a/courses/learn-harness-engineering/docs/zh/resources/templates/claude-progress.md b/courses/learn-harness-engineering/docs/zh/resources/templates/claude-progress.md deleted file mode 100644 index 8ee42eea..00000000 --- a/courses/learn-harness-engineering/docs/zh/resources/templates/claude-progress.md +++ /dev/null @@ -1,35 +0,0 @@ -# 进度日志 - -## 当前已验证状态 - -- 仓库根目录: -- 标准启动路径: -- 标准验证路径: -- 当前最高优先级未完成功能: -- 当前 blocker: - -## 会话记录 - -### Session 001 - -- 日期: -- 本轮目标: -- 已完成: -- 运行过的验证: -- 已记录证据: -- 提交记录: -- 更新过的文件或工件: -- 已知风险或未解决问题: -- 下一步最佳动作: - -### Session 002 - -- 日期: -- 本轮目标: -- 已完成: -- 运行过的验证: -- 已记录证据: -- 提交记录: -- 更新过的文件或工件: -- 已知风险或未解决问题: -- 下一步最佳动作: diff --git a/courses/learn-harness-engineering/docs/zh/resources/templates/clean-state-checklist.md b/courses/learn-harness-engineering/docs/zh/resources/templates/clean-state-checklist.md deleted file mode 100644 index 6a2717d4..00000000 --- a/courses/learn-harness-engineering/docs/zh/resources/templates/clean-state-checklist.md +++ /dev/null @@ -1,8 +0,0 @@ -# 干净状态检查清单 - -- [ ] 标准启动路径仍然可用 -- [ ] 标准验证路径仍然可运行 -- [ ] 当前进度已经记录到进度日志 -- [ ] 功能状态真实反映了 passing 和未验证的边界 -- [ ] 没有任何半成品步骤处于未记录状态 -- [ ] 下一轮会话无需人工修复即可继续 diff --git a/courses/learn-harness-engineering/docs/zh/resources/templates/evaluator-rubric.md b/courses/learn-harness-engineering/docs/zh/resources/templates/evaluator-rubric.md deleted file mode 100644 index cedc842c..00000000 --- a/courses/learn-harness-engineering/docs/zh/resources/templates/evaluator-rubric.md +++ /dev/null @@ -1,24 +0,0 @@ -# 评审评分表 - -在实现完成后、正式验收前,用这张表做一次评审。 - -| 维度 | 问题 | 分数 (0-2) | 备注 | -| --- | --- | --- | --- | -| 正确性 | 实现出来的行为是否符合目标功能? | | | -| 验证 | 要求的检查是否真的跑过,并留下证据? | | | -| 范围纪律 | 这一轮是否基本保持在选定功能范围内? | | | -| 可靠性 | 结果是否能在重启或重跑后继续工作? | | | -| 可维护性 | 代码和文档是否清楚到足以交给下一轮会话? | | | -| 交接准备度 | 新会话是否能只靠仓库内工件继续推进? | | | - -## 结论 - -- Accept -- Revise -- Block - -## 后续动作 - -- 缺失的证据: -- 必须补的修复: -- 下次复审触发条件: diff --git a/courses/learn-harness-engineering/docs/zh/resources/templates/feature_list.json b/courses/learn-harness-engineering/docs/zh/resources/templates/feature_list.json deleted file mode 100644 index 41b97536..00000000 --- a/courses/learn-harness-engineering/docs/zh/resources/templates/feature_list.json +++ /dev/null @@ -1,64 +0,0 @@ -{ - "project": "替换成你的项目名", - "last_updated": "YYYY-MM-DD", - "rules": { - "single_active_feature": true, - "passing_requires_evidence": true, - "do_not_skip_verification": true - }, - "status_legend": { - "not_started": "功能还没开始做。", - "in_progress": "这个功能是当前唯一正在进行的任务。", - "blocked": "因为已记录的阻塞问题,当前无法继续推进。", - "passing": "要求的验证已经通过,并且证据已经记录。" - }, - "features": [ - { - "id": "chat-001", - "priority": 1, - "area": "chat", - "title": "创建新会话", - "user_visible_behavior": "用户点击 New Chat 后,可以看到一个新的空白对话。", - "status": "not_started", - "verification": [ - "打开应用。", - "点击 New Chat。", - "确认侧边栏出现一个新会话。", - "确认主面板显示空白对话状态。" - ], - "evidence": [], - "notes": "" - }, - { - "id": "chat-002", - "priority": 2, - "area": "chat", - "title": "在当前会话里发送消息", - "user_visible_behavior": "用户提交一条消息后,可以在当前线程中看到它。", - "status": "not_started", - "verification": [ - "打开一个已有会话。", - "在输入框中输入消息。", - "提交消息。", - "确认线程中出现这条新消息。" - ], - "evidence": [], - "notes": "" - }, - { - "id": "chat-003", - "priority": 3, - "area": "chat", - "title": "持久化保存会话列表", - "user_visible_behavior": "应用重启后,用户仍能看到之前创建的会话。", - "status": "not_started", - "verification": [ - "创建两个会话。", - "重启应用。", - "确认两个会话仍显示在侧边栏中。" - ], - "evidence": [], - "notes": "" - } - ] -} diff --git a/courses/learn-harness-engineering/docs/zh/resources/templates/index.md b/courses/learn-harness-engineering/docs/zh/resources/templates/index.md deleted file mode 100644 index 93959708..00000000 --- a/courses/learn-harness-engineering/docs/zh/resources/templates/index.md +++ /dev/null @@ -1,226 +0,0 @@ -# 模板使用指南 - -这些模板可以直接复制到你的项目里用。每个文件在 agent 的工作流程中各有分工。复制过去后,把里面的命令、路径、功能名称和验证步骤换成你自己项目的。 - -## 怎么开始 - -先把这四个文件复制到项目根目录: - -1. `AGENTS.md` 或 `CLAUDE.md` -2. `init.sh` -3. `claude-progress.md` -4. `feature_list.json` - -等项目变复杂了,再补其余的文件。 - ---- - -## AGENTS.md - -根指令文件。agent 每次开工会先读这个文件。它定义了工作规则:写代码前要做什么、工作过程中怎么守规矩、收尾时要检查什么。 - -**怎么用:** - -- 复制到项目根目录 -- 把开工流程里的步骤换成你自己项目的路径和命令 -- 工作规则按你们团队的约定调整 -- 完成定义那一段别改——那是整个 harness 最关键的部分 - -**它帮 agent 做什么:** - -- 让它在开始工作前先读进度和功能状态 -- 逼它一次只做一个功能 -- 要求它拿出证据才能标记完成 -- 定义了什么叫"干净收尾" - -用 `AGENTS.md` 给 Codex 或其他 agent。用 `CLAUDE.md` 给 Claude Code——内容一样,格式按 Claude 的指令风格来的。 - -## init.sh - -启动脚本。一条命令完成依赖安装、验证和打印启动命令。 - -**怎么用:** - -- 复制到项目根目录 -- 改顶部这三个变量: - - `INSTALL_CMD` — 你的依赖安装命令(比如 `npm install`、`pip install -r requirements.txt`) - - `VERIFY_CMD` — 你的基础验证命令(比如 `npm test`、`pytest`) - - `START_CMD` — 你的开发服务器启动命令(比如 `npm run dev`) -- 加执行权限:`chmod +x init.sh` - -**它做什么:** - -1. 打印当前目录(确认跑在正确的地方) -2. 安装依赖 -3. 跑验证命令 -4. 打印启动命令(如果设了 `RUN_START_COMMAND=1` 就直接启动) - -如果验证失败了,agent 应该停下来先修基础状态,不要在坏的基础上继续叠新功能。 - -## claude-progress.md - -进度日志。每轮会话往里写,每轮新会话先读它。 - -**怎么用:** - -- 复制到项目根目录 -- 把"当前已验证状态"那段填上你的项目信息 -- 每轮会话结束后更新会话记录 - -**每个字段的意思:** - -- **当前已验证状态** — 项目当前进展的唯一真相 - - `仓库根目录` — 项目在哪 - - `标准启动路径` — 把项目跑起来的命令 - - `标准验证路径` — 跑测试的命令 - - `当前最高优先级未完成功能` — 下一轮该做什么 - - `当前 blocker` — 哪里卡住了 -- **会话记录** — 每轮一条 - - `本轮目标` — 打算做什么 - - `已完成` — 实际做了什么 - - `运行过的验证` — 跑了什么测试 - - `已记录证据` — 留下了什么证明 - - `提交记录` — 提了什么 commit - - `已知风险或未解决问题` — 哪里可能有问题 - - `下一步最佳动作` — 下一轮从哪开始 - -## feature_list.json - -功能清单。机器可读的功能列表,每个功能有状态、验证步骤和证据。 - -**怎么用:** - -- 复制到项目根目录 -- 把示例功能换成你自己的 -- 每个功能需要填: - - `id` — 短的唯一标识 - - `priority` — 整数,越小越优先 - - `area` — 属于应用的哪块(比如 "chat"、"import"、"search") - - `title` — 简短描述 - - `user_visible_behavior` — 功能正常时用户能看到什么 - - `status` — 四种状态之一:`not_started`、`in_progress`、`blocked`、`passing` - - `verification` — 逐步验证步骤 - - `evidence` — 验证通过的记录(agent 填) - - `notes` — 额外说明 - -**状态规则:** - -- `not_started` — 还没碰 -- `in_progress` — 当前正在做的那个(同一时间只能有一个) -- `blocked` — 有记录的阻塞问题,推不动 -- `passing` — 验证通过,证据已记录 - -agent 任何时候只能有一个功能处于 `in_progress`。 - -## session-handoff.md - -会话交接摘要。一轮会话结束时写,下一轮开始时读。让接手的人(或 agent)快速了解现状。 - -**怎么用:** - -- 复制到项目根目录 -- 每轮会话结束时填写(也可以让 agent 自己填) - -**每段写什么:** - -- **当前已验证** — 哪些是确认能用的,跑过什么验证 -- **本轮改动** — 改了什么代码或基础设施 -- **仍损坏或未验证** — 已知问题和风险区 -- **下一步最佳动作** — 下一轮该做什么,哪些东西不要动 -- **命令** — 启动、验证、调试命令,方便快速参考 - -短会话可以不写这个文件。会话长了或者项目有多个并行区域时,它就很关键了。 - -## clean-state-checklist.md - -收尾检查清单。每次会话结束前过一遍,确保仓库处于下一轮可以直接开工的状态。 - -**怎么用:** - -- 复制到项目根目录 -- 关掉会话前逐项检查 -- agent 的收尾流程里也应该包含这些检查 - -**检查什么:** - -- 标准启动路径还能用 -- 标准验证还能跑 -- 进度日志已更新 -- 功能清单真实反映了 passing 和未验证的边界(没有假 passing) -- 没有半成品处于未记录状态 -- 下一轮会话不需要人工修复就能继续 - -## evaluator-rubric.md - -评审评分表。会话结束后或到里程碑时,用它评估 agent 做的东西够不够格。 - -**怎么用:** - -- 复制到项目根目录 -- 一轮(或几轮)会话后,按六个维度打分 -- 每个维度 0-2 分 - -**六个维度:** - -1. **正确性** — 实现出来的行为是否符合目标功能 -2. **验证** — 要求的检查是否真的跑过,并留下证据 -3. **范围纪律** — 是否基本保持在选定功能范围内 -4. **可靠性** — 结果是否能在重启或重跑后继续工作 -5. **可维护性** — 代码和文档是否清楚到足以交给下一轮会话 -6. **交接准备度** — 新会话是否能只靠仓库内文件继续推进 - -**结论选项:** - -- Accept — 达标 -- Revise — 需要修补才能接受 -- Block — 有根本性问题,需要先解决 - -**Evaluator 需要校准。** 开箱即用的 agent 做评审很弱——它会发现问题,然后把自己说服到通过。你需要反复校准: - -1. 用 evaluator 给一个已完成的 sprint 打分。 -2. 把它的分数和你自己的人工判断对比。 -3. 有分歧的地方,把 rubric 里的通过/失败标准写得更具体。 -4. 对同一个输出重新跑 evaluator,看对齐了没有。 -5. 重复直到 evaluator 的判断和人工评审基本一致。 - -预计需要 3-5 轮校准。每轮记录改了什么、为什么改。 - -## quality-document.md - -质量快照。给项目的每个产品领域和架构层打分,跟踪代码库随时间是变强了还是变弱了。 - -**怎么用:** - -- 复制到项目根目录 -- 开始会话前:读它,了解代码库哪里最弱 -- 会话结束后:更新评级 -- 长期:对比不同时间点的快照,看哪些 harness 改动真正改善了代码库健康度 - -**评什么:** - -- **产品领域**(如文档导入、问答流程、索引):每个领域按验证状态、Agent 可读性、测试稳定性、关键缺口打分 -- **架构层**(如 Main Process、Preload、Renderer、Services):每层按边界执行和 Agent 可读性打分 - -**为什么需要它:** - -Evaluator rubric 评的是单次 agent 输出的质量。Quality document 评的是代码库本身的质量。它们回答的是不同的问题: - -- Evaluator rubric:"这轮 agent 做得好不好?" -- Quality document:"这个项目是在变强还是变弱?" - -**什么时候更新:** - -- 每轮重要会话之后 -- 做基准对比之前 -- 做清理或简化之后 -- 换新 agent 或新模型时 - -**和 harness 简化的关系:** - -Quality document 也可以用来验证 harness 是否可以简化。Harness 里的每个组件都编码了一个假设——"模型做不到这件事"。模型变强后,这些假设就过时了。检查某个组件是否还有必要: - -1. 拍一份 quality document 快照。 -2. 移除一个 harness 组件。 -3. 跑基准测试。 -4. 再拍一份快照。 -5. 对比——评级没降,说明那个组件是多余的。降了,就恢复。 diff --git a/courses/learn-harness-engineering/docs/zh/resources/templates/init.sh b/courses/learn-harness-engineering/docs/zh/resources/templates/init.sh deleted file mode 100755 index c153b2af..00000000 --- a/courses/learn-harness-engineering/docs/zh/resources/templates/init.sh +++ /dev/null @@ -1,29 +0,0 @@ -#!/usr/bin/env bash - -set -euo pipefail - -ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" -cd "$ROOT_DIR" - -# 按你的项目实际情况替换这些命令。 -INSTALL_CMD=(npm install) -VERIFY_CMD=(npm test) -START_CMD=(npm run dev) - -echo "==> 当前目录: $PWD" -echo "==> 同步依赖" -"${INSTALL_CMD[@]}" - -echo "==> 运行基础验证" -"${VERIFY_CMD[@]}" - -echo "==> 启动命令" -printf ' %q' "${START_CMD[@]}" -printf '\n' - -if [ "${RUN_START_COMMAND:-0}" = "1" ]; then - echo "==> 启动应用" - exec "${START_CMD[@]}" -fi - -echo "如果希望 init.sh 直接启动应用,请设置 RUN_START_COMMAND=1。" diff --git a/courses/learn-harness-engineering/docs/zh/resources/templates/quality-document.md b/courses/learn-harness-engineering/docs/zh/resources/templates/quality-document.md deleted file mode 100644 index a21367bd..00000000 --- a/courses/learn-harness-engineering/docs/zh/resources/templates/quality-document.md +++ /dev/null @@ -1,43 +0,0 @@ -# 质量文档 - -每个产品领域和架构层的质量快照。agent 和人都能通过这份文档快速了解项目哪里强、哪里弱。 - -**更新时机:** 每轮重要会话结束后,或开始新一阶段工作前。 - -**评级标准:** - -- **A**:验证全部通过,架构干净,agent 能读懂,测试稳定 -- **B**:验证通过,基本干净,可读性或测试覆盖有少量缺口 -- **C**:部分可用,有已知缺口,部分代码 agent 不容易理解 -- **D**:不可用,或存在重大结构问题 - ---- - -## 产品领域 - -| 领域 | 评级 | 验证状态 | Agent 可读性 | 测试稳定性 | 关键缺口 | 上次更新 | -|------|------|---------|-------------|-----------|---------|---------| -| 文档导入 | - | - | - | - | - | - | -| 文档管理 | - | - | - | - | - | - | -| 文档索引 | - | - | - | - | - | - | -| 问答流程 | - | - | - | - | - | - | -| 引用式回答 | - | - | - | - | - | - | - -## 架构层 - -| 层级 | 评级 | 边界执行 | Agent 可读性 | 关键缺口 | 上次更新 | -|------|------|---------|-------------|---------|---------| -| Main Process | - | - | - | - | - | -| Preload | - | - | - | - | - | -| Renderer | - | - | - | - | - | -| Services | - | - | - | - | - | - -## 变更历史 - -### YYYY-MM-DD - -- 变更内容: -- 提升: -- 下降: -- 新发现的缺口: -- 已关闭的缺口: diff --git a/courses/learn-harness-engineering/docs/zh/resources/templates/session-handoff.md b/courses/learn-harness-engineering/docs/zh/resources/templates/session-handoff.md deleted file mode 100644 index a24fc899..00000000 --- a/courses/learn-harness-engineering/docs/zh/resources/templates/session-handoff.md +++ /dev/null @@ -1,30 +0,0 @@ -# 会话交接 - -## 当前已验证 - -- 现在明确可用的部分: -- 这轮实际跑过的验证: - -## 本轮改动 - -- 新增了哪些代码或行为: -- 基础设施或 harness 发生了哪些变化: - -## 仍损坏或未验证 - -- 已知缺陷: -- 未验证路径: -- 下一轮会话需要注意的风险: - -## 下一步最佳动作 - -- 最高优先级未完成功能: -- 为什么它是下一步: -- 什么结果才算 passing: -- 这一步中哪些东西不要动: - -## 命令 - -- 启动命令: -- 验证命令: -- 定向调试命令: diff --git a/courses/learn-harness-engineering/docs/zh/skills/index.md b/courses/learn-harness-engineering/docs/zh/skills/index.md deleted file mode 100644 index 8f039270..00000000 --- a/courses/learn-harness-engineering/docs/zh/skills/index.md +++ /dev/null @@ -1,49 +0,0 @@ -# Skills(技能集) - -本目录包含课程附带的 AI agent 技能。每个技能都是自包含的提示词模板,可被 AI 编程智能体(Claude Code、Codex、Cursor、Windsurf 等)加载以执行专业任务。 - -## harness-creator - -面向 AI 编程智能体的生产级 harness 工程技能。帮助创建、评估和改进五个核心 harness 子系统:指令、状态、验证、范围和会话生命周期。 - -### 它能做什么 - -- **从零创建 harness** — AGENTS.md、功能清单、验证工作流 -- **改进已有 harness** — 五子系统评分 + 优先级改进建议 -- **设计会话连续性** — 记忆持久化、进度跟踪、交接机制 -- **应用生产级模式** — 记忆、上下文工程、工具安全、多智能体协调 - -### 快速开始 - -技能文件位于仓库的 [`skills/harness-creator/`](https://github.com/walkinglabs/learn-harness-engineering/tree/main/skills/harness-creator) 目录。 - -在 Claude Code 中使用时,将 `harness-creator/` 目录复制到你项目的技能路径下,或让 agent 直接读取 SKILL.md 文件即可。 - -### 参考模式 - -技能包含 6 个深入的模式参考文档: - -| 模式 | 适用场景 | -|------|----------| -| 记忆持久化 | agent 在会话间遗忘项目知识 | -| 上下文工程 | 上下文预算管理、按需加载、委托隔离 | -| 工具注册 | 工具安全、并发控制、权限管道 | -| 多智能体协调 | 并行化、专业化、研究员→实施者工作流 | -| 生命周期与引导 | 钩子、后台任务、初始化序列 | -| 常见陷阱 | 15 个容易踩坑的失败模式及修复方案 | - -### 模板 - -技能附带开箱即用的模板: - -- `agents.md` — AGENTS.md 脚手架,包含工作规则 -- `feature-list.json` — JSON Schema + 功能列表示例 -- `init.sh` — 标准初始化脚本 -- `progress.md` — 会话进度日志模板 - -### 开发过程 - -`harness-creator` 基于 **skill-creator** 方法论开发——这是 Anthropic 官方提供的元技能,用于创建、测试和迭代改进 agent 技能。skill-creator 提供了结构化的工作流(起草 → 测试 → 评估 → 迭代),内置评估运行器、评分器和基准查看器。 - -- **skill-creator 来源**:[anthropics/skills — skill-creator](https://github.com/anthropics/skills/tree/main/skills/skill-creator) -- **Claude Code 技能文档**:[anthropics/claude-code — plugin-dev/skills](https://github.com/anthropics/claude-code/tree/main/plugins/plugin-dev/skills) diff --git a/docs/.vitepress/active-course.mjs b/docs/.vitepress/active-course.mjs index f2153c33..8a810680 100644 --- a/docs/.vitepress/active-course.mjs +++ b/docs/.vitepress/active-course.mjs @@ -1,9 +1,10 @@ -import courseManifest from "../../courses/learn-harness-engineering/course.manifest.mjs"; +import { + FEATURED_COURSE_SLUG, + getFeaturedCourseManifest, +} from "./course-registry.mjs"; -export const ACTIVE_COURSE_SLUG = "learn-harness-engineering"; -export const ACTIVE_COURSE_CONTENT_DIR = - "../courses/learn-harness-engineering/docs"; -export const ACTIVE_COURSE_REPO_CONTENT_PREFIX = - "courses/learn-harness-engineering/docs"; +export const ACTIVE_COURSE_SLUG = FEATURED_COURSE_SLUG; +export const ACTIVE_COURSE_CONTENT_DIR = `./courses/${ACTIVE_COURSE_SLUG}`; +export const ACTIVE_COURSE_REPO_CONTENT_PREFIX = `docs/courses/${ACTIVE_COURSE_SLUG}`; -export default courseManifest; +export default getFeaturedCourseManifest(); diff --git a/docs/.vitepress/config.mts b/docs/.vitepress/config.mts index 4c59fbc6..9942546d 100644 --- a/docs/.vitepress/config.mts +++ b/docs/.vitepress/config.mts @@ -7,12 +7,10 @@ import { buildMermaidThemeVariables, } from "./course-branding.mjs"; import { - ACTIVE_COURSE_CONTENT_DIR, - ACTIVE_COURSE_REPO_CONTENT_PREFIX, -} from "./active-course.mjs"; -import { getLocaleSourceItems } from "./course-curriculum.mjs"; -import { getLocaleEntries } from "./course-locales.mjs"; -import { createLocaleDefinition } from "./course-navigation.mjs"; + getFeaturedCourseManifest, + getAllFontStylesheets, +} from "./course-registry.mjs"; +import { buildGlobalNav, buildGlobalSidebar } from "./course-navigation.mjs"; const docsBase = (() => { const configuredBase = process.env.DOCS_BASE_PATH ?? courseConfig.site.base; @@ -23,28 +21,15 @@ const docsBase = (() => { return configuredBase.endsWith("/") ? configuredBase : `${configuredBase}/`; })(); -const brandLogo = courseConfig.brand.logo; -const githubRepoTreeLink = courseConfig.site.repoTreeUrl; -const fontStylesheetLinks = buildFontStylesheetLinks(courseConfig); -const locales = Object.fromEntries( - getLocaleEntries().map((entry) => [ - entry.key, - createLocaleDefinition({ - locale: entry.key, - label: entry.label, - lang: entry.lang, - sourceItems: getLocaleSourceItems(entry.key), - labels: entry.labels, - repoTreeUrl: githubRepoTreeLink, - repoContentPrefix: ACTIVE_COURSE_REPO_CONTENT_PREFIX, - }), - ]), -); +const featuredCourseManifest = getFeaturedCourseManifest(); +const fontStylesheetLinks = buildFontStylesheetLinks({ + theme: { fontStylesheets: getAllFontStylesheets() }, +}); export default withMermaid( defineConfig({ - srcDir: ACTIVE_COURSE_CONTENT_DIR, + srcDir: ".", base: docsBase, title: courseConfig.site.title, description: courseConfig.site.description, @@ -53,14 +38,35 @@ export default withMermaid( ignoreDeadLinks: true, head: [ ...fontStylesheetLinks, - ["link", { rel: "icon", type: "image/svg+xml", href: brandLogo }], + [ + "link", + { + rel: "icon", + type: "image/svg+xml", + href: courseConfig.brand.logo, + }, + ], ], themeConfig: { - logo: brandLogo, + logo: courseConfig.brand.logo, + nav: buildGlobalNav(), + sidebar: buildGlobalSidebar(), + outline: { + level: [2, 3], + }, + docFooter: { + prev: "Previous", + next: "Next", + }, + returnToTopLabel: "Return to top", + sidebarMenuLabel: "Menu", + darkModeSwitchLabel: "Theme", + lightModeSwitchTitle: "Switch to light theme", + darkModeSwitchTitle: "Switch to dark theme", search: { provider: "local", }, - socialLinks: [{ icon: "github", link: githubRepoTreeLink }], + socialLinks: [{ icon: "github", link: courseConfig.site.repoTreeUrl }], }, markdown: { theme: { @@ -70,13 +76,12 @@ export default withMermaid( }, mermaid: { theme: "base", - themeVariables: buildMermaidThemeVariables(courseConfig), + themeVariables: buildMermaidThemeVariables(featuredCourseManifest), flowchart: { nodeSpacing: 40, rankSpacing: 56, padding: 12, }, }, - locales, }), ); diff --git a/docs/.vitepress/course-catalog.mjs b/docs/.vitepress/course-catalog.mjs new file mode 100644 index 00000000..5ffa4c55 --- /dev/null +++ b/docs/.vitepress/course-catalog.mjs @@ -0,0 +1,59 @@ +import { FEATURED_COURSE_SLUG, getAllCourses } from "./course-registry.mjs"; +import { + getCourseBasePath, + getCourseLocaleBasePath, + getPrimaryLocaleForManifest, +} from "./course-routes.mjs"; + +function getCourseRepoContentUrl(slug, repoTreeUrl) { + if (!repoTreeUrl) return null; + return repoTreeUrl.replace(/\/$/, ""); +} + +function getCurriculumSummary(manifest, locale) { + const curriculum = manifest.curriculum?.[locale] || {}; + + return { + lectures: curriculum.lectures?.length || 0, + projects: curriculum.projects?.length || 0, + resources: curriculum.resources?.length || 0, + }; +} + +function buildCourseEntry({ slug, manifest }) { + const primaryLocale = getPrimaryLocaleForManifest(manifest); + + return { + slug, + title: manifest.site.title, + description: manifest.site.description, + primaryLocale, + locales: manifest.locales.map((locale) => ({ + key: locale.key, + label: locale.label, + lang: locale.lang, + })), + counts: getCurriculumSummary(manifest, primaryLocale), + repoContentUrl: getCourseRepoContentUrl(slug, manifest.site.repoTreeUrl), + coursePath: getCourseBasePath(slug), + homePath: getCourseLocaleBasePath(slug, primaryLocale), + featured: slug === FEATURED_COURSE_SLUG, + }; +} + +export function getCourseCatalog() { + return getAllCourses() + .map(buildCourseEntry) + .sort((left, right) => { + if (left.featured && !right.featured) return -1; + if (!left.featured && right.featured) return 1; + return left.title.localeCompare(right.title, "pt-BR"); + }); +} + +export function getFeaturedCourseEntry() { + return ( + getCourseCatalog().find((course) => course.featured) || + getCourseCatalog()[0] + ); +} diff --git a/docs/.vitepress/course-content.mjs b/docs/.vitepress/course-content.mjs index 326c1538..d533c8eb 100644 --- a/docs/.vitepress/course-content.mjs +++ b/docs/.vitepress/course-content.mjs @@ -1,9 +1,25 @@ -import courseManifest from "./course.manifest.mjs"; +import { + FEATURED_COURSE_SLUG, + getCourseManifestBySlug, +} from "./course-registry.mjs"; +import { getPrimaryLocaleForManifest } from "./course-routes.mjs"; -export function resolveCourseLocale(locale) { - return courseManifest.homeByLocale[locale] ? locale : "en"; +export function resolveCourseLocale(slug, locale) { + const manifest = getCourseManifestBySlug(slug); + if (!manifest) return locale; + + return manifest.homeByLocale[locale] + ? locale + : getPrimaryLocaleForManifest(manifest); +} + +export function getCourseHomeForCourse(slug, locale) { + const manifest = getCourseManifestBySlug(slug); + if (!manifest) return null; + + return manifest.homeByLocale[resolveCourseLocale(slug, locale)]; } export function getCourseHome(locale) { - return courseManifest.homeByLocale[resolveCourseLocale(locale)]; + return getCourseHomeForCourse(FEATURED_COURSE_SLUG, locale); } diff --git a/docs/.vitepress/course-curriculum.mjs b/docs/.vitepress/course-curriculum.mjs index 3ca235e4..a2fec89d 100644 --- a/docs/.vitepress/course-curriculum.mjs +++ b/docs/.vitepress/course-curriculum.mjs @@ -1,5 +1,14 @@ -import courseManifest from "./course.manifest.mjs"; +import { + FEATURED_COURSE_SLUG, + getCourseManifestBySlug, +} from "./course-registry.mjs"; +import { getPrimaryLocale } from "./course-locales.mjs"; -export function getLocaleSourceItems(locale) { - return courseManifest.curriculum[locale] || courseManifest.curriculum.en; +export function getLocaleSourceItems(locale, slug = FEATURED_COURSE_SLUG) { + const manifest = getCourseManifestBySlug(slug); + if (!manifest) return null; + + return ( + manifest.curriculum[locale] || manifest.curriculum[getPrimaryLocale(slug)] + ); } diff --git a/docs/.vitepress/course-locales.mjs b/docs/.vitepress/course-locales.mjs index 1c41aa64..c1e57095 100644 --- a/docs/.vitepress/course-locales.mjs +++ b/docs/.vitepress/course-locales.mjs @@ -1,5 +1,16 @@ -import courseManifest from "./course.manifest.mjs"; +import { + FEATURED_COURSE_SLUG, + getCourseManifestBySlug, +} from "./course-registry.mjs"; +import { getPrimaryLocaleForManifest } from "./course-routes.mjs"; -export function getLocaleEntries() { - return courseManifest.locales; +export function getLocaleEntries(slug = FEATURED_COURSE_SLUG) { + return getCourseManifestBySlug(slug)?.locales || []; +} + +export function getPrimaryLocale(slug = FEATURED_COURSE_SLUG) { + const manifest = getCourseManifestBySlug(slug); + if (!manifest) return "en"; + + return getPrimaryLocaleForManifest(manifest); } diff --git a/docs/.vitepress/course-navigation.mjs b/docs/.vitepress/course-navigation.mjs index 5c23b86d..c49000e4 100644 --- a/docs/.vitepress/course-navigation.mjs +++ b/docs/.vitepress/course-navigation.mjs @@ -1,80 +1,109 @@ -const LOCALE_PREFIX_PATTERN = /^\/(en|zh|vi|ko|uz|ru|ja|es|fr|de|ar|zh-TW)\//; +import { getAllCourses, getPlatformConfig } from "./course-registry.mjs"; +import { getCourseLocaleBasePath } from "./course-routes.mjs"; -export function relinkItems(items, locale) { +const LOCALE_PREFIX_PATTERN = /^\/[^/]+\//; + +export function relinkItems(items, localeBasePath) { return items.map((item) => ({ ...item, - link: item.link.replace(LOCALE_PREFIX_PATTERN, `/${locale}/`), + link: item.link.replace(LOCALE_PREFIX_PATTERN, localeBasePath), })); } export function buildLocaleThemeConfig({ + slug, locale, sourceItems, labels, repoTreeUrl, - repoContentPrefix = "docs", }) { - const lectures = relinkItems(sourceItems.lectures, locale); - const projects = relinkItems(sourceItems.projects, locale); - const resources = relinkItems(sourceItems.resources, locale); - const skills = relinkItems(sourceItems.skills, locale); + const localeBasePath = getCourseLocaleBasePath(slug, locale); + const lectures = relinkItems(sourceItems.lectures, localeBasePath); + const projects = relinkItems(sourceItems.projects, localeBasePath); + const resources = relinkItems(sourceItems.resources, localeBasePath); + const skills = relinkItems(sourceItems.skills, localeBasePath); + const repoContentUrl = repoTreeUrl.replace(/\/$/, ""); return { nav: [ { text: labels.lectures, - link: lectures[1].link, - activeMatch: `^/${locale}/(lectures/.*)?$`, + link: lectures[1]?.link || localeBasePath, + activeMatch: `^${localeBasePath}(lectures/.*)?$`, }, { text: labels.projects, - link: projects[0].link, - activeMatch: `^/${locale}/projects/`, + link: projects[0]?.link || `${localeBasePath}projects/`, + activeMatch: `^${localeBasePath}projects/`, }, { text: labels.resources, - link: `/${locale}/resources/`, - activeMatch: `^/${locale}/resources/`, + link: `${localeBasePath}resources/`, + activeMatch: `^${localeBasePath}resources/`, }, { text: labels.skills, - link: `/${locale}/skills/`, - activeMatch: `^/${locale}/skills/`, + link: `${localeBasePath}skills/`, + activeMatch: `^${localeBasePath}skills/`, }, { text: labels.tryHarness, - link: `${repoTreeUrl.replace(/\/tree\/main$/, "/blob/main")}/${repoContentPrefix}/${locale}/resources/templates/index.md`, + link: repoContentUrl, target: "_blank", rel: "noopener noreferrer", }, ], sidebar: { - [`/${locale}/projects/`]: [{ text: labels.projects, items: projects }], - [`/${locale}/resources/`]: [ + [`${localeBasePath}projects/`]: [ + { text: labels.projects, items: projects }, + ], + [`${localeBasePath}resources/`]: [ { text: labels.resourceLibrary, items: resources }, ], - [`/${locale}/skills/`]: [{ text: labels.skills, items: skills }], - [`/${locale}/`]: [{ text: labels.lectures, items: lectures }], + [`${localeBasePath}skills/`]: [{ text: labels.skills, items: skills }], + [localeBasePath]: [{ text: labels.lectures, items: lectures }], }, - outline: { - level: [2, 3], - ...(labels.outline ? { label: labels.outline } : {}), - }, - docFooter: { - prev: labels.prev || "Previous", - next: labels.next || "Next", + }; +} + +export function buildGlobalSidebar() { + const sidebar = {}; + + for (const course of getAllCourses()) { + for (const localeEntry of course.manifest.locales) { + const themeConfig = buildLocaleThemeConfig({ + slug: course.slug, + locale: localeEntry.key, + sourceItems: + course.manifest.curriculum[localeEntry.key] || + course.manifest.curriculum[course.manifest.locales[0].key], + labels: localeEntry.labels, + repoTreeUrl: course.manifest.site.repoTreeUrl, + }); + + Object.assign(sidebar, themeConfig.sidebar); + } + } + + return sidebar; +} + +export function buildGlobalNav() { + const platformConfig = getPlatformConfig(); + + return [ + { + text: "Cursos", + link: "/", + activeMatch: "^/$|^/courses/", }, - lastUpdated: { - text: labels.lastUpdated || "Last updated", + { + text: "Repositório", + link: platformConfig.site.repoTreeUrl, + target: "_blank", + rel: "noopener noreferrer", }, - returnToTopLabel: labels.returnToTop || "Return to top", - sidebarMenuLabel: labels.sidebarMenu || "Menu", - darkModeSwitchLabel: labels.darkModeSwitch || "Theme", - lightModeSwitchTitle: - labels.lightModeSwitchTitle || "Switch to light theme", - darkModeSwitchTitle: labels.darkModeSwitchTitle || "Switch to dark theme", - socialLinks: [{ icon: "github", link: repoTreeUrl }], - }; + ]; } export function createLocaleDefinition({ @@ -84,18 +113,18 @@ export function createLocaleDefinition({ sourceItems, labels, repoTreeUrl, - repoContentPrefix, + slug = "example-course", }) { return { label, lang, - link: `/${locale}/`, + link: getCourseLocaleBasePath(slug, locale), themeConfig: buildLocaleThemeConfig({ + slug, locale, sourceItems, labels, repoTreeUrl, - repoContentPrefix, }), }; } diff --git a/docs/.vitepress/course-registry.mjs b/docs/.vitepress/course-registry.mjs new file mode 100644 index 00000000..ff47210b --- /dev/null +++ b/docs/.vitepress/course-registry.mjs @@ -0,0 +1,59 @@ +import { + COURSE_REGISTRY, + FEATURED_COURSE_SLUG, +} from "./generated-course-data.mjs"; + +export { COURSE_REGISTRY, FEATURED_COURSE_SLUG }; + +export function getAllCourses() { + return [...COURSE_REGISTRY]; +} + +export function getCourseEntryBySlug(slug) { + return COURSE_REGISTRY.find((course) => course.slug === slug) || null; +} + +export function getCourseManifestBySlug(slug) { + return getCourseEntryBySlug(slug)?.manifest || null; +} + +export function getFeaturedCourseManifest() { + return ( + getCourseManifestBySlug(FEATURED_COURSE_SLUG) || + COURSE_REGISTRY[0]?.manifest || + null + ); +} + +export function getPlatformConfig() { + const sharedBase = + COURSE_REGISTRY.find((course) => course.manifest.site.base)?.manifest.site + .base || "/course-builder/"; + const sharedRepoTreeUrl = + COURSE_REGISTRY.find((course) => course.manifest.site.repoTreeUrl)?.manifest + .site.repoTreeUrl || + "https://github.com/puppe1990/course-builder/tree/main"; + + return { + site: { + title: "Course Builder", + description: + "Reusable VitePress course builder that can publish multiple externally sourced courses in one build.", + base: sharedBase, + repoTreeUrl: sharedRepoTreeUrl, + }, + brand: { + logo: 'data:image/svg+xml;utf8,', + }, + }; +} + +export function getAllFontStylesheets() { + return [ + ...new Set( + COURSE_REGISTRY.flatMap( + (course) => course.manifest.theme.fontStylesheets || [], + ), + ), + ]; +} diff --git a/docs/.vitepress/course-routes.mjs b/docs/.vitepress/course-routes.mjs new file mode 100644 index 00000000..6e1cfdb3 --- /dev/null +++ b/docs/.vitepress/course-routes.mjs @@ -0,0 +1,67 @@ +import { getAllCourses, getCourseEntryBySlug } from "./course-registry.mjs"; + +export function getPrimaryLocaleForManifest(manifest) { + return manifest.locales[0]?.key || "en"; +} + +export function getCourseBasePath(slug) { + return `/courses/${slug}/`; +} + +export function getCourseLocaleBasePath(slug, locale) { + return `${getCourseBasePath(slug)}${locale}/`; +} + +export function getDefaultCoursePath(slug) { + const course = getCourseEntryBySlug(slug); + if (!course) return getCourseBasePath(slug); + + return getCourseLocaleBasePath( + slug, + getPrimaryLocaleForManifest(course.manifest), + ); +} + +export function matchCourseRoute(routePath = "/") { + return ( + getAllCourses().find((course) => { + const basePath = getCourseBasePath(course.slug); + return routePath === basePath || routePath.startsWith(basePath); + }) || null + ); +} + +export function resolveCourseContextFromPath(routePath = "/") { + const course = matchCourseRoute(routePath); + if (!course) return null; + + const basePath = getCourseBasePath(course.slug); + const locale = + course.manifest.locales.find((entry) => + routePath.startsWith(getCourseLocaleBasePath(course.slug, entry.key)), + )?.key || getPrimaryLocaleForManifest(course.manifest); + + return { + ...course, + locale, + basePath, + localeBasePath: getCourseLocaleBasePath(course.slug, locale), + }; +} + +export function resolveCourseContextFromRelativePath(relativePath = "") { + const normalized = relativePath.replace(/\\/g, "/"); + const match = normalized.match(/^courses\/([^/]+)\/([^/]+)\//); + if (!match) return null; + + const [, slug, locale] = match; + const course = getCourseEntryBySlug(slug); + if (!course) return null; + + return { + ...course, + locale, + basePath: getCourseBasePath(slug), + localeBasePath: getCourseLocaleBasePath(slug, locale), + }; +} diff --git a/docs/.vitepress/course.config.mjs b/docs/.vitepress/course.config.mjs index 649bb390..7ab75591 100644 --- a/docs/.vitepress/course.config.mjs +++ b/docs/.vitepress/course.config.mjs @@ -1,7 +1,9 @@ -import courseManifest from "./course.manifest.mjs"; +import { + getFeaturedCourseManifest, + getPlatformConfig, +} from "./course-registry.mjs"; export default { - site: courseManifest.site, - brand: courseManifest.brand, - theme: courseManifest.theme, + ...getPlatformConfig(), + theme: getFeaturedCourseManifest().theme, }; diff --git a/docs/.vitepress/theme/components/CourseCatalogHome.vue b/docs/.vitepress/theme/components/CourseCatalogHome.vue new file mode 100644 index 00000000..b007f316 --- /dev/null +++ b/docs/.vitepress/theme/components/CourseCatalogHome.vue @@ -0,0 +1,104 @@ + + + diff --git a/docs/.vitepress/theme/components/CourseHome.vue b/docs/.vitepress/theme/components/CourseHome.vue index 71433d92..f72cd85e 100644 --- a/docs/.vitepress/theme/components/CourseHome.vue +++ b/docs/.vitepress/theme/components/CourseHome.vue @@ -1,21 +1,66 @@

    -;5I>n2zsF;{;x5L_&fS`m#yJ`iF&-2Iio*(y_ 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