threadcase

package
v0.1.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jul 27, 2026 License: Apache-2.0 Imports: 20 Imported by: 0

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

Constants

This section is empty.

Variables

This section is empty.

Functions

This section is empty.

Types

type ConversationMessage

type ConversationMessage struct {
	Timestamp string
	UserID    string
	UserName  string
	Text      string
}

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").

func (Decision) Validate

func (d Decision) Validate() error

Validate enforces the mention decision's per-kind invariants so a malformed terminal output is rejected inside planexec's Run[Decision] regeneration loop rather than producing an empty reply or a blank materialize. It satisfies planexec.Validatable.

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.

func New

func New(deps *agent.CommonDeps, runner *planexec.Runner) (*UseCase, error)

New builds a threadcase UseCase. Both deps and runner are required.

func (*UseCase) RunTurn

func (uc *UseCase) RunTurn(ctx context.Context, req TurnRequest) (*Result, error)

RunTurn executes one thread-mode turn: acquire the per-thread lock, run the planexec loop with read-only sub-agent tools, and return the parsed terminal Decision for the host to apply.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL