forge-lcdl

ContractSpec v2 (metadata alongside v1 Markdown)

LCDL task contracts ship as Markdown under:

src/forge_lcdl/contracts/<task_id>/<version>/contract.md

Sprint 1 adds an optional machine-readable sidecar:

src/forge_lcdl/contracts/<task_id>/<version>/contract.json

The Markdown file stays the narrative source for humans. The JSON file (when present) carries ContractSpec fields for tooling: schemas, success criteria, failure modes, and policy hints for cheap models, benchmarks, and future verifiers.

Coexistence with v1

  • Directory names and layout are unchanged.
  • TaskRunner and run_task behavior are unchanged; they do not read contract.json yet.
  • Tasks without contract.json still work: load_contract_spec synthesizes a minimal spec from the registry plus Markdown (best-effort title), so callers never fail only because metadata is missing.
  • Unknown task_id / version raises UnknownContractError.

API

Use forge_lcdl.contracts_api (src/forge_lcdl/contracts_api.py) — not a Python package named after the bundled contracts/ asset directory.

  • load_contract_spec(task_id, version="v1") -> ContractSpec
  • validate_contract_input(spec, input_obj) / validate_contract_output(spec, output_obj)

ContractSpec fields

  • task_id, version, title
  • input_schema / output_schema: JSON-Schema-like dicts (subset enforced by LCDL)
  • success_criteria, failure_modes: lists of strings
  • retry_policy, verification_policy, fallback_policy: extensible dicts
  • contract_markdown_path: relative path string matching the bundled layout (e.g. extract_schema_from_text/v1/contract.md)

contract.json format

Top-level object; use "contract_spec_format": 2 for the current metadata shape (format 1 in existing files is still accepted).

ContractSpec fields (v2)

All v1 fields, plus:

  • capabilities: extensible dict (e.g. uses_llm, supports_rag, returns_citations)
  • execution_policy: dict (e.g. complexity, risk, max_attempts)
  • inference_policy: dict (e.g. mode, min_confidence)
  • rag_policy: dict (e.g. mode, top_k, require_citations, max_context_tokens)
  • prompt_cache_policy: dict (e.g. enabled, stable/dynamic part hints)
  • extensions: dict of unknown top-level keys preserved from contract.json (vendor-specific metadata)

Built-in defaults apply when contract.json omits these objects or when the spec is synthesized from Markdown-only tasks.

Unknown keys

Any top-level key in contract.json that is not part of the known ContractSpec mapping is stored in ContractSpec.extensions (not dropped).

Validation subset

validate_contract_input / validate_contract_output perform lightweight checks only (no jsonschema dependency):

  1. Top-level value must be a dict.
  2. If the schema has required (list of strings), every key must be present.
  3. If properties maps a key to {"type": "<json-schema-type>"}, when that key is present its value must match the shallow type (string, number, integer, boolean, object, array, null).

Nested object schemas are not fully validated in this sprint.

On failure, ContractValidationError is raised with a short message (missing keys or type mismatch).

Import smoke test

Install paths match the rest of the library: use a venv and pip install -e ".[dev]" (see README), then run python / python3 as usual. On a clean checkout without an install, point Python at src/:

cd forge-lcdl
PYTHONPATH=src python3 - <<'PY'
from forge_lcdl.contracts_api import load_contract_spec
for task_id in [
    "extract_schema_from_text",
    "llm_boolean_gate",
    "llm_enum_route",
    "decompose_problem",
    "plan_decision_pack",
]:
    spec = load_contract_spec(task_id, "v1")
    print(task_id, spec.version, bool(spec.output_schema))
PY

Packaging

Wheel/sdist include both contracts/**/*.md and contracts/**/*.json (see pyproject.toml).

See also

  • Benchmark harness: docs/BENCHMARKS.md