agenttools

package
v0.1.5 Latest Latest
Warning

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

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

Documentation

Overview

Package agenttools is the agent's tool catalog: the bounded set of capabilities the LLM is allowed to invoke. It embodies two non-negotiable constraints in its very shape:

Anti-self-escalation. The catalog exposes ONLY read tools – scoped to the agent's own engagement – plus a single propose-only execute tool. There is NO tool to mutate scope, the authorization window, RoE, live-recon, credentials, or the approval mode; those are operator-only. The catalog literally holds no writer for any of them, so a hostile or confused model cannot widen its own authority by calling a tool. Data minimization. Scope data, tokens, and AUP records are NEVER returned to the LLM. The agent does not need the scope inventory to be useful – the safety gate enforces scope server-side regardless of what the model proposes – so it is never disclosed to the (third-party, untrusted) LLM provider. Read tools are locked to the session's engagement id; an engagement id is not even accepted as a tool argument.

Read tools return DATA (fed back to the model as a tool message). The execute tool (start_recon) returns a *ProposedAction ENVELOPE and runs nothing – the orchestrator MUST pass it through safety.Gate before any execution. Every dispatch is recorded in the append-only audit log as the agent (actor = "agent:<sessionID>").

Index

Constants

View Source
const (
	ToolListFindings            = "list_findings"
	ToolGetFindingDetail        = "get_finding_detail"
	ToolListSASTValidation      = "list_sast_validation"
	ToolPlanRuntimeVerification = "plan_runtime_verification"
	ToolListEvidence            = "list_evidence"
	ToolVerifyCustody           = "verify_custody"
	ToolListReconTools          = "list_recon_tools"
	ToolStartRecon              = "start_recon"
	ToolProposePlan             = "propose_plan"              // propose a multi-step recon plan (DAG); runs nothing
	ToolProposeFinding          = "propose_finding"           // record an UNPROVEN exploitation claim at score 0
	ToolReachabilityContext     = "reachability_context"      // read dep-graph reachability facts (T0/T1)
	ToolProposeReachability     = "propose_reachability"      // propose a reachability Judgment (score 0; verify is human-only)
	ToolProposeSASTValidation   = "propose_sast_validation"   // propose a gated CapSAST judgment for verifier review; score 0, no execution
	ToolProposeCritique         = "propose_critique"          // propose an adversarial critique Judgment against a finding (score 0)
	ToolEvidenceSufficiency     = "evidence_sufficiency"      // read-only advisory – what's missing for a finding to reach the bar
	ToolProposeRiskNarrative    = "propose_risk_narrative"    // propose a risk-narrative Judgment (ungated; a human accepts)
	ToolProposeThreat           = "propose_threat"            // propose a STRIDE threat Judgment over the architecture model (score 0; a human ratifies)
	ToolProposeWriteupDraft     = "propose_writeup_draft"     // propose a finding write-up DRAFT (prose) awaiting human sign-off (NOT a judgment)
	ToolProposeAttackChain      = "propose_attack_chain"      // propose an attack-chain HYPOTHESIS finding (score 0; gated until a human verifies)
	ToolProposeVexJustification = "propose_vex_justification" // propose an OpenVEX not_affected justification Judgment (score 0; a human ratifies)
)

Catalog tool names. These are the EXACT set advertised to the LLM; the anti-self-escalation test pins that no scope/credential/mode-mutating tool is ever added here.

View Source
const MaxPlanNodes = agent.MaxPlanNodes

MaxPlanNodes bounds how many nodes a single propose_plan may carry (mirrors the domain cap); a larger proposal is rejected, never truncated.

Variables

This section is empty.

Functions

This section is empty.

Types

type AgentToolset

type AgentToolset struct {
	Findings      findingProposer      // required: propose_finding
	Hypotheses    hypothesisProposer   // required: propose_attack_chain
	Reachability  scanResultReader     // required: reachability_context (read-only)
	Judgments     judgmentProposer     // optional (nil ⇒ off): propose_reachability/sast_validation/critique/risk_narrative/threat/vex_justification
	WriteupDrafts writeupdraftProposer // optional (nil ⇒ off): propose_writeup_draft
}

AgentToolset bundles the dependencies that switch on the agent's engagement-scoped tools beyond the always-available read tools. Both composition roots — the inline agent in synapse-api and the durable agent in synapse-worker — enable their orchestrator catalog through THIS one struct, so a given agent run advertises an IDENTICAL tool set no matter WHERE it executes. Before this, the worker enabled only planning + finding proposals while the API enabled the full set, so the same session saw a smaller toolset when driven durably — a correctness bug (issue #161).

Findings, Hypotheses and Reachability are REQUIRED: a durable run must never silently advertise fewer PROPOSAL tools than the inline run. Judgments and WriteupDrafts are OPTIONAL and mirror their feature flags — a nil field leaves that tool off in BOTH binaries (they are gated by SYNAPSE_JUDGMENTS_ENABLED / SYNAPSE_WRITEUP_DRAFTS_ENABLED). To keep a tool off, the composition root leaves the field nil; it must NOT assign a typed-nil service pointer (a non-nil interface wrapping a nil pointer), because that would wire a tool backed by nothing. Each field is a propose/read-only slice — the agent can never confirm.

type Catalog

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

