Skip to main content

Agent Runtime — Hooks API

Table of Contents​

Top

upsquad/runtime/hook/v0alpha1/hook_service.proto​

hook_service.proto — HookService gRPC bridge between the Python agent-worker and the Go-side hook registry (LLD 15 primitives).

Founder decision Q5 (#380): the package is tagged v0alpha to mark the shape as unstable while Wave 4A burns in. The wire-format package name is v0alpha1 because buf's STANDARD lint rules require a canonical vN[alpha|beta]M suffix — that is the buf-compliant spelling of the intent behind Q5. Once the four lifecycle points stabilise (post-LLD 17 audit enrichment, post-LLD 18 client dashboards), a migration to upsquad.runtime.hook.v1 lands in its own proto, with v0alpha1 kept for one release for rollback.

Why this service exists​

The Go core owns the Wave 4A hook dispatcher (LLD 15 §3.3 — one Registry per binary). Blocking on_session_start and pre_llm_call hooks run before any Python-side work begins, so they are invoked inside Go. But the LangGraph worker (services/agent-worker) also needs to fire two async points — post_tool_call and on_error — at the moment they occur inside Python code. Rather than maintain a second hook registry in Python, the worker reaches across the process boundary via this RPC and lets the Go registry dispatch.

Latency invariant (§4 of task prompt): FireHook P95 < 30 ms including proto marshal + network hop + Go-side InvokeBlocking. BudgetPreLLMCallAggregate (LLD 15 threshold.go) is 30 ms for the whole point, so the bridge has to keep its own overhead below ~5 ms on localhost for the budget to leave any room for real hook work. FireHookAsync pays only the enqueue cost — the Go-side async worker pool drains the task after the RPC returns.

FireHookAsyncRequest​

FireHookAsyncRequest is the request type for the fire-and-forget FireHookAsync RPC.

FieldTypeLabelDescription
commonHookRequestCommoncommon carries the shared identity + payload fields.

FireHookAsyncResponse​

FireHookAsyncResponse is returned as soon as the async task is on the Go-side queue. Always empty — the caller does not observe the hook result. Present only because gRPC requires a typed response.

FireHookRequest​

FireHookRequest is the request type for the blocking FireHook RPC.

FieldTypeLabelDescription
commonHookRequestCommoncommon carries the shared identity + payload fields.

FireHookResponse​

FireHookResponse is the response type for the blocking FireHook RPC.

FieldTypeLabelDescription
outputgoogle.protobuf.Structoutput is the merged hook output. Shape mirrors the LLD 15 typed Out structs (PreLLMCallOut / OnSessionStartOut). Empty when no hooks are registered for the point.

HookRequestCommon​

HookRequestCommon carries the identity fields shared by both RPC request types. Kept as a nested message so the linter's RPC_REQUEST_STANDARD_NAME rule is satisfied by distinct FireHookRequest / FireHookAsyncRequest wrappers.

FieldTypeLabelDescription
hook_namestringhook_name is the canonical point name — one of "on_session_start", "pre_llm_call", "post_tool_call", or "on_error". An unknown value returns InvalidArgument.
session_idstringsession_id is the UUID of the agent session firing the hook. Used by the Go-side disable store (LLD 15 §3.2 founder Q3) to scope per-session circuit-breaker state. REQUIRED.
org_idstringorg_id is the tenant UUID. Propagated to audit hooks in LLD 17. REQUIRED.
agent_idstringagent_id is the agent UUID. Set when the hook has an associated agent (all four points do). REQUIRED.
payloadgoogle.protobuf.Structpayload carries the typed per-point input as a JSON-like Struct. The server-side translator expects field names matching the Go struct (e.g. PreLLMCallIn has Model, Messages, Tools, LoopCount, UserID). Unknown fields are ignored — the contract is "new fields are additive" so Python and Go can roll out independently.

HookService​

HookService is the Go-side gRPC entry point invoked by the Python agent-worker to run Wave 4A hooks. Two RPCs, one per flavour:

  • FireHook — blocking; returns the merged hook output or an error. Used for pre_llm_call and on_session_start when the caller is Python.
  • FireHookAsync — fire-and-forget; enqueues and returns immediately. Used for post_tool_call and on_error.

Wiring (LLD 16 §Wiring and shelfware gate): this service is registered on the agent-orchestrator gRPC server (main.go → runtimeserver.Config.HookRegistry) and consumed by the worker-side client in agent_worker/hooks.py.

Method NameRequest TypeResponse TypeDescription
FireHookFireHookRequestFireHookResponseFireHook invokes a blocking hook (on_session_start / pre_llm_call). Returns FireHookResponse on success, or a gRPC status with code=FailedPrecondition when the hook itself returned an error. Transient errors (Unavailable/DeadlineExceeded) are safe to retry; FailedPrecondition is not.
FireHookAsyncFireHookAsyncRequestFireHookAsyncResponseFireHookAsync enqueues a fire-and-forget hook (post_tool_call / on_error). The server returns as soon as the task is on the registry's async queue. Drops on a full queue are recorded on the Go side and surface only via metrics — the client never sees an error for a drop.

Scalar Value Types​

.proto TypeNotesC++JavaPythonGoC#PHPRuby
doubledoubledoublefloatfloat64doublefloatFloat
floatfloatfloatfloatfloat32floatfloatFloat
int32Uses variable-length encoding. Inefficient for encoding negative numbers – if your field is likely to have negative values, use sint32 instead.int32intintint32intintegerBignum or Fixnum (as required)
int64Uses variable-length encoding. Inefficient for encoding negative numbers – if your field is likely to have negative values, use sint64 instead.int64longint/longint64longinteger/stringBignum
uint32Uses variable-length encoding.uint32intint/longuint32uintintegerBignum or Fixnum (as required)
uint64Uses variable-length encoding.uint64longint/longuint64ulonginteger/stringBignum or Fixnum (as required)
sint32Uses variable-length encoding. Signed int value. These more efficiently encode negative numbers than regular int32s.int32intintint32intintegerBignum or Fixnum (as required)
sint64Uses variable-length encoding. Signed int value. These more efficiently encode negative numbers than regular int64s.int64longint/longint64longinteger/stringBignum
fixed32Always four bytes. More efficient than uint32 if values are often greater than 2^28.uint32intintuint32uintintegerBignum or Fixnum (as required)
fixed64Always eight bytes. More efficient than uint64 if values are often greater than 2^56.uint64longint/longuint64ulonginteger/stringBignum
sfixed32Always four bytes.int32intintint32intintegerBignum or Fixnum (as required)
sfixed64Always eight bytes.int64longint/longint64longinteger/stringBignum
boolboolbooleanbooleanboolboolbooleanTrueClass/FalseClass
stringA string must always contain UTF-8 encoded or 7-bit ASCII text.stringStringstr/unicodestringstringstringString (UTF-8)
bytesMay contain any arbitrary sequence of bytes.stringByteStringstr[]byteByteStringstringString (ASCII-8BIT)