Skip to content

How It Works

Every webhook event follows the same path through the agent. Understanding this pipeline is essential before extending anything.

sequenceDiagram
    autonumber
    participant WH as Webhook Source
    participant XA as XianixAgent
    participant EO as EventOrchestrator
    participant RE as RulesEvaluator
    participant PW as ProcessingWorkflow
    participant CA as ContainerActivities
    participant EX as Executor Container

    WH->>XA: webhook (name, payload, tenantId)
    XA->>EO: OrchestrateAsync(name, payload, tenantId)
    EO->>RE: EvaluateAsync(name, payload)
    RE-->>EO: matched executions + inputs
    EO-->>XA: OrchestrationResult[]

    loop For each match
        XA->>PW: Start ProcessingWorkflow
        PW->>CA: EnsureWorkspaceVolumeAsync
        PW->>CA: StartContainerAsync
        CA->>EX: docker create + start
        EX->>EX: git clone/fetch → plugins → Claude Code
        EX-->>CA: stdout: JSON result
        PW->>CA: CleanupContainerAsync
    end
  1. Webhook arrives — GitHub or Azure DevOps fires an event to the Xians platform, which routes it to the registered agent.

  2. XianixAgent.OnWebhook — The integrator workflow handler receives the event and passes it to the EventOrchestrator.

  3. EventOrchestrator — Calls WebhookRulesEvaluator.EvaluateAsync to match the event against rules.json (loaded from Xians Knowledge). Returns a list of OrchestrationResult objects — one per matched execution block.

  4. ProcessingWorkflow — For each match, a Temporal workflow is started. It orchestrates the container lifecycle via ContainerActivities.

  5. ContainerActivities — Uses Docker.DotNet to create a volume, start the executor container with the right env vars, wait for output, and clean up.

  6. Executor container — Clones/fetches the repo, installs Claude Code plugins, runs the prompt, and writes a JSON result to stdout.

The WebhookRulesEvaluator is the brain of the matching logic. It reads rules.json from Xians Knowledge and evaluates each rule set against the incoming webhook.

Key behavior:

  • The webhook field matches the incoming webhook name (case-insensitive)
  • First matching rule set in the array wins
  • Each rule set can have multiple executions blocks — all matching blocks fire
  • match-any entries are OR’d — at least one must pass
  • use-inputs extracts values from the payload via JSON paths
  • execute-prompt supports {{placeholder}} interpolation from extracted inputs

The rules evaluator is stateless and has no Temporal dependency, making it straightforward to unit-test.

EventOrchestrator sits between the webhook handler and the workflow layer. For each matched execution block, it builds an OrchestrationResult containing:

  • TenantId — identifies who triggered the event
  • WebhookName — the event type (e.g. github-pr)
  • Inputs — extracted payload values (PR number, branch, repo URL, etc.)
  • Execution — the plugins to install and prompt to run

Each ProcessingWorkflow manages one container from birth to death:

StepActivityWhat Happens
1EnsureWorkspaceVolumeAsyncCreates (or finds) a Docker volume named xianix-{tenantId}-{repoHash}
2StartContainerAsyncPulls the executor image, creates the container with env vars and volume mount, starts it
3WaitAndCollectOutputAsyncStreams stdout/stderr until exit; enforces timeout
4CleanupContainerAsyncRemoves the container (volume persists for next run)

After collection, ProcessingWorkflow parses the executor’s JSON stdout for cost/token metrics and reports them to the Xians platform.

Every execution runs in its own Docker container. This gives you:

  • Filesystem isolation — each tenant+repo pair gets its own persistent Docker volume
  • Process isolation — containers can’t interact with each other
  • Resource limits — CPU, memory, and PID limits are enforced per container
  • Credential scoping — only the relevant platform token is injected
┌─────────────────────────────────────────────────┐
│ Host: .NET Agent (Control Plane) │
│ │
│ ProcessingWorkflow ──── Docker API ────┐ │
├──────────────────────────────────────────┤ │
│ Docker Engine │ │
│ │ │
│ ┌─ Volume: xianix-tenant-abc-xyz ────┐ │ │
│ │ /workspace/repo/ (bare clone) │ │ │
│ └──────────┬─────────────────────────┘ │ │
│ │ mounted into │ │
│ ┌──────────▼─────────────────────────┐ │ │
│ │ Executor Container (ephemeral) │ │ │
│ │ Non-root user, all caps dropped │ │ │
│ │ No Docker socket, no host access │ │ │
│ └────────────────────────────────────┘ │ │
└─────────────────────────────────────────┘ │
SettingValue
UserNon-root (1000:1000)
CapDropALL
SecurityOptno-new-privileges
PidsLimit256
MemoryConfigurable via CONTAINER_MEMORY_MB
NetworkBridge (full outbound, no Docker socket)

Workspace volumes survive container restarts. On first run, the repo is cloned. Subsequent runs do a fast git fetch. This avoids re-cloning large repos on every event.

Learn how the Executor works inside those containers.