Skip to content

Commit 378db17

Browse files
dmealingclaude
andcommitted
refactor(ai,python): author voRequest/voResponse columns; remove derive_trace_fields + pre_freeze hook
Trace entities now AUTHOR their typed voRequest/voResponse jsonb columns explicitly (field.object + @objectref + @storage:jsonb) rather than having the loader DERIVE them by injecting synthetic field.object nodes at load time. This removes the only load-time metadata mutation in the Python AI-trace feature. - Delete loader/derive_trace_fields.py + its unit test. - Remove the generic pre_freeze loader hook from MetaDataLoader (__init__ param/assignment/comment, the load() invocation, the from_directory passthrough, and the now-unused Callable import). - cli._load_root reverts to plain MetaDataLoader.from_directory(metadata_dir). - trace_helper_generator is unchanged (already defines a local LLM_CALL_BASE; never imported from the deriver) — it emits the typed columns off the prompt's @responseRef/@payloadRef, no derivation needed. - Integration fixture restores the Slice-1 authored voResponse shape; the round-trip test loads with plain MetaDataLoader(strict=True) and drops the no-longer-authored voRequest DDL column. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
1 parent 36a19af commit 378db17

6 files changed

Lines changed: 16 additions & 278 deletions

File tree

server/python/src/metaobjects/cli.py

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,6 @@
4141
from pathlib import Path
4242

4343
from metaobjects import MetaDataLoader
44-
from metaobjects.loader.derive_trace_fields import derive_trace_fields
4544
from metaobjects.agent_context import (
4645
AGENT_CONTEXT_MANIFEST_PATH,
4746
Manifest,
@@ -107,10 +106,7 @@ def _default_generators() -> list[Generator]:
107106

108107
def _load_root(metadata_dir: str) -> tuple[MetaData | None, list[str]]:
109108
"""Load metadata; return ``(root, error_messages)``. ``root`` is None on error."""
110-
# AI-trace pre-pass: derive typed voRequest/voResponse jsonb columns onto
111-
# LlmCallBase-derived entities so the trace-helper generator (and gen/verify)
112-
# see them without the author restating them. No-op when no trace entities.
113-
result = MetaDataLoader.from_directory(metadata_dir, pre_freeze=derive_trace_fields)
109+
result = MetaDataLoader.from_directory(metadata_dir)
114110
if result.errors:
115111
msgs = [f"{e.code}: {e.message}" for e in result.errors]
116112
return None, msgs

server/python/src/metaobjects/loader/derive_trace_fields.py

Lines changed: 0 additions & 96 deletions
This file was deleted.

server/python/src/metaobjects/loader/meta_data_loader.py

Lines changed: 1 addition & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,6 @@
1515
from __future__ import annotations
1616

1717
import json
18-
from collections.abc import Callable
1918
from dataclasses import dataclass, field
2019
from pathlib import Path
2120

@@ -83,17 +82,10 @@ def __init__(
8382
self,
8483
providers: list[Provider] | None = None,
8584
strict: bool = False,
86-
pre_freeze: Callable[[MetaData], None] | None = None,
8785
) -> None:
8886
self._registry: TypeRegistry = compose_registry(
8987
providers if providers is not None else list(core_providers)
9088
)
91-
# Optional pre-freeze enrichment hook (mirrors the TS loader's `preFreeze`
92-
# option and the Java MetaDataLoader.setPreFreeze). Runs inside `load`
93-
# after extends-resolution and before the validation passes, so any nodes
94-
# it injects (e.g. AI-trace voRequest/voResponse jsonb columns) are
95-
# validated like authored ones and reach BOTH codegen and the runtime.
96-
self._pre_freeze = pre_freeze
9789
# ADR-0023 — strict load closes the open-attr policy: an authored own
9890
# @-attr matching no per-type schema and no commonAttr → ERR_UNKNOWN_ATTR
9991
# (alongside Python's always-on unknown TYPE/SUBTYPE rejection). Defaults
@@ -140,12 +132,6 @@ def load(self, sources: list[MetaDataSource]) -> LoadResult:
140132
)
141133
resolve_supers(result.root, result.errors)
142134

