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.
npm install response-goal-engineThe 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";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.
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.
Contracts support three explicit formats:
jsonparses the response as JSON and validates configured required and present optional fields on JSON objects, including declared value types.markdownrecognizes configured ATX headings and requires non-empty content for required sections; unconfigured headings are findings.lenient-textperforms only case-insensitive required-key checks, always reportsLENIENT_VALIDATION_USED, and returnsconfidence: "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.
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.
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.
MIT. See LICENSE.
Tested on Node.js 22 and 24.