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)