Class: Rubino::Context::PromptAssembler
- Inherits:
-
Object
- Object
- Rubino::Context::PromptAssembler
- Defined in:
- lib/rubino/context/prompt_assembler.rb
Overview
Assembles the complete prompt from all context sources. Returns the message array (system + summary + history) for LLM submission.
Constant Summary collapse
- TOOL_USE_ENFORCEMENT_MODELS =
Model-name substrings that trigger tool-use-enforcement steering under the "auto" default. Ported from Hermes' TOOL_USE_ENFORCEMENT_MODELS, plus "minimax" — rubino's default targets it and its field notes document the same text-leaked-tool-call / narrate-instead-of-act failures.
%w[ gpt codex gemini gemma grok glm qwen deepseek minimax ].freeze
Class Method Summary collapse
-
.reset_all_snapshots! ⇒ Object
Test/teardown hook.
-
.reset_snapshot!(session_id) ⇒ Object
Drops the cached snapshot for a session so the next assembly captures fresh memory state.
-
.snapshot_for(session_id) ⇒ Object
Returns the cached snapshot for a session, computing it via the supplied block on first access.
Instance Method Summary collapse
-
#build ⇒ Object
Builds and returns the full message array for LLM submission.
-
#initialize(session:, memory_context:, config:, agent_definition: nil, ignore_rules: false) ⇒ PromptAssembler
constructor
A new instance of PromptAssembler.
Constructor Details
#initialize(session:, memory_context:, config:, agent_definition: nil, ignore_rules: false) ⇒ PromptAssembler
Returns a new instance of PromptAssembler.
54 55 56 57 58 59 60 61 62 63 64 65 66 |
# File 'lib/rubino/context/prompt_assembler.rb', line 54 def initialize(session:, memory_context:, config:, agent_definition: nil, ignore_rules: false) @session = session @memory_context = memory_context @config = config @agent_definition = agent_definition # --ignore-rules suppresses project-context discovery # (AGENTS.md/CLAUDE.md/.rubino.md/.cursorrules). The flag is threaded # from Lifecycle so the CLI option genuinely skips discovery (#47), not # just the trust gate. @ignore_rules = ignore_rules @message_store = Session::Store.new end |
Class Method Details
.reset_all_snapshots! ⇒ Object
Test/teardown hook. Not part of the public API.
41 42 43 |
# File 'lib/rubino/context/prompt_assembler.rb', line 41 def reset_all_snapshots! @snapshots_mutex.synchronize { @snapshots.clear } end |
.reset_snapshot!(session_id) ⇒ Object
Drops the cached snapshot for a session so the next assembly captures fresh memory state. Use this when a tool call must influence the very next turn (trade-off: the freeze stops protecting against same-turn poisoning).
36 37 38 |
# File 'lib/rubino/context/prompt_assembler.rb', line 36 def reset_snapshot!(session_id) @snapshots_mutex.synchronize { @snapshots.delete(session_id) } end |
.snapshot_for(session_id) ⇒ Object
Returns the cached snapshot for a session, computing it via the supplied block on first access. The block receives no args and must return the memory-context hash to freeze.
26 27 28 29 30 |
# File 'lib/rubino/context/prompt_assembler.rb', line 26 def snapshot_for(session_id) @snapshots_mutex.synchronize do @snapshots[session_id] ||= yield end end |
Instance Method Details
#build ⇒ Object
Builds and returns the full message array for LLM submission
69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 |
# File 'lib/rubino/context/prompt_assembler.rb', line 69 def build = [] # Single system message. The content is split into a STABLE PREFIX # (identity / product / env / user-profile snapshot / skills / project # context) and a VOLATILE TAIL (freshly-retrieved relevant-memories + # the post-compaction session-summary). The prefix carries the prompt- # cache breakpoint (#311); the tail — which can change turn-to-turn — # sits AFTER it so the cached bytes stay byte-stable. Both regions live # in ONE role:"system" entry (#253), built as a Content::Raw array of # text blocks when caching is on, or a plain joined String otherwise. << { role: "system", content: system_content } # Conversation history. Repair tool pairing across the FULL list before # mapping to wire format — this is the defensive "net" that recovers # sessions already corrupted by the historical metadata-dropping bug in # compaction/fork (those rows exist in prod). Mirrors Claude Code's # pre-call sanitization: never emit an orphan tool block that 400s a # strict provider. Conservative by design — when in doubt, keep. # # Drop `[harness control]` messages FIRST: they are ephemeral within-turn # runtime control (Loop's iteration-cap summary nudge, the resume / # truncation / blocked-tool reminders, #75), written on the user's behalf # for ONE model call — not durable conversation. They stay in the DB for # audit/scrollback (and are already excluded from the rewind picker, # chat_command#rewindable_message?), but must never re-enter the model's # context on a LATER turn. Re-feeding the synthetic "you've reached the # maximum tool-calling iterations" user turn made the model believe it # was perpetually capped and parrot a "start a new session" refusal on # every subsequent turn instead of acting. This is the single chokepoint # every surface (CLI + web/API) rebuilds context through, so filtering # here fixes them all. durable = @message_store.for_session(@session[:id]).reject { |m| harness_control?(m) } history = repair_tool_pairs(durable) history.each do |msg| << msg.to_context end end |