Skip to content

Commit e0044ff

Browse files
authored
Add contract metadata handling in runtime
1 parent c35a583 commit e0044ff

1 file changed

Lines changed: 133 additions & 2 deletions

File tree

ix/runtime.py

Lines changed: 133 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,12 +19,20 @@
1919
from .ast import (
2020
AgentBlock,
2121
AssertStatement,
22+
AttemptBlock,
23+
ClaimBoundaryStatement,
24+
EvidenceRequirementStatement,
25+
FalsifyIfStatement,
26+
HandoffContractStatement,
2227
IfStatement,
2328
LetStatement,
29+
NonGoalStatement,
30+
ObligationBlock,
2431
OnBlock,
2532
PolicyStatement,
2633
PrintStatement,
2734
Program,
35+
PurposeStatement,
2836
RecallStatement,
2937
RememberStatement,
3038
ReplyStatement,
@@ -85,6 +93,7 @@ class ExecutionResult:
8593
handoffs: list[dict[str, Any]] = field(default_factory=list)
8694
branches: list[dict[str, Any]] = field(default_factory=list)
8795
trace: list[TraceEvent] = field(default_factory=list)
96+
contract_metadata: dict[str, Any] = field(default_factory=dict)
8897

8998
def trace_as_dicts(self) -> list[dict[str, Any]]:
9099
"""Return the trace ledger in JSON-serializable form."""
@@ -105,6 +114,7 @@ def to_dict(self) -> dict[str, Any]:
105114
"tool_results": list(self.tool_results),
106115
"handoffs": list(self.handoffs),
107116
"branches": list(self.branches),
117+
"contract_metadata": dict(self.contract_metadata),
108118
"trace": self.trace_as_dicts(),
109119
}
110120

@@ -210,10 +220,27 @@ def run(
210220
joined = "\n".join(diagnostic.format() for diagnostic in diagnostics)
211221
raise IXRuntimeError(f"IX validation failed:\n{joined}")
212222

213-
result = ExecutionResult(status="running")
223+
result = ExecutionResult(
224+
status="running",
225+
contract_metadata=self._contract_metadata(program),
226+
)
214227
values: dict[str, Any] = dict(inputs or {})
215228
statements = self._select_statements(program, agent=agent, event=event)
216229
self._emit(result, "run.start", "IX execution started", program.span)
230+
231+
if result.contract_metadata["counts"]["attempts"] > 0:
232+
self._emit(
233+
result,
234+
"contract.metadata",
235+
"Cognition contract metadata extracted; no cognition executed",
236+
program.span,
237+
counts=result.contract_metadata["counts"],
238+
attempts=[
239+
attempt["name"]
240+
for attempt in result.contract_metadata["attempts"]
241+
],
242+
)
243+
217244
self._run_statements(program, statements, values, result, depth=0, inherited_policies=())
218245
result.variables = dict(values)
219246
result.status = "completed"
@@ -245,7 +272,7 @@ def _select_statements(
245272
if agent is None and event is None:
246273
selected: list[Statement] = []
247274
for statement in program.statements:
248-
if not isinstance(statement, AgentBlock):
275+
if not isinstance(statement, AgentBlock | AttemptBlock):
249276
selected.append(statement)
250277
return tuple(selected)
251278

@@ -267,6 +294,110 @@ def _select_agent_event(self, agent: AgentBlock, *, event: str) -> tuple[Stateme
267294
def _collect_policies(self, statements: tuple[Statement, ...]) -> tuple[PolicyStatement, ...]:
268295
return tuple(statement for statement in statements if isinstance(statement, PolicyStatement))
269296

297+
def _contract_metadata(self, program: Program) -> dict[str, Any]:
298+
attempts = [
299+
self._attempt_metadata(statement)
300+
for statement in program.statements
301+
if isinstance(statement, AttemptBlock)
302+
]
303+
obligation_count = sum(len(attempt["obligations"]) for attempt in attempts)
304+
evidence_count = sum(
305+
len(obligation["evidence_required"])
306+
for attempt in attempts
307+
for obligation in attempt["obligations"]
308+
)
309+
falsification_count = sum(
310+
len(obligation["falsify_if"])
311+
for attempt in attempts
312+
for obligation in attempt["obligations"]
313+
)
314+
315+
return {
316+
"contract_type": "ix.cognition.contracts",
317+
"schema_version": "1.0",
318+
"runtime_semantics": "metadata_only_not_executed",
319+
"counts": {
320+
"attempts": len(attempts),
321+
"obligations": obligation_count,
322+
"evidence_requirements": evidence_count,
323+
"falsification_gates": falsification_count,
324+
},
325+
"attempts": attempts,
326+
}
327+
328+
def _attempt_metadata(self, attempt: AttemptBlock) -> dict[str, Any]:
329+
purposes: list[str] = []
330+
non_goals: list[str] = []
331+
claim_boundaries: list[str] = []
332+
approvals: list[str] = []
333+
handoff_contracts: list[dict[str, Any]] = []
334+
obligations: list[dict[str, Any]] = []
335+
336+
for statement in attempt.statements:
337+
if isinstance(statement, PurposeStatement):
338+
purposes.append(self._contract_text(statement.text))
339+
elif isinstance(statement, NonGoalStatement):
340+
non_goals.append(self._contract_text(statement.text))
341+
elif isinstance(statement, ClaimBoundaryStatement):
342+
claim_boundaries.append(self._contract_text(statement.text))
343+
elif isinstance(statement, RequireApprovalStatement):
344+
approvals.append(self._contract_text(statement.reason))
345+
elif isinstance(statement, HandoffContractStatement):
346+
handoff_contracts.append(
347+
{
348+
"target": statement.target,
349+
"schema": statement.schema_name,
350+
"source": self._span_dict(statement.span),
351+
}
352+
)
353+
elif isinstance(statement, ObligationBlock):
354+
obligations.append(self._obligation_metadata(statement))
355+
356+
return {
357+
"name": attempt.name,
358+
"source": self._span_dict(attempt.span),
359+
"purpose": purposes,
360+
"non_goals": non_goals,
361+
"claim_boundaries": claim_boundaries,
362+
"human_approval_required": approvals,
363+
"handoff_contracts": handoff_contracts,
364+
"obligations": obligations,
365+
}
366+
367+
def _obligation_metadata(self, obligation: ObligationBlock) -> dict[str, Any]:
368+
evidence_required: list[str] = []
369+
falsify_if: list[str] = []
370+
371+
for statement in obligation.statements:
372+
if isinstance(statement, EvidenceRequirementStatement):
373+
evidence_required.append(statement.artifact)
374+
elif isinstance(statement, FalsifyIfStatement):
375+
falsify_if.append(statement.condition)
376+
377+
return {
378+
"id": obligation.identifier,
379+
"source": self._span_dict(obligation.span),
380+
"evidence_required": evidence_required,
381+
"falsify_if": falsify_if,
382+
}
383+
384+
def _contract_text(self, value: str) -> str:
385+
stripped = value.strip()
386+
try:
387+
parsed = py_ast.literal_eval(stripped)
388+
except (SyntaxError, ValueError):
389+
parsed = stripped
390+
if isinstance(parsed, str):
391+
return parsed
392+
return stripped
393+
394+
def _span_dict(self, span: SourceSpan) -> dict[str, Any]:
395+
return {
396+
"filename": span.filename,
397+
"line": span.line,
398+
"column": span.column,
399+
}
400+
270401
def _execute_statement(
271402
self,
272403
statement: Statement,

0 commit comments

Comments
 (0)