.NET quickstart¶
dotnet add package ResponsibleAI.AgentHooks --prerelease
The package bundles the native core library (libagent_hooks_ffi)
for linux-x64, osx-x64, osx-arm64, and win-x64 under the standard
runtimes/ layout, so restore is the only step. Versions up to
0.1.0-alpha.3 shipped managed code only; on those, build the native
library from source and make it resolvable:
git clone https://github.com/responsibleai/agent-hooks
cargo build --release -p agent-hooks-ffi --manifest-path agent-hooks/sdk/rust/Cargo.toml
export LD_LIBRARY_PATH=$PWD/agent-hooks/sdk/rust/target/release # PATH on Windows
A minimal host with one control interceptor:
using System.Text.Json.Nodes;
using AgentHooks;
var emitter = new InterceptionEmitter();
emitter.Register(new EgressGuard(), name: "egress-guard");
var build = new AgentContextBuilder("support-agent", "quickstart", "s-001");
var ok = await emitter.EmitAsync(
build.PreToolCall("t-1", "web_search", new JsonObject { ["q"] = "docs" }));
Console.WriteLine($"allowed: {ok.Record.Verdict.Decision}");
try
{
await emitter.EmitAsync(build.PreToolCall(
"t-2", "send_email", new JsonObject { ["body"] = "account_balance: 991" }));
}
catch (InterceptionBlockedException blocked)
{
Console.WriteLine($"denied: {blocked.Result.Verdict.Reason}");
}
sealed class EgressGuard : IInterceptor
{
private static readonly string[] Confidential = ["account_balance", "ssn"];
public ValueTask<Verdict> InterceptAsync(AgentContext context, CancellationToken ct = default)
{
if (context.InterceptionPoint != InterceptionPoint.PreToolCall)
return ValueTask.FromResult(Verdict.Allow);
var args = context.Json["tool_call"]?["args"]?.ToJsonString() ?? "";
foreach (var marker in Confidential)
if (args.Contains(marker))
return ValueTask.FromResult(new Verdict(Decision.Deny, Reason: "egress_blocked"));
return ValueTask.FromResult(Verdict.Allow);
}
}
Output:
allowed: Allow
denied: egress_blocked
Things to notice:
Verdict.Allowis a static readonly instance, not a method; denies are constructed with the record syntaxnew Verdict(Decision.Deny, Reason: ...), andVerdict.Warn(...)/Verdict.Escalate(...)sugar exists.EmitAsyncthrowsInterceptionBlockedException(record on.Result) on any block; on success it returns anEmitOutcomewhoseTargetis the effective value your action must consume.- The emitter enforces the recommended 5000 ms timeout on interceptors and resolvers, mapping breaches to the reserved fail-closed reasons.