Summary
Add an object operation to PTC-JSON that constructs maps where values can be evaluated expressions. This enables the memory contract (returning maps to persist data) which is currently impossible to express correctly.
Context
Architecture reference: Memory contract is documented in lib/ptc_runner/schema.ex to_prompt/0 function (lines 478-491)
Specification: docs/ptc-json-specification.md - will need to document the new object operation
Dependencies: None
Related issues: None
Current State
The PTC-JSON DSL cannot construct a map with computed values. The merge operation requires each element to be a complete operation with an "op" field:
{"op":"merge","objects":[
{"op":"literal","value":{"key":null}},
{"key":{"op":"var","name":"x"}}
]}
Verified: Running the above pattern returns {:error, {:validation_error, "Missing required field 'op'"}}.
The system prompt's memory example (schema.ex:488) teaches this invalid pattern, causing LLMs to fail on memory-related tests (observed in demo test runner with deepseek model: tests 13 & 14 failed with "Missing required field 'op'" errors).
Current workarounds and why they fail:
literal wraps values as data, not evaluated expressions
merge requires all objects to have "op" field (validated via {:list, :expr} type at validator.ex:251-255)
- No operation exists to build
{"key": <evaluated-expr>}
Acceptance Criteria
Implementation Hints
Files to modify:
lib/ptc_runner/schema.ex - Add object operation definition (~line 78 near merge)
lib/ptc_runner/json/validator.ex - Add validate_object/2 for recursive field validation (~line 65 near validate_let/3)
lib/ptc_runner/json/operations.ex - Add eval("object", ...) implementation (~line 125 near eval("merge", ...))
lib/ptc_runner/schema.ex - Update to_prompt/0 memory example (line 488) to use object
test/ptc_runner/json/operations/collection_test.exs - Add object operation tests
docs/ptc-json-specification.md - Document the new object operation
Patterns to follow:
- Schema definition pattern: see
merge operation at lib/ptc_runner/schema.ex:78
- Custom validation pattern: see
validate_let/3 at lib/ptc_runner/json/validator.ex:65
- Operation eval pattern: see
eval("merge", ...) at lib/ptc_runner/json/operations.ex:125
Proposed schema definition:
"object" => %{
"description" => "Construct object with evaluated values. Example: {op:'object', fields:{count:{op:'var', name:'n'}, name:'test'}}",
"fields" => %{
"fields" => %{"type" => :map, "required" => true}
}
}
Proposed evaluation logic:
def eval("object", node, context, _eval_fn) do
fields = Map.get(node, "fields", %{})
Enum.reduce_while(fields, {:ok, %{}, context.memory}, fn {key, value}, {:ok, acc, memory} ->
ctx = %{context | memory: memory}
case evaluate_field_value(value, ctx) do
{:ok, result, new_memory} -> {:cont, {:ok, Map.put(acc, key, result), new_memory}}
{:error, _} = err -> {:halt, err}
end
end)
end
defp evaluate_field_value(value, ctx) when is_map(value) and is_map_key(value, "op") do
Interpreter.eval(value, ctx)
end
defp evaluate_field_value(value, context) do
{:ok, value, context.memory}
end
Edge cases to consider:
- Empty fields map:
{"op":"object","fields":{}} → returns {}
- Mixed literal and expression values
- Nested object operations
- Error in one field evaluation should halt and return error
Test Plan
Unit tests:
object with all literal values returns map
object with expression values evaluates them
object with mixed literal/expression values
object with nested object operation
object with var reference to memory
object with empty fields returns empty map
- Error in field expression propagates error
E2E test:
Multi-turn memory scenario:
- Turn 1: Store count using
object: {"op":"let","name":"cnt","value":{...count...},"in":{"op":"object","fields":{"delivered-count":{"op":"var","name":"cnt"},"result":{"op":"var","name":"cnt"}}}}
- Turn 2: Read from memory:
{"op":"var","name":"delivered-count"}
- Verify stored value matches computed value
Out of Scope
- Changing
merge behavior (it continues to require "op" on all objects)
- Dynamic keys (keys must be string literals in the JSON)
- Spread/rest syntax for including other objects
Documentation Updates
lib/ptc_runner/schema.ex - Update to_prompt/0 memory example to use object
docs/ptc-json-specification.md - Add object to Combine operations section (~line 774)
Summary
Add an
objectoperation to PTC-JSON that constructs maps where values can be evaluated expressions. This enables the memory contract (returning maps to persist data) which is currently impossible to express correctly.Context
Architecture reference: Memory contract is documented in
lib/ptc_runner/schema.exto_prompt/0function (lines 478-491)Specification:
docs/ptc-json-specification.md- will need to document the newobjectoperationDependencies: None
Related issues: None
Current State
The PTC-JSON DSL cannot construct a map with computed values. The
mergeoperation requires each element to be a complete operation with an"op"field:{"op":"merge","objects":[ {"op":"literal","value":{"key":null}}, {"key":{"op":"var","name":"x"}} ]}Verified: Running the above pattern returns
{:error, {:validation_error, "Missing required field 'op'"}}.The system prompt's memory example (schema.ex:488) teaches this invalid pattern, causing LLMs to fail on memory-related tests (observed in demo test runner with deepseek model: tests 13 & 14 failed with "Missing required field 'op'" errors).
Current workarounds and why they fail:
literalwraps values as data, not evaluated expressionsmergerequires all objects to have"op"field (validated via{:list, :expr}type at validator.ex:251-255){"key": <evaluated-expr>}Acceptance Criteria
objectoperation defined in schema withfieldsparameter (type:map)objectoperation, recursively validating values that have"op"fieldobject, evaluating values with"op"field, passing through literals{"op":"object","fields":{"my-key":{"op":"var","name":"x"},"result":42}}storesmy-keyand returns mapobjectImplementation Hints
Files to modify:
lib/ptc_runner/schema.ex- Addobjectoperation definition (~line 78 nearmerge)lib/ptc_runner/json/validator.ex- Addvalidate_object/2for recursive field validation (~line 65 nearvalidate_let/3)lib/ptc_runner/json/operations.ex- Addeval("object", ...)implementation (~line 125 neareval("merge", ...))lib/ptc_runner/schema.ex- Updateto_prompt/0memory example (line 488) to useobjecttest/ptc_runner/json/operations/collection_test.exs- Add object operation testsdocs/ptc-json-specification.md- Document the newobjectoperationPatterns to follow:
mergeoperation atlib/ptc_runner/schema.ex:78validate_let/3atlib/ptc_runner/json/validator.ex:65eval("merge", ...)atlib/ptc_runner/json/operations.ex:125Proposed schema definition:
Proposed evaluation logic:
Edge cases to consider:
{"op":"object","fields":{}}→ returns{}Test Plan
Unit tests:
objectwith all literal values returns mapobjectwith expression values evaluates themobjectwith mixed literal/expression valuesobjectwith nestedobjectoperationobjectwithvarreference to memoryobjectwith empty fields returns empty mapE2E test:
Multi-turn memory scenario:
object:{"op":"let","name":"cnt","value":{...count...},"in":{"op":"object","fields":{"delivered-count":{"op":"var","name":"cnt"},"result":{"op":"var","name":"cnt"}}}}{"op":"var","name":"delivered-count"}Out of Scope
mergebehavior (it continues to require"op"on all objects)Documentation Updates
lib/ptc_runner/schema.ex- Updateto_prompt/0memory example to useobjectdocs/ptc-json-specification.md- Addobjectto Combine operations section (~line 774)