agent

package
v0.3.0 Latest Latest
Warning

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

Go to latest
Published: Sep 7, 2026 License: Apache-2.0 Imports: 22 Imported by: 0

Documentation

Overview

Package agent contains the Slack-independent pieces shared by the agent hosts (`casebound`, `threadcase`, `proposal`, `wsagent`, `job`): the toolset resolver every host builds its sub-agent tools from, and the LLM call counter the plan-execute runtime reports through.

Slack SDK / pkg/service/slack imports are forbidden inside this package; hosts communicate with their Slack side through their own small Host interfaces (e.g. proposal.Host, threadcase.Host).

Index

Constants

View Source
const (
	ToolSetCoreRO   = "core_ro"
	ToolSetSlackRO  = "slack_ro"
	ToolSetNotion   = "notion"
	ToolSetGitHub   = "github"
	ToolSetWebFetch = "webfetch"
	ToolSetJira     = "jira"
	// ToolSetCore is the FULL action toolset (create / update / archive), as
	// opposed to the read-only core_ro handed to investigation sub-agents. Only
	// the channel-mode case agent asks for it: a thread-mode workspace manages
	// no Actions at all.
	ToolSetCore = "core"
	// ToolSetMemo is the Case-scoped memo toolset (memo__*). Built only when a
	// memo mutator and a memo schema are configured for the workspace.
	ToolSetMemo = "memo"
	// ToolSetKnowledge is the workspace knowledge toolset INCLUDING the write
	// tools. The read tools are always available to every agent regardless of
	// what it requested (see Resolve); this ID is what additionally grants
	// create/update, and a host withholds it while processing a private case so
	// that case's contents cannot leak into workspace-wide knowledge.
	ToolSetKnowledge = "knowledge"
	// ToolSetCaseWrite is the writer toolset for the single case the turn is
	// pinned to: the full casewriter set (case__update_case, case__assign,
	// case__unassign, and the mode-appropriate case__update_case_status /
	// case__close_case). Every mention-driven host grants it, so a sub-agent can
	// carry out any case edit the user asked for. Note that a mention turn's
	// terminal `materialize` decision replaces title / description wholesale, so
	// a host offering both must tell the planner to pick one path per turn (see
	// threadcase's system prompt).
	ToolSetCaseWrite = "case_write"
	// ToolSetCaseMulti is the cross-case ("workspace-scoped") toolset used by the
	// workspace-channel agent. Unlike core/case_write (pinned to one case),
	// its tools take case_id as a call-time argument so a single turn can operate
	// across every case the requesting user can access. Advertised only via
	// KnownToolSetIDsWorkspaceChannel (never the default lists) so it is not
	// offered to the per-case mention / proposal planners.
	ToolSetCaseMulti = "case_multi"
	// ToolSetSlackWrite is the read-only Slack set PLUS slack__post_message,
	// pinned to the channel of the case the run is on. Like ToolSetKnowledge it
	// REPLACES the read-only set rather than adding to it, so no tool is offered
	// twice. Only the assist agent asks for it: assist exists to write its
	// findings back into the case channel, whereas a mention turn's reply is
	// posted by its host, not by a tool the model may call at will.
	ToolSetSlackWrite = "slack_write"
	// ToolSetCoreJob is the action toolset an UNATTENDED run gets: the read tools
	// plus create / update / status / assignee / steps, but NOT archive,
	// unarchive or delete_action_step. Those three are withheld because a Job
	// acts on its own judgement with nobody reviewing it, and a wrong archive is
	// work the team can no longer see.
	ToolSetCoreJob = "core_job"
	// ToolSetSlackPost is the channel-pinned poster (slack__post_to_case_channel).
	// A Job's output reaches people only through it, which is why an unattended
	// run has it while an interactive turn — whose reply its host posts — does not.
	ToolSetSlackPost = "slack_post"
	// ToolSetWSMeta is the workspace-metadata read set (list_workspaces /
	// get_workspace). It is what the case-draft planner picks a workspace with:
	// that flow is not pinned to one workspace, so it must read the candidates'
	// field schemas and configured sources before it can propose anything.
	ToolSetWSMeta = "wsmeta"
)

