Documentation
¶
Overview ¶
Package threadcase hosts the thread-mode agent: a plan-and-execute turn (planexec.Runner) that runs when a Case is created from a monitored channel post (materialize the Case fields) or when the bot is mentioned in a Case thread (investigate and respond / update fields / close). Slack SDK imports are forbidden here; the host communicates via the Handler interface and the returned Decision, exactly like casebound / proposal.
Index ¶
- type ConversationMessage
- type CreateDecision
- type CreatePayload
- type Decision
- type DecisionField
- type DecisionKind
- type Handler
- type HandlerFuncs
- func (h HandlerFuncs) Create(ctx context.Context, ssn *model.Session, p CreatePayload) (*model.Case, error)
- func (h HandlerFuncs) Question(ctx context.Context, ssn *model.Session, q QuestionPayload) error
- func (h HandlerFuncs) TraceAppend(ctx context.Context, line string)
- func (h HandlerFuncs) TraceReplace(ctx context.Context, line string)
- type Mode
- type QuestionItem
- type QuestionItemType
- type QuestionPayload
- type Result
- type Status
- type TurnRequest
- type UseCase
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
This section is empty.
Types ¶
type ConversationMessage ¶
ConversationMessage is a single pre-fetched thread message handed to the runtime. The host resolves user display names; the runtime only formats.
type CreateDecision ¶
type CreateDecision struct {
Title string `json:"title" description:"A concise case title summarising the thread." required:"true"`
Description string `json:"description" description:"A clear case description derived from the thread and your investigation." required:"true"`
Fields []DecisionField `` /* 134-byte string literal not displayed */
}
CreateDecision is the structured final output of a ModeCreate turn (Run[CreateDecision]): the title / description / custom fields the planner wants the new case to carry. It reuses DecisionField (shared with the materialize decision). The planner schema is derived from these struct tags via gollem.ToSchema; Validate enforces the shape invariants. Workspace-schema field validation (required / options / types) is applied by the host when it commits the case (validateCreateDecision), not here — the value type has no access to the field schema.
func (CreateDecision) Validate ¶
func (d CreateDecision) Validate() error
Validate enforces the create decision's shape invariants so a title-less or description-less proposal is rejected inside planexec's Run[CreateDecision] regeneration loop. It satisfies planexec.Validatable. Field-value validity is checked by the host against the workspace schema (validateCreateDecision).
type CreatePayload ¶
type CreatePayload struct {
Title string
Description string
Fields map[string]model.FieldValue
}
CreatePayload is handed to Handler.Create when the ModeCreate planner commits a new case. Fields are the already type-validated custom field values (Type injected, options/required checked by the runtime). The host owns the case identity (workspace / channel / thread / reporter) — those are captured when the host builds the Handler, not carried here.
type Decision ¶
type Decision struct {
Kind DecisionKind `` /* 300-byte string literal not displayed */
Message string `json:"message,omitempty" description:"For respond: the reply text shown to the user. Omit for materialize."`
Title string `json:"title,omitempty" description:"For materialize: a concise case title summarising the thread."`
Description string `json:"description,omitempty" description:"For materialize: a clear case description derived from the thread."`
Fields []DecisionField `json:"fields,omitempty" description:"For materialize: custom field assignments. Only include fields you are confident about."`
}
Decision is the structured final output of a mention turn (Run[Decision]). The schema handed to the planner is derived from these struct tags via gollem.ToSchema; Validate enforces the per-kind invariants the schema cannot (a plain JSON schema cannot say "materialize requires title + description").
type DecisionField ¶
type DecisionField struct {
FieldID string `json:"field_id" description:"The field id from the workspace schema." required:"true"`
Value string `json:"value,omitempty" description:"Scalar value (text / number / url / single select option id)."`
Values []string `json:"values,omitempty" description:"Multi-select option ids."`
}
DecisionField is one custom-field assignment emitted by a materialize decision. Value carries the scalar form (text / number / url / single select); Values carries the multi-select form. The host maps field_id to the workspace field schema to build the typed FieldValue.
type DecisionKind ¶
type DecisionKind string
DecisionKind discriminates the terminal action a mention turn resolves to. It is the `kind` field of the structured final output (Run[Decision]).
Closing / transitioning the case is NOT a Decision kind: that is a side effect the sub-agent performs during investigation via the case__update_case_status tool. The terminal Decision only covers the two host-applied outcomes — reply to the user, or materialize case content.
const ( // DecisionRespond posts Message as a reply in the case thread. DecisionRespond DecisionKind = "respond" // DecisionMaterialize writes Title / Description / Fields onto the Case // (the host applies it via CaseUC.MaterializeThreadCase). DecisionMaterialize DecisionKind = "materialize" )
type Handler ¶
type Handler interface {
// TraceAppend records a milestone line that must stay visible (planner
// rounds, task results) in the thread-side trace block.
TraceAppend(ctx context.Context, line string)
// TraceReplace overwrites the single transient activity line in place, so
// per-tool chatter ("Searching…", "Fetching…") does not accumulate.
TraceReplace(ctx context.Context, line string)
// Question posts a question to the user. Returning an error aborts the turn.
Question(ctx context.Context, ssn *model.Session, q QuestionPayload) error
// Create persists a new case for the ModeCreate flow and returns it. The host
// invokes it once, AFTER the turn completes, with the field values the
// in-loop finalizer already validated against the workspace schema. A returned
// error is a persistence failure the model cannot repair by re-emitting JSON,
// so it is NOT fed back for regeneration; the host surfaces it and falls back.
// Only called in ModeCreate turns.
Create(ctx context.Context, ssn *model.Session, p CreatePayload) (*model.Case, error)
}
Handler is the host-side surface for one thread-mode turn. TraceAppend / TraceReplace render progress lines; Question posts a question to the thread; Create commits a new case for the ModeCreate flow.
type HandlerFuncs ¶
type HandlerFuncs struct {
TraceAppendFn func(ctx context.Context, line string)
TraceReplaceFn func(ctx context.Context, line string)
QuestionFn func(ctx context.Context, ssn *model.Session, q QuestionPayload) error
CreateFn func(ctx context.Context, ssn *model.Session, p CreatePayload) (*model.Case, error)
}
HandlerFuncs is a struct-of-funcs adapter for tests and minimal hosts. Missing entries are treated as no-ops (Create errors when unset, since a ModeCreate turn cannot commit without it).
func (HandlerFuncs) Create ¶
func (h HandlerFuncs) Create(ctx context.Context, ssn *model.Session, p CreatePayload) (*model.Case, error)
func (HandlerFuncs) Question ¶
func (h HandlerFuncs) Question(ctx context.Context, ssn *model.Session, q QuestionPayload) error
func (HandlerFuncs) TraceAppend ¶
func (h HandlerFuncs) TraceAppend(ctx context.Context, line string)
func (HandlerFuncs) TraceReplace ¶
func (h HandlerFuncs) TraceReplace(ctx context.Context, line string)
type Mode ¶
type Mode int
Mode discriminates the purpose of a thread-mode turn.
const ( // ModeMention is a user @-mention in a case thread. The planner may // respond, update fields (materialize), or close the case. ModeMention Mode = iota // ModeMaterialize runs right after a case is auto-created from a // monitored-channel post. The planner investigates the message and emits // a materialize decision to fill title / description / fields. ModeMaterialize // ModeCreate runs when a monitored-channel post arrives but NO case exists // yet. The planner investigates / asks the user, and only commits a new // case (final create decision) once it can satisfy validation. ModeCreate )
type QuestionItem ¶
type QuestionItem struct {
ID string
Text string
Type QuestionItemType
Options []string
}
QuestionItem is one question within a QuestionPayload.
type QuestionItemType ¶
type QuestionItemType string
QuestionItemType discriminates how the host renders a question's answer control. Mirrors planexec.QuestionItemType values.
const ( QuestionItemSelect QuestionItemType = "select" QuestionItemMultiSelect QuestionItemType = "multi_select" QuestionItemFreeText QuestionItemType = "free_text" )
type QuestionPayload ¶
type QuestionPayload struct {
Reason string
Items []QuestionItem
}
QuestionPayload is forwarded to the host when the planner needs human input. The host posts it to the Slack thread; the turn then ends and the user resumes by mentioning the bot again (the conversation history is keyed on Session.ID so the next turn continues seamlessly).
type Result ¶
type Result struct {
Status Status
Decision *Decision
BusyOwner *model.Session
// Case is the newly created case for a ModeCreate turn that committed it
// (via Handler.Create inside the planner loop). Nil for other modes and
// for non-completed statuses.
Case *model.Case
// FallbackReason is the human-readable cause of a StatusFallback turn
// (propagated from planexec's RunResult.FallbackReason). Empty for other
// statuses. The host surfaces it to the user as the technical note of the
// fallback message instead of a generic error; it never crosses back into
// the loop.
FallbackReason string
}
Result is the outcome of RunTurn.
type Status ¶
type Status int
Status discriminates the terminal shapes RunTurn returns.
const ( // StatusCompleted means the turn finished and Decision is populated. StatusCompleted Status = iota // StatusBusy means another turn was running; BusyOwner is set. StatusBusy // StatusIdempotent means the trigger duplicates a live turn; drop silently. StatusIdempotent // StatusQuestion means the planner asked the user a question; the turn // ended and will resume on the next mention. Decision is nil. StatusQuestion // StatusFallback means the planner exhausted its budget or errored before // reaching a decision. Decision is nil. StatusFallback )
type TurnRequest ¶
type TurnRequest struct {
Session *model.Session
Workspace *model.WorkspaceEntry
Case *model.Case
ChannelID string
ThreadTS string
MentionTS string
MentionText string
SystemMessages []ConversationMessage
DeltaMessages []ConversationMessage
// TriggerTS is the Slack TS used as both the trace ID seed and the lock
// trigger key (duplicate-event dedup).
TriggerTS string
// Mode selects the turn purpose (materialize on creation vs mention).
Mode Mode
// CreateInstruction is an optional extra instruction appended to the
// ModeCreate planner system prompt (under a "# Trigger context" heading).
// It lets a host inject trigger-specific guidance the generic prompt cannot
// know — e.g. that the case was raised by a reaction on one message and the
// surrounding conversation must be read. Empty and ignored for other modes.
CreateInstruction string
Handler Handler
}
TurnRequest collects the inputs resolved by the host before handing control to the threadcase runtime.
type UseCase ¶
type UseCase struct {
// contains filtered or unexported fields
}
UseCase runs one thread-mode agent turn on top of the shared planexec runtime. It owns the per-thread turn lock and the planexec wiring; the host applies the returned Decision (post reply / update fields / close) through the case usecase.