143-
# Pre-freeze enrichment hook (mirrors TS/Java): runs after extends-
144-
# resolution and before validation, so DERIVED nodes (e.g. AI-trace
145-
# voRequest/voResponse jsonb columns) are validated + reach codegen+runtime.
146-
if self._pre_freeze is not None:
147-
self._pre_freeze(result.root)
148-
149135
run_validations(
150136
result.root,
151137
self._registry,
@@ -167,15 +153,13 @@ def from_directory(
167153
exclude: list[str] | None = None,
168154
recurse: bool = True,
169155
strict: bool = False,
170-
pre_freeze: Callable[[MetaData], None] | None = None,
171156
) -> LoadResult:
172157
"""Load every JSON/YAML file under ``directory`` (recursive by default).
173158
174159
``strict`` (ADR-0023) — when True, an undeclared own ``@-attr`` →
175160
``ERR_UNKNOWN_ATTR``. Defaults False (downstream-friendly open policy).
176-
``pre_freeze`` — optional in-load tree-enrichment hook (see ``__init__``).
177161
"""
178-
loader = cls(providers=providers, strict=strict, pre_freeze=pre_freeze)
162+
loader = cls(providers=providers, strict=strict)
179163
sources = list(
180164
DirectorySource(directory, exclude=exclude, recurse=recurse).expand()
181165
)
Lines changed: 10 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,35 +1,26 @@
1-
# Test-only trace entity for the AI-trace recorder PG round-trip (Python).
2-
# Loaded alongside the shipped library/ai/llm-call.yaml (LlmCallBase). The typed
3-
# voRequest/voResponse jsonb columns are NOT declared here — they are DERIVED by
4-
# derive_trace_fields (Slice 3) from the nested template.prompt's
5-
# @payloadRef/@responseRef when the loader runs with the pre_freeze hook. This
6-
# proves derivation reaches the metadata-driven ObjectManager runtime.
1+
# Test-only trace entity for the AI-trace recorder PG round-trip (Python, Slice 1).
2+
# Loaded alongside the shipped library/ai/llm-call.yaml (LlmCallBase). Declares a
3+
# typed voResponse jsonb column by hand (Slice 3 will switch this to derivation
4+
# from a nested template.prompt).
75
metadata:
86
package: metaobjects::ai
97
children:
10-
# Typed request payload VO (referenced by @payloadRef → derived voRequest).
11-
- object.value:
12-
name: GreetingRequest
13-
children:
14-
- field.string: { name: prompt }
15-
16-
# Typed response VO (referenced by @responseRef → derived voResponse).
8+
# Typed value object stored in the voResponse jsonb column.
179
- object.value:
1810
name: GreetingResponse
1911
children:
2012
- field.string: { name: greeting }
2113
- field.int: { name: score }
2214

2315
# Concrete trace entity: inherits the 18 LlmCallBase fields, declares its
24-
# table + primary identity + a template.prompt. The deriver injects
25-
# voRequest/voResponse field.object jsonb columns from the prompt refs.
16+
# table + primary identity + a typed voResponse jsonb column.
2617
- object.entity:
2718
name: GreetingCall
2819
extends: metaobjects::ai::LlmCallBase
2920
children:
3021
- source.rdb: { table: llm_call, role: primary }
3122
- identity.primary: { name: primary, fields: ["spanId"] }
32-
- template.prompt:
33-
name: greet
34-
payloadRef: metaobjects::ai::GreetingRequest
35-
responseRef: metaobjects::ai::GreetingResponse
23+
- field.object:
24+
name: voResponse
25+
storage: jsonb
26+
objectRef: metaobjects::ai::GreetingResponse

server/python/tests/integration/test_llm_call_trace.py

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,6 @@
1717
from pathlib import Path
1818

1919
from metaobjects import FileSource, MetaDataLoader
20-
from metaobjects.loader.derive_trace_fields import derive_trace_fields
2120
from metaobjects.runtime import (
2221
LlmCallInput,
2322
ObjectManager,
@@ -50,7 +49,6 @@
5049
"startedAt" timestamp,
5150
"llmRequest" jsonb,
5251
"llmResponse" jsonb,
53-
"voRequest" jsonb,
5452
"voResponse" jsonb
5553
)
5654
"""
@@ -73,10 +71,10 @@ def _connect(info):
7371

7472

7573
def test_typed_trace_round_trips_through_postgres() -> None:
76-
# voRequest/voResponse are NOT authored in the fixture — they are DERIVED
77-
# in-load from the prompt's @payloadRef/@responseRef via the pre_freeze hook,
78-
# proving derivation reaches the metadata-driven ObjectManager runtime.
79-
loader = MetaDataLoader(strict=True, pre_freeze=derive_trace_fields)
74+
# voResponse is AUTHORED in the fixture (field.object + @objectRef + jsonb).
75+
# No derivation: this proves the recorder persists the authored typed column
76+
# through the metadata-driven ObjectManager runtime.
77+
loader = MetaDataLoader(strict=True)
8078
result = loader.load([
8179
FileSource(_repo_file("library/ai/llm-call.yaml")),
8280
FileSource(Path(__file__).parent / "meta_ai_trace.yaml"),

server/python/tests/unit/test_derive_trace_fields.py

Lines changed: 0 additions & 135 deletions
This file was deleted.

0 commit comments

Comments
 (0)