ToolSet IDs known to the planner. Sub-agents request a subset of these per investigation task and the resolver below maps each ID to a concrete []gollem.Tool slice.

Variables

KnownToolSetIDs is the canonical list of identifiers a planner is allowed to request. Anything outside this list is rejected at plan validation.

KnownToolSetIDsAssist is the palette of the assist agent: the mutating action tools, Slack read plus post, and the read-only auxiliary sets. It deliberately omits case_write, memo and knowledge — assist runs unattended on every open case of a workspace, and today's assist agent has none of them.

KnownToolSetIDsCaseChannel is the full palette of the channel-mode case agent: the mutating action tools, the single-case writer tools, memos, knowledge writes, and every read-only auxiliary set. Unlike the planner-facing lists above it is not a menu an LLM chooses from — the channel-mode agent runs a single ReAct loop and is handed the whole set at once.

KnownToolSetIDsJob is the palette of an unattended Job run. It matches what the pre-agentkit buildJobTools assembled: the Job-safe action set, the case writer, the channel-pinned poster, Slack reads, the read-only integrations, memos and knowledge. Compared with the interactive mention agent it withholds archive / unarchive / delete_action_step (see ToolSetCoreJob) and GitHub.

KnownToolSetIDsNoCore is KnownToolSetIDs without the core (action) toolset. Thread-mode agents advertise this list to the planner: a thread-mode workspace manages no Actions, so the planner must never be offered the core read tools (list/get action). Paired with ToolSetDeps.OmitCore so the resolver also withholds the underlying tools.

View Source
var KnownToolSetIDsProposal = append(append([]string{}, KnownToolSetIDs...), ToolSetWSMeta)

KnownToolSetIDsProposal is the palette of the case-draft agent. It is KnownToolSetIDs plus wsmeta: the draft flow is not pinned to a workspace, so reading the candidates' field schemas and configured sources is the first thing it must do.

View Source
var KnownToolSetIDsThreadWrite = append(append([]string{}, KnownToolSetIDsNoCore...), ToolSetCaseWrite)

KnownToolSetIDsThreadWrite is KnownToolSetIDsNoCore plus the case writer toolset. Thread-mode agents advertise this to the planner on mention turns, where a concrete case exists to act on and a human asked for the change: the sub-agent may then edit, assign, and transition that case. Materialize and creation turns advertise the plain KnownToolSetIDsNoCore instead, so the planner is never offered a writer tool the resolver cannot wire — the prompt-vs-capability mismatch the architecture rule forbids.

KnownToolSetIDsWorkspaceChannel is the planner-advertised list for the workspace-channel agent: the cross-case toolset plus the read-only auxiliary toolsets. It deliberately omits core_ro (the case-pinned action read tools) — case_multi carries the cross-case action tools instead.

Functions

func IsKnownToolSetID

func IsKnownToolSetID(id string) bool

IsKnownToolSetID reports whether id is a member of KnownToolSetIDs.

Types

type LLMCallCounter

type LLMCallCounter struct {
	// contains filtered or unexported fields
}

LLMCallCounter is a minimal gollem trace.Handler that does nothing except count StartLLMCall invocations. It is attached to sub-agents (via gollem.WithTrace) so the driver can read back how many ReAct loops the sub-agent burned through and surface the number in the user-facing trace.

The counter is safe for concurrent use; the underlying value is an atomic int64.

func NewLLMCallCounter

func NewLLMCallCounter() *LLMCallCounter

NewLLMCallCounter returns a fresh counter ready to be passed to gollem.

func (*LLMCallCounter) AddEvent

func (c *LLMCallCounter) AddEvent(_ context.Context, _ string, _ any)

func (*LLMCallCounter) EndAgentExecute

func (c *LLMCallCounter) EndAgentExecute(_ context.Context, _ error)

func (*LLMCallCounter) EndChildAgent

