Class: Insika::Refinement::Proposer
- Inherits:
-
Object
- Object
- Insika::Refinement::Proposer
- Defined in:
- lib/insika/refinement/proposer.rb
Overview
Writes a CANDIDATE from a run's findings — the one place in refinement where a model is asked for anything.
It is deliberately the WEAKEST link and it is built that way: everything this
class produces is data that the CandidateBuilder bounds (allowlist, size,
growth, a before that must still match) and the Gate then scores by REPLAYING
the golden set. A hallucinated rationale, a misread finding, an invented anchor
— the worst outcome of each is a candidate that gets dropped or fails to improve
a score, and never reaches a customer. Nothing here is trusted; it is measured.
Pure over an injected ask (prompt -> raw model text), like Evals::Judge and
Safety::Factory: unit-testable without a provider, and the real ask is one
lambda built by ProposerFactory.
What it is shown
The findings (already redacted at collection — a snippet went through the same
output filter a customer-facing turn does) and the CURRENT CONTENT of the
allowlisted files, verbatim and unmasked. That is not a leak: those files are
the agent's own instructions, which are sent to a model on every single turn.
Masking them here would buy nothing and would break anchoring — a before
copied from a masked view never matches the real file, so every edit near a
secret would drop as stale.
Files OUTSIDE the allowlist are not shown at all. A model that can read them proposes edits to them, which drop, which spends the operator's attention on rejects.
Defined Under Namespace
Classes: Unusable
Constant Summary collapse
- MAX_FINDINGS =
10
Instance Attribute Summary collapse
-
#model ⇒ Object
readonly
The model ref, so a panel can name WHICH proposer failed without guessing.
Instance Method Summary collapse
-
#initialize(ask:, model: "unknown") ⇒ Proposer
constructor
ask: ->(prompt) { "
" }, or something answering #contentplus#input_tokens/#output_tokens(a RubyLLM message). -
#propose(agent_id:, findings:, files:, limits: {}) ⇒ Object
-> a RAW candidate hash (string keys) for
CandidateBuilder.build.
Constructor Details
#initialize(ask:, model: "unknown") ⇒ Proposer
ask: ->(prompt) { "#content
plus #input_tokens/#output_tokens (a RubyLLM message). The second
shape is what lets the panel's budget count what a proposal cost; a
plain String stays valid and simply reports no cost, which is what
every existing caller and every fake does.
model: what to record as the candidate's proposer — the ref an operator
reads on the review card and in :refinement_proposed.
51 52 53 54 |
# File 'lib/insika/refinement/proposer.rb', line 51 def initialize(ask:, model: "unknown") @ask = ask @model = model.to_s end |
Instance Attribute Details
#model ⇒ Object (readonly)
The model ref, so a panel can name WHICH proposer failed without guessing.
42 43 44 |
# File 'lib/insika/refinement/proposer.rb', line 42 def model @model end |
Instance Method Details
#propose(agent_id:, findings:, files:, limits: {}) ⇒ Object
-> a RAW candidate hash (string keys) for CandidateBuilder.build. It is not
a Candidate: this class does not get to decide what is in bounds.
findings: the run's findings as stored (string keys). files: name => current content, allowlist only.
61 62 63 64 65 66 67 68 69 70 71 |
# File 'lib/insika/refinement/proposer.rb', line 61 def propose(agent_id:, findings:, files:, limits: {}) raise Unusable, "there is nothing to propose from — the run found no findings" if Array(findings).empty? raise Unusable, "no writable file has any content to anchor an edit in" if files.empty? answer = @ask.call(build_prompt(agent_id, Array(findings).first(MAX_FINDINGS), files, limits)) parsed = parse(text_of(answer)) parsed["proposer"] = @model parsed["tokens"] = tokens_of(answer) parsed["cached"] = cached_of(answer) parsed end |