Skip to content

Commit c72317f

Browse files
committed
기능: 브라우저 행동 수렴 계약을 제품화
proof-carrying action에 후보 2개, 재관찰 1회, 첫 effect 30초 제한을 추가했다. effect 재시도 0회와 provider-neutral 영수증을 NativeCdpSpace와 FrameSpace에 적용했다. Control, JavaScript, Python 공개 표면과 설치 제품 문서 및 지속 원장을 같은 의미로 맞췄다. stale, ambiguous, occlusion, navigation 교체를 packed Edge 제품 게이트와 CI에 고정했다. 검증: npm test, npm run test:contracts, npm run test:types, npm run skills:check 검증: npm run test:action-convergence, npm run test:frame-space, npm run test:browser-control 검증: npm run test:browser-control-stress, npm run test:apx, npm run test:automation-lifecycle 검증: npm run test:mcp-product, npm run test:control-product, npm run test:python-sdk 검증: npm run test:package, maxAttempts 3 음성 변형 RED, 복원 뒤 GREEN
1 parent fa98e83 commit c72317f

45 files changed

Lines changed: 1126 additions & 161 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.github/workflows/ci.yml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,9 @@ jobs:
124124
- run: npm run test:automation-lifecycle
125125
env:
126126
PYPROC_BROWSER: /usr/bin/google-chrome
127+
- run: npm run test:action-convergence
128+
env:
129+
PYPROC_BROWSER: /usr/bin/google-chrome
127130

128131
# 실제 Microsoft Edge의 Windows 배포 경계. 설치 tarball/골든 경로와 핵심 브라우저
129132
# 계약을 Chrome과 독립해 문다. examples는 Chrome의 동일 쇼케이스를 그대로 반복해 비용이
@@ -181,6 +184,7 @@ jobs:
181184
- run: npm run test:browser-control
182185
- run: npm run test:browser-control-stress
183186
- run: npm run test:automation-lifecycle
187+
- run: npm run test:action-convergence
184188
- uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7
185189
if: always()
186190
with:

README.ko.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,12 @@ Machine image 계약을 사용한다.
114114
Effect-free doctor는 `machine.run`을 정본 의미로 고정하고 shell, JavaScript, Python, MCP에 매핑한 하나의
115115
구조화된 첫 결과 행동도 반환한다.
116116

117+
proof-carrying browser action은 provider-neutral `pyproc.actionConvergence` version 1 영수증을 반환한다.
118+
Native CDP와 FrameSpace는 후보 최대 2개, 재관찰 최대 1회, effect retry 0회, 첫 effect 전 30000 ms를
119+
같이 지킨다. 같은 문서 stale target과 교체된 문서는 유일한 동일 권한 대상에만 수렴하고, ambiguous와
120+
지속 가림은 effect 0회인 같은 영수증으로 거절한다. 성공은 action terminal, 안전 거절은 error details에
121+
영수증을 싣는다.
122+
117123
[Machine Entrance](skills/use-pyproc-machine/references/machine-entrance.md)에서 시작한 뒤
118124
[JavaScript Control SDK](skills/control-pyproc/references/javascript-control.md),
119125
[Python SDK](skills/control-pyproc/references/python-sdk.md), 또는 완전한

README.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -139,6 +139,12 @@ continued with a continuation-only `automation.observe` call. Every page carries
139139
snapshot receipt, complete-inventory digest, and observation evidence binding. A document replacement rejects
140140
the old continuation instead of treating a partial prefix as complete.
141141

142+
Proof-carrying browser actions expose one provider-neutral `pyproc.actionConvergence` version 1 receipt. Native
143+
CDP and FrameSpace inspect at most two candidates, reobserve the original typed focus at most once, never retry a
144+
sent effect, and bound the first-effect search to 30000 ms. A unique same-document stale target or replaced
145+
document can converge; ambiguity and persistent occlusion return the same receipt with zero effect attempts.
146+
Successful Control output carries it on the action terminal, while a safe refusal carries it in error details.
147+
142148
`automation.space.inspect` also returns one provider-neutral `resources` snapshot. After deleting owned
143149
artifacts, detaching sessions, and closing owned targets, every counter returns to the starting baseline. A new
144150
isolated profile therefore returns zero for targets, sessions, locators, continuations, watchers, artifacts,

mainPlan/9-agentComputerStandardReadiness/README.md

Lines changed: 16 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,8 @@
1414