func (c *LLMCallCounter) EndChildAgent(_ context.Context, _ error)

func (*LLMCallCounter) EndLLMCall

func (c *LLMCallCounter) EndLLMCall(_ context.Context, _ *trace.LLMCallData, _ error)

func (*LLMCallCounter) EndSubAgent

func (c *LLMCallCounter) EndSubAgent(_ context.Context, _ error)

func (*LLMCallCounter) EndToolExec

func (c *LLMCallCounter) EndToolExec(_ context.Context, _ map[string]any, _ error)

func (*LLMCallCounter) Finish

func (c *LLMCallCounter) Finish(_ context.Context) error

func (*LLMCallCounter) LLMCalls

func (c *LLMCallCounter) LLMCalls() int64

LLMCalls returns the number of StartLLMCall invocations seen so far.

func (*LLMCallCounter) StartAgentExecute

func (c *LLMCallCounter) StartAgentExecute(ctx context.Context) context.Context

func (*LLMCallCounter) StartChildAgent

func (c *LLMCallCounter) StartChildAgent(ctx context.Context, _ string) context.Context

func (*LLMCallCounter) StartLLMCall

func (c *LLMCallCounter) StartLLMCall(ctx context.Context) context.Context

func (*LLMCallCounter) StartSubAgent

func (c *LLMCallCounter) StartSubAgent(ctx context.Context, _ string) context.Context

func (*LLMCallCounter) StartToolExec

func (c *LLMCallCounter) StartToolExec(ctx context.Context, _ string, _ map[string]any) context.Context

type TaskContext added in v0.3.0

type TaskContext struct {
	WorkspaceID string
	// CaseID is the case the run works on. Zero when there is none yet (a
	// case-draft turn) or the run spans several (the workspace-channel agent).
	CaseID int64
	// SlackChannelID is the channel the run's Slack tools are pinned to.
	SlackChannelID string
	// SlackThreadTS is the thread the run's conversation lives in: the case
	// thread for a thread-mode case, the triggering thread for a mention.
	SlackThreadTS string
}

TaskContext is the identifier block a plan-execute host hands to every sub-agent it spawns (planexec.Input.TaskContext).

It exists because a sub-agent's system prompt is built from the planner's task text alone, while its tools are pinned to the run's subject by the kernel tool factory. Without the identifiers a task told to read the case conversation has to invent a channel id and a message timestamp, and slack__get_messages then rejects the call ("targets[0] requires both channel_id and ts") or looks up a message that does not exist. The host knows the values its tools were pinned to, so the host supplies them.

Every field is optional: a run that is not pinned to a case leaves CaseID zero, and a workspace with no Slack leaves the Slack fields empty. Render then omits those lines rather than emitting an empty value the model could pass on.

func (TaskContext) IsZero added in v0.3.0

func (c TaskContext) IsZero() bool

IsZero reports whether there is nothing worth telling a sub-agent.

func (TaskContext) Render added in v0.3.0

func (c TaskContext) Render() (string, error)

Render returns the block, or an empty string when there is nothing to say — so a host can pass the result through unconditionally and the sub-agent prompt simply omits the section.

type ToolSetDeps

