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
- Variables
- func IsKnownToolSetID(id string) bool
- type LLMCallCounter
- func (c *LLMCallCounter) AddEvent(_ context.Context, _ string, _ any)
- func (c *LLMCallCounter) EndAgentExecute(_ context.Context, _ error)
- func (c *LLMCallCounter) EndChildAgent(_ context.Context, _ error)
- func (c *LLMCallCounter) EndLLMCall(_ context.Context, _ *trace.LLMCallData, _ error)
- func (c *LLMCallCounter) EndSubAgent(_ context.Context, _ error)
- func (c *LLMCallCounter) EndToolExec(_ context.Context, _ map[string]any, _ error)
- func (c *LLMCallCounter) Finish(_ context.Context) error
- func (c *LLMCallCounter) LLMCalls() int64
- func (c *LLMCallCounter) StartAgentExecute(ctx context.Context) context.Context
- func (c *LLMCallCounter) StartChildAgent(ctx context.Context, _ string) context.Context
- func (c *LLMCallCounter) StartLLMCall(ctx context.Context) context.Context
- func (c *LLMCallCounter) StartSubAgent(ctx context.Context, _ string) context.Context
- func (c *LLMCallCounter) StartToolExec(ctx context.Context, _ string, _ map[string]any) context.Context
- type TaskContext
- type ToolSetDeps
- type ToolSetResolver
Constants ¶
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 ¶
var KnownToolSetIDs = []string{ ToolSetCoreRO, ToolSetSlackRO, ToolSetNotion, ToolSetGitHub, ToolSetWebFetch, ToolSetJira, }
KnownToolSetIDs is the canonical list of identifiers a planner is allowed to request. Anything outside this list is rejected at plan validation.
var KnownToolSetIDsAssist = []string{ ToolSetCore, ToolSetSlackWrite, ToolSetNotion, ToolSetGitHub, ToolSetWebFetch, ToolSetJira, }
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.
var KnownToolSetIDsCaseChannel = []string{ ToolSetCore, ToolSetSlackRO, ToolSetNotion, ToolSetGitHub, ToolSetWebFetch, ToolSetJira, ToolSetCaseWrite, ToolSetMemo, ToolSetKnowledge, }
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.
var KnownToolSetIDsJob = []string{ ToolSetCoreJob, ToolSetCaseWrite, ToolSetSlackPost, ToolSetSlackRO, ToolSetNotion, ToolSetWebFetch, ToolSetJira, ToolSetMemo, ToolSetKnowledge, }
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.
var KnownToolSetIDsNoCore = []string{ ToolSetSlackRO, ToolSetNotion, ToolSetGitHub, ToolSetWebFetch, ToolSetJira, }
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.
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.
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.
var KnownToolSetIDsWorkspaceChannel = []string{ ToolSetCaseMulti, ToolSetSlackRO, ToolSetNotion, ToolSetGitHub, ToolSetWebFetch, ToolSetJira, }
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 ¶
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 (*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 (*LLMCallCounter) StartLLMCall ¶
func (c *LLMCallCounter) StartLLMCall(ctx context.Context) context.Context
func (*LLMCallCounter) StartSubAgent ¶
func (*LLMCallCounter) StartToolExec ¶
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). |