1515
| 질문 | 현재 판정 | 실사용 증거 | 완성까지 남은 것 |
1616
|---|---|---|---|
17-
| agent 진입점 | 매우 강함, 장기 수명주기 검증은 계속 | exact install 뒤 package engine 자동 선택, effect-free doctor, 네 adapter의 같은 CPython 첫 결과가 공개 계약으로 완결 | M2의 wrong-effect 없는 자동 수렴 상한 |
18-
| 눈과 팔 | 강함, 완성 아님, 북극성 8.8 | APX Situation이 링크를 식별하고 proof-carrying click 뒤 `Smallest start` 출현을 confirmed로 봉인. 설치 제품 20회가 모든 소유 자원을 기준선으로 되돌림 | stale, ambiguous, occluded 자동 수렴과 실제 GPU visual oracle 확대 |
17+
| agent 진입점 | 매우 강함, 장기 수명주기 검증은 계속 | exact install 뒤 package engine 자동 선택, effect-free doctor, 네 adapter의 같은 CPython 첫 결과가 공개 계약으로 완결 | hardware GPU 결과 oracle과 이후 몸체 확대 |
18+
| 눈과 팔 | 매우 강함, 완성 아님, 북극성 9.3 | APX Situation, 20회 무잔류 수명주기, stale, ambiguous, occluded, navigation의 2후보, 1재관찰, effect retry 0회 수렴 영수증 | 실제 GPU visual oracle 확대 |
1919
| 비-agent 컴퓨팅 몸체 | 훌륭한 브라우저 컴퓨터, 로컬 OS 완전 대체는 아님 | owned CPython, worker process, OPFS disk, checkpoint, Machine image, Python과 x86 guest gate | 임의 native wheel, shared-memory thread, wasm 도구층, Node guest, quota 축출 계약 |
2020
| 단독 자립성 | Python 기본 Machine은 높음, 전체 WebComputer는 미완성 | source-built CPython과 stdlib가 npm에 포함되고 기본 부팅의 제3자 요청은 0 | x86 emulator와 firmware의 독립 재현, GPU 실기 CI, 브라우저 범위 확대 |
2121
| 웹 표준 후보 가능성 | 기반은 있음, 후보라고 부르기에는 이름 | WebAssembly, Worker, cross-origin isolation, bucket file system 같은 표준 기반 위에 제품 계약이 동작 | vendor-neutral specification, 독립 구현, WPT형 conformance, 공개 incubation과 wide review |
@@ -83,7 +83,7 @@ implementation experience는 독립적이고 상호운용 가능한 구현, 저
8383

8484
- `pyproc-mcp init --recipe pythonOnly`가 설치 tarball 내부 owned engine으로 manifest를 만든다.
8585
- 순수 parser는 package 경로를 추측하지 않고 initializer만 설치 경계를 해석한다.
86-
- contract 36 suites, package gate, 전체 12개 게이트, 설치 MCP 제품 21개가 GREEN이다.
86+
- contract 37 suites, package gate, 전체 13개 게이트, 설치 MCP 제품 gate가 GREEN이다.
8787
- 첫 문서 교체 probe는 수정 전 stale locator와 effect 0을 재현했고, 수정 후 postcondition confirmed와
8888
effect 정확히 1회로 수렴했다. 설치 MCP 제품 gate도 같은 여정을 고정한다.
8989
- browser control, 3회 48-action stress, APX 11개 실브라우저 게이트가 GREEN이다.
@@ -104,7 +104,8 @@ implementation experience는 독립적이고 상호운용 가능한 구현, 저
104104
- 완료: 반복 Situation, screenshot, proof-carrying action, artifact cleanup에서 handle과 process 잔류 0을
105105
증명한다. 각 owner가 직접 센 자원을 `automation.space.inspect.resources`로 합성하고, 격리 profile의
106106
packed Control 제품 20회와 NativeCdpSpace, FrameSpace, MCP, Python adapter가 0 수렴을 검증한다.
107-
- stale, ambiguous, occluded, navigation 교체를 wrong effect 없이 자동 수렴시키는 상한을 고정한다.
107+
- 완료: stale, ambiguous, occluded, navigation 교체를 후보 최대 2개, 재관찰 최대 1회, effect retry 0회,
108+
첫 effect 전 30000 ms로 고정한다. 성공과 안전 거절은 같은 version 1 영수증을 반환한다.
108109
- hardware GPU runner에서 pixel 결과 oracle을 추가해 수동 증거 상한을 제거한다.
109110

