{"id":18087,"plugin_id":"plugins_6a85a5758d408191bbc948af18f557a2","kind":"skill","collection_source":null,"comparison_source":null,"observed_at":"2026-09-30T23:14:36.854Z","digest":"13b6595093e0718d7a7ec0d24eef6498e209d7fcf7c5563226d19e5583c92e7e","against":null,"payload":{"description":"Define or review hgraph operator contracts and overload families in C++ and Python. Use when adding a C++ operator marker or Python `@operator`, replacing input-type or wiring-policy branches with overload selection, implementing node or graph overloads, registering C++ or Python candidates, designing defaults/resolvers/requires predicates, diagnosing overload ranking or ambiguity, generating the Python operator surface, or testing operator dispatch and cross-language parity.","included_files":[{"relative_path":"agents/openai.yaml","size_in_bytes":232}],"name":"hgraph-write-operators","skill_md_contents":"---\nname: hgraph-write-operators\ndescription: Define or review hgraph operator contracts and overload families in C++ and Python. Use when adding a C++ operator marker or Python `@operator`, replacing input-type or wiring-policy branches with overload selection, implementing node or graph overloads, registering C++ or Python candidates, designing defaults/resolvers/requires predicates, diagnosing overload ranking or ambiguity, generating the Python operator surface, or testing operator dispatch and cross-language parity.\n---\n\n# HGraph Operator Authoring\n\nDefine a public function-like contract once, then let wiring select the most\nspecific registered implementation for the concrete argument schemas and\nfixed scalar choices. Keep the contract pure and the implementations typed.\n\n## Read the existing family first\n\n1. Read the current repository's `AGENTS.md`. In the hgraph core checkout,\n   also read `docs/source/developer_guide/operators.rst`; in a downstream\n   project, use its extension conventions and the public operator\n   documentation for the pinned hgraph version instead.\n2. Inspect the nearest operator marker, implementation header, registration\n   translation unit, C++ tests, and Python compatibility tests.\n3. For a compute or sink implementation, also follow\n   `../hgraph-compute-sink-node/SKILL.md`. For a graph overload, follow\n   `../hgraph-write-graphs/SKILL.md`.\n4. Inspect `include/hgraph/types/operator_dispatch.h` only when the task needs\n   defaults, `requires_`, output resolution, variadic arguments, or dispatch\n   diagnostics beyond the established neighbouring pattern.\n\n## Define a pure function contract\n\nTreat an operator as a function interface. It names one logical operation,\ndescribes the general call shape, and documents behaviour shared by every\nimplementation. It is not executable. \"Pure contract\" means\nimplementation-free here; a sink operator may still contractually describe a\nside effect.\n\n- Give the marker only its name and abstract signature. Do not put `eval`,\n  `start`, `stop`, `compose`, state, or implementation decisions on it.\n- Document the semantic result, tick/validity expectations, errors, wiring-time\n  policies, and argument meanings. Describe the supported family without\n  promising one representation or algorithm unless that is part of the\n  contract.\n- Choose parameter names deliberately; implementations should preserve those\n  names and their roles so named calls remain coherent.\n- Use broad base type contracts in the marker. Use independent type variables\n  when operands or output may differ; repeat a variable only when equality of\n  those types is part of the public contract.\n- Match the public Python operator name exactly, including any trailing\n  underscore.\n\nFor example:\n\n```cpp\n/** Normalize values according to the wiring-time mode.\n    @param ts Values to normalize.\n    @param mode Supported normalization contract.\n    @return Values in the overload-selected output shape. */\nstruct normalize : Operator<\"normalize\",\n                            In<\"ts\", TsVar<\"S\">>,\n                            Scalar<\"mode\", NormalizeMode>,\n                            Out<TsVar<\"O\">>>\n{\n};\n```\n\nThe marker signature is documentary. Candidate matching uses each\nimplementation's own signature, which is what permits a single operator to\ncover concrete, generic, and heterogeneous types through node and graph\nrealizations. Output-producing and sink contracts remain distinct call shapes.\n\n## Use overloads instead of type switches\n\nWhen implementation code starts branching on an input type, schema kind,\nscalar type, or fixed policy, move that choice into operator resolution. Wiring\nknows these facts and should select the implementation once.\n\n- Add an overload to the existing operator when the behaviour already has a\n  contract.\n- Introduce a new operator when no contract exists for the functionality; do\n  not leave a standalone type-switching node as the public abstraction.\n- Use a graph-level dynamic switch only when the selector is time-series data\n  and the choice must legitimately change during execution.\n- Keep representation-only dispatch inside an established erased ops-table;\n  do not use erasure to conceal semantic implementation selection.\n\n## Refine the contract in implementations\n\nMake every candidate more precise than, or otherwise compatible with, the\ngeneral function contract:\n\n- Preserve the contract's base argument order, names, and semantic roles.\n- Refine `TsVar`/`ScalarVar` inputs to concrete schemas, constrained variables,\n  aligned repeated variables, or structural shapes that the implementation\n  actually supports.\n- Refine the output independently when the result differs from the inputs.\n- Use a compute or sink node overload for primitive runtime work. Keep its\n  logic in `eval` and its lifecycle work in `start`/`stop`.\n- Use a graph overload when the implementation composes existing operations\n  at wiring time.\n- Use a concrete scalar type to refine by policy type. Use a context-aware\n  `requires_` predicate to refine by a wiring-time scalar value, and use\n  `resolve_default_types` only to bind output variables that inputs cannot\n  determine. Keep candidates mutually exclusive or deliberately ranked.\n\nAn implementation may add positional parameters or keyword arguments not\nshown by the abstract signature because dispatch is driven by candidate call\nshapes. Use that freedom sparingly: overload-specific parameters are difficult\nto discover. Prefer a common contract parameter, a separate operator, or a\nstrongly typed policy enum. When an extra is justified, document it in the\noperator's public documentation and exercise the named call in both C++ and\nPython tests.\n\n## Register C++ overloads explicitly\n\nFollow the standard family layout:\n\n1. Put the abstract marker and its documentation in\n   `include/hgraph/lib/std/operators/<family>.h`.\n2. Put concrete node/graph implementations under the corresponding `impl`\n   boundary, normally\n   `include/hgraph/lib/std/operators/impl/<family>_impl.h`.\n3. Register node candidates in the family registration translation unit with:\n\n   ```cpp\n   register_overload<normalize, normalize_float>();\n   register_overload<normalize, normalize_tsd>();\n   ```\n\n4. Register graph candidates with:\n\n   ```cpp\n   register_graph_overload<normalize, normalize_tsl_map>();\n   ```\n\n5. Add a new family registration function to\n   `register_standard_operators()` when introducing a standard family. An\n   extension should expose and call its own explicit registration entry point.\n\nWhen a new standard family is genuinely needed, also add its definition header\nto `operators/operators.h`, its implementation header to\n`impl/operators_impl.h`, and its registration translation unit to the CMake\nsource list. Prefer adding an operator to the nearest existing family over\ncreating a one-operator family.\n\nNever register from a static initializer. Registries are reset between tests,\nand candidate patterns borrow interned type metadata. Register standard types\nfirst, then overloads, once per registry lifetime. Tests that need standard\noperators call `stdlib::register_standard_operators()` before wiring.\n\n## Connect the same registry to Python\n\nKeep C++ as the source of truth for core runtime semantics. The Python surface\nmust adapt to the same native operator registry rather than implement a second\ndispatcher or a parallel runtime operation.\n\nFor a public native operator:\n\n- Register the C++ overloads under the exact public name. The Python module\n  calls `register_standard_operators()` at initialization, and registered names\n  become lazy `hgraph.<name>` callables through `operator_function`.\n- Preserve enough marker documentation and overload metadata for generated\n  signatures and docstrings.\n- Regenerate the public catalogue, Python typing declarations, runtime\n  docstrings, and API inventory with:\n\n  ```sh\n  .venv/bin/python tools/api_inventory.py\n  ```\n\n- Add Python tests through the public operator name to prove authoring and\n  bridge parity with the native C++ test.\n\nFor a Python-authored operator or extension overload, declare the contract and\nattach implementations with the normal decorators:\n\n```python\n@operator\ndef normalize(ts: TIME_SERIES_TYPE, mode: NormalizeMode) -> OUT: ...\n\n@compute_node(overloads=normalize)\ndef normalize_float(ts: TS[float], mode: NormalizeMode) -> TS[float]:\n    ...\n\n@graph(overloads=normalize)\ndef normalize_tsd(ts: TSD[K, TS[float]], mode: NormalizeMode) -> TSD[K, TS[float]]:\n    ...\n```\n\n`overloads=` registers each Python candidate through\n`register_python_overload`; the native matcher still owns argument\nnormalisation, type binding, ranking, `requires`, and selection. Use decorator\n`resolvers=` and `requires=` for wiring-time facts. Do not write Python-side\ntype dispatch. When the behaviour belongs in core, provide the first-class C++\npath and equivalent C++ tests rather than leaving it as a Python-only runtime\nimplementation.\n\n## Test the contract and selection\n\nTest through the operator, not by wiring the concrete candidate directly.\n\n1. Register the intended overload family after test registry reset.\n2. Use native `eval_node<Op>` or a minimal concrete graph to prove every\n   Python-visible behaviour at the same level.\n3. Cover the generic fallback and each more-specific winner. Include mixed\n   types, structural shapes, scalar-value policies, defaults, invalid inputs,\n   and output-only resolution as applicable.\n4. Add no-match and ambiguity coverage when adding ranking-sensitive\n   candidates. An accidental tie is a design error, not a registration-order\n   selection rule.\n5. Add equivalent Python `eval_node` coverage through the public operator.\n6. Regenerate and check the API inventory for a public native operator.\n7. Run the focused tests, then the acceptance gates required by `AGENTS.md`.\n\nBefore handing off, confirm that the marker contains no implementation, no\nnode chooses behaviour by inspecting an input type, every overload is\nexplicitly registered, overload-specific parameters are justified and\ndocumented, Python uses the same registry name, and tests prove which candidate\nwins rather than only the candidate's isolated arithmetic.\n"},"changes":[],"summary":"First saved snapshot. No earlier version is available for comparison.","summary_kind":"deterministic","summary_metadata":{}}