Imported from zeroroot-ai/cve-triage (
AGENTS.md). Install upstream withnpx skills add zeroroot-ai/cve-triage. Copyright stays with the author.
AGENTS.md — cve-triage
This directory is a Gibson agent. An agent is a stateful, LLM-driven
gRPC process the Gibson daemon dials when its node in a mission DAG
becomes active. The daemon supplies a Harness to your Execute()
function; the harness owns LLMs, tools, plugins, and the typed
observation emit path into the World (ADR-0007).
This file is the contract. If a doc and the SDK source disagree, the SDK source wins — paths below are bare so you can grep them.
What you implement
A type satisfying agent.Agent, defined in
core/sdk/agent/agent.go. Or — much shorter — use the builder pattern:
sdk.NewAgent(
sdk.WithName("cve-triage"),
sdk.WithVersion("0.1.0"),
sdk.WithDescription("..."),
sdk.WithLLMSlot("primary", llm.SlotRequirements{MinContextWindow: 8000}),
sdk.WithExecuteFunc(execute),
)
The builder is in core/sdk/agent/builder.go; root re-exports are in
core/sdk/gibson.go and core/sdk/options.go.
The Execute function
func execute(ctx context.Context, h agent.Harness, task agent.Task) (agent.Result, error)
task.Goal (string) is the LLM-generated objective for this run. Return
agent.Result{Status: agent.StatusCompleted, Output: ...} or surface
the error.
The Harness — your single API surface
Defined in core/sdk/agent/harness.go. The harness gives you:
- LLM access —
h.Complete(ctx, slot, messages, opts...),h.CompleteWithTools(ctx, slot, messages, tools),h.CompleteStructured(...). Slot names matchWithLLMSlot(e.g."primary"). The harness resolves slots to concrete providers (Anthropic / OpenAI / Gemini / Ollama) at runtime. - Tool execution —
h.ExecuteTool(ctx, name, input proto.Message). Tools are remote, called via Redis work queue. You get the response back asproto.Message; assert to your generated type. - Plugin queries — agents do not invoke plugins directly; ask the
daemon to dispatch via
h.QueryPlugin(...). - Observations —
h.Observe(ctx, obs)emits a typed observation (host/domain/subdomain/credential/account, with ports/services as sub-state) into the World. You report what you saw; the brain resolves identity and topology. You do not author graph nodes or edges, and you do not read the graph back — the relevant world state is ambiently projected to you (ADR-0001, ADR-0007). Seecore/sdk/agent/observation.go. - Findings —
h.SubmitFinding(ctx, f). A finding is an emit too; it flows into the World as an observation (you don't query findings back). - Sub-agents —
h.DelegateToAgent(ctx, name, task). - Observability —
h.Logger()(slog),h.Tracer()(OTel).
The full LLM types live in core/sdk/llm/ (Message, RoleSystem/User/
Assistant, SlotRequirements, MinContextWindow, FeatureToolUse).
LLM slots
You declare what the agent needs; the platform decides what to give
it. See core/sdk/llm/slot.go. Common features:
| Feature | When to require |
|---|---|
tool_use |
You'll call CompleteWithTools |
vision |
You'll send images |
streaming |
Need token-stream callbacks |
json_mode |
Use CompleteStructured |
Lifecycle
Agents are stateful. Optional methods (defaults supplied if you don't override):
Initialize(ctx, AgentConfig) error— once per process startHealth(ctx) types.HealthStatus— readiness probeShutdown(ctx) error— graceful drain
Dispatch, not enrollment
This agent is dispatched, not enrolled (ADR-0016). It has no bootstrap
token, no host key, no ~/.gibson/agent/ state, and no run loop. Gibson
launches the image once per mission run inside an ephemeral setec sandbox,
hands it a capability grant scoped to that tenant and run, and reaps the
sandbox when the process exits.
The scaffold's enrollment flow — mint a bootstrap token, gibson component register, then gibson component run serving gRPC on 50051 — describes
dispatchMode: agent, a long-lived registered worker. That is a different
component with different isolation properties, and this one is not it. A
triage agent that runs after every scan should hold nothing between runs,
whereas an enrolled identity stands permanently once a human establishes it.
The launch contract is gibson's, defined by the envAgent* constants in
internal/engine/harness/sandboxed/agent.go. Gibson is the only writer of
these names:
| Variable | Carries |
|---|---|
GIBSON_CG_JWT |
the per-dispatch capability grant — the whole credential |
GIBSON_CALLBACK_ENDPOINT |
the HarnessCallbackService address to dial back |
GIBSON_AGENT_TASK_B64 |
base64 protojson of the gibson.types.v1.Task |
GIBSON_MODEL |
the model resolved for the tenant at dispatch |
GIBSON_MISSION_ID / GIBSON_MISSION_RUN_ID / GIBSON_AGENT_RUN_ID |
run scope for provenance |
internal/dispatch reads it. A missing grant fails the launch rather than
dialing unauthenticated, because an unauthenticated dial surfaces as a
transport error from inside a pass instead of "this run was launched without
a credential".
Decode the task through proto. Task.context and Task.metadata are
map<string, TypedValue> and arrive shaped {"stringValue":"…"}. Reading
them as plain strings drops every key silently: the agent starts, finds no
Application, and reports a clean pass over nothing.
To run it by hand, set those variables and execute the binary; there is no CLI wrapper and nothing to register first.
Do not
- Call the daemon directly outside the harness — the harness is the contract; raw gRPC dials skip authz interceptors.
- Read or write secrets via env vars — your runtime credential is your only credential channel; LLM API keys are owned by the daemon and reach you only via slot-resolved completions.
- Reintroduce a polling or enrolled path beside the dispatched one. The cutover deleted it; a flag gating both is the parallel codepath ADR-0027 forbids.
- Open Neo4j / Redis / etcd directly. The harness wraps everything.
- Use
replacedirectives ingo.mod, or add a workspace-rootgo.work. Polyrepo discipline pins by SDK tag.
Where to look in the SDK
| Topic | Path |
|---|---|
| Agent interface | core/sdk/agent/agent.go |
| Harness API | core/sdk/agent/harness.go |
| Builder + options | core/sdk/agent/builder.go, core/sdk/options.go |
| LLM types | core/sdk/llm/ |
| Observation types | core/sdk/agent/observation.go |
| Result + Task types | core/sdk/agent/types.go |
| Serve (gRPC) | core/sdk/serve/serve.go |
| Runtime enrollment (CG) | core/sdk/capabilitygrant/ |
Naming convention
Per the polyrepo steering (structure.md), agents follow
{domain}-{function} — e.g. network-recon, prompt-injector. The
DNS-label regex ^[a-z][a-z0-9-]{0,61}[a-z0-9]$ is enforced by
gibson component init.