110111
### M3. 컴퓨팅 몸체 확대
@@ -305,5 +306,14 @@ implementation experience는 독립적이고 상호운용 가능한 구현, 저
305306
전체 axis의 score, next, browser lane, 실행 lane, evidence 실존과 사다리 연속성을 `npm test`가 검사한다.
306307
- 원장 gate 이빨: 새 축의 lifecycle evidence를 존재하지 않는 `.missing` 경로로 바꾼 음성 변형은
307308
`agentBrowserAutomation: missing evidence`를 보고 12/13 RED였고, 복원 뒤 13/13 GREEN이다.
308-
- 다음 직렬 작업: stale, ambiguous, occluded, navigation 교체를 wrong effect 없이 자동 수렴시키는 횟수와
309-
시간 상한을 고정한다.
309+
- action 수렴 첫 RED: 같은 문서에서 분리된 target을 5.09초 동안 60회 확인한 뒤 재관찰 없이
310+
actionability timeout으로 끝났다. ambiguous도 effect 0회로 안전했지만 모호함을 판정하지 못했고,
311+
다섯 시나리오 모두 공통 영수증이 없었다.
312+
- action 수렴 제품 증거: packed Control과 Edge에서 first-effect 시간은 같은 문서 stale 259 ms,
313+
ambiguous 172 ms, 일시 가림 1288 ms, 지속 가림 711 ms, navigation 교체 201 ms였다. effect 횟수는
314+
각각 1, 0, 1, 0, 1이며 terminal PNG 756x488을 직접 확인했다. FrameSpace도 stale, 일시 가림,
315+
지속 가림과 outcome unknown 1회 무재시도를 같은 영수증으로 증명한다.
316+
- action 수렴 gate 이빨: `ACTION_CONVERGENCE_MAX_ATTEMPTS`를 2에서 3으로 바꾼 음성 변형은
317+
`3 !== 2`를 보고 RED였고, 2로 복원한 같은 계약 gate는 GREEN이다.
318+
- 다음 직렬 작업: hardware GPU runner에서 pixel과 compute 결과 oracle을 실행해 정확한 command byte
319+
검증에 머문 수동 증거 상한을 제거한다.

mainPlan/README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,8 @@
1717

1818
## 현재 상태
1919

20-
현재 미완료 이니셔티브는 없다. 새 이니셔티브는 계약 실태와 소비 피드백의 중복을 먼저 확인하고,
21-
구체적인 종료 조건을 갖춘 다음 번호로 등록한다.
20+
현재 미완료 이니셔티브는 [9-agentComputerStandardReadiness](9-agentComputerStandardReadiness/README.md)다.
21+
M2의 wrong-effect 없는 action 수렴까지 완료했고 hardware GPU 결과 oracle부터 직렬로 이어간다.
2222

2323
이 번호는 agent-computer 포트폴리오의 실행 순서다. North Star ceiling ladder의 기존 번호와 우선순위를
2424
대체하지 않는다.

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,7 @@
112112
"test:browser-control": "node tests/browser/browserControl.mjs",
113113
"test:browser-control-stress": "node tests/browser/browserControlStress.mjs",
114114
"test:automation-lifecycle": "node tests/browser/automationLifecycleProduct.mjs",
115+
"test:action-convergence": "node tests/browser/actionConvergenceProduct.mjs",
115116
"test:browser": "node tests/browser/run.mjs",
116117
"test:examples": "node tests/browser/examples.mjs",
117118
"bench:speed": "node tests/browser/speedBench.mjs",

pythonSdk/src/pyprocControl/__init__.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
"""Official Python client for the pyproc Control Protocol."""
22

