Skip to content

PUBLISH ingress P2: NL-description -> IntegrationSpec (opt-in LLM, aix-backed) - #37

Merged
thorwhalen merged 3 commits into
mainfrom
claude/nl-ingress
Jun 17, 2026
Merged

PUBLISH ingress P2: NL-description -> IntegrationSpec (opt-in LLM, aix-backed)#37
thorwhalen merged 3 commits into
mainfrom
claude/nl-ingress

Conversation

@thorwhalen

Copy link
Copy Markdown
Owner

What

Adds the second PUBLISH ingress arm (landscape doc §9.2): refine a
natural-language description into a draft IntegrationSpec — proposed tools
with inferred input schemas — routing generation through aix (provider-agnostic)
via oa's prompt-as-function machinery. Kept strictly opt-in (DECISIONS D18).

coact describe "a connector that looks up the weather for a city"
from coact import integration_spec_from_description
spec = integration_spec_from_description("expose os.path.basename as a tool")
print(spec.render())

Design

  • coact/nl_ingress.pyintegration_spec_from_description(description, *, llm=None, model=None, name=None, infer_tool_schemas=True). Backend is injectable (callable / model-name / Noneaix.chat); oa/aix imported lazily so import coact stays provider-free (D10, regression-tested in a subprocess).
  • IntegrationSpec grows tool_specs: list[ToolSpec] (name / description / input_schema / handler) alongside the unchanged tools refs — non-breaking; aligns with landscape §9.1. A ToolSpec with a handler is bound (runnable); without, a proposed draft. New runnable_refs() + render().
  • publish_mcpb builds the server config from runnable_refs(); a pure draft (no runnable ref) raises with guidance instead of writing a dead bundle; proposed tools are listed in the manifest with a warning.
  • Per-tool input-schema inference via oa.infer_schema_from_verbal_description, made backend-injectable upstream (oa#14, merged; first customer = coact).
  • Authoring prompt SSOT in coact (DFLT_AUTHORING_PROMPTS), injectable via prompt_template= (pyrompt = the iteration home, not a hard dep).
  • CLI coact describe; exports ToolSpec + integration_spec_from_description; extra coact[nl] (oa, aix); skill + README updated.

Why aix, not oa's default

Per the route-through-aix policy, NL generation defaults to aix (multi-provider), not oa's OpenAI default — keeping the multi-target promise honest. oa.infer_schema_from_verbal_description was hardwired to oa's chat; it was made backend-injectable upstream so this path can use any provider.

Review

An adversarial multi-agent review of the diff (3 finder dimensions → per-finding verification) surfaced 9 confirmed findings, all fixed with regression tests (see the harden: commit): silent handler loss through the list branch (high), non-string/brace-prose robustness, path-aware optional-dep check, clearer guards, and manifest/runtime name fidelity.

Tests

393 passed, 4 skipped; ruff clean. All NL-ingress tests are offline (injected fake backend — no provider call).

… aix-backed)

The second PUBLISH ingress arm (landscape doc 9.2): refine a natural-language
description into a *draft* IntegrationSpec via oa's prompt-as-function machinery,
routed through aix (provider-agnostic) -- NOT oa's OpenAI default -- to keep the
multi-target promise honest. Kept strictly opt-in (DECISIONS D18):

- coact/nl_ingress.py: integration_spec_from_description(description, *, llm=None,
  model=None, name=None, infer_tool_schemas=True). Backend injectable (callable /
  model-name / None -> aix.chat); oa/aix imported LAZILY so importing coact stays
  provider-free (D10, regression-tested in a subprocess).
- IntegrationSpec grows tool_specs: list[ToolSpec] (name/description/input_schema/
  handler) alongside the unchanged tools refs (non-breaking; landscape 9.1).
  A ToolSpec with a handler is bound (runnable); without, it is a proposed draft.
  Added runnable_refs() + render(); is_empty() now counts tool_specs.
- publish_mcpb builds the server config from runnable_refs(); a pure draft (no
  runnable ref) RAISES with guidance instead of writing a dead bundle; proposed
  tools are listed in the manifest with a warning.
- per-tool input-schema inference via oa.infer_schema_from_verbal_description,
  made backend-injectable upstream (oa PR; first customer = coact).
- authoring prompt SSOT in coact (DFLT_AUTHORING_PROMPTS), injectable via
  prompt_template= (pyrompt = the iteration home, not a hard dep).
- CLI verb 'coact describe'; exports ToolSpec + integration_spec_from_description;
  extra coact[nl] (oa, aix); skill + README updated.
- 17 offline tests (fake backend, no provider call). Full suite: 380 passed.
An adversarial multi-agent review of the P2 diff surfaced 9 confirmed issues,
all fixed (offline regression tests added for each):

- [high] integration_spec_from dropped a spec's tool_specs/resources/prompts when
  the spec was passed inside a list -> bound handlers silently lost. Now carried
  through; the empty-guard counts them too.
- [med] non-string LLM field values (number/list/object where a string was asked)
  crashed _spec_from_extracted with a raw AttributeError/TypeError. Now coerced.
- [med] _parse_json_object seized the FIRST balanced {...}; brace-bearing prose
  before the real JSON defeated it. Now tries whole-text, fenced body, then each
  top-level balanced span.
- [med] NL ingress hard-required aix even when a callable backend was injected.
  check_requirements is now path-aware (aix only on the default/model-name path).
- [low] a bare-string resources/prompts was exploded per character. Now wrapped.
- [low] .mcpb draft-guard said "0 proposed tool(s)" for a resources/prompts-only
  spec. Now a distinct, accurate message.
- [low] manifest could advertise a curated name the server would not serve. Keep
  the bound function's own name/docstring (runtime truth); a curated description
  only fills an empty one.
- [low] str-llm (model-name) branch was untested; README NL snippet implied it was
  offline. Added tests + an honest README caveat.

Full suite: 393 passed, 4 skipped; ruff clean.
…essions

CI does not install the optional `coact[nl]` extra, so the NL ingress path
(which genuinely needs `oa` as its prompt orchestrator) cannot run there. Match
the project's established posture (litellm/langgraph/crewai backend tests skip in
bare CI, run in dev):

- test_nl_ingress.py guards the whole module with pytest.importorskip("oa")
  (aix is never imported — backends are injected/monkeypatched).
- the oa-FREE mechanical regressions moved to test_publish.py so they still run
  in CI: the high-severity list-branch handler-preservation fix (#5), manifest
  fidelity (#4), the resources-only guard message (#7), proposed-tool warning,
  and the D10 import-isolation check.
- the `coact describe` CLI test guards with importorskip("oa").

Dev: 393 passed, 4 skipped. Bare-CI simulation (oa/aix blocked): the affected
files run with 0 failures (oa-dependent tests skip, oa-free ones pass).
@thorwhalen
thorwhalen merged commit cc07608 into main Jun 17, 2026
12 checks passed
@thorwhalen
thorwhalen deleted the claude/nl-ingress branch June 17, 2026 12:35
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant