Class: AssistantReplyJob

Inherits:
ApplicationJob show all
Defined in:
app/jobs/assistant_reply_job.rb

Overview

The shared planning assistant (cardinal.md §5): a CONTINUING claude session per card (assistant_session_id) — context and repo exploration carry across replies instead of being re-paid per message. Falls back to a fresh session (with the transcript embedded) if the stored session can't be resumed.

Constant Summary collapse

FALLBACK_MODEL =
"claude-haiku-4-5-20251001".freeze
MAX_TURNS =
20
ROLE_REMINDER =
"Reminder: you are the PLANNING assistant. You refine cards and write briefs — " \
"you never implement. No code files, no full implementations, no 'run these " \
"commands'. Your tools are READ-ONLY: you are physically incapable of modifying " \
"any file, so NEVER claim to have made, applied, or reverted a change — any such " \
"claim would be false. If the user approves an approach, finalize the " \
"Ready-for-execution brief and tell them to drag the card to a work column.".freeze
KICKOFF_TURN =
<<~MSG.freeze
  This card just entered the Planning column. Open the discussion: greet me in one short
  sentence, then ask the 2-3 most important clarifying questions that would make THIS
  card execution-ready. Be specific to the card's actual content — never generic. If the
  card is already crystal clear, say so and propose a "Ready for execution" brief instead.
MSG

Instance Method Summary collapse

Instance Method Details

#perform(card, kickoff: false) ⇒ Object

kickoff: true generates the opening message when a card enters planning.



26
27
28
29
30
31
32
33
34
35
36
37
38
# File 'app/jobs/assistant_reply_job.rb', line 26

def perform(card, kickoff: false)
  unless ClaudeCli.available?
    card.log!("assistant_message", actor: "assistant",
              text: "I'm here to help shape this card. What's the goal, and how will we know it's done? (Install the claude CLI for a smarter assistant.)")
    return
  end

  reply, session_id = converse(card, kickoff:)
  card.update!(assistant_session_id: session_id) if session_id.present?
  card.log!("assistant_message", actor: "assistant", text: reply) if reply.present?
rescue ClaudeCli::Error => e
  card.log!("error", text: "The planning assistant #{e.message}.", detail: e.detail)
end