polydeukes-sdk 0.19.1__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,6 @@
1
+ .venv/
2
+ __pycache__/
3
+ *.pyc
4
+ .ruff_cache/
5
+ dist/
6
+ *.egg-info/
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Hyung Soo Seung
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,75 @@
1
+ Metadata-Version: 2.5
2
+ Name: polydeukes-sdk
3
+ Version: 0.19.1
4
+ Summary: Call the Polydeukes covenant and memory commands from Python
5
+ Project-URL: Repository, https://github.com/huskyhoochu/polydeukes
6
+ Project-URL: Documentation, https://polydeukes.vercel.app
7
+ Author: Polydeukes contributors
8
+ License-Expression: MIT
9
+ License-File: LICENSE
10
+ Classifier: Development Status :: 4 - Beta
11
+ Classifier: Programming Language :: Python :: 3 :: Only
12
+ Classifier: Programming Language :: Python :: 3.12
13
+ Classifier: Programming Language :: Python :: 3.14
14
+ Classifier: Typing :: Typed
15
+ Requires-Python: >=3.12
16
+ Description-Content-Type: text/markdown
17
+
18
+ # `polydeukes-sdk` (Python)
19
+
20
+ **English** · [한국어](README.ko.md)
21
+
22
+ Call the Polydeukes judge and memory commands from Python 3.12 or newer. Four synchronous
23
+ verbs share one executable resolver and process runner. The package has zero runtime dependencies.
24
+
25
+ ```sh
26
+ python -m pip install polydeukes-sdk
27
+ ```
28
+
29
+ Download `pdks-linux-x64` or `pdks-darwin-arm64` from a
30
+ [GitHub Release](https://github.com/huskyhoochu/polydeukes/releases), mark it executable, and
31
+ put it on an absolute `PATH` directory as `pdks`, or pass its path as `executable`.
32
+ Keep the SDK and executable on the same minor release. These are the supported platforms:
33
+ Linux x64 and macOS arm64. On macOS, a browser download may require
34
+ `xattr -d com.apple.quarantine /path/to/pdks`.
35
+
36
+ ```python
37
+ from polydeukes_sdk import (
38
+ CheckCovenantSpec,
39
+ CovenantInput,
40
+ Upheld,
41
+ check_covenant,
42
+ CheckChangeSetSpec,
43
+ check_change_set,
44
+ MemoryIngestSpec,
45
+ memory_ingest,
46
+ MemorySearchSpec,
47
+ memory_search,
48
+ Found,
49
+ Empty,
50
+ Unavailable,
51
+ )
52
+
53
+ payload: CovenantInput = {"toolCalls": [], "subagentSpawns": [], "userMessages": []}
54
+ verdict = check_covenant(CheckCovenantSpec(repo_root=".", input=payload, timeout=10))
55
+ if isinstance(verdict, Upheld):
56
+ print("The proposed operation may proceed", verdict.advisories)
57
+
58
+ verdict = check_change_set(CheckChangeSetSpec(repo_root=".", diff="", timeout=10))
59
+ ingest = memory_ingest(MemoryIngestSpec(root=".", timeout=30))
60
+ recall = memory_search(MemorySearchSpec(root=".", query="design decision", limit=5, timeout=10))
61
+ match recall:
62
+ case Found(hits=hits):
63
+ print(hits[0]["sectionTitle"])
64
+ case Empty():
65
+ print("No matches")
66
+ case Unavailable(reason=reason):
67
+ print("Memory unavailable:", reason)
68
+ ```
69
+
70
+ The host supplies IR evidence and decides whether to execute the proposed operation. Only
71
+ `Upheld` permits it in this example. The child reads configuration, judges disciplines, writes
72
+ telemetry, and manages the memory index.
73
+
74
+ The [Python SDK reference](https://github.com/huskyhoochu/polydeukes/blob/main/docs/reference/packages/sdk-python.md)
75
+ describes every spec, outcome, timeout, and the injectable `spawn` boundary.
@@ -0,0 +1,57 @@
1
+ # `polydeukes-sdk` (Python)
2
+
3
+ [English](README.md) · **한국어**
4
+
5
+ Python 3.12 이상에서 Polydeukes 판정기와 기억(memory) 명령을 호출합니다. 동기 동사 네 개가
6
+ 실행 파일 해석기와 프로세스 실행기 하나를 공유합니다. 런타임 의존성은 없습니다.
7
+
8
+ ```sh
9
+ python -m pip install polydeukes-sdk
10
+ ```
11
+
12
+ [GitHub Release](https://github.com/huskyhoochu/polydeukes/releases)에서 `pdks-linux-x64` 또는
13
+ `pdks-darwin-arm64`를 받아 실행 권한을 부여하고, 절대 `PATH` 디렉터리에 `pdks`라는 이름으로
14
+ 두거나 `executable`에 경로를 지정합니다. SDK와 실행 파일의 minor 버전을 맞춥니다. 지원 플랫폼은
15
+ Linux x64와 macOS arm64입니다. macOS에서 브라우저로 받았다면
16
+ `xattr -d com.apple.quarantine /path/to/pdks`가 필요할 수 있습니다.
17
+
18
+ ```python
19
+ from polydeukes_sdk import (
20
+ CheckCovenantSpec,
21
+ CovenantInput,
22
+ Upheld,
23
+ check_covenant,
24
+ CheckChangeSetSpec,
25
+ check_change_set,
26
+ MemoryIngestSpec,
27
+ memory_ingest,
28
+ MemorySearchSpec,
29
+ memory_search,
30
+ Found,
31
+ Empty,
32
+ Unavailable,
33
+ )
34
+
35
+ payload: CovenantInput = {"toolCalls": [], "subagentSpawns": [], "userMessages": []}
36
+ verdict = check_covenant(CheckCovenantSpec(repo_root=".", input=payload, timeout=10))
37
+ if isinstance(verdict, Upheld):
38
+ print("제안한 작업을 진행할 수 있습니다", verdict.advisories)
39
+
40
+ verdict = check_change_set(CheckChangeSetSpec(repo_root=".", diff="", timeout=10))
41
+ ingest = memory_ingest(MemoryIngestSpec(root=".", timeout=30))
42
+ recall = memory_search(MemorySearchSpec(root=".", query="설계 결정", limit=5, timeout=10))
43
+ match recall:
44
+ case Found(hits=hits):
45
+ print(hits[0]["sectionTitle"])
46
+ case Empty():
47
+ print("검색 결과 없음")
48
+ case Unavailable(reason=reason):
49
+ print("기억을 사용할 수 없음:", reason)
50
+ ```
51
+
52
+ 호스트는 IR 증거를 제공하고 제안한 작업의 실행 여부를 결정합니다. 이 예시는 `Upheld`일 때만
53
+ 실행을 허용합니다. 자식 프로세스가 설정을 읽고 규율(discipline)을 판정하며 텔레메트리와 기억
54
+ 색인을 관리합니다.
55
+
56
+ [Python SDK 참조](https://github.com/huskyhoochu/polydeukes/blob/main/docs/reference/packages/sdk-python.ko.md)에서
57
+ 모든 spec, 결과, 기한과 주입 가능한 `spawn` 경계를 설명합니다.
@@ -0,0 +1,58 @@
1
+ # `polydeukes-sdk` (Python)
2
+
3
+ **English** · [한국어](README.ko.md)
4
+
5
+ Call the Polydeukes judge and memory commands from Python 3.12 or newer. Four synchronous
6
+ verbs share one executable resolver and process runner. The package has zero runtime dependencies.
7
+
8
+ ```sh
9
+ python -m pip install polydeukes-sdk
10
+ ```
11
+
12
+ Download `pdks-linux-x64` or `pdks-darwin-arm64` from a
13
+ [GitHub Release](https://github.com/huskyhoochu/polydeukes/releases), mark it executable, and
14
+ put it on an absolute `PATH` directory as `pdks`, or pass its path as `executable`.
15
+ Keep the SDK and executable on the same minor release. These are the supported platforms:
16
+ Linux x64 and macOS arm64. On macOS, a browser download may require
17
+ `xattr -d com.apple.quarantine /path/to/pdks`.
18
+
19
+ ```python
20
+ from polydeukes_sdk import (
21
+ CheckCovenantSpec,
22
+ CovenantInput,
23
+ Upheld,
24
+ check_covenant,
25
+ CheckChangeSetSpec,
26
+ check_change_set,
27
+ MemoryIngestSpec,
28
+ memory_ingest,
29
+ MemorySearchSpec,
30
+ memory_search,
31
+ Found,
32
+ Empty,
33
+ Unavailable,
34
+ )
35
+
36
+ payload: CovenantInput = {"toolCalls": [], "subagentSpawns": [], "userMessages": []}
37
+ verdict = check_covenant(CheckCovenantSpec(repo_root=".", input=payload, timeout=10))
38
+ if isinstance(verdict, Upheld):
39
+ print("The proposed operation may proceed", verdict.advisories)
40
+
41
+ verdict = check_change_set(CheckChangeSetSpec(repo_root=".", diff="", timeout=10))
42
+ ingest = memory_ingest(MemoryIngestSpec(root=".", timeout=30))
43
+ recall = memory_search(MemorySearchSpec(root=".", query="design decision", limit=5, timeout=10))
44
+ match recall:
45
+ case Found(hits=hits):
46
+ print(hits[0]["sectionTitle"])
47
+ case Empty():
48
+ print("No matches")
49
+ case Unavailable(reason=reason):
50
+ print("Memory unavailable:", reason)
51
+ ```
52
+
53
+ The host supplies IR evidence and decides whether to execute the proposed operation. Only
54
+ `Upheld` permits it in this example. The child reads configuration, judges disciplines, writes
55
+ telemetry, and manages the memory index.
56
+
57
+ The [Python SDK reference](https://github.com/huskyhoochu/polydeukes/blob/main/docs/reference/packages/sdk-python.md)
58
+ describes every spec, outcome, timeout, and the injectable `spawn` boundary.
@@ -0,0 +1,56 @@
1
+ [build-system]
2
+ requires = ["hatchling==1.32.4"]
3
+ build-backend = "hatchling.build"
4
+
5
+ [project]
6
+ name = "polydeukes-sdk"
7
+ version = "0.19.1"
8
+ description = "Call the Polydeukes covenant and memory commands from Python"
9
+ readme = "README.md"
10
+ requires-python = ">=3.12"
11
+ license = "MIT"
12
+ license-files = ["LICENSE"]
13
+ authors = [{name = "Polydeukes contributors"}]
14
+ dependencies = []
15
+ classifiers = [
16
+ "Development Status :: 4 - Beta",
17
+ "Programming Language :: Python :: 3 :: Only",
18
+ "Programming Language :: Python :: 3.12",
19
+ "Programming Language :: Python :: 3.14",
20
+ "Typing :: Typed",
21
+ ]
22
+
23
+ [project.urls]
24
+ Repository = "https://github.com/huskyhoochu/polydeukes"
25
+ Documentation = "https://polydeukes.vercel.app"
26
+
27
+ [dependency-groups]
28
+ dev = [
29
+ "datamodel-code-generator==0.86.0",
30
+ "pyright==1.1.414",
31
+ "ruff==0.17.1",
32
+ "build==1.6.1",
33
+ ]
34
+
35
+ [tool.hatch.build.targets.wheel]
36
+ packages = ["src/polydeukes_sdk"]
37
+
38
+ [tool.hatch.build.targets.sdist]
39
+ include = ["src/", "README.md", "README.ko.md", "LICENSE", "pyproject.toml"]
40
+
41
+ [tool.pyright]
42
+ include = ["src", "tests/typing/valid.py"]
43
+ extraPaths = ["src"]
44
+ pythonVersion = "3.12"
45
+ typeCheckingMode = "strict"
46
+
47
+ [tool.ruff]
48
+ target-version = "py312"
49
+ line-length = 100
50
+ exclude = ["src/polydeukes_sdk/ir.py", "src/polydeukes_sdk/memory.py"]
51
+
52
+ [tool.ruff.lint]
53
+ select = ["E4", "E7", "E9", "F", "I"]
54
+
55
+ [tool.ruff.lint.isort]
56
+ combine-as-imports = true
@@ -0,0 +1,49 @@
1
+ """Typed synchronous clients for a standalone pdks executable."""
2
+
3
+ from ._api import (
4
+ check_change_set as check_change_set,
5
+ check_covenant as check_covenant,
6
+ memory_ingest as memory_ingest,
7
+ memory_search as memory_search,
8
+ )
9
+ from ._contract import (
10
+ Blocked as Blocked,
11
+ CheckChangeSetSpec as CheckChangeSetSpec,
12
+ CheckCovenantSpec as CheckCovenantSpec,
13
+ CheckCovenantVerdict as CheckCovenantVerdict,
14
+ Empty as Empty,
15
+ Enforce as Enforce,
16
+ Found as Found,
17
+ Ingested as Ingested,
18
+ MemoryIngestOutcome as MemoryIngestOutcome,
19
+ MemoryIngestSpec as MemoryIngestSpec,
20
+ MemorySearchOutcome as MemorySearchOutcome,
21
+ MemorySearchSpec as MemorySearchSpec,
22
+ Path as Path,
23
+ Spawn as Spawn,
24
+ SpawnOutcome as SpawnOutcome,
25
+ SpawnSpec as SpawnSpec,
26
+ Unavailable as Unavailable,
27
+ Unjudged as Unjudged,
28
+ Upheld as Upheld,
29
+ )
30
+ from .ir import (
31
+ Actor as Actor,
32
+ Channels as Channels,
33
+ CovenantInput as CovenantInput,
34
+ FileChange as FileChange,
35
+ FileChangeCreate as FileChangeCreate,
36
+ FileChangeDelete as FileChangeDelete,
37
+ FileChangeModify as FileChangeModify,
38
+ Session as Session,
39
+ SessionToolCall as SessionToolCall,
40
+ SessionUserMessage as SessionUserMessage,
41
+ SubagentSpawn as SubagentSpawn,
42
+ ToolCall as ToolCall,
43
+ Tools as Tools,
44
+ UserMessage as UserMessage,
45
+ )
46
+ from .memory import (
47
+ MemorySearchOutput as MemorySearchOutput,
48
+ MemorySearchResult as MemorySearchResult,
49
+ )
@@ -0,0 +1,131 @@
1
+ """Four synchronous process clients for the pdks executable."""
2
+
3
+ import json
4
+ import os
5
+ from typing import cast
6
+
7
+ from ._contract import (
8
+ Blocked,
9
+ CheckChangeSetSpec,
10
+ CheckCovenantSpec,
11
+ CheckCovenantVerdict,
12
+ Empty,
13
+ Found,
14
+ Ingested,
15
+ MemoryIngestOutcome,
16
+ MemoryIngestSpec,
17
+ MemorySearchOutcome,
18
+ MemorySearchSpec,
19
+ SpawnOutcome,
20
+ Unavailable,
21
+ Unjudged,
22
+ Upheld,
23
+ )
24
+ from ._process import run
25
+ from .memory import MemorySearchResult
26
+
27
+
28
+ def _reason(error: Exception) -> str:
29
+ return str(error) or type(error).__name__
30
+
31
+
32
+ def _failure(outcome: SpawnOutcome) -> str:
33
+ return outcome.stderr.decode("utf-8", errors="replace") or (
34
+ f"pdks exited with status {outcome.status}"
35
+ )
36
+
37
+
38
+ def _check(spec: CheckCovenantSpec | CheckChangeSetSpec) -> CheckCovenantVerdict:
39
+ try:
40
+ args = ("covenant", "check")
41
+ if isinstance(spec, CheckChangeSetSpec):
42
+ args += ("--diff",)
43
+ stdin = spec.diff.encode("utf-8")
44
+ else:
45
+ stdin = json.dumps(spec.input, ensure_ascii=False, allow_nan=False).encode("utf-8")
46
+ args += ("--enforce", spec.enforce)
47
+ if spec.config_layer is not None:
48
+ args += ("--config-layer", os.fspath(spec.config_layer))
49
+ if spec.telemetry_path is not None:
50
+ args += ("--telemetry-path", os.fspath(spec.telemetry_path))
51
+ outcome = run(spec.repo_root, spec.executable, spec.timeout, spec.spawn, args, stdin)
52
+ if outcome.status == 0:
53
+ return Upheld(outcome.stderr.decode("utf-8"))
54
+ if outcome.status == 2:
55
+ return Blocked(outcome.stderr.decode("utf-8"))
56
+ return Unjudged(_failure(outcome))
57
+ except Exception as error:
58
+ return Unjudged(_reason(error))
59
+
60
+
61
+ def check_covenant(spec: CheckCovenantSpec) -> CheckCovenantVerdict:
62
+ """Check caller-provided IR; only Upheld authorizes the proposed action."""
63
+ return _check(spec)
64
+
65
+
66
+ def check_change_set(spec: CheckChangeSetSpec) -> CheckCovenantVerdict:
67
+ """Check a unified diff without changing its content or line endings."""
68
+ return _check(spec)
69
+
70
+
71
+ def memory_ingest(spec: MemoryIngestSpec) -> MemoryIngestOutcome:
72
+ """Ingest through pdks, returning Unavailable for every process failure."""
73
+ try:
74
+ outcome = run(
75
+ spec.root, spec.executable, spec.timeout, spec.spawn, ("memory", "ingest"), b""
76
+ )
77
+ return Ingested() if outcome.status == 0 else Unavailable(_failure(outcome))
78
+ except Exception as error:
79
+ return Unavailable(_reason(error))
80
+
81
+
82
+ def _invalid_constant(value: str) -> object:
83
+ raise ValueError(f"Invalid JSON constant: {value}")
84
+
85
+
86
+ def _hits(data: bytes) -> tuple[MemorySearchResult, ...]:
87
+ payload: object = json.loads(data.decode("utf-8"), parse_constant=_invalid_constant)
88
+ if not isinstance(payload, dict):
89
+ raise ValueError("memory search response must be an object")
90
+ root = cast(dict[str, object], payload)
91
+ results = root.get("results")
92
+ if not isinstance(root.get("ingestedAt"), str) or not isinstance(results, list):
93
+ raise ValueError("memory search response requires ingestedAt and results")
94
+ for value in cast(list[object], results):
95
+ if not isinstance(value, dict):
96
+ raise ValueError("memory search hit must be an object")
97
+ hit = cast(dict[str, object], value)
98
+ if not all(
99
+ isinstance(hit.get(field), str)
100
+ for field in ("id", "conceptId", "docTitle", "sectionTitle", "status")
101
+ ):
102
+ raise ValueError("memory search hit requires string identity and status fields")
103
+ if hit.get("trust") not in ("human-reviewed", "machine-verified", "unverified"):
104
+ raise ValueError("memory search hit has invalid trust")
105
+ if hit.get("matchPath") not in ("and", "or", "like"):
106
+ raise ValueError("memory search hit has invalid matchPath")
107
+ superseded = hit.get("supersededBy")
108
+ if (
109
+ not isinstance(hit.get("stale"), bool)
110
+ or not isinstance(superseded, list)
111
+ or not all(isinstance(item, str) for item in cast(list[object], superseded))
112
+ ):
113
+ raise ValueError("memory search hit requires stale and supersededBy")
114
+ return tuple(cast(list[MemorySearchResult], results))
115
+
116
+
117
+ def memory_search(spec: MemorySearchSpec) -> MemorySearchOutcome:
118
+ """Return ranked hits, a valid empty search, or an unavailable response."""
119
+ try:
120
+ args = ("memory", "search", spec.query, "--json")
121
+ if spec.limit is not None:
122
+ if type(spec.limit) is not int or spec.limit <= 0:
123
+ raise ValueError("limit must be a positive integer")
124
+ args += ("--limit", str(spec.limit))
125
+ outcome = run(spec.root, spec.executable, spec.timeout, spec.spawn, args, b"")
126
+ if outcome.status != 0:
127
+ return Unavailable(_failure(outcome))
128
+ hits = _hits(outcome.stdout)
129
+ return Found(hits) if hits else Empty()
130
+ except Exception as error:
131
+ return Unavailable(_reason(error))
@@ -0,0 +1,135 @@
1
+ """Immutable call specifications and result variants."""
2
+
3
+ from collections.abc import Callable
4
+ from dataclasses import dataclass
5
+ from os import PathLike
6
+ from typing import Literal
7
+
8
+ from .ir import CovenantInput
9
+ from .memory import MemorySearchResult
10
+
11
+ type Path = str | PathLike[str]
12
+ type Enforce = Literal["advise", "block"]
13
+
14
+
15
+ @dataclass(frozen=True, kw_only=True)
16
+ class SpawnSpec:
17
+ """One direct executable invocation; an injected spawn enforces its timeout."""
18
+
19
+ command: str
20
+ args: tuple[str, ...]
21
+ cwd: str
22
+ stdin: bytes
23
+ timeout: float | None
24
+
25
+
26
+ @dataclass(frozen=True, kw_only=True)
27
+ class SpawnOutcome:
28
+ """Captured process output, with a missing status representing a signal."""
29
+
30
+ status: int | None
31
+ stdout: bytes
32
+ stderr: bytes
33
+
34
+
35
+ type Spawn = Callable[[SpawnSpec], SpawnOutcome]
36
+
37
+
38
+ @dataclass(frozen=True, kw_only=True)
39
+ class CheckCovenantSpec:
40
+ """Check a caller-built input in the repository root."""
41
+
42
+ repo_root: Path
43
+ input: CovenantInput
44
+ enforce: Enforce = "block"
45
+ config_layer: Path | None = None
46
+ telemetry_path: Path | None = None
47
+ executable: Path | None = None
48
+ timeout: float | None = None
49
+ spawn: Spawn | None = None
50
+
51
+
52
+ @dataclass(frozen=True, kw_only=True)
53
+ class CheckChangeSetSpec:
54
+ """Check the exact UTF-8 bytes of a caller's unified diff."""
55
+
56
+ repo_root: Path
57
+ diff: str
58
+ enforce: Enforce = "block"
59
+ config_layer: Path | None = None
60
+ telemetry_path: Path | None = None
61
+ executable: Path | None = None
62
+ timeout: float | None = None
63
+ spawn: Spawn | None = None
64
+
65
+
66
+ @dataclass(frozen=True, kw_only=True)
67
+ class MemoryIngestSpec:
68
+ """Ask the executable to ingest documents under root."""
69
+
70
+ root: Path
71
+ executable: Path | None = None
72
+ timeout: float | None = None
73
+ spawn: Spawn | None = None
74
+
75
+
76
+ @dataclass(frozen=True, kw_only=True)
77
+ class MemorySearchSpec:
78
+ """Search the executable's memory index with one literal query argument."""
79
+
80
+ root: Path
81
+ query: str
82
+ limit: int | None = None
83
+ executable: Path | None = None
84
+ timeout: float | None = None
85
+ spawn: Spawn | None = None
86
+
87
+
88
+ @dataclass(frozen=True)
89
+ class Upheld:
90
+ """The executable upheld the covenant, possibly with advisories."""
91
+
92
+ advisories: str
93
+
94
+
95
+ @dataclass(frozen=True)
96
+ class Blocked:
97
+ """The covenant command returned its blocking exit status."""
98
+
99
+ reason: str
100
+
101
+
102
+ @dataclass(frozen=True)
103
+ class Unjudged:
104
+ """No covenant verdict was available."""
105
+
106
+ reason: str
107
+
108
+
109
+ @dataclass(frozen=True)
110
+ class Ingested:
111
+ """The memory ingest command succeeded."""
112
+
113
+
114
+ @dataclass(frozen=True)
115
+ class Unavailable:
116
+ """The memory command or its response could not be consumed."""
117
+
118
+ reason: str
119
+
120
+
121
+ @dataclass(frozen=True)
122
+ class Found:
123
+ """Nonempty search hits in the executable's ranking order."""
124
+
125
+ hits: tuple[MemorySearchResult, ...]
126
+
127
+
128
+ @dataclass(frozen=True)
129
+ class Empty:
130
+ """A valid successful search returned no hits."""
131
+
132
+
133
+ type CheckCovenantVerdict = Upheld | Blocked | Unjudged
134
+ type MemoryIngestOutcome = Ingested | Unavailable
135
+ type MemorySearchOutcome = Found | Empty | Unavailable
@@ -0,0 +1,70 @@
1
+ """Executable resolution and binary process transport shared by all verbs."""
2
+
3
+ import math
4
+ import os
5
+ import subprocess
6
+
7
+ from ._contract import Path, Spawn, SpawnOutcome, SpawnSpec
8
+
9
+
10
+ def _absolute(path: Path, cwd: str) -> str:
11
+ return os.path.normpath(os.path.join(cwd, os.fspath(path)))
12
+
13
+
14
+ def _resolve(executable: Path | None, cwd: str) -> str:
15
+ if executable is not None:
16
+ return _absolute(executable, cwd)
17
+ for directory in os.environ.get("PATH", "").split(os.pathsep):
18
+ if os.path.isabs(directory):
19
+ candidate = os.path.join(directory, "pdks")
20
+ if os.path.isfile(candidate) and os.access(candidate, os.X_OK):
21
+ return candidate
22
+ raise FileNotFoundError("No executable pdks found in absolute PATH directories")
23
+
24
+
25
+ def _spawn(spec: SpawnSpec) -> SpawnOutcome:
26
+ child = subprocess.Popen(
27
+ (spec.command, *spec.args),
28
+ cwd=spec.cwd,
29
+ stdin=subprocess.PIPE,
30
+ stdout=subprocess.PIPE,
31
+ stderr=subprocess.PIPE,
32
+ close_fds=True,
33
+ )
34
+ try:
35
+ stdout, stderr = child.communicate(spec.stdin, timeout=spec.timeout)
36
+ except BaseException:
37
+ child.kill()
38
+ child.wait()
39
+ # A surviving descendant can keep these pipes open after the direct child exits.
40
+ for stream in (child.stdin, child.stdout, child.stderr):
41
+ if stream is not None:
42
+ stream.close()
43
+ raise
44
+ return SpawnOutcome(
45
+ status=child.returncode if child.returncode >= 0 else None,
46
+ stdout=stdout,
47
+ stderr=stderr,
48
+ )
49
+
50
+
51
+ def run(
52
+ root: Path,
53
+ executable: Path | None,
54
+ timeout: float | None,
55
+ spawn: Spawn | None,
56
+ args: tuple[str, ...],
57
+ stdin: bytes,
58
+ ) -> SpawnOutcome:
59
+ if timeout is not None and (not math.isfinite(timeout) or timeout < 0):
60
+ raise ValueError("timeout must be finite and nonnegative")
61
+ cwd = os.getcwd()
62
+ return (spawn or _spawn)(
63
+ SpawnSpec(
64
+ command=_resolve(executable, cwd),
65
+ args=args,
66
+ cwd=_absolute(root, cwd),
67
+ stdin=stdin,
68
+ timeout=timeout,
69
+ )
70
+ )
@@ -0,0 +1,83 @@
1
+ # generated by datamodel-codegen:
2
+ # filename: covenant-input.schema.json
3
+
4
+ from __future__ import annotations
5
+
6
+ from typing import Any, Literal, NotRequired, TypedDict
7
+
8
+
9
+ class FileChangeCreate(TypedDict):
10
+ kind: Literal['create']
11
+ path: str
12
+ post: str
13
+
14
+
15
+ class FileChangeModify(TypedDict):
16
+ kind: Literal['modify']
17
+ path: str
18
+ pre: str
19
+ post: str
20
+
21
+
22
+ class FileChangeDelete(TypedDict):
23
+ kind: Literal['delete']
24
+ path: str
25
+ pre: NotRequired[str]
26
+
27
+
28
+ type FileChange = FileChangeCreate | FileChangeModify | FileChangeDelete
29
+
30
+
31
+ class SubagentSpawn(TypedDict):
32
+ kind: str
33
+
34
+
35
+ class UserMessage(TypedDict):
36
+ text: str
37
+
38
+
39
+ class Channels(TypedDict):
40
+ sidecar: NotRequired[str]
41
+
42
+
43
+ class Actor(TypedDict):
44
+ agentType: NotRequired[str]
45
+
46
+
47
+ class Tools(TypedDict):
48
+ mutating: list[str]
49
+ shell: list[str]
50
+ commandArgs: list[str]
51
+
52
+
53
+ class SessionUserMessage(TypedDict):
54
+ text: str
55
+ timestampMs: NotRequired[float]
56
+
57
+
58
+ class SessionToolCall(TypedDict):
59
+ name: str
60
+ args: NotRequired[dict[str, Any]]
61
+ succeeded: NotRequired[bool]
62
+
63
+
64
+ class ToolCall(TypedDict):
65
+ name: str
66
+ args: NotRequired[dict[str, Any]]
67
+ fileChange: NotRequired[FileChange]
68
+
69
+
70
+ class Session(TypedDict):
71
+ evidencePath: NotRequired[str]
72
+ userMessages: list[SessionUserMessage]
73
+ toolCalls: list[SessionToolCall]
74
+ channels: NotRequired[Channels]
75
+
76
+
77
+ class CovenantInput(TypedDict):
78
+ toolCalls: list[ToolCall]
79
+ subagentSpawns: list[SubagentSpawn]
80
+ userMessages: list[UserMessage]
81
+ actor: NotRequired[Actor]
82
+ tools: NotRequired[Tools]
83
+ session: NotRequired[Session]
@@ -0,0 +1,23 @@
1
+ # generated by datamodel-codegen:
2
+ # filename: memory-search-output.schema.json
3
+
4
+ from __future__ import annotations
5
+
6
+ from typing import Literal, TypedDict
7
+
8
+
9
+ class MemorySearchResult(TypedDict):
10
+ id: str
11
+ conceptId: str
12
+ docTitle: str
13
+ sectionTitle: str
14
+ status: str
15
+ trust: Literal['human-reviewed', 'machine-verified', 'unverified']
16
+ stale: bool
17
+ matchPath: Literal['and', 'or', 'like']
18
+ supersededBy: list[str]
19
+
20
+
21
+ class MemorySearchOutput(TypedDict):
22
+ ingestedAt: str
23
+ results: list[MemorySearchResult]
File without changes