nomark-engine 0.1.0__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,8 @@
1
+ .venv/
2
+ .venv-old/
3
+ __pycache__/
4
+ *.pyc
5
+ *.egg-info/
6
+ dist/
7
+ build/
8
+ .pytest_cache/
@@ -0,0 +1,67 @@
1
+ Metadata-Version: 2.4
2
+ Name: nomark-engine
3
+ Version: 0.1.0
4
+ Summary: NOMARK Engine — open-core agent outcome quality resolver
5
+ Project-URL: Homepage, https://github.com/nomark-dev/nomark
6
+ Project-URL: Repository, https://github.com/nomark-dev/nomark
7
+ Author: Reece Frazier
8
+ License-Expression: Apache-2.0
9
+ Keywords: agent,ai,intent,nomark,preferences,quality,trust
10
+ Classifier: Development Status :: 3 - Alpha
11
+ Classifier: Intended Audience :: Developers
12
+ Classifier: License :: OSI Approved :: Apache Software License
13
+ Classifier: Programming Language :: Python :: 3
14
+ Classifier: Programming Language :: Python :: 3.10
15
+ Classifier: Programming Language :: Python :: 3.11
16
+ Classifier: Programming Language :: Python :: 3.12
17
+ Classifier: Programming Language :: Python :: 3.13
18
+ Classifier: Topic :: Software Development :: Libraries
19
+ Requires-Python: >=3.10
20
+ Requires-Dist: pydantic<3,>=2.0
21
+ Description-Content-Type: text/markdown
22
+
23
+ # nomark-engine
24
+
25
+ Open-core agent outcome quality resolver. Understands what a human means from incomplete input by learning preferences across sessions and platforms.
26
+
27
+ ## Install
28
+
29
+ ```bash
30
+ pip install nomark-engine
31
+ ```
32
+
33
+ ## Quick start
34
+
35
+ ```python
36
+ from nomark_engine import create_resolver, parse_ledger, ResolverConfig
37
+
38
+ # Parse a NOMARK ledger
39
+ entries = parse_ledger(open("nomark-ledger.jsonl").read())
40
+
41
+ # Create resolver
42
+ resolver = create_resolver(ResolverConfig(entries=entries))
43
+
44
+ # Resolve all preference dimensions
45
+ result = resolver.resolve_all()
46
+ for dim, res in result.dimensions.items():
47
+ if res.winner:
48
+ print(f"{dim}: {res.winner.pref.target} (score: {res.winner.score})")
49
+
50
+ # Resolve intent from natural language
51
+ result = resolver.resolve_input("make it shorter")
52
+ for match in result.meaning_maps:
53
+ print(f"Matched: {match.trigger} -> {match.intent}")
54
+ ```
55
+
56
+ ## Modules
57
+
58
+ - **Schema** — Pydantic v2 models for all signal types (pref, map, asn, meta, rub)
59
+ - **Classifier** — Input tier classification (pass-through, routing, extraction)
60
+ - **Resolver** — MEE weighted scoring with scope matching and instability detection
61
+ - **Ledger** — JSONL parser/writer with capacity constraints
62
+ - **Decay** — Time-based decay with contradiction acceleration
63
+ - **Utility** — Multi-factor utility scoring and capacity-bounded pruning
64
+
65
+ ## License
66
+
67
+ Apache 2.0
@@ -0,0 +1,45 @@
1
+ # nomark-engine
2
+
3
+ Open-core agent outcome quality resolver. Understands what a human means from incomplete input by learning preferences across sessions and platforms.
4
+
5
+ ## Install
6
+
7
+ ```bash
8
+ pip install nomark-engine
9
+ ```
10
+
11
+ ## Quick start
12
+
13
+ ```python
14
+ from nomark_engine import create_resolver, parse_ledger, ResolverConfig
15
+
16
+ # Parse a NOMARK ledger
17
+ entries = parse_ledger(open("nomark-ledger.jsonl").read())
18
+
19
+ # Create resolver
20
+ resolver = create_resolver(ResolverConfig(entries=entries))
21
+
22
+ # Resolve all preference dimensions
23
+ result = resolver.resolve_all()
24
+ for dim, res in result.dimensions.items():
25
+ if res.winner:
26
+ print(f"{dim}: {res.winner.pref.target} (score: {res.winner.score})")
27
+
28
+ # Resolve intent from natural language
29
+ result = resolver.resolve_input("make it shorter")
30
+ for match in result.meaning_maps:
31
+ print(f"Matched: {match.trigger} -> {match.intent}")
32
+ ```
33
+
34
+ ## Modules
35
+
36
+ - **Schema** — Pydantic v2 models for all signal types (pref, map, asn, meta, rub)
37
+ - **Classifier** — Input tier classification (pass-through, routing, extraction)
38
+ - **Resolver** — MEE weighted scoring with scope matching and instability detection
39
+ - **Ledger** — JSONL parser/writer with capacity constraints
40
+ - **Decay** — Time-based decay with contradiction acceleration
41
+ - **Utility** — Multi-factor utility scoring and capacity-bounded pruning
42
+
43
+ ## License
44
+
45
+ Apache 2.0
@@ -0,0 +1,43 @@
1
+ [build-system]
2
+ requires = ["hatchling"]
3
+ build-backend = "hatchling.build"
4
+
5
+ [project]
6
+ name = "nomark-engine"
7
+ version = "0.1.0"
8
+ description = "NOMARK Engine — open-core agent outcome quality resolver"
9
+ readme = "README.md"
10
+ license = "Apache-2.0"
11
+ requires-python = ">=3.10"
12
+ authors = [
13
+ { name = "Reece Frazier" },
14
+ ]
15
+ keywords = ["nomark", "ai", "agent", "preferences", "intent", "quality", "trust"]
16
+ classifiers = [
17
+ "Development Status :: 3 - Alpha",
18
+ "Intended Audience :: Developers",
19
+ "License :: OSI Approved :: Apache Software License",
20
+ "Programming Language :: Python :: 3",
21
+ "Programming Language :: Python :: 3.10",
22
+ "Programming Language :: Python :: 3.11",
23
+ "Programming Language :: Python :: 3.12",
24
+ "Programming Language :: Python :: 3.13",
25
+ "Topic :: Software Development :: Libraries",
26
+ ]
27
+ dependencies = [
28
+ "pydantic>=2.0,<3",
29
+ ]
30
+
31
+ [project.urls]
32
+ Homepage = "https://github.com/nomark-dev/nomark"
33
+ Repository = "https://github.com/nomark-dev/nomark"
34
+
35
+ [tool.hatch.build.targets.wheel]
36
+ packages = ["src/nomark_engine"]
37
+
38
+ [tool.pytest.ini_options]
39
+ testpaths = ["tests"]
40
+
41
+ [tool.pyright]
42
+ include = ["src"]
43
+ pythonVersion = "3.10"
@@ -0,0 +1,51 @@
1
+ """NOMARK Engine — open-core agent outcome quality resolver."""
2
+
3
+ __version__ = "0.1.0"
4
+
5
+ from .schema import (
6
+ Context, Outcome, RequestType, PatternType, RubricStage, SignalType, Scope,
7
+ ContextCounts, OutcomeCounts,
8
+ SigPref, SigMap, SigAsn, SigMeta, SigRub,
9
+ LedgerEntryMeta, LedgerEntryPref, LedgerEntryMap, LedgerEntryAsn, LedgerEntryRub,
10
+ LedgerEntry,
11
+ parse_ledger_entry,
12
+ )
13
+ from .decay import compute_decay, effective_weight
14
+ from .ledger import (
15
+ parse_ledger, write_ledger, parse_ledger_line, format_ledger_line,
16
+ count_by_type, check_capacity, estimate_tokens,
17
+ ENTRY_CAPS, TOTAL_CAP,
18
+ )
19
+ from .utility import utility_score, is_protected, prune_to_capacity
20
+ from .classifier import classify, ClassificationResult, InputTier
21
+ from .resolver import (
22
+ scope_specificity, scope_matches, resolver_score,
23
+ resolve_dimension, match_meaning_maps, find_defaults,
24
+ create_resolver, Resolver, ResolverConfig, ResolverResult,
25
+ DimensionResult, MeaningMapMatch, DefaultMatch, ScoredPref, ScoringFactors, ResolverMeta,
26
+ )
27
+
28
+ __all__ = [
29
+ # Schema types
30
+ "Context", "Outcome", "RequestType", "PatternType", "RubricStage", "SignalType", "Scope",
31
+ "ContextCounts", "OutcomeCounts",
32
+ "SigPref", "SigMap", "SigAsn", "SigMeta", "SigRub",
33
+ "LedgerEntryMeta", "LedgerEntryPref", "LedgerEntryMap", "LedgerEntryAsn", "LedgerEntryRub",
34
+ "LedgerEntry",
35
+ "parse_ledger_entry",
36
+ # Decay
37
+ "compute_decay", "effective_weight",
38
+ # Ledger
39
+ "parse_ledger", "write_ledger", "parse_ledger_line", "format_ledger_line",
40
+ "count_by_type", "check_capacity", "estimate_tokens",
41
+ "ENTRY_CAPS", "TOTAL_CAP",
42
+ # Utility
43
+ "utility_score", "is_protected", "prune_to_capacity",
44
+ # Classifier
45
+ "classify", "ClassificationResult", "InputTier",
46
+ # Resolver
47
+ "scope_specificity", "scope_matches", "resolver_score",
48
+ "resolve_dimension", "match_meaning_maps", "find_defaults",
49
+ "create_resolver", "Resolver", "ResolverConfig", "ResolverResult",
50
+ "DimensionResult", "MeaningMapMatch", "DefaultMatch", "ScoredPref", "ScoringFactors", "ResolverMeta",
51
+ ]
@@ -0,0 +1,134 @@
1
+ """Input classification (MEE Spec Section 1.1).
2
+
3
+ Ports packages/engine/src/classifier.ts.
4
+
5
+ Tier 0: Pass-through — already resolved. Confirmations, selections, JSON, exit codes.
6
+ Tier 1: Routing — match to established pattern. Skill invocations, continuations, corrections.
7
+ Tier 2: Extraction — full intent reconstruction through resolver + gate.
8
+ """
9
+
10
+ from __future__ import annotations
11
+
12
+ import json
13
+ import re
14
+ from dataclasses import dataclass
15
+ from typing import Literal, Protocol
16
+
17
+
18
+ InputTier = Literal[0, 1, 2]
19
+
20
+
21
+ @dataclass(frozen=True)
22
+ class ClassificationResult:
23
+ tier: InputTier
24
+ reason: str
25
+
26
+
27
+ class ClassifierRule(Protocol):
28
+ tier: InputTier
29
+ reason: str
30
+
31
+ def test(self, input: str) -> bool: ...
32
+
33
+
34
+ @dataclass(frozen=True)
35
+ class _Rule:
36
+ tier: InputTier
37
+ reason: str
38
+ _test: object # callable
39
+
40
+ def test(self, input: str) -> bool:
41
+ return self._test(input) # type: ignore[operator]
42
+
43
+
44
+ def _is_confirmation(s: str) -> bool:
45
+ return bool(re.match(
46
+ r"^(y|yes|no|n|ok|done|skip|cancel|approve|reject|confirm|confirmed)$",
47
+ s.strip(), re.IGNORECASE,
48
+ ))
49
+
50
+
51
+ def _is_numeric(s: str) -> bool:
52
+ return bool(re.match(r"^[0-9]+$", s.strip()))
53
+
54
+
55
+ def _is_json(s: str) -> bool:
56
+ t = s.strip()
57
+ if not (t.startswith("{") or t.startswith("[")):
58
+ return False
59
+ try:
60
+ json.loads(s)
61
+ return True
62
+ except (json.JSONDecodeError, ValueError):
63
+ return False
64
+
65
+
66
+ def _is_exit(s: str) -> bool:
67
+ return bool(re.match(r"^(exit|quit|bye|stop)\s*$", s.strip(), re.IGNORECASE))
68
+
69
+
70
+ def _is_hash(s: str) -> bool:
71
+ return bool(re.match(r"^[a-f0-9]{6,40}$", s.strip(), re.IGNORECASE))
72
+
73
+
74
+ def _is_skill(s: str) -> bool:
75
+ return bool(re.match(r"^/\w", s.strip()))
76
+
77
+
78
+ def _is_continuation(s: str) -> bool:
79
+ return bool(re.match(
80
+ r"^(continue|go ahead|proceed|next|keep going|resume)\s*$",
81
+ s.strip(), re.IGNORECASE,
82
+ ))
83
+
84
+
85
+ def _is_correction(s: str) -> bool:
86
+ t = s.strip()
87
+ return bool(
88
+ re.match(r"^(no[,.]?\s+(not that|wrong|different|the other|I meant))", t, re.IGNORECASE)
89
+ or re.match(r"^(actually|wait|hold on|scratch that|never\s?mind)", t, re.IGNORECASE)
90
+ )
91
+
92
+
93
+ def _is_letter_selection(s: str) -> bool:
94
+ t = s.strip()
95
+ return 0 < len(t) <= 3 and bool(re.match(r"^[a-z]$", t, re.IGNORECASE))
96
+
97
+
98
+ _TIER_0_RULES: list[_Rule] = [
99
+ _Rule(0, "confirmation", _is_confirmation),
100
+ _Rule(0, "numeric_selection", _is_numeric),
101
+ _Rule(0, "json_data", _is_json),
102
+ _Rule(0, "exit_signal", _is_exit),
103
+ _Rule(0, "hash_or_id", _is_hash),
104
+ ]
105
+
106
+ _TIER_1_RULES: list[_Rule] = [
107
+ _Rule(1, "skill_invocation", _is_skill),
108
+ _Rule(1, "continuation", _is_continuation),
109
+ _Rule(1, "correction", _is_correction),
110
+ _Rule(1, "letter_selection", _is_letter_selection),
111
+ ]
112
+
113
+
114
+ def classify(input: str, custom_rules: list[_Rule] | None = None) -> ClassificationResult:
115
+ """Classify input into Tier 0 (pass-through), Tier 1 (routing), or Tier 2 (extraction)."""
116
+ trimmed = input.strip()
117
+
118
+ if not trimmed:
119
+ return ClassificationResult(tier=0, reason="empty_input")
120
+
121
+ if custom_rules:
122
+ for rule in custom_rules:
123
+ if rule.test(trimmed):
124
+ return ClassificationResult(tier=rule.tier, reason=rule.reason)
125
+
126
+ for rule in _TIER_0_RULES:
127
+ if rule.test(trimmed):
128
+ return ClassificationResult(tier=rule.tier, reason=rule.reason)
129
+
130
+ for rule in _TIER_1_RULES:
131
+ if rule.test(trimmed):
132
+ return ClassificationResult(tier=rule.tier, reason=rule.reason)
133
+
134
+ return ClassificationResult(tier=2, reason="substantive_input")
@@ -0,0 +1,46 @@
1
+ """Continuous decay computation (MEE Spec Section 8).
2
+
3
+ Ports packages/engine/src/decay.ts.
4
+
5
+ Base: max(0.1, 0.98^(days/30))
6
+ Contradiction acceleration: decay * 0.85 when recent_contradictions >= 2
7
+ Reinforcement recovery: decay * 1.1 (capped 1.0) when reinforced within 7 days
8
+ Floor: 0.1 — never total erasure
9
+ """
10
+
11
+ from __future__ import annotations
12
+
13
+ from datetime import datetime, timezone
14
+
15
+
16
+ def compute_decay(
17
+ last_date: str,
18
+ _contradictions: int,
19
+ recent_contradictions: int,
20
+ recent_reinforcement: bool,
21
+ now: datetime | None = None,
22
+ ) -> float:
23
+ if now is None:
24
+ now = datetime.now(timezone.utc)
25
+
26
+ last = datetime.fromisoformat(last_date)
27
+ if last.tzinfo is None:
28
+ last = last.replace(tzinfo=timezone.utc)
29
+ if now.tzinfo is None:
30
+ now = now.replace(tzinfo=timezone.utc)
31
+
32
+ days_since_last = max(0.0, (now - last).total_seconds() / 86400)
33
+
34
+ decay = max(0.1, 0.98 ** (days_since_last / 30))
35
+
36
+ if recent_contradictions >= 2:
37
+ decay = max(0.1, decay * 0.85)
38
+
39
+ if recent_reinforcement:
40
+ decay = min(1.0, decay * 1.1)
41
+
42
+ return round(decay * 1000) / 1000
43
+
44
+
45
+ def effective_weight(w: float, decay: float) -> float:
46
+ return round(w * decay * 1000) / 1000
@@ -0,0 +1,91 @@
1
+ """Ledger JSONL parser/writer with capacity constraints.
2
+
3
+ Ports packages/engine/src/ledger.ts.
4
+ """
5
+
6
+ from __future__ import annotations
7
+
8
+ import json
9
+ import re
10
+
11
+ from .schema import LedgerEntry, SignalType, parse_ledger_entry
12
+
13
+ ENTRY_CAPS: dict[SignalType, int] = {
14
+ "meta": 1,
15
+ "pref": 20,
16
+ "map": 10,
17
+ "asn": 5,
18
+ "rub": 4,
19
+ }
20
+
21
+ TOTAL_CAP = 40
22
+
23
+ _SIGNAL_PREFIX_RE = re.compile(r"^\[sig:(\w+)\]\s+(.+)$")
24
+
25
+
26
+ def parse_ledger_line(line: str) -> LedgerEntry | None:
27
+ """Parse a single ledger line: `[sig:type] {json}`. Returns None for empty or unparseable lines."""
28
+ trimmed = line.strip()
29
+ if not trimmed:
30
+ return None
31
+
32
+ match = _SIGNAL_PREFIX_RE.match(trimmed)
33
+ if not match:
34
+ return None
35
+
36
+ signal_type = match.group(1)
37
+ try:
38
+ data = json.loads(match.group(2))
39
+ except (json.JSONDecodeError, ValueError):
40
+ return None
41
+
42
+ return parse_ledger_entry(signal_type, data)
43
+
44
+
45
+ def format_ledger_line(entry: LedgerEntry) -> str:
46
+ """Format a ledger entry back to `[sig:type] {json}` string."""
47
+ return f"[sig:{entry.type}] {json.dumps(entry.data.model_dump(exclude_none=True))}"
48
+
49
+
50
+ def parse_ledger(content: str) -> list[LedgerEntry]:
51
+ """Parse a full ledger JSONL string into typed entries."""
52
+ results: list[LedgerEntry] = []
53
+ for line in content.split("\n"):
54
+ entry = parse_ledger_line(line)
55
+ if entry is not None:
56
+ results.append(entry)
57
+ return results
58
+
59
+
60
+ def write_ledger(entries: list[LedgerEntry]) -> str:
61
+ """Serialize ledger entries to JSONL string with typed prefixes."""
62
+ return "\n".join(format_ledger_line(e) for e in entries) + "\n"
63
+
64
+
65
+ def count_by_type(entries: list[LedgerEntry]) -> dict[SignalType, int]:
66
+ """Count entries by type."""
67
+ counts: dict[SignalType, int] = {"meta": 0, "pref": 0, "map": 0, "asn": 0, "rub": 0}
68
+ for entry in entries:
69
+ counts[entry.type] += 1
70
+ return counts
71
+
72
+
73
+ def check_capacity(entries: list[LedgerEntry]) -> list[str]:
74
+ """Check if ledger exceeds capacity constraints. Returns violations or empty list."""
75
+ violations: list[str] = []
76
+ counts = count_by_type(entries)
77
+
78
+ if len(entries) > TOTAL_CAP:
79
+ violations.append(f"total {len(entries)} exceeds cap {TOTAL_CAP}")
80
+
81
+ for signal_type, cap in ENTRY_CAPS.items():
82
+ count = counts.get(signal_type, 0)
83
+ if count > cap:
84
+ violations.append(f"{signal_type} count {count} exceeds cap {cap}")
85
+
86
+ return violations
87
+
88
+
89
+ def estimate_tokens(entries: list[LedgerEntry]) -> int:
90
+ """Estimate token count for ledger entries (~75 tokens per entry)."""
91
+ return len(entries) * 75