33
from .client import ControlRequest, PyProcClient
4-
from .models import Attachment, ControlError, ControlResult
4+
from .models import ActionConvergenceReceipt, Attachment, ControlError, ControlResult
55
from .perception import (
66
PerceptionClient,
77
PerceptionEntity,
@@ -27,6 +27,7 @@
2727

2828
__all__ = [
2929
"Attachment",
30+
"ActionConvergenceReceipt",
3031
"ControlError",
3132
"ControlProtocolError",
3233
"ControlRequest",

pythonSdk/src/pyprocControl/models.py

Lines changed: 88 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
"""Immutable public values returned by the pyproc Control Protocol client."""
22

33
from dataclasses import dataclass
4-
from typing import Any
4+
from typing import Any, Mapping
55

66

77
@dataclass(frozen=True, slots=True)
@@ -22,6 +22,93 @@ class ControlResult:
2222
terminal: str = "completed"
2323

2424

25+
@dataclass(frozen=True, slots=True)
26+
class ActionConvergenceReceipt:
27+
"""Provider-neutral first-effect convergence receipt for a proof-carrying action."""
28+
29+
state: str
30+
reason: str
31+
attempts: int
32+
maxAttempts: int
33+
reobservations: int
34+
maxReobservations: int
35+
effectAttempts: int
36+
effectRetries: int
37+
effectOutcome: str
38+
maxPreEffectDurationMs: int
39+
preEffectDurationMs: int
40+
durationMs: int
41+
actionabilityPolls: int
42+
actionabilityReasonsSeen: tuple[str, ...]
43+
fromSituationRef: str | None = None
44+
toSituationRef: str | None = None
45+
fromDocumentEpoch: int | None = None
46+
toDocumentEpoch: int | None = None
47+
protocol: str = "pyproc.actionConvergence"
48+
version: int = 1
49+
50+
@classmethod
51+
def fromMapping(cls, value: Mapping[str, Any]) -> "ActionConvergenceReceipt":
52+
if not isinstance(value, Mapping):
53+
raise TypeError("action convergence receipt must be a mapping")
54+
if value.get("protocol") != "pyproc.actionConvergence" or type(value.get("version")) is not int \
55+
or value.get("version") != 1:
56+
raise ValueError("action convergence receipt protocol is invalid")
57+
def integer(key: str) -> int:
58+
entry = value.get(key)
59+
if type(entry) is not int:
60+
raise ValueError(f"action convergence {key} is invalid")
61+
return entry
62+
63+
state = str(value.get("state") or "")
64+
reason = str(value.get("reason") or "")
65+
effectOutcome = str(value.get("effectOutcome") or "")
66+
attempts = integer("attempts")
67+
maxAttempts = integer("maxAttempts")
68+
reobservations = integer("reobservations")
69+
maxReobservations = integer("maxReobservations")
70+
effectAttempts = integer("effectAttempts")
71+
effectRetries = integer("effectRetries")
72+
maxPreEffectDurationMs = integer("maxPreEffectDurationMs")
73+
preEffectDurationMs = integer("preEffectDurationMs")
74+
durationMs = integer("durationMs")
75+
actionabilityPolls = integer("actionabilityPolls")
76+
reasons = value.get("actionabilityReasonsSeen")
77+
if state not in {"converged", "refused", "unknown", "effectObserved"} \
78+
or reason not in {"ready", "staleTarget", "documentReplacement", "occlusionCleared",
79+
"ambiguousTarget", "targetUnavailable", "authorityChanged",
80+
"actionabilityTimeout", "convergenceTimeout", "cancelled", "providerRejected"} \
81+
or effectOutcome not in {"notSent", "rejected", "applied", "outcomeUnknown"} \
82+
or attempts not in {1, 2} or maxAttempts != 2 \
83+
or reobservations not in {0, 1} or maxReobservations != 1 \
84+
or effectAttempts not in {0, 1} or effectRetries != 0 \
85+
or maxPreEffectDurationMs != 30000 \
86+
or not 0 <= preEffectDurationMs <= maxPreEffectDurationMs \
87+
or durationMs < preEffectDurationMs or actionabilityPolls < 0 \
88+
or not isinstance(reasons, (list, tuple)):
89+
raise ValueError("action convergence receipt bounds are invalid")
90+
return cls(
91+
state=state,
92+
reason=reason,
93+
attempts=attempts,
94+
maxAttempts=maxAttempts,
95+
reobservations=reobservations,
96+
maxReobservations=maxReobservations,
97+
effectAttempts=effectAttempts,
98+
effectRetries=effectRetries,
99+
effectOutcome=effectOutcome,
100+
maxPreEffectDurationMs=maxPreEffectDurationMs,
101+
preEffectDurationMs=preEffectDurationMs,
102+
durationMs=durationMs,
103+
actionabilityPolls=actionabilityPolls,
104+
actionabilityReasonsSeen=tuple(map(str, reasons)),
105+
fromSituationRef=(str(value["fromSituationRef"]) if value.get("fromSituationRef") is not None else None),
106+
toSituationRef=(str(value["toSituationRef"]) if value.get("toSituationRef") is not None else None),
107+
fromDocumentEpoch=(int(value["fromDocumentEpoch"]) if value.get("fromDocumentEpoch") is not None else None),
108+
toDocumentEpoch=(int(value["toDocumentEpoch"]) if value.get("toDocumentEpoch") is not None else None),
109+
)
110+
111+
25112
class ControlError(RuntimeError):
26113
"""A verified request or connection error from the remote product."""
27114

0 commit comments

Comments
 (0)