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.
Package threadcase hosts the thread-mode case agents: the one that materialises a new Case from the conversation that triggered it, and the one that answers a mention on an existing Case. Both are plan-and-execute agents running on the agentkit runtime.
A turn is a durable process: StartTurn spawns it and returns, and its decision is applied by the completion handler through the Host port. Slack SDK imports are forbidden here; the usecase layer owns the Slack service and i18n.
Index ¶
- type ConversationMessage
- type CreateDecision
- type CreatePayload
- type Decision
- type DecisionField
- type DecisionKind
- type Durable
- 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 Host
- type Mode
- type QuestionItem
- type QuestionItemType
- type QuestionPayload
- type Result
- type Status
- type Target
- type TurnRequest
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 Durable ¶ added in v0.3.0
type Durable struct {
// contains filtered or unexported fields
}
Durable runs the thread-mode agents on the agentkit runtime: one plan-execute agent for a mention on an existing case, and one for materialising a new case from the conversation that triggered it.
It coexists with the in-process planexec runner: a deployment that has not wired this keeps taking UseCase.RunTurn's synchronous path.
func NewDurable ¶ added in v0.3.0
func NewDurable(repo interfaces.Repository, registry *model.WorkspaceRegistry, host Host, locator agentkernel.Locator, models agentkernel.ModelPolicy, ) (*Durable, error)
NewDurable builds the durable thread-mode host. The registry is required because the create turn validates its proposed field values against the workspace schema, and a durable run resolves that schema from its own scope rather than from a value captured at spawn.
locator is used only to tell a re-delivered Slack event from a busy thread; a nil locator makes every delivery look fresh, which the idempotency key still covers.
func (*Durable) Bind ¶ added in v0.3.0
func (d *Durable) Bind(k *agentkit.Kernel, probe *agentkernel.ToolSetProbe)
Bind hands over the Kernel the registered agents run on, and the probe that tells this host which toolset ids actually resolve to a tool for a given run.
func (*Durable) Register ¶ added in v0.3.0
func (d *Durable) Register( reg *agentkit.Registry, taskAgent agentkit.Agent[react.Input], progress planexec.Progress, limiter agentkit.Limiter, store agentkit.HistoryStore, ) error
Register registers both thread-mode agents and wires this host as their completion handler. Call it before building the Kernel, and Bind after.
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 Host ¶ added in v0.3.0
type Host interface {
// ApplyMention applies a completed mention turn's terminal decision: post the
// reply, or write the proposed content onto the case and confirm it.
ApplyMention(ctx context.Context, target Target, decision *Decision) error
// CreateCase commits a completed create turn's proposal and posts its outcome.
CreateCase(ctx context.Context, target Target, payload CreatePayload) error
// AskQuestion posts the planner's question and records it on the session. The
// turn has ended; the user's answer starts the next one.
AskQuestion(ctx context.Context, target Target, question QuestionPayload) error
// ReportFallback tells the user the turn reached no conclusion. reason is the
// technical cause; the host decides how much of it to show.
ReportFallback(ctx context.Context, target Target, reason string) error
}
Host is the Slack-facing surface a finished thread-mode turn needs. Each method is called at most once per turn, from the completion handler.
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
// BusyOwner is the session whose turn holds the thread, set only on
// StatusBusy.
BusyOwner *model.Session
}
Result is the outcome of StartTurn.
type Status ¶
type Status int
Status discriminates what StartTurn did.
const ( // StatusStarted means the turn was spawned; its decision is applied by the // run's own completion handler, so the caller has nothing to apply or post. StatusStarted Status = iota // StatusBusy means another turn holds this thread; BusyOwner names its session. StatusBusy // StatusIdempotent means the trigger duplicates a turn already started; drop // it silently. StatusIdempotent )
type Target ¶ added in v0.3.0
type Target struct {
WorkspaceID string
// CaseID is 0 for a create turn: the case does not exist yet.
CaseID int64
SessionID string
// ChannelID / ThreadTS are the run's own thread — the case thread, which the
// Session is keyed on and where the case's content belongs.
ChannelID string
ThreadTS string
// UIChannelID / UIThreadTS are the thread the requester is watching. They
// differ from the pair above only for a case raised by a reaction in another
// channel; otherwise they hold the same values.
UIChannelID string
UIThreadTS string
// ProcessID is the run that produced this outcome. A host that records a
// question needs it so the turn started by the answer can inherit this run's
// conversation.
ProcessID string
}
Target locates a finished run: the thread it reports into, and the case and session it belongs to.
It is rebuilt from the Process metadata rather than captured at spawn, because the completion handler runs after the turn — possibly on another instance, where the spawning call's variables no longer exist.
type TurnRequest ¶
type TurnRequest struct {
Session *model.Session
Workspace *model.WorkspaceEntry
Case *model.Case
ChannelID string
ThreadTS string
// UIChannelID / UIThreadTS locate the thread the requester is watching, for the
// one flow where that is not ChannelID / ThreadTS: a case raised by a reaction
// lives in the monitored channel while the reactor watches the thread they
// reacted in. Progress, questions and failure notices go there. Empty means the
// two are the same thread.
UIChannelID string
UIThreadTS string
MentionTS string
// MentionText is the raw text of the mention that triggered this turn.
MentionText string
// MentionUserID / MentionUserName identify its author. The ID is what makes
// a self-referential request ("assign me") actionable: case__assign takes
// Slack user IDs and no tool resolves a display name into one. It is also the
// run's access actor.
MentionUserID string
MentionUserName string
SystemMessages []ConversationMessage
DeltaMessages []ConversationMessage
// TriggerTS is the Slack TS of the event that started this turn. It is the
// idempotency key, which is what makes a re-delivered Slack event resolve to
// the run it already started instead of starting a second one.
TriggerTS string
// InheritFrom continues a finished run's conversation in this one. It is how an
// answered question resumes: the answering turn is a NEW run — its own budget,
// its own record — but it must see the request, the investigation and the
// question that produced it. Empty starts a fresh conversation.
InheritFrom 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
}
TurnRequest collects the inputs resolved by the host before handing control to the threadcase runtime.