Basic agent
Basic agent
The smallest possible governed agent: define one tool, wrap it for governance, and run a single governed call. Start here to see the SDK’s interception shape with nothing else in the way.
What this example demonstrates
- Importing and using the Agent Assembly Go SDK.
- Defining a tool that satisfies the
assembly.Toolinterface (Name,Description,Call). - Wrapping a tool with
assembly.WrapToolsso each call is intercepted. - Observing the allow decision path through console output.
- Using an offline mock
GovernanceClientso the example runs with no live gateway.
The framework / library
No framework. This example uses only the Go SDK
(github.com/ai-agent-assembly/go-sdk)
and the standard library. It’s the bare-metal view of how WrapTools
intercepts a call.
How it works
The governance flow is the same one the Quick Start describes, reduced to its essentials:
- An
echoToolimplementsassembly.Tool—Name,Description, and aCallthat returns its input unchanged. assembly.WrapToolswraps the tool with aGovernanceClient.- Before each
Call, the wrapper sends aCheckRequestto the client and gets back aDecision. - The mock client always returns
Decision{Denied: false}, so the call is allowed and proceeds to the inner tool. A denied decision would instead surface as anassembly.PolicyViolationError(see Tool policy for that path).
Prerequisites & running it
Complete the one-time Preparing the runtime environment steps first. Then:
cd agent-assembly-examples/go/basic-agent
go mod download
go run .No gateway and no API key are needed — the example uses the offline mock client.
Code walkthrough
The tool is a plain struct satisfying assembly.Tool:
type echoTool struct{}
func (e *echoTool) Name() string { return "echo" }
func (e *echoTool) Description() string { return "Returns its input string unchanged." }
func (e *echoTool) Call(_ context.Context, input string) (string, error) {
return input, nil
}main tags the context with an agent ID, builds the mock client, wraps the
tool, then calls it:
func main() {
// Tag the context so governance records include this agent's ID.
ctx := assembly.WithAgentID(context.Background(), "basic-agent-demo")
// Use the offline mock governance client.
fmt.Println("[assembly] using offline mock governance client")
client := &mockClient{}
// Wrap the tool — every Call now goes through the governance client first.
tools := assembly.WrapTools([]assembly.Tool{&echoTool{}}, client)
input := "Hello, Agent Assembly!"
result, err := tools[0].Call(ctx, input)
if err != nil {
log.Fatalf("[assembly] tool call failed: %v", err)
}
fmt.Printf("[assembly] tool result: %s\n", result)
}The mock client in policy.go implements GovernanceClient and allows
everything:
type mockClient struct{}
func (m *mockClient) Check(_ context.Context, req assembly.CheckRequest) (assembly.Decision, error) {
return assembly.Decision{Denied: false, Reason: "allowed by offline mock"}, nil
}
// WaitForApproval, RecordResult, and Close round out the interface.Notes & caveats
No
Inithere. This example never callsassembly.Init— it constructs theGovernanceClientdirectly. That’s why no gateway is required. The trade-off is that nothing is enforced beyond the mock’s always-allow rule.
To use a real gateway, replace
mockClientinpolicy.gowith a transport-backedGovernanceClient. TheWrapToolscall and your tool code stay exactly the same.
Expected behavior
[assembly] using offline mock governance client
[assembly] governance: ALLOWED tool=echo input="Hello, Agent Assembly!"
[assembly] tool result: Hello, Agent Assembly!Running go test ./... exercises the same path offline.
Links
- Example directory:
go/basic-agent README.md- Next: Tool policy — see a tool get denied.