Catalog dispatches the agent's tool calls: read tools against the engagement's own data, and start_recon into a gated proposal. It never executes a tool or mutates anything.

func New

func New(findings findingReader, evidences evidenceReader, reconTools []ports.ReconTool, audit ports.AuditLogger, clock ports.Clock, ids ports.IDGenerator) (*Catalog, error)

New validates its dependencies. reconTools is the SAME set wired into the recon use-case (passed by the composition root); duplicates are rejected.

func (*Catalog) Dispatch

func (c *Catalog) Dispatch(ctx context.Context, sess agent.Session, call agent.ToolCall) (Result, error)

Dispatch routes a single LLM tool call. The engagement is ALWAYS the session's; an engagement id is never read from the call arguments (anti cross-engagement self-escalation).

func (*Catalog) EnableAgentToolset

func (c *Catalog) EnableAgentToolset(t AgentToolset) error

EnableAgentToolset turns on planning + the proposal/read tools from a single dependency set, so the inline (API) and durable (worker) catalogs are wired identically — the durable/inline parity guarantee (issue #161). It FAILS CLOSED: a missing REQUIRED dependency returns an error rather than advertising a partial toolset (a durable agent with fewer tools than the inline one is a correctness bug, not a valid degraded mode). Planning is always on — both composition roots pair it with an orchestrator PlanStore.

It calls the same narrow Enable* setters a composition root would, so the propose-only, never-confirm invariant is unchanged: the agent still only proposes (score 0); a distinct human/verifier confirms out of band. Optional deps are wired only when non-nil, matching each feature flag.

func (*Catalog) EnableFindingProposals

func (c *Catalog) EnableFindingProposals(p findingProposer)

EnableFindingProposals turns on the propose_finding tool. The agent can then RECORD an unproven exploitation claim (score 0); it still cannot raise its own score or confirm – that needs a distinct verifier out of band. The composition root supplies the exploitation service.

func (*Catalog) EnableHypotheses

func (c *Catalog) EnableHypotheses(p hypothesisProposer)

EnableHypotheses turns on the propose_attack_chain tool. The agent can then record an attack-chain HYPOTHESIS (a Kind=hypothesis finding at score 0, linking constituent findings); it still cannot raise the score or confirm – a distinct human verifies it out of band (the same gate as an exploitation finding).

func (*Catalog) EnableJudgments

func (c *Catalog) EnableJudgments(p judgmentProposer)

EnableJudgments turns on the propose_reachability + propose_sast_validation + propose_critique + propose_risk_narrative + propose_threat tools: the agent can RECORD a judgment (score 0); it still cannot raise the score or confirm – a distinct human reviewer verifies it via PermReview, out of band. The composition root supplies the analysis service.

func (*Catalog) EnablePlanning

func (c *Catalog) EnablePlanning()

EnablePlanning turns on the propose_plan tool. The composition root calls this for the orchestrator's catalog when (and only when) it also wires a PlanStore + SetPlanStore, so the catalog never advertises a capability the orchestrator cannot drive. Left off for the MCP/read-only catalog, which has no planner.

func (*Catalog) EnableReachability

func (c *Catalog) EnableReachability(r scanResultReader)

EnableReachability turns on the reachability_context read tool: the agent can read the engagement's dependency-graph FACTS for a vulnerable package (dep path, direct flag, scope, manifest location) to reason about T0/T1 reachability. Read-only – it forms NO verdict (the agent proposes a ReachabilityClaim out of band). The composition root supplies the scan-result store.

func (*Catalog) EnableWriteupDrafts

func (c *Catalog) EnableWriteupDrafts(p writeupdraftProposer)

EnableWriteupDrafts turns on the propose_writeup_draft tool: the agent can DRAFT a finding's description + remediation PROSE as a proposal (awaiting human sign-off). It still cannot edit, accept, or reject a draft – those are human actions behind PermReview + SoD (the proposer cannot sign off its own draft). The composition root supplies the writeupdraft service (which satisfies the narrow proposer).

func (*Catalog) ProposeForNode

func (c *Catalog) ProposeForNode(sess agent.Session, node agent.PlanNode) (agent.ProposedAction, error)

ProposeForNode rebuilds the approval-required ProposedAction for a plan node at execution time, reusing the node's STABLE ActionID so the evidence-chain idempotency holds across redeliveries. It runs + audits nothing (the plan was audited at proposal); the orchestrator admits the result through safety.Gate. Same argv/target guards as start_recon (one source).

func (*Catalog) Tools

func (c *Catalog) Tools() []agent.ToolSchema

Tools returns the JSON-schema tool definitions advertised to the LLM. The read tools take no arguments – they operate on the session's engagement, which is never an LLM-supplied id.

type Result

type Result struct {
	Data     json.RawMessage
	Proposal *agent.ProposedAction
	// Plan is set by propose_plan: a validated, NOT-yet-persisted execution DAG (Go minted the
	// node ids, classified risk, and validated acyclicity). It has executed nothing – the
	// orchestrator persists it and drives each node through safety.Gate.Admit.
	Plan *agent.Plan
}

Result is a tool dispatch outcome. Exactly one field is set: Data for a read tool (JSON to feed back to the model as a tool message), or Proposal for an execute tool (an approval-required envelope the orchestrator must run through safety.Gate). A non-nil Proposal has executed NOTHING.

Jump to

Keyboard shortcuts

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