Skip to content

Lifecycle Hooks

Run containers before and after your agent — fetch context from external systems, upload artifacts, notify Slack, clean up resources. Lifecycle hooks let you wire arbitrary setup and teardown logic into the agent execution flow without modifying the agent itself.

How It Works

AgentRun created
  → Pending phase
    → Controller creates workspace PVC (if postRun defined)
    → Controller creates lifecycle RBAC (if rbac defined)
    → PreRun init containers execute sequentially
  → Running phase
    → Agent container runs
  → PostRunning phase (if postRun defined)
    → Controller creates follow-up Job
    → PostRun init containers execute sequentially
  → Succeeded / Failed
    → Workspace PVC cleaned up
    → Lifecycle RBAC garbage-collected (owner reference)

PreRun Hooks

PreRun hooks execute as init containers before the agent starts. They have access to:

Path Description
/workspace Shared working directory — write files here for the agent to read
/ipc IPC bus (tool calls, task input)
/tmp Scratch space

Use cases: Fetch incident context from PagerDuty, clone a git repo, download test data, warm caches.

PostRun Hooks

PostRun hooks execute in a follow-up Job after the agent completes. They receive everything preRun hooks get, plus:

Env Var Description
AGENT_EXIT_CODE "0" on success, non-zero on failure
AGENT_RESULT The agent's final response text (truncated to 32Ki)

The workspace is shared between the agent and postRun hooks via a PersistentVolumeClaim. PostRun failures are best-effort — they're recorded as a PostRunFailed Condition but don't change the agent's final phase.

Use cases: Upload artifacts to S3, post a summary to Slack, clean up temporary resources, trigger downstream pipelines.

Environment Variables

All lifecycle hook containers receive these env vars:

Env Var Description
AGENT_RUN_ID Unique identifier for this agent run
INSTANCE_NAME The Agent this run belongs to
AGENT_NAMESPACE Kubernetes namespace
Custom env vars Any spec.env entries from the AgentRun

RBAC for Hooks

By default, lifecycle hook containers run with the sympozium-agent ServiceAccount, which has no Kubernetes permissions. If your hooks need to interact with the Kubernetes API (e.g., create or delete ConfigMaps), declare the required RBAC rules:

spec:
  lifecycle:
    rbac:
      - apiGroups: [""]
        resources: ["configmaps"]
        verbs: ["get", "list", "create", "delete"]
    preRun:
      - name: create-context
        image: bitnami/kubectl:latest
        command: ["kubectl", "create", "configmap", "run-context",
                  "--from-literal=started=$(date)"]
    postRun:
      - name: cleanup-context
        image: bitnami/kubectl:latest
        command: ["kubectl", "delete", "configmap", "run-context"]

The controller creates a namespace-scoped Role and RoleBinding for the run, bound to sympozium-agent. These are garbage-collected when the AgentRun is deleted — no standing permissions.

Examples

Fetch PagerDuty incidents before the agent runs

apiVersion: sympozium.ai/v1alpha1
kind: Agent
metadata:
  name: oncall-agent
spec:
  agents:
    default:
      model: gpt-4o
      lifecycle:
        preRun:
          - name: fetch-incidents
            image: curlimages/curl:latest
            command: ["sh", "-c",
              "curl -s -H 'Authorization: Token token=$PD_TOKEN' \
               https://api.pagerduty.com/incidents?statuses[]=triggered \
               > /workspace/context/incidents.json"]
            env:
              - name: PD_TOKEN
                value: "your-pagerduty-token"

The agent's system prompt can then instruct it to read /workspace/context/incidents.json for current incident context.

Upload artifacts to S3 after completion

spec:
  lifecycle:
    postRun:
      - name: upload-report
        image: amazon/aws-cli:latest
        command: ["sh", "-c",
          "aws s3 cp /workspace/report.md s3://my-bucket/reports/$AGENT_RUN_ID.md"]
        env:
          - name: AWS_ACCESS_KEY_ID
            value: "AKIA..."
          - name: AWS_SECRET_ACCESS_KEY
            value: "..."

Create and clean up a ConfigMap

spec:
  lifecycle:
    rbac:
      - apiGroups: [""]
        resources: ["configmaps"]
        verbs: ["create", "delete", "get"]
    preRun:
      - name: create-config
        image: bitnami/kubectl:latest
        command: ["sh", "-c",
          "kubectl create configmap agent-scratch --from-literal=run=$AGENT_RUN_ID"]
    postRun:
      - name: delete-config
        image: bitnami/kubectl:latest
        command: ["kubectl", "delete", "configmap", "agent-scratch"]

Ensemble with lifecycle hooks

apiVersion: sympozium.ai/v1alpha1
kind: Ensemble
metadata:
  name: oncall-team
spec:
  personas:
    - name: triage-agent
      systemPrompt: "You are an SRE triage agent..."
      lifecycle:
        preRun:
          - name: fetch-alerts
            image: curlimages/curl:latest
            command: ["sh", "-c", "curl -s $ALERTMANAGER_URL/api/v2/alerts > /workspace/context/alerts.json"]

Response Gate

