Class: Insika::Refinement::Proposer

Inherits:
Object
  • Object
show all
Defined in:
lib/insika/refinement/proposer.rb

Overview

Writes a CANDIDATE from a run's findings (RFC-0013 §3.4, phase C / PR 3b) — 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

Instance Method Summary collapse

Constructor Details

#initialize(ask:, model: "unknown") ⇒ Proposer

ask: ->(prompt) { "" }, or something answering #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

#modelObject (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.

Raises:



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