type ToolSetDeps struct {
	Core      core.Deps
	Slack     slacktool.Deps
	Notion    notiontool.Deps
	GitHub    *githubtool.Client
	WebFetch  *webfetch.Client
	Knowledge knowledgetool.Deps

	// Jira carries the already-expanded Jira read tools (see
	// pkg/agent/tool/jira). nil/empty means Jira is not configured, so the
	// "jira" ToolSet ID resolves to nothing.
	Jira []gollem.Tool

	// SlackPost backs the slack_post toolset. Built when a poster and a channel
	// are both known; a zero value leaves the toolset empty so requesting the id
	// resolves to nothing rather than to a tool that posts nowhere.
	SlackPost slackpost.Deps

	// CaseWrite backs the case_write toolset (the full single-case writer set).
	// The tools are built when CaseUC and CaseID identify a concrete case; a zero
	// value (no case yet, or no mutator wired) leaves the toolset empty so
	// requesting the ID resolves to nothing. StatusSet selects the mode-specific
	// "mark done" tool (case__update_case_status when set, case__close_case when
	// not) and Schema drives case__update_case's custom-field coercion.
	CaseWrite casewriter.Deps

	// OmitCore omits the core (action) toolset entirely. Set by thread-mode
	// agents: a thread-mode workspace manages no Actions, so even the
	// read-only list/get-action tools must not exist. Without this the
	// resolver would always build them (they only need Repo), since the
	// core read tools do not depend on ActionUC being wired.
	OmitCore bool

	// CaseMulti backs the case_multi (cross-case) toolset. Built only when
	// CaseMulti.CaseUC is non-nil (the workspace-channel host wires it); a nil
	// CaseUC leaves the toolset empty so requesting the ID resolves to nothing.
	CaseMulti casemulti.Deps

	// Memo backs the memo toolset. Built only when Memo.MemoUC and a memo
	// schema are present; a zero value leaves the toolset empty so requesting
	// the ID resolves to nothing.
	Memo memotool.Deps

	// WSMeta backs the wsmeta toolset (list_workspaces / get_workspace). Only the
	// case-draft flow needs it: every other host already knows which workspace it
	// runs in, and hands that workspace's schema to its tools directly.
	WSMeta wsmeta.Deps
}

ToolSetDeps carries the per-turn deps that flavor each toolset's binding. Optional fields (SlackSearch / NotionClient / GitHubClient) may be nil; the corresponding toolset is empty in that case.

type ToolSetResolver

type ToolSetResolver struct {
	// contains filtered or unexported fields
}

ToolSetResolver builds gollem.Tool slices for sub-agents based on a list of ToolSet IDs. The resolver is created once per turn (with the deps that vary per turn — workspace, case, slack/notion/github clients) and called per sub-agent.

func NewToolSetResolver

func NewToolSetResolver(d ToolSetDeps) *ToolSetResolver

NewToolSetResolver builds the per-toolset slices once so each sub-agent just picks the union of its requested IDs. The "core" pool is the read-only subset (list / get only) — investigation sub-agents must not mutate the case while a turn is forming.

func (*ToolSetResolver) Has added in v0.3.0

func (r *ToolSetResolver) Has(id string) bool

Has reports whether id contributes at least one tool. It is what a host filters its planner-facing palette with, so an id that would resolve to nothing is never offered as a choice.

It deliberately does NOT count the knowledge read tools Resolve always includes: those are present for every sub-agent regardless of what was requested, so counting them would report every id as available.

ToolSetKnowledge is the one id whose availability is not its own slice — requesting it REPLACES that always-present base with the read+write set — so it is answered by whether that write-bearing set was built.

func (*ToolSetResolver) Resolve

func (r *ToolSetResolver) Resolve(ids []string) []gollem.Tool

Resolve returns the concatenated tool list for the requested IDs. Unknown IDs are skipped (they should already have been rejected by plan validation, but Resolve never panics so a stray ID does not crash a turn).

Directories

Path Synopsis
Package casebound hosts the case-channel agent: a Slack mention in the channel of a channel-mode Case.
Package casebound hosts the case-channel agent: a Slack mention in the channel of a channel-mode Case.
Package job is the event-driven Agent Job runtime.
Package job is the event-driven Agent Job runtime.
Package planexec hosts the reusable plan-and-execute loop shared by the proposal (case-draft) host and the planexec-strategy Job host.
Package planexec hosts the reusable plan-and-execute loop shared by the proposal (case-draft) host and the planexec-strategy Job host.
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).
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).
Package wsagent hosts the workspace-channel agent: the plan-and-execute agent that runs when the bot is mentioned in a channel-mode workspace's configured workspace channel ([slack] workspace_channel).
Package wsagent hosts the workspace-channel agent: the plan-and-execute agent that runs when the bot is mentioned in a channel-mode workspace's configured workspace channel ([slack] workspace_channel).

Jump to

Keyboard shortcuts

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