A response gate is a PostRun hook with gate: true. It holds the agent's output from reaching users until the gate explicitly approves, rejects, or rewrites it. This is useful for compliance checks, content filtering, PII scanning, or human-in-the-loop approval workflows.

How It Works

Without a gate, the agent's response is published to channels (Slack, Telegram, web UI) the instant the agent finishes. With a gate:

  1. The IPC bridge suppresses the completion event
  2. The PostRun Job runs the gate hook container
  3. The gate hook inspects the agent's output (via AGENT_RESULT env var)
  4. The gate hook writes a verdict by patching an annotation on the AgentRun
  5. The controller reads the verdict, applies it, and publishes the (possibly rewritten) response

If no verdict is written (hook fails, times out, or the hook is designed for manual approval), the gateDefault field controls behavior: "block" (default) replaces the output with an error, "allow" passes it through.

Declaring a Gated Instance

Add gate: true to one PostRun hook in your instance's lifecycle config. At most one PostRun hook may be a gate. The gate hook needs RBAC permission to patch the AgentRun annotation:

apiVersion: sympozium.ai/v1alpha1
kind: Agent
metadata:
  name: gated-agent
spec:
  agents:
    default:
      model: gpt-4o
      lifecycle:
        gateDefault: block   # or "allow"
        rbac:
          - apiGroups: ["sympozium.ai"]
            resources: ["agentruns"]
            verbs: ["get", "patch"]
        postRun:
          - name: content-filter
            image: my-org/content-filter:latest
            gate: true
            command: ["sh", "-c"]
            args:
              - |
                # Inspect $AGENT_RESULT, then patch the verdict:
                kubectl patch agentrun $AGENT_RUN_ID -n $AGENT_NAMESPACE \
                  --type=merge \
                  -p "{\"metadata\":{\"annotations\":{\"sympozium.ai/gate-verdict\":\
                  \"{\\\"action\\\":\\\"approve\\\"}\"}}}"

Verdict Format

The gate hook patches the annotation sympozium.ai/gate-verdict with a JSON object:

Action Effect Fields
approve Passes the original response through unchanged {"action":"approve"}
reject Replaces the response with a custom message {"action":"reject","response":"Blocked by policy"}
rewrite Replaces the response with a sanitized version {"action":"rewrite","response":"cleaned output"}

All actions accept an optional reason field for audit logging.

Manual (Human-in-the-Loop) Approval

If you want a human to approve or reject each response:

  1. Set the gate hook to sleep indefinitely (or for a long timeout)
  2. Set gateDefault: block so unapproved responses are blocked
  3. Use the web UI or API to approve or reject

In the web UI, gated runs show an amber "Approval" badge on the runs list and an approval bar on the run detail page with Approve and Reject buttons. A warning toast fires when a run requires approval.

Via the API:

# Approve
curl -X POST http://localhost:9090/api/v1/runs/my-run/gate-verdict?namespace=default \
  -H 'Content-Type: application/json' \
  -d '{"action":"approve","reason":"reviewed by operator"}'

# Reject
curl -X POST http://localhost:9090/api/v1/runs/my-run/gate-verdict?namespace=default \
  -H 'Content-Type: application/json' \
  -d '{"action":"reject","response":"Content not approved","reason":"PII detected"}'

Example: PII Scanner Gate

spec:
  lifecycle:
    gateDefault: block
    rbac:
      - apiGroups: ["sympozium.ai"]
        resources: ["agentruns"]
        verbs: ["get", "patch"]
    postRun:
      - name: pii-scanner
        image: my-org/pii-scanner:latest
        gate: true
        command: ["sh", "-c"]
        args:
          - |
            if echo "$AGENT_RESULT" | pii-detect --strict; then
              VERDICT='{"action":"reject","response":"Response blocked: PII detected","reason":"pii-scanner"}'
            else
              VERDICT='{"action":"approve","reason":"pii-scanner-clean"}'
            fi
            kubectl patch agentrun $AGENT_RUN_ID -n $AGENT_NAMESPACE --type=merge \
              -p "{\"metadata\":{\"annotations\":{\"sympozium.ai/gate-verdict\":$(echo $VERDICT | jq -Rs .)}}}"

Gate Status in the UI

State Web UI Indicator
Awaiting approval Amber "Approval" badge on runs list, amber approval bar on detail page
Approved Green "approved" banner on detail page
Rejected Red "rejected" banner on detail page
Rewritten Blue "rewritten" banner on detail page
Timeout/error Red "timeout" or "error" banner on detail page

Agent Sandbox Compatibility

Lifecycle hooks work with both standard Job mode and Agent Sandbox mode:

  • PreRun hooks are injected as init containers into the Sandbox CR — they execute inside the gVisor/Kata sandbox.
  • PostRun hooks always run as a separate follow-up Job (outside the sandbox), since the sandbox is torn down after the agent completes.
  • The workspace PVC is shared between both.

Phases

With lifecycle hooks, the AgentRun phase transitions become:

PendingRunningPostRunningSucceeded (or Failed)

The PostRunning phase is only entered when postRun hooks are defined. Without them, the flow is the standard PendingRunningSucceeded/Failed.