Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
100 changes: 80 additions & 20 deletions src/ofw/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,20 +13,54 @@
)

from ofw.contracts import (
AssetAccess,
ComponentKind,
GitCommit,
HarnessAsset,
HarnessComponent,
HarnessErrorCode,
HarnessRevision,
HarnessRevisionId,
HarnessValidationError,
RepositorySnapshot,
Sha256Digest,
WorkspaceFile,
)
from ofw.harness import EditableFile, Harness, Subagent, Tool, editable
from ofw.mine import (
AdaptationRequest,
AdaptationResult,
BehaviorObservation,
CompletionCheck,
CompletionStatus,
Confidence,
ConstraintKind,
EnvironmentCheckId,
EnvironmentCheckRequest,
EnvironmentSource,
EnvironmentSourceId,
EnvironmentSourceKind,
EnvironmentVerification,
EvidenceKind,
EvidenceRecordId,
EvidenceReference,
FailureBehavior,
FailureBehaviorKind,
FailureMiningResult,
FailureMiningRun,
FailurePhase,
FailureSource,
FailureSourceId,
FailureSourceKind,
Mine,
MiningContext,
MiningInvalidReason,
MiningNomination,
MiningTask,
MiningVerdict,
RecoveryStatus,
RequiredOutcome,
TaskConstraint,
TaskId,
ToolAccess,
ToolCapability,
ToolName,
TraceMiningCase,
)
from ofw.observability.langfuse import (
CollectionError,
CollectionErrorCode,
Expand All @@ -50,6 +84,7 @@
read_trace_observations,
search_observation_content,
)
from ofw.repository import process_repository
from ofw.runtime import (
CanaryCase,
CaseId,
Expand Down Expand Up @@ -79,9 +114,6 @@ class _OfwNamespace:
CanaryCase = CanaryCase
CaseId = CaseId

def editable(self, path: Path) -> EditableFile:
return editable(path)

def collect(
self,
revision: HarnessRevision,
Expand Down Expand Up @@ -113,25 +145,42 @@ def read_observation_content(
) -> ObservationContent:
return read_observation_content(collection, reference)


ofw = _OfwNamespace()

__all__ = [
"AssetAccess",
"AdaptationRequest",
"AdaptationResult",
"BehaviorObservation",
"CanaryCase",
"CaseId",
"ComponentKind",
"CollectionError",
"CollectionErrorCode",
"CollectionResult",
"CommandLoop",
"CommandVerifier",
"CompletionCheck",
"CompletionStatus",
"Confidence",
"ConstraintKind",
"E2BSandbox",
"EditableFile",
"EnvironmentCheckId",
"EnvironmentCheckRequest",
"EnvironmentSource",
"EnvironmentSourceId",
"EnvironmentSourceKind",
"EnvironmentVerification",
"EvidenceKind",
"EvidenceRecordId",
"EvidenceReference",
"FailureMiningResult",
"FailureMiningRun",
"FailureBehavior",
"FailureBehaviorKind",
"FailurePhase",
"FailureSource",
"FailureSourceId",
"FailureSourceKind",
"GitCommit",
"Harness",
"HarnessAsset",
"HarnessComponent",
"HarnessErrorCode",
"HarnessRevision",
"HarnessRevisionId",
Expand All @@ -141,31 +190,42 @@ def read_observation_content(
"LangfuseProject",
"LangfuseSpan",
"ModelFingerprint",
"Mine",
"MiningContext",
"MiningInvalidReason",
"MiningNomination",
"MiningTask",
"MiningVerdict",
"ObservationContent",
"ObservationContentField",
"ObservationContentHit",
"ObservationContentMatch",
"ObservationContentQuery",
"ObservationContentReference",
"RepositorySnapshot",
"RecoveryStatus",
"RequiredOutcome",
"ProcessCommand",
"ProcessLimits",
"RunErrorCode",
"RunResult",
"RunStatus",
"Sha256Digest",
"Subagent",
"Tool",
"TaskConstraint",
"TaskId",
"ToolAccess",
"ToolCapability",
"ToolName",
"TraceWindow",
"TraceMiningCase",
"VerifierResult",
"VerifierVerdict",
"WorkspaceFile",
"collect",
"editable",
"get_client",
"is_default_export_span",
"observe",
"ofw",
"process_repository",
"propagate_attributes",
"read_observation_content",
"read_trace_observations",
Expand Down
145 changes: 3 additions & 142 deletions src/ofw/contracts.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
"""Immutable, language-neutral harness component contracts."""
"""Immutable, language-neutral repository revision contracts."""

from __future__ import annotations

Expand All @@ -14,43 +14,14 @@ class HarnessSchemaVersion(IntEnum):
V1 = 1


class ComponentKind(StrEnum):
PROMPT = "prompt"
TOOL = "tool"
SKILL = "skill"
SUBAGENT = "subagent"
MIDDLEWARE = "middleware"


class AssetAccess(StrEnum):
FROZEN = "frozen"
FIT_EDITABLE = "fit_editable"


class HarnessErrorCode(StrEnum):
INVALID_NAME = "invalid_name"
INVALID_SOURCE = "invalid_source"
ROOT_NOT_FOUND = "root_not_found"
ROOT_NOT_DIRECTORY = "root_not_directory"
PROMPT_REQUIRED = "prompt_required"
MISSING_ASSET = "missing_asset"
PATH_OUTSIDE_ROOT = "path_outside_root"
NOT_A_FILE = "not_a_file"
DUPLICATE_ASSET = "duplicate_asset"
CONFLICTING_ACCESS = "conflicting_access"
COMPONENT_OVERLAP = "component_overlap"
INVALID_TOOL_NAME = "invalid_tool_name"
DUPLICATE_TOOL = "duplicate_tool"
INVALID_SUBAGENT_NAME = "invalid_subagent_name"
DUPLICATE_SUBAGENT = "duplicate_subagent"
GIT_REPOSITORY_REQUIRED = "git_repository_required"
GIT_COMMAND_FAILED = "git_command_failed"
MANIFEST_WRITE_FAILED = "manifest_write_failed"
SENSITIVE_ASSET = "sensitive_asset"
RUNTIME_INCOMPLETE = "runtime_incomplete"
CANARY_FAILED = "canary_failed"
DUPLICATE_VERIFIER = "duplicate_verifier"
RUNTIME_INVALID = "runtime_invalid"


class HarnessValidationError(Exception):
Expand Down Expand Up @@ -88,58 +59,19 @@ def __str__(self) -> str:
return self.value


@dataclass(frozen=True, slots=True)
class WorkspaceFile:
relative_path: Path


@dataclass(frozen=True, slots=True)
class HarnessAsset:
name: str | None
access: AssetAccess
source: WorkspaceFile
digest: Sha256Digest


@dataclass(frozen=True, slots=True)
class HarnessComponent:
kind: ComponentKind
assets: tuple[HarnessAsset, ...]
digest: Sha256Digest


@dataclass(frozen=True, slots=True)
class RepositorySnapshot:
commit: GitCommit
is_dirty: bool
dirty_digest: Sha256Digest | None


@dataclass(frozen=True, slots=True)
class RuntimeConfiguration:
execution: Sha256Digest
lifecycle: Sha256Digest
verifiers: tuple[Sha256Digest, ...]

def canonical_json(self) -> str:
verifiers = ",".join(_quote(str(verifier)) for verifier in self.verifiers)
return (
"{"
f'"execution":{_quote(str(self.execution))},'
f'"lifecycle":{_quote(str(self.lifecycle))},'
f'"verifiers":[{verifiers}]'
"}"
)


@dataclass(frozen=True, slots=True)
class HarnessRevisionContent:
schema_version: HarnessSchemaVersion
harness_name: str
repository: RepositorySnapshot
components: tuple[HarnessComponent, ...]
observability: LangfuseConnectionManifest | None
runtime: RuntimeConfiguration | None

def canonical_json(self) -> str:
return _render_content(self)
Expand All @@ -152,72 +84,32 @@ class HarnessRevision:
harness_name: str
root: Path
repository: RepositorySnapshot
components: tuple[HarnessComponent, ...]
observability: LangfuseConnectionManifest | None
runtime: RuntimeConfiguration | None
canary_digest: Sha256Digest | None

@property
def manifest_path(self) -> Path:
return self.root / ".ofw" / "revisions" / str(self.id) / "manifest.json"

@property
def canary_path(self) -> Path:
return self.manifest_path.with_name("canary.json")

@property
def assets(self) -> tuple[HarnessAsset, ...]:
return tuple(asset for component in self.components for asset in component.assets)

@property
def editable_files(self) -> tuple[Path, ...]:
return tuple(
asset.source.relative_path
for asset in self.assets
if asset.access is AssetAccess.FIT_EDITABLE
)

@property
def frozen_files(self) -> tuple[Path, ...]:
return tuple(
asset.source.relative_path
for asset in self.assets
if asset.access is AssetAccess.FROZEN
)

def component(self, kind: ComponentKind) -> HarnessComponent | None:
for component in self.components:
if component.kind is kind:
return component
return None

def to_json(self) -> str:
components = ",".join(_render_component(component) for component in self.components)
return (
"{"
f'"schema_version":{int(self.schema_version)},'
f'"id":{_quote(str(self.id))},'
f'"harness_name":{_quote(self.harness_name)},'
f'"root":{_quote(self.root.as_posix())},'
f'"repository":{_render_repository(self.repository)},'
f'"observability":{_render_observability(self.observability)},'
f'"runtime":{_render_runtime(self.runtime)},'
f'"canary_digest":{_render_digest(self.canary_digest)},'
f'"components":[{components}]'
f'"observability":{_render_observability(self.observability)}'
"}"
)


def _render_content(content: HarnessRevisionContent) -> str:
components = ",".join(_render_component(component) for component in content.components)
return (
"{"
f'"schema_version":{int(content.schema_version)},'
f'"harness_name":{_quote(content.harness_name)},'
f'"repository":{_render_repository(content.repository)},'
f'"observability":{_render_observability(content.observability)},'
f'"runtime":{_render_runtime(content.runtime)},'
f'"components":[{components}]'
f'"observability":{_render_observability(content.observability)}'
"}"
)

Expand All @@ -240,36 +132,5 @@ def _render_observability(connection: LangfuseConnectionManifest | None) -> str:
return "null" if connection is None else connection.to_json()


def _render_runtime(runtime: RuntimeConfiguration | None) -> str:
return "null" if runtime is None else runtime.canonical_json()


def _render_digest(digest: Sha256Digest | None) -> str:
return "null" if digest is None else _quote(str(digest))


def _render_component(component: HarnessComponent) -> str:
assets = ",".join(_render_asset(asset) for asset in component.assets)
return (
"{"
f'"kind":{_quote(component.kind.value)},'
f'"digest":{_quote(str(component.digest))},'
f'"assets":[{assets}]'
"}"
)


def _render_asset(asset: HarnessAsset) -> str:
name = "null" if asset.name is None else _quote(asset.name)
return (
"{"
f'"name":{name},'
f'"access":{_quote(asset.access.value)},'
f'"source":{{"relative_path":{_quote(asset.source.relative_path.as_posix())}}},'
f'"digest":{_quote(str(asset.digest))}'
"}"
)


def _quote(value: str) -> str:
return json.dumps(value, ensure_ascii=False, separators=(",", ":"))
Loading