Skip to content

Fix --inputs-file: inputs from a JSON file or stdin never reach the objective - #37

Open
rr-aniket-wankhede wants to merge 1 commit into
boundflow:mainfrom
rr-aniket-wankhede:fix/inputs-file-reaches-objective
Open

rr-aniket-wankhede wants to merge 1 commit into
boundflow:mainfrom
rr-aniket-wankhede:fix/inputs-file-reaches-objective

Conversation

@rr-aniket-wankhede

@rr-aniket-wankhede rr-aniket-wankhede commented Sep 21, 2026 •

Copy link
Copy Markdown

The bug

--inputs-file is documented in DESIGN.md but was never implemented. The run
command parses its extra args as declared-input flags, so the token was consumed
as an input named inputs_file whose value was the path. The real inputs never
arrived, and the task failed before the model was invoked.

Root cause: charter/cli.py in run(): the flag loop (key = token[2:].replace("-", "_") → context[key] = _typed(value)) has no special case for --inputs-file, so the flag-path and file-path never converge on the same invoke context.

Repro

printf '{"ticket_id": "4821"}' > /tmp/inputs.json
charter run input-file-repro --inputs-file /tmp/inputs.json
# ACTUAL:   task starts, then fails:
#           "ticket_id was not given, and the objective needs it. Run: ..."
#           llm_calls: 0  (model never invoked — empty invoke context)
# EXPECTED: same as the flag path:
charter run input-file-repro --ticket_id 4821   # completes, llm_calls: 1

Agent config used:

apiVersion: charter/v1
kind: AgentConfig
name: input-file-repro
version: 1
model: <any model on your provider>
objective: |
  Report the ticket id {{ inputs.ticket_id }}.
inputs:
  ticket_id: { type: string, required: true }
response_format:
  answer:
    type: string
    description: The ticket id.

The fix

run() now recognises --inputs-file (space, =, and - for stdin) and merges
the parsed JSON object into the same invoke_workflow(context=...) the flags
build (_read_inputs). A file that is missing, unparsable, or not a JSON object
is refused locally with a clear error, without submitting a request.

While testing this, the = form exposed a latent parser bug that the fix also
corrects: the loop replaced - with _ across the whole token before splitting
on =, so any --flag=value-with-dashes was silently mangled (--inputs-file=/some-path
became /some_path). The split now happens first, so only the flag name is normalized.

Tests

  • Unit (tests/test_cli.py::TestRunInputsFile): the CLI path with a file,
    stdin -, the = form mixed with flags, and local refusal of bad files
    all asserting the exact context handed to invoke_workflow.
  • E2E (tests/e2e/test_inputs_file.py): the real charter run command in a
    subprocess, against a real control plane and store, with a scripted model that
    asserts the objective the model is handed contains the rendered value, the
    structured result, and total_llm_calls == 1 (the bug showed 0).

Full suites pass: 345 unit tests, 31 e2e (4 skipped: no live providers locally).

The run command's flag loop treated --inputs-file as an input named
inputs_file whose value was the path, so inputs from a file or stdin
never reached the objective: the worker saw every declared input as
missing and failed the task before calling the model (llm_calls: 0).
--inputs-file now reads the file (or stdin via -), parses the JSON
object, and merges it into the same invoke context the flags build.

Also fix the flag parser mangling dashes in --flag=value (it normalized
the whole token before splitting on =), which --inputs-file=<path>
needs and which silently corrupted any value containing a dash.

This branch has not been deployed

No deployments
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