ZeroRoot Docs

Your First Agent

Build a Gibson agent with the SDK, the Agent contract, the Harness, and a working hello-world example.

An agent is the autonomous reasoning layer of a Gibson mission. It decides what to do next, calls tools, queries the knowledge graph, and submits findings. Gibson runs your agent inside a mission alongside any other agents, tools, and plugins the mission references.

This page shows how to write an agent from scratch with the Gibson SDK.

Prerequisites

  • You completed Install, and gibson inspect returns successfully for a registered agent identity.
  • Go 1.26+ (the ADK pins the exact version in its gibson/go.mod).

If you have not enrolled an agent yet, do that first. gibson agent enroll --name <name> --kind agent mints a bootstrap token. gibson component register --token <T> exchanges the token for the runtime credential the SDK needs.

What an agent looks like

In the SDK, an agent is anything that satisfies the agent.Agent contract from github.com/zeroroot-ai/sdk/agent. Do not write the struct by hand. Use the builder:

cfg := agent.NewConfig().
    SetName("my-agent").
    SetVersion("0.1.0").
    SetDescription("…").
    AddCapability("recon").
    AddTargetType("web_app").
    AddTechniqueType("input_fuzzing").
    AddLLMSlot("primary", llm.SlotRequirements{}).
    SetExecuteFunc(execute)

a, err := agent.New(cfg)

What the fields mean:

FieldPurpose
NameUnique within your tenant. Mission nodes refer to your agent by name.
VersionSemantic version. Useful for canarying upgrades.
DescriptionHuman-readable purpose. Shown in the dashboard.
CapabilitiesFree-form labels ("recon", "prompt_injection", …). Used for routing and discovery.
TargetTypesThe shape of system this agent works against ("web_app", "k8s_cluster", "llm_chat").
TechniqueTypesAttack techniques employed.
LLMSlotsSlot identifiers + requirements. The mission binds each slot to a real model at dispatch time.
SetExecuteFuncThe actual work. See below.

The Harness

Your agent never talks to Gibson directly. It calls agent.Harness, which is the view of the platform for the agent. Gibson passes the harness into Execute. The harness exposes everything an agent might need:

You want to…Use
Call an LLMh.Complete, h.CompleteWithTools, h.Stream, h.CompleteStructured
Call a toolh.CallToolProto, h.CallToolProtoStream, h.QueueToolWork, h.ToolResults
Query a pluginh.QueryPlugin, h.ListPlugins
Hand a subtask to another agenth.DelegateToAgent, h.ListAgents
Persist a security findingh.SubmitFinding, h.GetFindings
Read the knowledge graphh.QueryNodes, h.FindSimilarAttacks, h.FindSimilarFindings, h.GetAttackChains
Write to the knowledge graphh.StoreNode
Use working / mission / long-term memoryh.Memory()
See the mission's target and metadatah.Mission(), h.Target()
Spawn a sub-missionh.CreateMission, h.RunMission
Read or write a workspace fileh.Workspace(), h.Workspaces()
Extra authz check before a sensitive oph.Authorize(ctx, action, resource)

The agent defines its LLM slot identifiers ("primary", "summarizer", …). The slot policy of your tenant maps each one to a real provider and model at dispatch time. See Observability for cost and latency metrics.

A complete hello-world

package main

import (
    "context"
    "log"

    "github.com/zeroroot-ai/sdk/agent"
    "github.com/zeroroot-ai/sdk/llm"
)

func execute(ctx context.Context, h agent.Harness, t agent.Task) (agent.Result, error) {
    resp, err := h.Complete(ctx, "primary", []llm.Message{
        {Role: llm.RoleSystem, Content: "You greet people warmly in one sentence."},
        {Role: llm.RoleUser, Content: t.Prompt},
    })
    if err != nil {
        return agent.NewErrorResult(err), nil
    }
    return agent.NewSuccessResult(resp.Content), nil
}

func main() {
    cfg := agent.NewConfig().
        SetName("hello-agent").
        SetVersion("0.1.0").
        SetDescription("Says hi via the primary LLM slot.").
        AddCapability("greeting").
        AddTargetType("none").
        AddLLMSlot("primary", llm.SlotRequirements{}).
        SetExecuteFunc(execute)

    a, err := agent.New(cfg)
    if err != nil {
        log.Fatal(err)
    }

    if err := agent.Serve(context.Background(), a); err != nil {
        log.Fatal(err)
    }
}

agent.Serve reads ~/.gibson/agent/credentials, connects to your tenant, registers the agent, and waits for missions to dispatch work.

Run the agent

go run .

Leave the process running. In the Missions section of the dashboard, add a node that points at the name of your agent. Click Run. The mission detail page streams events as your agent works. See Missions for the full event taxonomy.

A more realistic example

This bug-bounty triage agent uses a tool and the graph, and submits a finding:

func execute(ctx context.Context, h agent.Harness, t agent.Task) (agent.Result, error) {
    target := h.Target()

    // 1. Run a port scan tool we already enrolled.
    scan, err := h.CallToolProto(ctx, "port-scan", &portscanv1.Request{
        Host: target.Address,
    })
    if err != nil {
        return agent.NewErrorResult(err), nil
    }

    // 2. Pull similar prior findings from the knowledge graph for context.
    similar, err := h.FindSimilarFindings(ctx, agent.SimilarityRequest{
        Description: "exposed admin endpoint",
        Limit:       5,
    })
    if err != nil {
        return agent.NewErrorResult(err), nil
    }

    // 3. Have the LLM decide what's worth flagging.
    plan, err := h.CompleteStructured(ctx, "primary", planSchema, llm.Messages{
        {Role: llm.RoleSystem, Content: "Triage scan output. Decide what to flag."},
        {Role: llm.RoleUser, Content: render(scan, similar)},
    })
    if err != nil {
        return agent.NewErrorResult(err), nil
    }

    // 4. Submit findings.
    for _, item := range plan.Flag {
        if err := h.SubmitFinding(ctx, agent.Finding{
            Title:       item.Title,
            Severity:    item.Severity,
            TargetRef:   target.Id,
            Description: item.Description,
            Evidence:    item.Evidence,
        }); err != nil {
            return agent.NewErrorResult(err), nil
        }
    }
    return agent.NewSuccessResult("triage complete"), nil
}

Their own pages explain tools, plugins, and the graph: Tools, Plugins, Knowledge graph.

Lifecycle hooks

Beyond Execute, the SDK supports optional lifecycle hooks:

  • Initialize(ctx, cfg): runs once when the agent process starts.
  • Shutdown(ctx): runs on graceful shutdown.
  • Health(ctx): Gibson polls this hook to confirm the agent is still alive.

The builder accepts these hooks through SetInitializeFunc, SetShutdownFunc, SetHealthFunc. The defaults are no-ops and a Healthy status. Most agents do not need to override them.

Next steps

  • Tools: define and call a tool from your agent.
  • Findings: what SubmitFinding does and how findings appear in the dashboard.
  • Knowledge graph: query and write the graph from inside an agent.
  • Missions: chain your agent with other components in a DAG.
  • Taxonomy and Ontology: extend the type system with custom node types or industry-vocab mappings for your component.

On this page