Skip to content

feat: Add --require-complete and expose finish_reason in CLI JSON #484

Description

@Arthur-Ficial

Value: Shell scripts need to distinguish a finished response from a useful-looking fragment before moving to the next command. This matters for generated configuration, migrations, structured text, and code-only output where silent truncation can invalidate the artifact. An opt-in completion requirement gives automation a reliable branch without disrupting today's interactive-friendly partial output.

Problem today: Sources/CLI.swift:75 prints the result and then returns zero at :86; the length outcome becomes only a stderr warning at :114. Sources/Models.swift:10 has no finish reason in ApfelResponse, so --json consumers cannot branch on it. Sources/Core/Chat/StreamOutcome.swift:45 already represents partial output as a length finish. This proposal preserves #128/#130's existing default and is separate from #435's exhausted tool-loop error.

Proposal: Add --require-complete for single-prompt, streaming, --messages, and --code workflows. When generation ends with .length, return a new documented CLI exit code, proposed 8. For non-streaming output, check completion before writing stdout; for streaming, retain already emitted text but return the nonzero status. A .stop result follows the current output path, and existing model/tool/refusal errors retain their established classifications.

Add an optional/additive finish_reason field to the normal JSON envelope wherever an engine outcome is available. Do not infer completion from closing fences, valid JSON, or the absence of an exception. This shell workflow should only print the generated artifact when apfel reports completion:

if result=$(apfel --require-complete --code --max-tokens 128 'Write a small Python CSV reader.'); then
  printf '%s\n' "$result"
fi

Acceptance criteria:

  • First add failing output-policy tests for .stop, explicit cap exhaustion, and output-side context exhaustion using injected outcomes.
  • Non-streaming --require-complete plus a length finish produces no stdout and exits 8; streaming may have partial stdout but must exit 8.
  • Plain output, JSON envelopes, --code, and --messages agree on status; quiet mode cannot suppress a failure exit.
  • JSON reports finish_reason without changing the meaning or encoding of content; unsupported flag combinations produce specific CLI parse errors.
  • Preserve existing default partial-output behavior and add a live deliberately capped request test alongside deterministic policy coverage.
  • Document set -o pipefail for callers that place apfel inside a pipeline, and document that completion is not a correctness guarantee.

Golden-goal alignment: Gives the UNIX tool an explicit success contract for scripts that require a complete generated artifact.

Effort: M

Risks / open questions:

  • Reserve exit 8 through the existing exit-code catalogue and stability policy; never reuse an established meaning.
  • The framework can stop naturally after a semantically incomplete answer; this flag reports engine termination, not factual or syntactic validation.
  • A streaming command cannot retract bytes already consumed downstream; the nonzero exit is the contract.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or requestunix-toolThe apfel UNIX tool surface (CLI, pipes, exit codes)

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions