Skip to content

Repository files navigation

response-goal-engine

response-goal-engine builds explicit response contracts for AI-assisted work. It is provider-independent: it does not call a model API, depend on a model SDK, or select a provider. It also never truncates output. Token targets are validation findings only; callers retain complete control over any output they send, store, or display.

Installation

npm install response-goal-engine

The package is ESM. The core API has no file-system or YAML dependency. File configuration loaders are opt-in subpath imports:

import { loadJsonConfig } from "response-goal-engine/adapters/json";
import { loadYamlConfig } from "response-goal-engine/adapters/yaml";

Canonical functional flow

Resolve configuration, select a declared goal, build an immutable contract, append it to your provider prompt, then validate the returned text.

import {
  augmentPrompt,
  buildContract,
  resolveResponseGoalConfig,
  selectGoal,
  validateResponse,
} from "response-goal-engine";

const configResult = resolveResponseGoalConfig(
  { defaults: { consumer: "user", decision: "verify completion" } },
  undefined,
  {
    taskTypes: {
      implementation: {
        format: "json",
        required: [{ key: "status", valueType: "string" }],
        completion: ["complete"],
      },
    },
  },
);
if (!configResult.ok) throw new Error(configResult.error.message);

const goalResult = selectGoal({ taskType: "implementation" }, configResult.value);
if (!goalResult.ok) throw new Error(goalResult.error.message);

const contractResult = buildContract(goalResult.value, configResult.value);
if (!contractResult.ok) throw new Error(contractResult.error.message);

const prompt = augmentPrompt("Implement the change.", contractResult.value);
// Send `prompt` with any provider, then pass its complete returned string here.
const validation = validateResponse('{"status":"complete"}', contractResult.value);

Every fallible core operation returns Result<T, EngineError> rather than throwing expected configuration or contract errors. EngineError.code is one of INVALID_CONFIG, GOAL_NOT_FOUND, or CONTRADICTORY_CONTRACT.

Configuration precedence

resolveResponseGoalConfig(defaults, fileConfig, overrides) merges inputs in that order: later inputs win. Plain objects merge recursively, while arrays replace the earlier array as a whole. This makes policy lists deterministic:

const config = resolveResponseGoalConfig(
  { defaults: { required: [{ key: "summary" }] } },
  { defaults: { required: [{ key: "verification" }] } },
  undefined,
);
// config.value.defaults.required is [{ key: "verification" }]

selectGoal uses explicit context values first, then the selected task-type policy, consumer policy (for a decision), and defaults. It does not infer an undeclared task, consumer, or decision.

Formats and validation

Contracts support three explicit formats:

  • json parses the response as JSON and validates configured required and present optional fields on JSON objects, including declared value types.
  • markdown recognizes configured ATX headings and requires non-empty content for required sections; unconfigured headings are findings.
  • lenient-text performs only case-insensitive required-key checks, always reports LENIENT_VALIDATION_USED, and returns confidence: "reduced".

validateResponse also checks completion strings, prohibited content, and token limits. It returns valid, complete, confidence, plus separate errors and warnings; it does not modify the output string.

Enforcement and tokens

Findings are errors by default. Set a task policy's enforcement map to downgrade supported reason codes to warnings (or explicitly keep them as errors). A soft token target is always advisory and produces a warning. A hard ceiling produces a finding and can follow the configured enforcement policy.

The default token counter is a deterministic whitespace-segment approximation, not a provider tokenizer. Supply a tokenizer when building a contract if your application needs a different measure:

const built = buildContract(goal, config, {
  tokenCounter: (text) => myTokenizer.encode(text).length,
});

Token counts never cause truncation. They only affect validation findings.

Extension interfaces

GoalSelector and OutputValidator describe compatible extension functions; they let an application compose its own policy without coupling the core to a provider. For example:

import type {
  GoalSelector,
  OutputValidator,
  ValidationResult,
} from "response-goal-engine";

const selectReleaseGoal: GoalSelector = (context, config) => {
  if (context.taskType !== "release") {
    return { ok: false, error: { code: "GOAL_NOT_FOUND", message: "Release only." } };
  }
  return {
    ok: true,
    value: { taskType: "release", consumer: "operator", decision: "approve deploy" },
  };
};

const requireApproval: OutputValidator = (output, contract): ValidationResult => ({
  valid: output.includes("approved"),
  complete: output.includes("approved"),
  confidence: "high",
  errors: output.includes("approved") ? [] : [{
    code: "MISSING_COMPLETION_RULE",
    message: "Approval is required.",
  }],
  warnings: [],
});

The core root exports configuration resolution and validation, goal selection, contract construction, prompt augmentation, token counting, structured and lenient validators, all public types, and Result/EngineError. Use the adapter subpaths above only when loading JSON or YAML files is desired.

License

MIT. See LICENSE.

Tested on Node.js 22 and 24.

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages