Skip to content

feat(cli, conversation, tool): Record every tool inquiry round-trip#827

Open
JeanMertz wants to merge 4 commits into
mainfrom
rfd-082
Open

feat(cli, conversation, tool): Record every tool inquiry round-trip#827
JeanMertz wants to merge 4 commits into
mainfrom
rfd-082

Conversation

@JeanMertz

Copy link
Copy Markdown
Collaborator

Every Outcome::NeedsInput question a tool asks now produces a paired InquiryRequest/InquiryResponse in the conversation stream, no matter how it gets answered: interactive prompt, a "remember for this turn" cache hit, or a static QuestionConfig.answer. Previously only questions routed through the LLM inquiry backend were recorded, so the audit trail was incomplete for the common TTY-prompt and static-answer paths.

InquiryResponse is now an enum instead of a flat struct: Answered { id, answer }, Cancelled { id, reason }, and Redacted { id }. Cancellation carries a CancellationReason (user, backend error, or one of two routing-guard reasons) so a closed-without- answer inquiry no longer disappears from the stream. Deserialization still accepts the old pre-082 flat { id, answer } shape as Answered, so existing conversation histories keep loading.

jp_tool::Question gains a Secret answer type for values that must not be persisted, e.g. jp_tool::Question::secret("token", "API key"). A secret's answer reaches the tool in-memory but is recorded as Redacted on disk, is never echoed at the terminal prompt (via a new PromptBackend::password method), and is refused if routing would send it to the assistant or fall back to a non-interactive backend. Question IDs are now validated at construction (QuestionId rejects a .), since the persisted inquiry ID format changes from <tool_call_id>.<question_id> to <tool_call_id>.<question_id>.<attempt> to stay unique when a question is re-asked within a turn.

TurnState splits its old single persisted_inquiry_responses map into remembered_tool_answers and remembered_permission_decisions, keyed by new ToolAnswerCacheKey/PermissionCacheKey types, so a "remember" answer to a tool question can no longer be conflated with a tool's permission decision cache.

Orphaned-inquiry cleanup in ConversationStream::sanitize is now turn-scoped, so an InquiryId reused across turns can no longer cross-satisfy a request/response pair from a different turn.

Implements RFD 082, promoted from Discussion to Accepted.

JeanMertz added 4 commits July 2, 2026 12:33
Every `Outcome::NeedsInput` question a tool asks now produces a paired
`InquiryRequest`/`InquiryResponse` in the conversation stream, no
matter how it gets answered: interactive prompt, a "remember for this
turn" cache hit, or a static `QuestionConfig.answer`. Previously only
questions routed through the LLM inquiry backend were recorded, so the
audit trail was incomplete for the common TTY-prompt and static-answer
paths.

`InquiryResponse` is now an enum instead of a flat struct:
`Answered { id, answer }`, `Cancelled { id, reason }`, and
`Redacted { id }`. Cancellation carries a `CancellationReason` (user,
backend error, or one of two routing-guard reasons) so a closed-without-
answer inquiry no longer disappears from the stream. Deserialization
still accepts the old pre-082 flat `{ id, answer }` shape as `Answered`,
so existing conversation histories keep loading.

`jp_tool::Question` gains a `Secret` answer type for values that must
not be persisted, e.g. `jp_tool::Question::secret("token", "API key")`.
A secret's answer reaches the tool in-memory but is recorded as
`Redacted` on disk, is never echoed at the terminal prompt (via a new
`PromptBackend::password` method), and is refused if routing would send
it to the assistant or fall back to a non-interactive backend. Question
IDs are now validated at construction (`QuestionId` rejects a `.`),
since the persisted inquiry ID format changes from
`<tool_call_id>.<question_id>` to `<tool_call_id>.<question_id>.<attempt>`
to stay unique when a question is re-asked within a turn.

`TurnState` splits its old single `persisted_inquiry_responses` map into
`remembered_tool_answers` and `remembered_permission_decisions`, keyed by
new `ToolAnswerCacheKey`/`PermissionCacheKey` types, so a "remember"
answer to a tool question can no longer be conflated with a tool's
permission decision cache.

Orphaned-inquiry cleanup in `ConversationStream::sanitize` is now
turn-scoped, so an `InquiryId` reused across turns can no longer
cross-satisfy a request/response pair from a different turn.

Implements RFD 082, promoted from Discussion to Accepted.

Signed-off-by: Jean Mertz <git@jeanmertz.com>
Signed-off-by: Jean Mertz <git@jeanmertz.com>
Signed-off-by: Jean Mertz <git@jeanmertz.com>
Signed-off-by: Jean Mertz <git@jeanmertz.com>
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