Skip to content

Architecture

What are the components of the agent execution stack?

This is a living document. It must always reflect the current state of architectural decisions. When an ADR is accepted (or superseded), this document is updated to match. ADRs are point-in-time records that may receive minor annotations but are not substantially rewritten; this document is where the current truth lives. A reader should be able to understand the system's architecture from this document alone, without tracing a chain of ADRs.

This document names the parts of the system without deciding how they work. It establishes shared vocabulary that the problem documents can reference when discussing design choices. Each component gets a responsibility statement and open questions — implementation decisions live in the problem docs and will crystallize into ADRs as they mature.

This is not exhaustive. Not every problem doc maps to a component here, and not every component here has a corresponding problem doc yet.

Execution Stack

Five components form the vertical execution path from event to agent action:

  1. Agent Dispatch and Coordination Layer — translates events into agent tasks
  2. Agent Infrastructure — provisions and runs agent workloads
  3. Agent Sandbox — enforces isolation (network, filesystem)
  4. Agent Harness — assembles configuration and context (skills, prompts, tools)
  5. Agent Runtime — the LLM in execution

Control flows strictly downward through this stack. No layer may influence, configure, or depend on layers above it. This is the execution stack's primary structural invariant. (See ADR 0016.)

The remaining components described in this document (Policy Store, Intent Source, Identity Provider, Observability, Agent Registry) are cross-cutting concerns that feed into the stack from the side. They are not part of the vertical control flow, but they follow the same principle: no component within the stack can modify the cross-cutting systems that constrain it.

Agent Infrastructure

The compute and orchestration layer that runs agent workloads. Responsible for provisioning, scheduling, scaling, and lifecycle management of agent execution environments.

This is the "where do agents physically run" question — whether that's a managed platform, internal Kubernetes, CI runners repurposed for agent work, or something purpose-built.

Infrastructure platform choice and configuration live in each target repository's .fullsend/ directory. Per-repo installation is the sole supported deployment model (ADR 0033); the dedicated org-level <org>/.fullsend config repo is deprecated (ADR 0044).

Decided:

  • Forge abstraction: all forge operations go through the forge.Client interface, keeping the rest of the codebase forge-agnostic (ADR 0005).
  • Conversation surface: agents participate in GitHub Discussions and later other chat systems through a narrow conversation.Client (parallel to tracker.Client for issue content), not by extending forge.Client (ADR 0086). A conversation is the container (Discussion / Slack channel) with exactly one category and optional M:M labels; a thread is the top-level message plus replies that share its parent_id (parent_id == id on the root message).
  • Event-source routing for status notifications: the notification destination for run-status comments and reactions is dynamically determined by event provenance — a Jira-triggered run posts status to Jira, a GitHub-triggered run posts to GitHub — rather than being hardwired to the code-output forge. Status notifications route through tracker.Client; reactions are an optional tracker.Reactor capability (Jira Cloud supports comment reactions but not issue reactions, so Reactor is not implemented for Jira currently) (ADR 0093).
  • Installation model: ordered layer stack (install forward, uninstall reverse, analyze for status reporting) with idempotent operations. Current stack: config-repo → workflows → vendor-binary → secrets → inference → dispatch → enrollment (ADR 0006).
  • Cross-repo dispatch: enrolled repos call .fullsend via workflow_call; a dispatch workflow mints OIDC tokens exchanged at a central token mint (GCP Cloud Function or Cloudflare Worker) for scoped GitHub App installation tokens per agent role. App PEM secrets are stored in Secret Manager (GCF mint), Worker secrets (CF mint), or the local filesystem (standalone mint), not the config repo (ADR 0008).
  • Shim workflow security: pull_request_target prevents PR authors from modifying the shim workflow. No long-lived secrets flow through the shim — OIDC tokens are issued by the GitHub runtime and scoped to the workflow run (ADR 0009).
  • Repo maintenance: a workflow in .fullsend (.github/workflows/repo-maintenance.yml) reconciles enrollment shims in target repos when config.yaml changes or on manual dispatch. The CLI's EnrollmentLayer.Install() dispatches this workflow via workflow_dispatch and monitors it for completion, then reports any enrollment PRs created in target repos.
  • Installer scaffold: the WorkflowsLayer deploys content from an embedded scaffold (internal/scaffold/), keeping deployable files as real files under version control rather than Go string constants.
  • Reusable workflows: agent workflows in .fullsend are thin callers (~40-70 lines) that delegate infrastructure logic to upstream reusable workflows (fullsend-ai/fullsend/.github/workflows/reusable-*.yml) via workflow_call. Infrastructure patches ship once upstream and propagate to all orgs without re-install (ADR 0031). --vendor (ADR 0047) commits workflows and agent content at install time; layered installs (default) fetch upstream at runtime.
  • Event-driven stage dispatch: eliminate workflow_dispatch + gh workflow run fan-out from dispatch.yml in favor of synchronous workflow_call so the dispatched run stays linked to the caller (ADR 0041).
  • Multi-repo management: a fullsend repos subcommand group with a declarative repos.yaml manifest for managing per-repo installations at scale — install, convergence (provision, sync, upgrade), status, and uninstall across repos and orgs (ADR 0057, ADR 0074).
  • Dispatch version-skew resolution: per-repo reusable-dispatch.yml inlines stage workflow jobs directly, eliminating @v0 references to reusable-{stage}.yml (ADR 0062).
  • Ready-made configuration presets: fullsend github setup --config <path-or-url> installs a vendor preset as .fullsend/config.base.yaml and a stub .fullsend/config.yaml overlay in the target repository; mint URL, inference backend, and related settings live in configuration files resolved through accessor methods, not CLI flags. Shared-infrastructure presets will reduce per-adopter enrollment (target state): mint via job_workflow_ref trust per ADR 0059; inference authorization model undecided (ADR 0069); enrollment remains required until follow-on ADRs land.
  • GitLab event dispatch: cron-based polling for all events (issues/comments/labels, MR-open review, MR-merge retro, and closed-unmerged retro). Native merge_request_event dispatch was removed (#7322); protected CI/CD variables are unavailable on unprotected MR refs. No external infrastructure (no webhook bridge). Bot PAT stored as a protected CI/CD variable. Per-repo only (ADR 0067).

Open questions:

  • Do we adopt a 3rd party platform, use existing internal infrastructure, or build our own? (See agent-infrastructure.md for the three directions.)
  • Can different agent types (short-lived review vs. long-running code) run on different infrastructure?
  • Who in the org owns and operates this, and how does it relate to existing platform or CI ownership?
  • Should model and MCP (or other tool-protocol) traffic from agent runtimes go through a shared gateway for authentication, spend limits, allowlists, and telemetry? (See landscape.md.)

Agent Sandbox

The isolation boundary around a running agent. Responsible for filesystem access control and network regulation — ensuring an agent can only reach what it's authorized to reach and cannot affect other agents or systems outside its boundary.

The sandbox is a security primitive. Its job is containment: if an agent is compromised or misbehaves, the blast radius is limited to what the sandbox permits.

Ecosystem projects reuse the word sandbox for different workload shapes. For example, Kubernetes SIG Agent Sandbox targets stateful, singleton agent runtimes (long-lived sessions), whereas many fullsend-style workflows emphasize short-lived, task-scoped runs with tight isolation and observability. How those patterns compare is discussed in agent-infrastructure.md.

Sandbox defaults (network policy, filesystem restrictions) are configured in each target repository's .fullsend/ directory (ADR 0033).

Open questions:

  • What is the right isolation level — process, container, microVM, or separate cluster? (See agent-infrastructure.md and security-threat-model.md.)
  • How granular is network regulation? Allowlist of endpoints, or coarser controls? Decided in ADR 0065: network access is granted through provider profiles with per-endpoint allowlists.
  • Does the sandbox provide a pre-built environment (tools, language runtimes, repo clones), or does the agent set up its own workspace within the sandbox?
  • Is the sandbox the same for all agent roles, or does each role get a differently-scoped sandbox? Decided in ADR 0020: each agent gets its own sandbox with policies designed for its responsibility.

Decided:

  • Provider-backed policy composition: network access is granted through provider profiles declared in harness files. Policy files define only non-composable sandbox restrictions (filesystem, landlock, process). A single base.yaml replaces per-agent policy files in the scaffold. Inline network_policies continue to work but providers are the recommended approach (ADR 0065).

Agent Harness

The configuration and context layer that prepares an agent for its task. Responsible for providing skills, system prompts, codebase context, tool definitions, and behavioral instructions to the agent runtime.

The harness is what makes a generic LLM into a specific agent with a specific role. It assembles what the agent needs to know and what it's allowed to do before the agent starts working.

The harness draws its configuration from each target repository's .fullsend/ directory — skills, workflow definitions, and agent behavioral instructions are assembled from the layered config (fullsend defaults, then repo baseline and overrides) (ADR 0033).

Decided:

  • Output schema enforcement: a harness post-script validates every agent's output against a declared JSON schema on the host. Non-compliant output triggers a retry (capped); exhaustion is a hard failure — no unvalidated output is emitted (ADR 0022). An iteration killed at timeout_minutes is not retried: the run ends with a distinct timeout error unless an iteration's output already validated, and the budget reaches the sandbox as FULLSEND_TIMEOUT_MINUTES plus a per-iteration FULLSEND_ITERATION_DEADLINE (ADR 0105).
  • Forge-portable harness schema: role and slug move into the harness YAML (eliminating the config.yaml agents: block dependency), and a forge: section separates platform-specific config from platform-neutral fields (see Harness Field Reference for the current field classifications and merge rules). Forge blocks inherit from top-level defaults and override only deltas (ADR 0045, superseded by ADR 0088).
  • Unified env var delivery: a single env: key with runner and sandbox sub-maps replaces runner_env and manual .env files. The runner generates the sandbox .env file from env.sandbox at bootstrap. runner_env is deprecated (ADR 0055, amending ADR 0024).
  • Agent configuration env vars: behavioral knobs use {AGENT}_{SETTING_NAME} naming (e.g., REVIEW_SEVERITY_THRESHOLD), delivered via env.runner and env.sandbox in the harness YAML. Each agent documents its config vars in docs/agents/<agent>.md (ADR 0049).
  • Config surface boundary: a knob that applies to one agent is an {AGENT}_-prefixed harness env var (never a config.yaml field); a knob that applies across agents or governs dispatch/policy is a config.yaml field (never also an env var) (ADR 0080).
  • CI workflow env: scope: the workflow env: block is reserved for infrastructure plumbing (credentials, project IDs, regions) and values computable only at CI runtime; agent behavior defaults are set via harness env.runner/env.sandbox and overridden through base: composition, never the workflow file (ADR 0081).
  • Agent-driven branch targeting: the code agent writes its chosen target branch to structured output. The post-script validates the choice against an allowlist and falls back to the repo's auto-detected default branch. Branch-targeting logic lives in the portable post-script, not in workflow YAML (ADR 0053).
  • Harness trigger expressions: each harness may declare an optional CEL trigger boolean evaluated against a required forge-neutral normalized entity and an optional prompting NormalizedEvent. fullsend dispatch resolves each event's entity before matching; scheduled discovery evaluates resolved entities with no prompting event. Harnesses without entity sources remain event-triggered only and may rely on the event being present (ADR 0061, partially superseded by ADR 0098).
  • Portable provider and profile resolution: provider and profile definitions can be URL-referenced (sha256-pinned) or specified as local file paths in the harness, enabling portable base harnesses that carry their own provider/profile dependencies. URL-resolved providers are validated against allowed_remote_resources and merged with local definitions at resolution time (ADR 0075).
  • Run-stage-scoped privilege levels: a privilege_levels field in harness config maps run-stages (pre_script, runtime, post_script) to named mint privilege levels. A default key covers unspecified run-stages. When omitted, the harness defaults to write for all run-stages, preserving backward compatibility (ADR 0073).
  • Pre-script skip signalling: the harness pre_script runs exactly once, inside fullsend run; a pre-script stops the run before sandbox creation by writing skipped=true to the CLI-provided FULLSEND_PRESCRIPT_OUTPUT file or by exiting with code 78 (neutral skip) (contract: docs/normative/prescript-output/v1), replacing the inline workflow pre-checks and their scaffold script copies (ADR 0072).
  • CEL-guarded overlays: an overlays: list of CEL-guarded config overlays generalizes the forge: block, letting harness authors condition scripts, skills, env vars, and other fields on any event property (source system, event type, etc.) rather than only the forge platform. forge: is deprecated but remains functional (ADR 0088).

Open questions:

  • Does the harness live inside the sandbox (configuring the agent from within its isolation boundary) or outside it (preparing the environment before the agent starts)? (Security hooks are injected as a runner-owned hooks.json loaded via --settings; see ADR 0027. General harness placement remains open.)
  • How is codebase context assembled? (See codebase-context.md.)
  • How do we version and test harness configurations? (See testing-agents.md.) (Functional tests now test the full pipeline including harness-assembled configuration — ADR 0052. Harness versioning remains open.)

Agent Runtime

The agent itself in execution — the LLM, its tool-use loop, and the interface to the model provider. Responsible for performing the assigned task within the boundaries set by the sandbox and the configuration provided by the harness.

This is the thing that actually reasons and acts. Everything else in this document exists to support, constrain, or coordinate it.

The runner talks to every runtime through one contract, so the harness, sandbox, hook scripts and credentials are shared; only the in-sandbox config directory and the way hooks are wired differ per runtime:

Loading diagram...

Decided (implementation):

  • The fullsend run runner delegates in-sandbox agent execution to a runtime.Runtime interface; production orgs default to Claude Code, with pi available as an opt-in second runtime (runtime: pi, Claude-on-Vertex through the same WIF credential path) and codex as a third (runtime: codex, OpenAI-only through a custom model provider whose bearer token comes from a runner-seeded file, with the sandbox tool hooks behind a translating adapter — ADR 0099 and ADR 0100). Runtime selection is configured per repo with runtime: in .fullsend/config.yaml (per-agent runtime/model/effort/subagents on the agent's agents: entry sit above it and below the --runtime/--model/--effort flags and FULLSEND_* variables, ADR 0091) and resolved via runtime.ResolveForAgent(). Test-only runtimes — dummy (scripted operations) and dummy-playback (playlist-based replay of canned results) — execute in the real OpenShell sandbox for behaviour tests without inference. Bootstrap uses a portable BootstrapInput interface with optional extensions such as SandboxHooksBootstrap for the runtime-neutral sandbox tool hooks (ADR 0090); runtimes declare further capabilities through small optional interfaces (DebugLogNamer, ContextBridger) rather than Name() checks in the runner. Transcript and debug artifact handling use a separate TranscriptHandler interface. See runtimes.md for the per-runtime security feature matrix required when adding a new backend.
  • Plugins are runtime-scoped harness resources: a harness declares plugins: as one list of directories in its own repository (same trust and fetch path as skills), and each entry's format decides which runtime loads it — a plugin.json bundle is Claude Code's, a directory pi's -e loader resolves is uploaded and loaded after a tree-hash preflight computed from the host copy. Each runtime names and skips the entries in the other format, so the list survives a runtime switch. Because --no-extensions plus explicit -e closes the set of code that can register tools, no per-tool declaration is needed (ADR 0094).

Behaviour testing

End-to-end behaviour tests use the shared framework in pkg/behaviourtest/ (with live-test infrastructure in internal/e2etest/); the in-repo runner and Gherkin features live under e2e/behaviour/. They validate deterministic platform code — dispatch routing, harness loading, sandbox policy, SCM mutations — with the LLM layer removed via the dummy and dummy-playback runtimes. Tests exercise real GitHub (and GitLab) SCM and GitHub Actions CI through pluggable drivers; Gherkin scenarios stay install-mode agnostic while runner env vars select backends. This coverage is orthogonal to LLM and instruction testing in testing-agents.md. See ADR 0066.

Open questions:

  • Is the runtime a single model call, a loop (plan-act-observe), or something more structured?
  • How does the runtime interact with the sandbox boundaries — does it know what it can't do, or does it just hit walls? (For tool access: both — prose instructions inform the runtime, and permissions.deny hard-blocks execution; see ADR 0027. Broader sandbox interaction remains open.)
  • How do we swap model providers or versions without changing the rest of the stack?
  • What is the interface between the harness and the runtime? (A system prompt? A configuration file? An API contract?) (Decided: the runner-side contract is runtime.Runtime + BootstrapInput, the runtime-neutral sandbox tool-hook contract in ADR 0090, and optional capability interfaces. Each runtime translates the harness prompt into its own instruction slot — pi's APPEND_SYSTEM.md, codex's developer_instructions (ADR 0099) — rather than a shared format.)

Agent Identity Provider

The system that gives agents credentials to act on external services. Responsible for issuing, scoping, rotating, and revoking the identities agents use to interact with GitHub, container registries, and other APIs.

Identity is not the same as trust. An agent's identity lets it authenticate to external services; the trust model is defined by repository permissions and CODEOWNERS, not by which credentials the agent holds. (See agent-architecture.md — "trust derives from repository permissions, not agent identity.")

Decided:

  • Credential delivery model: four tiers — (1) prefetch + post-process for agents with enumerable inputs (zero credential access), (2) OpenShell providers + L7 egress policies for static token auth (credentials never enter sandbox), (3) host-side REST server for operations providers cannot handle — long-running operations, sandbox capability gaps, credentials in request bodies, response transformation, and multi-step atomic operations (see ADR 0046), (4) host files + L7 policies for complex auth requiring in-sandbox credential files. L7 policies enforce both method + path and binary-level restrictions. Providers are preferred over REST servers when viable (ADR 0017, extended by ADR 0025). OpenAI inference on the pi runtime is the first tier-2 use: the runner exchanges the job's GitHub OIDC token for a short-lived OpenAI access token and hands it to a run-scoped OpenShell provider, so no OpenAI credential enters the sandbox (ADR 0092); the codex runtime reuses that provider and follows its refreshes by re-reading a runner-seeded token file through codex's auth.command, since its process environment cannot carry a placeholder that survives a refresh (ADR 0099).
  • Host-side API server design: Credential delivery tier 3 servers follow a uniform process contract (--port, --token, --bind-address, /healthz, /tools.json, SIGTERM). Network access is controlled via composable provider profiles — atomic capability profiles composed per-harness. Per-run UUID bearer tokens are delivered through OpenShell provider placeholders. File transfer uses openshell sandbox upload/download (ADR 0046).
  • Per-role GitHub Apps with manifest-based creation. Each agent role gets its own app with scoped permissions. PEMs stored in Secret Manager as fullsend-{role}-app-pem — one secret per role, shared across orgs on a mint. ROLE_APP_IDS uses the same shared-per-role model (coder → app ID). Org isolation is enforced via ALLOWED_ORGS, WIF conditions, and installation verification (ADR 0007, ADR 0033). Public multi-tenant mint (ALLOWED_ORGS=*) with upstream-only workflow provenance is defined in ADR 0059; upstream-only provenance limits which workflows can call the mint, complementing ADR 0029 multi-tenant blast-radius concerns.
  • Cross-org mint authorization: workflows may request tokens for a different org via optional target_org when the target org installs the role App and sets FULLSEND_FOREIGN_<role>_REPOS (ADR 0060). Repo-level FULLSEND_FOREIGN_<role>_REPOS variables enable per-repo foreign grants (scoped to the specific target repo) and intra-org cross-repo access for per-repo callers, with disjoint authorization boundaries from org-level grants — repo-level for repo-scoped requests, org-level for installation-wide requests (ADR 0083).
  • Mint repos scope: foreign mints with repos: ["*"] require an org-level FOREIGN grant; foreign mints with specific repos require per-repo FOREIGN grants on each requested repo (org-level grants are not consulted for repo-scoped requests). Per-repo callers (repo in PER_REPO_WIF_REPOS) must list exactly the requesting repository unless authorized by repo-level FOREIGN grants for other repos. Per-org callers (org in ALLOWED_ORGS, repo not in PER_REPO_WIF_REPOS) get org-mode shapes: .fullsend callers may use any non-empty validated list; other callers may use [.fullsend] or {self,.fullsend}. Same-org installation-wide tokens are denied (ADR 0077, simplified in ADR 0078).
  • Workflow-host allow-list: WORKFLOW_HOST_REPOS controls which repos may host workflows calling the mint for per-repo and public-mode callers (default: fullsend-ai/fullsend). Per-org callers hard-wire to {org}/.fullsend and upstream. Public mode is not special-cased — it uses the same per-repo validation path with WORKFLOW_HOST_REPOS and the basename allowlist. This separates caller enrollment from workflow-host trust (ADR 0082).
  • Standalone mint deployment: cmd/mint/ provides a self-contained HTTP server that uses direct JWKS verification and filesystem PEM storage instead of GCP infrastructure. It shares the internal/mintcore/ library with the GCF mint and adds support for custom role permissions and a fallback proxy to an upstream mint. Custom role permissions live in mintcore (not cmd/mint/) so that HasRole, RolePermissionsForLevel, and CreateInstallationToken return a unified view without callers needing to distinguish built-in from custom roles. Both the standalone and GCF mints call ParseCustomRolePermissions + RegisterCustomRoleLevels when CUSTOM_ROLE_PERMISSIONS is set. See the standalone mint guide. For mintcore internals (platform accessors, load-site construction, WASM constraints), see the mintcore contributor guide.
  • Hosted public community mint: steady-state deployment on Cloudflare Workers (JWKS + WAF + single ops console), with interim GCP Cloud Function acceptable until the Worker port is production-ready. Trust policy (ALLOWED_ORGS=*, upstream-only workflow provenance) is in ADR 0059; deployment, edge security, monitoring, and phasing are in ADR 0068. Enrollment is installing the shared Apps—no per-org mint env registration (#1145).
  • Named privilege levels: each role defines named levels as keys — read and write are mandatory, extra named levels are allowed on custom roles. The mint looks up the requested level on the role and returns the stored permission map, or fails if the level is missing. Built-in roles statically define both read (all values "read") and write (the canonical ceiling) in the permission table. The mint API accepts an optional level field (default write — temporary compatibility default; a future release will change to read). Flat-format CUSTOM_ROLE_PERMISSIONS entries are stored as both read and write (same permissions for either level). Multi-level format uses a levels key for distinct per-level maps; extra named levels beyond read/write are permitted. The harness privilege_levels flag maps run-stages to levels; omitting it defaults to write, preserving backward compatibility for existing harness configurations (ADR 0073).

One concrete implementation option is oidcx: a service that accepts OIDC identity tokens and exchanges them for short-lived access tokens. It can mint tokens scoped to selected GitHub repositories and permissions, or to selected Oxide silos and permissions, and it also ships with a GitHub Action wrapper. In a Fullsend deployment, this can be used by the sandbox entrypoint to narrow a broad GitHub App identity down to only the specific permissions an agent needs for the current run.

Open questions:

  • What identity model fits best — separate bot accounts per agent role, a single bot account with role metadata, GitHub App installations, or something else? Decided in ADR 0007.
  • How are credentials rotated and revoked, and who has authority to do that?
  • Does the identity provider integrate with existing secrets management, or is it a new system?
  • How will per-role identity work on GitLab and Forgejo, which lack GitHub's app manifest flow? GitLab uses a bot PAT stored as a protected CI/CD variable — see ADR 0067.
  • Which agent roles need Discussions (or other chat) write scopes, and how do those scopes map onto named mint privilege levels? Conversation participation requires least-privilege identity deltas per ADR 0086.

Agent Dispatch and Coordination Layer

The mechanism that assigns work to agents and prevents conflicts. Responsible for translating triggers (GitHub events, schedules, manual requests) into agent tasks and ensuring two agents don't work the same problem simultaneously.

The existing design principle is that the repo is the coordinator — branch protection, CODEOWNERS, status checks, and GitHub events provide coordination without a central orchestrator. The agent dispatch and coordination layer may be nothing more than the glue that connects GitHub webhooks to agent infrastructure. Or it may need to be more.

Decided:

  • Event-driven stage dispatch runs synchronously via workflow_call to preserve run correlation in the GitHub Actions UI (see ADR 0041).
  • Routing moves from workflow bash to harness CEL trigger expressions evaluated by fullsend dispatch with pluggable input/output drivers operating on a NormalizedEvent struct (ADR 0061, partially superseded by ADR 0098).
  • Automatic runs are serialized per harness and event subject. Later events do not cancel an active run. Each event still passes through authorization and CEL routing; the execution platform coalesces matching events into one latest pending run, and each run reconciles the subject's current state. Authority over other comments and content discovered during reconciliation remains a separate decision (ADR 0106).
  • Per-repo polling complements webhook dispatch: fullsend poll uses poll input drivers to discover work from remote systems (Jira first), coordinates via source-native write-then-verify locks, and feeds the same dispatch pipeline as webhooks (ADR 0063). Initial scope is per-repo mode only.
  • Harness routing uses one CEL predicate over a required normalized entity and an optional prompting event. Webhooks provide low-latency candidates; fullsend poll and its input drivers enumerate and resolve scheduled candidates without reconstructing a complete event stream. Entity activity retains actor and authorization provenance, while harness-defined evidence — existing entity activity or explicit receipts — distinguishes handled work (ADR 0098, partially superseding ADR 0063).
  • GitLab dispatch uses cron-polled scheduled pipelines for all events (issues/comments/labels, MR-open review, MR-merge retro, and closed-unmerged retro). Native merge_request_event dispatch was removed in #7322. No webhook bridge required (see ADR 0067).
  • Conversation participation: GitHub Discussions (and future chat systems) enter dispatch as resolved entities with entity.kind: conversation; when a prompting event is available, it expresses threading on transition.comment.id / parent_id (parent_id always names the thread root). They reuse CEL harness triggers and ADR 0054 authorization, and write back through host/post-script or host-side API servers via conversation.Client — not a separate always-on chat bot and not an extension of forge.Client (ADR 0086).
  • Event-backed dispatch authorization: event-triggered paths authorize the prompting actor before dispatch. This includes schedule/manual dispatch represented as a NormalizedEvent, whose actor is the configured service identity. GitHub paths check the acting user's collaborator permission via the repository API (write or above for mutation commands; triage or above for observation stages). Non-GitHub event paths map source-system roles to dispatch authorization roles (read, write, admin) using source-native role resolution, with no cross-system identity verification (Authorization Contract v1; ADR 0054).
  • Poll entity-discovery authorization: fullsend poll has no prompting event actor; verified, non-user-assertable Fullsend invocation provenance authorizes entity enumeration and evaluation, and callers without it are denied. Before each candidate harness's CEL predicate, the platform fail-closed enumerates a closed superset of action-indicating entity elements, resolves each actor's current permission, and removes elements below that harness's observation or mutation threshold. Later input-selection and injection filtering are defense in depth for prompt construction. Entity-first execution remains disabled until its versioned normalized-entity contract exists. Every run uses the harness's configured identity (ADR 0098).

Open questions:

  • What normative entity-history, query-planning, and handled-state contract can support entity-first harness evaluation without unbounded provider reads (ADR 0098)?
  • Is GitHub's event system sufficient for forge-native duplicate protection, or do we need additional coordination beyond label/state conventions and agent idempotency? (Jira polling per ADR 0063 uses entity-property locks and runner lock refresh; ADR 0098 does not resolve this question.)
  • How does work assignment interact with the backlog/priority agent described in agent-architecture.md?
  • How should explicit cancellation, retry, and reassignment interact with the automatic event-coalescing policy in ADR 0106?
  • Does the coordinator need state (a queue, a lock, a claim system), or can it be stateless and event-driven?
  • When should a conversation or thread be linked to a work item (e.g. Discussion → issue) so a conversation-native agent can hand off to /fs-code without violating entity-context separation (ADR 0076, ADR 0086)?
  • How should concurrent agent runs that touch the same conversation thread be coordinated (ADR 0086)?

Policy Store

Where agent behavioral rules live. Responsible for holding autonomy levels, review requirements, allowed operations, and escalation rules — the configuration that governs what agents may do.

Policy is distinct from the harness (which configures how an agent works) and from intent (which defines what work is authorized). Policy defines the boundaries of agent behavior — what an agent is allowed to do regardless of what it's asked to do.

Each target repository's .fullsend/ directory holds policy configuration — guardrails, autonomy levels, and escalation rules governed by the repo's CODEOWNERS and review process (ADR 0033).

Open questions:

  • How is policy versioned, and how do we ensure agents run under the correct policy version?
  • Who can change policy, and what approval process governs policy changes? (See governance.md.)
  • How does policy interact with the autonomy spectrum — is the auto-merge vs. escalate decision a policy setting? (See autonomy-spectrum.md.)

Intent Source

The system that provides authorized intent for agent work. Responsible for representing what changes are wanted, who authorized them, and at what intent authorization tier of approval.

Intent answers the question "should this change exist?" before anyone asks "is this change correct?" Without authorized intent, an agent has no basis for deciding what to work on or whether its output matches what was asked for.

Each target repository's .fullsend/ directory holds the pointer to the intent source (for example, intent_repo: your-org/features), so tooling discovers where intent lives without hardcoding (ADR 0033).

Open questions:

  • What is the right representation — forge issues, a dedicated intent repo, RFCs, or tiered combinations? (See intent-representation.md.)
  • How do agents verify that intent is authentic and hasn't been tampered with?
  • How do different intent authorization tiers (standing rules, tactical issues, strategic features) map to different authorization requirements?
  • How does intent interact with the "try it" phase — agents building exploratory drafts before authorization? (See intent-representation.md.)

Observability

The logging, tracing, and audit layer for agent actions. Responsible for making every agent action attributable, traceable, and reviewable — both for debugging failures and for security auditability.

Observability is a cross-cutting concern that touches every other component. Each component produces signals; this component is responsible for collecting, storing, and making them useful.

Decided:

  • JSONL reasoning trace exposure: raw JSONL conversation transcripts are extracted from sandboxes and stored with owner-scoped access. Credential scanning acts as an invariant check on ADR 0017's isolation model. Agents handling data from protected sources beyond the target repo can opt in to JSONL suppression via configuration (ADR 0021).
  • Event-driven stage dispatch remains traceable end-to-end in the GitHub Actions UI by using synchronous workflow_call dispatch (see ADR 0041).
  • Scheduled entity-discovery runs are attributable to the verified Fullsend invocation identity, target repository, effective policy, harness revision, and resolved entity; retained action-indicating elements preserve their actor provenance. State-only predicates trace to the configured service identity and versioned policy/harness configuration (ADR 0098).
  • Distributed tracing: framework-native OpenTelemetry instrumentation with zero-configuration baseline. Every run produces run-telemetry.jsonl locally; optional live OTLP export to any compatible backend. W3C trace context propagation links multi-agent pipelines into unified traces. OTEL GenAI semantic conventions enable LLM-aware backends (ADR 0050).
  • Eval measurements: the concept of scoring traces (fail-open). OTEL primary facts stay on the run trace (run-telemetry.jsonl); OTEL derived products are the scores (eval-measurements.jsonl) (ADR 0087). See Eval Measurements. When OTEL_EXPORTER_OTLP_* is set, scores also export as gen_ai.evaluation.result span events on the same TraceID (same OTLP path as agent traces; fail-open).

Open questions:

  • What signals matter most — cost, latency, token usage, action logs, decision traces, or something else?
  • How do we balance detailed tracing (useful for debugging) with the volume of data agents will produce? Decided in ADR 0050: instrument all lifecycle steps comprehensively; volume is managed by backends not by suppressing data at the source.
  • How do we score wild agent traces for trends without a second export stack? Decided in ADR 0087: eval measurements write local JSONL beside telemetry when at least one new score row is produced (including label: skip); portable remote export uses the same OTLP config as traces (gen_ai.evaluation.result events). The JSONL is absent (not empty) when telemetry/manifest is missing, no traces match, or every candidate is already in the ledger.
  • What is the retention and access model for agent logs? Who can see what? (JSONL trace access model decided in ADR 0021; retention policy and broader log access remain open.)
  • How does observability interact with the security requirement that "every action is logged, attributable, and reviewable"? Scheduled entity-discovery attribution is decided in ADR 0098; broader audit-log requirements remain open. (See security-threat-model.md.)
  • Is there a real-time monitoring requirement (agent is stuck, agent is behaving anomalously), or is observability primarily forensic?

Agent Registry

The catalog of available agent roles and their configurations. Responsible for defining what agent types exist, what capabilities each has, and how they are instantiated.

The registry is the bridge between the abstract roles defined in agent-architecture.md (correctness sub-agent, intent & coherence sub-agent, security sub-agent, etc.) and the concrete runtime configurations that the harness uses to set up each agent.

Fullsend provides a base set of agent definitions. Each target repository's .fullsend/ directory extends this with repo-specific agents, following the inheritance model: fullsend defaults, then repo baseline (config.base.yaml / harness base: references), then repo overrides (ADR 0033, ADR 0058).

Decided:

  • Config-level agent registration: an agents list in both OrgConfig and PerRepoConfig declares agent harness sources as pinned URLs or local paths, replacing compiled-in agent discovery (ADR 0058).
  • Runtime resolution: fullsend run <name> resolves agents in two tiers: (1) config entries from OrgConfig.Agents (highest priority), (2) runtime fallback to the fullsend-ai/agents repository for known first-party agents not in config. The agents-repo fallback is a transitional mechanism for the agent extraction; it will be removed once all users have migrated to config-driven registration (ADR 0058 Phase 5).
  • Config lookup: config entries are looked up directly via findConfigAgentEntry; the agents-repo fallback operates independently when the agent is not found in config. Builds on ADR 0045 harness identity model.
  • CLI management: fullsend agent add|list|set|update|remove manages config entries and auto-pins URLs to a commit SHA with an integrity hash.
  • Agent generation: fullsend agent new <name> writes a complete custom agent — harness, agent definition, result schema, post-script, and the policy, providers and profiles a per-repo install does not vendor — validates it with the loader dispatch uses, and registers it through the agent add path above. A trigger: is mandatory, because a trigger-less harness registers and validates and is then silently never dispatched; --role is a closed table of the roles the hosted mint serves, so an unservable role fails locally rather than as a 403 at first dispatch (ADR 0102).

Open questions:

  • How are new agent roles added, tested, and promoted to production? (See testing-agents.md.) (Functional tests provide a framework for testing agent roles against controlled fixtures — ADR 0052. Promotion workflow remains open.)
  • Does the registry include version information, so we can roll back to a previous agent configuration?
  • How does the registry relate to the policy store — does policy reference registry entries, or are they independent?

Reference workflow components (ADR 0002)

The Initial Fullsend Design describes a concrete GitHub-centric issue→merge workflow. Its building blocks are named below so this document and the ADR stay aligned. Descriptions are brief; the ADR is normative for behavior.

1. Webhook + dispatch service

Normalizes GitHub events (issue/PR/label/comment/check/merge), deduplicates flapping events, and dispatches work to agent runtimes. ADR 0002: Building block 1.

2. Slash-command parser + ACL

Parses /fs-triage, /fs-code, /fs-review, and related commands and enforces who is allowed to invoke each. Commands are restricted to the entity context where their agent's inputs exist — /fs-code dispatches only from issues (no associated PR), /fs-fix and /fs-review only from PRs (ADR 0076). Conversation surfaces (GitHub Discussions and future chat systems) are a separate entity context: conversation-native agents may listen on conversations/threads there, but code-mutating slash commands do not (ADR 0086). ADR 0002: Building block 2.

3. Label state machine guard

Validates legal label transitions and enforces mutual exclusion and run-start reset semantics (triage start clears duplicate and downstream labels; blocked is cleared by the post-script when a non-blocked outcome is reached; PR/review strips per ADR). ADR 0002: Building block 3.

4. triage agent runtime

Runs triage from issue title/body + GitHub-native attachments only; each run starts with duplicate and other reset labels cleared; duplicate detection, prerequisite detection (cross-repo), readiness, reproducibility, test handoff; can close as duplicate again if still a match, label blocked when progress depends on another open issue or PR, or create upstream prerequisite issues when no tracking issue exists (controlled by create_issues.allow_targets config). ADR 0002: Building block 4.

Provides candidate duplicate retrieval and confidence scoring for triage duplicate decisions. ADR 0002: Building block 5.

6. Repro sandbox template

Isolated environment used by triage for reproducibility checks. ADR 0002: Building block 6.

7. Test artifact formatter

Formats triage test artifacts in repo-native conventions for PR handoff. ADR 0002: Building block 7.

8. code agent runtime

Implements changes, runs local/CI-equivalent tests, handles check failures, and opens or updates a PR. Review dispatch is triggered automatically by pull_request_target events. ADR 0002: Building block 8.

9. PR sandbox / CI mirror

Execution environment for Code and test loops, aligned to contributor/CI toolchains. ADR 0002: Building block 9.

10. Check failure triage

Fetches and classifies failing check logs to guide code agent remediation loops. ADR 0002: Building block 10.

11. review agent runtime

Runs N parallel review agent invocations and produces structured review verdicts/comments. ADR 0002: Building block 11.

Decided:

  • PR-level risk assessment scoring: pre-pass sub-agent computes a composite 1–5 risk score from metadata, git history, and linked-issue signals (ADR 0089).

12. Coordinator merge algorithm

Aggregates review verdicts and applies labels:

  • unanimous approve-merge → ready-for-merge (for the current PR head at the end of that round only)
  • unanimous rework → triggers fix agent
  • split/conflicting (including conflicting security severities) → requires-manual-review
  • each review run start (including push-triggered re-review) clears ready-for-merge together with ready-for-review so merge approval is never stale after new commits ADR 0002: Building block 12.

13. Observability

Traceability layer across issue, Triage, Code, Review, checks, and merge for incident response and correlation across automation runs. ADR 0002: Building block 13.

14. retro agent runtime

Retrospective analyst — examines completed or in-progress agent workflows, identifies improvement opportunities, and files proposals as GitHub issues. Runs automatically on PR close (merged or rejected) and on-demand via /fs-retro command. Analyzes the full workflow graph (triage, code, review, fix agent interactions and human interventions) and posts a summary comment on the originating PR/issue linking to all filed proposals.

Configuration layering

Fullsend uses a three-tier configuration inheritance model for all configuration: agent definitions, skills, plugins, policies, harness definitions, and guardrails. Each configuration tier can extend or override the one below it. Guardrails can only be tightened, never weakened.



  ┌──────────────────────────────────────────────────────────────────┐
  │  fullsend-ai/fullsend                    (upstream open source)  │
  │                                                                  │
  │  Framework defaults:                                             │
  │    base agents, skills, plugins, policies                         │
  │    fullsend CLI (fullsend run, fullsend install, ...)            │
  │    scaffold templates, security scanners                         │
  │                                                                  │
  │  Owned by: fullsend project maintainers                          │
  ├──────────────────────────────────────────────────────────────────┤
  │  <org>/.fullsend                              (dedicated repo)   │
  │                                                                  │
  │  Org-wide configuration:                                         │
  │    agents/            org agent definitions (.md)                │
  │    skills/            org skills (shared across repos)           │
  │    policies/          sandbox network/filesystem policies        │
  │    harness/           per-agent harness configs (.yaml)          │
  │    guardrails.yaml    org-wide guardrails (can only be tightened)│
  │    config.yaml        intent repo, runtime, infrastructure       │
  │                                                                  │
  │  Owned by: org platform team (CODEOWNERS, human-only)            │
  ├──────────────────────────────────────────────────────────────────┤
  │  <org>/<repo>                               (directory in repo)  │
  │                                                                  │
  │  Repo-specific overrides:                                        │
  │    AGENTS.md          per-repo agent instructions                │
  │    skills/            repo-specific skills (domain knowledge)    │
  │    .fullsend/config   overrides -  adjust timeouts, prompts      │
  │                                                                  │
  │  Owned by: repo maintainers (CODEOWNERS)                         │
  └──────────────────────────────────────────────────────────────────┘

  Inheritance:  fullsend defaults  <  org .fullsend config  <  per-repo overrides
                (base)                (extend/override)        (extend/tighten)

In per-repo installation the middle tier is replaced by files inside the target repo: .fullsend/config.base.yaml (vendor preset or baseline) and .fullsend/config.yaml (repo overlay), with code defaults below both. The org-tier box above describes the historical per-org model, now deprecated (ADR 0044, ADR 0069).

Skills flow downward through this stack. A repo-level skill might encode domain knowledge ("this repo uses a custom ORM — here's how queries work"). An org-level skill might encode org conventions ("all services use structured logging via zerolog"). Upstream fullsend provides foundational skills (code implementation, triage coordination, testing conventions).

AGENTS.md files follow the same layering. A repo's .fullsend/AGENTS.md gives agents repo-specific instructions (build commands, test patterns, architectural constraints). The org's .fullsend/agents/ directory provides role-specific agent definitions that apply across all enrolled repos.

See ADR 0003 for the config repo convention and ADR 0024 for harness definitions.

Decided:

  • Agent configuration: upstream defaults (agents, skills, plugins, schemas, harness, policies, scripts) are resolved at runtime from fullsend-ai/agents, or from vendored files when --vendor was used at install (detected via .defaults/action.yml — see ADR 0047). Customization uses base: harness composition, URL resource references, and config-based agent registration (ADR 0045, ADR 0064).

Multi-org deployment model

Each organization that adopts fullsend operates independently. There is no shared control plane, no central service, and no relationship between orgs. Each org brings its own inference API keys and runs its own version of fullsend.

  ┌──────────────────────┐  ┌──────────────────────┐  ┌──────────────────────┐
  │  Org A               │  │  Org B               │  │  Org C               │
  │                      │  │                      │  │                      │
  │  .fullsend repo      │  │  .fullsend repo      │  │  .fullsend repo      │
  │  ┌────────────────┐  │  │  ┌────────────────┐  │  │  ┌────────────────┐  │
  │  │ config.yaml    │  │  │  │ config.yaml    │  │  │  │ config.yaml    │  │
  │  │ agents/        │  │  │  │ agents/        │  │  │  │ agents/        │  │
  │  │ skills/        │  │  │  │ skills/        │  │  │  │ skills/        │  │
  │  │ harness/       │  │  │  │ harness/       │  │  │  │ harness/       │  │
  │  └────────────────┘  │  │  └────────────────┘  │  │  └────────────────┘  │
  │                      │  │                      │  │                      │
  │  API keys: own       │  │  API keys: own       │  │  API keys: own       │
  │  Enrolled repos: ... │  │  Enrolled repos: ... │  │  Enrolled repos: ... │
  │  fullsend v0.2.0     │  │  fullsend v0.4.1     │  │  fullsend v0.2.0     │
  │                      │  │                      │  │                      │
  └──────────┬───────────┘  └──────────┬───────────┘  └──────────┬───────────┘
             │                         │                         │
             │            no relationship between orgs           │
             │                         │                         │
             └─────────────────────────┼─────────────────────────┘

                            ┌──────────┴───────────┐
                            │  fullsend-ai/fullsend│
                            │                      │
                            │  Open source project │
                            │  CLI, base agents,   │
                            │  skills, scaffold    │
                            │                      │
                            │  Orgs pull releases  │
                            │  at their own pace   │
                            └──────────────────────┘

Each org is a fully independent instance. They choose when to upgrade. They configure their own agents, skills, plugins, and policies. They use their own model providers and API keys. The only shared element is the upstream fullsend project they all pull from.

Downstream/upstream federation

Independent orgs can optionally collaborate across the forge boundary. A downstream org — a vendor, contributor, or consumer — runs its own fullsend instance for internal work. An agent in that downstream instance can push feature proposals upstream to a project that has its own full SDLC.

  ┌─── Upstream Project ───────────────────────────────────────────┐
  │                                                                │
  │       Refinement ──► Prioritization ──► Execution              │
  │      ╱                                           ╲             │
  │  Discovery                                        Verification │
  │      ╲                                           ╱             │
  │       Feedback ◄─────── Monitor ◄──────── Release              │
  │          ▲                                   │                 │
  └──────────│───────────────────────────────────│─────────────────┘
             │                                   └─────────┐
             │      upstreaming agent                      │
             │     proposes enhancement                    │ release
             └────────────────────────────────┐            │
                                              │            │
  ┌─── Downstream Org (vendor/consumer) ──────│────────────│───────┐
  │                                           │            │       │
  │       Refinement ──► Prioritization ──► Execution      │       │
  │      ╱                                                 ▼       │
  │  Discovery                                        Verification │
  │      ╲                                           ╱             │
  │       Feedback ◄──── Monitor ◄──────── Delivery                │
  │                                                                │
  └────────────────────────────────────────────────────────────────┘

Both orgs run the full SDLC loop. The two cross-org handoff points are:

  1. Downstream Prioritization → Upstreaming agent → Upstream Refinement. When the downstream org's SDLC prioritizes work that belongs upstream, the handoff at Prioritization → Execution goes to an upstreaming agent instead of a coding agent. This agent drafts proposals (issues or PRs) and ferries them into the upstream project's Refinement or Prioritization process via the forge.

  2. Upstream Delivery → Downstream Verification. When the upstream project delivers a release, the downstream org consumes it. The new release enters the downstream SDLC at Verification — the downstream validates against its own integration tests, compatibility requirements, and deployment constraints.

The forge (GitHub) is the interface between the two orgs. The upstream project doesn't need to know or care that the proposal was generated by an agent in a downstream fullsend instance — it evaluates contributions through its own SDLC the same way it evaluates any human or agent contribution.

This connects to the downstream/upstream problem doc, which explores how competing sources of strategic intent get reconciled when multiple downstream contributors propose features into the same upstream project.

Runtime execution flow

The diagrams below show the runtime path from event to completed agent task. The installer, admin CLI, and enrollment machinery are not shown — only what happens when an agent actually runs.

The architecture is a set of concentric layers, each wrapping the next:

Dispatcher → Agent Runner → Sandbox → Agent Runtime → LLM

Each outer layer configures and constrains the layer inside it. No inner layer can modify an outer layer. Credentials exist only in the outermost layers and never cross the sandbox boundary inward.

Abstract model

This diagram is platform-agnostic. It uses a nested-box layout to show the concentric wrapping structure: each layer wraps the one inside it, and control flows inward (setup), then outward (teardown and delivery). No specific SCM, CI system, sandbox runtime, or LLM is named.

event ──► DISPATCHER
          Filters event, selects agent role, dispatches run


          ╔═══════════════════════════════════════════════════════╗
          ║ AGENT RUNNER                                          ║
          ║                                                       ║
          ║ Loads harness definition for agent role:              ║
          ║   agent prompt, sandbox image, network policy,        ║
          ║   skills, pre/post scripts, validation config,        ║
          ║   output schema, host files, env vars                 ║
          ║                                                       ║
          ║ Runs pre-script on host:                              ║
          ║   validate inputs, prefetch data                      ║
          ║   may request skip, exiting before sandbox creation   ║
          ║                                                       ║
          ║ ┌───────────────────────────────────────────────────┐ ║
          ║ │ SANDBOX (ephemeral, per-run)                      │ ║
          ║ │                                                   │ ║
          ║ │ Created with image + network policy.              │ ║
          ║ │ Bootstrapped with agent def, skills, repo code,   │ ║
          ║ │ env vars, host files, security hooks.             │ ║
          ║ │ No credentials present inside this boundary.      │ ║
          ║ │                                                   │ ║
          ║ │ Pre-agent security scan (context injection).      │ ║
          ║ │                                                   │ ║
          ║ │ ┌───────────────────────────────────────────────┐ │ ║
          ║ │ │ AGENT RUNTIME                                 │ │ ║
          ║ │ │                                               │ │ ║
          ║ │ │ LLM tool-use loop:                            │ │ ║
          ║ │ │   read code, edit files, run tests, iterate   │ │ ║
          ║ │ │                                               │ │ ║
          ║ │ │ Boundaries enforced by enclosing sandbox:     │ │ ║
          ║ │ │   network policy, security hooks,             │ │ ║
          ║ │ │   no credentials, filesystem restrictions     │ │ ║
          ║ │ │                                               │ │ ║
          ║ │ │ Produces: modified repo, output artifacts     │ │ ║
          ║ │ └───────────────────────────────────────────────┘ │ ║
          ║ │                                                   │ ║
          ║ └───────────────────────────────────────────────────┘ ║
          ║                                                       ║
          ║ Extracts from destroyed sandbox:                      ║
          ║   output files, reasoning transcripts, modified repo  ║
          ║                                                       ║
          ║ Post-agent security scan (redact secrets from output) ║
          ║                                                       ║
          ║ Validation loop (if configured):                      ║
          ║   schema check on host                                ║
          ║   ├─ pass: continue                                   ║
          ║   ├─ fail + agent killed at timeout: HARD FAILURE     ║
          ║   │   (no retry; "agent timed out" error, unless an   ║
          ║   │   earlier or this iteration's output validated)   ║
          ║   ├─ fail + retries remain: re-run agent w/ feedback  ║
          ║   └─ fail + retries exhausted: HARD FAILURE           ║
          ║     (no unvalidated output emitted)                   ║
          ║                                                       ║
          ║ Runs post-script on host (outside sandbox):           ║
          ║   push code, create PR, post comments, apply labels   ║
          ║                                                       ║
          ╚═══════════════════════════════════════════════════════╝


          Results applied to external system

Key invariants visible in this layout:

  • Credentials never cross the sandbox boundary. They exist in the agent runner layer; the sandbox and everything inside it operate without them.
  • Control flows inward (setup) then outward (teardown). The harness configures the sandbox; the sandbox constrains the runtime. No inner layer can modify an outer layer.
  • Validation gates output. When configured, no unvalidated output crosses from runner to external system. Exhausted retries are a hard failure, not a fallback.
  • The sandbox is ephemeral. Created per-run, destroyed after extraction. No state carries between runs.

MVP embodiment: GitHub + GitHub Actions + OpenShell + Claude Code

The same wrapping structure, with each layer mapped to its concrete technology.

GitHub event ──► SHIM WORKFLOW (fullsend.yml in enrolled repo)
                 Evaluates dispatch conditions (event type, labels, /slash commands).
                 Calls workflow_call to .fullsend repo (dispatch.yml).


                 ╔═══════════════════════════════════════════════════════════════╗
                 ║ DISPATCH WORKFLOW (.fullsend repo, dispatch.yml)              ║
                 ║                                                               ║
                 ║ Mints OIDC token → Cloud Function (token mint) → scoped       ║
                 ║ GitHub App installation token per agent role.                 ║
                 ║ Dispatches per-role agent workflows (code.yml, triage.yml).   ║
                 ╚═══════════════════════════════════════════════════════════════╝


                 ╔═══════════════════════════════════════════════════════════════╗
                 ║ AGENT WORKFLOW (.fullsend repo, e.g. code.yml)                ║
                 ║                                                               ║
                 ║ Validates source repo is enrolled in config.yaml.             ║
                 ║ Uses scoped GitHub App tokens:                                ║
                 ║   read-only token → enters sandbox (clone, read issues)       ║
                 ║   read-write token → stays on runner (push, create PR)        ║
                 ║ Checks out .fullsend repo + target repo.                      ║
                 ║                                                               ║
                 ║ ┌───────────────────────────────────────────────────────────┐ ║
                 ║ │ FULLSEND CLI (fullsend run code)                          │ ║
                 ║ │                                                           │ ║
                 ║ │ Loads harness/code.yaml:                                  │ ║
                 ║ │   agent: agents/code.md                                   │ ║
                 ║ │   image: ghcr.io/fullsend-ai/fullsend-code:latest         │ ║
                 ║ │   policy: policies/base.yaml                              │ ║
                 ║ │   providers: [vertex-ai, github, package-registries]      │ ║
                 ║ │   skills: [skills/code-implementation]                    │ ║
                 ║ │   pre_script: scripts/pre-code.sh                         │ ║
                 ║ │   post_script: scripts/post-code.sh                       │ ║
                 ║ │                                                           │ ║
                 ║ │ Pre-script: validates ISSUE_NUMBER, REPO_FULL_NAME,       │ ║
                 ║ │ URL consistency.                                          │ ║
                 ║ │                                                           │ ║
                 ║ │ ┌───────────────────────────────────────────────────────┐ │ ║
                 ║ │ │ OPENSHELL SANDBOX                                     │ │ ║
                 ║ │ │                                                       │ │ ║
                 ║ │ │ Created with --from image, --policy base.yaml.        │ │ ║
                 ║ │ │ Bootstrapped via openshell upload/exec:               │ │ ║
                 ║ │ │   agent def    → /sandbox/claude-config/agents/       │ │ ║
                 ║ │ │   skills       → /sandbox/claude-config/skills/       │ │ ║
                 ║ │ │   .env, host files (GCP creds), security hooks        │ │ ║
                 ║ │ │   target repo  → /sandbox/workspace/target-repo/      │ │ ║
                 ║ │ │                                                       │ │ ║
                 ║ │ │ Network policy enforced (L7, per-binary):             │ │ ║
                 ║ │ │   Vertex AI     → claude, node only                   │ │ ║
                 ║ │ │   GitHub API    → gh, git only                        │ │ ║
                 ║ │ │   Pkg registries → npm, pip, go                       │ │ ║
                 ║ │ │                                                       │ │ ║
                 ║ │ │ Pre-agent scan: fullsend scan context                 │ │ ║
                 ║ │ │ (injection detection on CLAUDE.md, AGENTS.md, etc.)   │ │ ║
                 ║ │ │                                                       │ │ ║
                 ║ │ │ ┌───────────────────────────────────────────────────┐ │ │ ║
                 ║ │ │ │ CLAUDE CODE (claude --agent code)                 │ │ │ ║
                 ║ │ │ │                                                   │ │ │ ║
                 ║ │ │ │ Tool-use loop:                                    │ │ │ ║
                 ║ │ │ │   read files, edit code, run tests, iterate       │ │ │ ║
                 ║ │ │ │                                                   │ │ │ ║
                 ║ │ │ │ Model: Opus (via Vertex AI)                       │ │ │ ║
                 ║ │ │ │ Security hooks active: Tirith, SSRF, secret scan  │ │ │ ║
                 ║ │ │ │ No credentials in environment.                    │ │ │ ║
                 ║ │ │ │                                                   │ │ │ ║
                 ║ │ │ │ Produces: modified repo, output artifacts         │ │ │ ║
                 ║ │ │ └───────────────────────────────────────────────────┘ │ │ ║
                 ║ │ │                                                       │ │ ║
                 ║ │ └───────────────────────────────────────────────────────┘ │ ║
                 ║ │                                                           │ ║
                 ║ │ Extracts from destroyed sandbox:                          │ ║
                 ║ │   /sandbox/workspace/output/, JSONL transcripts,          │ ║
                 ║ │   SafeDownload repo (sanitize symlinks, strip hooks)      │ ║
                 ║ │                                                           │ ║
                 ║ │ Post-agent secret scan (redact from extracted output).    │ ║
                 ║ │                                                           │ ║
                 ║ │ Post-script (scripts/post-code.sh, with PUSH_TOKEN):      │ ║
                 ║ │   1. Verify feature branch (not main/master)              │ ║
                 ║ │   2. Protected-path check                                 │ ║
                 ║ │   3. gitleaks secret scan                                 │ ║
                 ║ │   4. pre-commit hooks                                     │ ║
                 ║ │   5. git push --force-with-lease                          │ ║
                 ║ │   6. Create/update PR with ready-for-review label         │ ║
                 ║ │                                                           │ ║
                 ║ └───────────────────────────────────────────────────────────┘ ║
                 ║                                                               ║
                 ║ Upload artifacts (fullsend-code)                              ║
                 ╚═══════════════════════════════════════════════════════════════╝


                 Branch pushed, PR created with ready-for-review label

Layer mapping (abstract → MVP):

Abstract layerMVP technologyADR
DispatcherShim workflow (fullsend.yml) in enrolled repo → workflow_call to .fullsend/dispatch.yml → OIDC mint → per-role agent workflows (thin callers → upstream reusable workflows)ADR 0008, ADR 0031
Agent runnerGitHub Actions job → fullsend run CLI (via fullsend-ai/fullsend@<version> composite action)
Harness storeYAML files in .fullsend/harness/ (e.g. code.yaml, triage.yaml)
SandboxOpenShell with per-agent L7 network policies (endpoint + binary restrictions)
Agent runtimeClaude Code (claude --agent --dangerously-skip-permissions); pi (pi --print --mode json) and Codex (codex exec --json) as opt-in runtimesruntimes.md
Sandbox imageghcr.io/fullsend-ai/fullsend-code:latest (pre-built with tools, runtimes, security scanners)
Credential isolationRead-only GitHub App token inside sandbox; write token only in post-scriptADR 0017
ValidationHost-side schema validation script with retry loopADR 0022
Post-scriptpost-code.sh (in fullsend-ai/agents): protected-path check, gitleaks scan, pre-commit, push, PR creation
ObservabilityJSONL transcript extraction, security findings, trace ID correlationADR 0021

Two runtimes inside the same sandbox

The OpenShell box above is drawn for Claude Code. With runtime: pi the outer layers are identical — same dispatch, same sandbox creation, same policy, same scans, same extraction — and only the innermost box changes. The diagram shows the two side by side; the amber step is pi's integrity check on its hook adapter, which has no Claude Code equivalent because Claude loads hooks from a runner-owned --settings file.

Loading diagram...

See runtimes.md for the control-by-control security matrix, the config-key mapping and how to select a runtime per repo.

Repository layout (design workspace vs. web delivery)

The repository combines design documents, Go CLI code, and a small public web surface. Decided: Browser-oriented static source lives under web/ (the landing page is web/public/index.html at /, the interactive document graph is web/public/graph.html at /graph.html, and the custom 404 page is web/public/404.html). The user-facing documentation site is built from docs/ by VitePress (npm run docs:build) and served under /docs/. Cloudflare Wrangler configuration and deploy-time static assets live under cloudflare_site/ (single wrangler.toml with not_found_handling = "404-page"; CI stages _bundle/ on the deploy runner and copies only public/ and worker/ from the artifact into that tree so wrangler.toml is never taken from the PR-built zip). See ADR 0019.

The admin installation SPA that formerly lived under web/admin/ was removed on 2026-08-20, together with the OAuth BFF it required in the site Worker. The site Worker (cloudflare_site/worker/) is now a static-asset passthrough that needs no vars or secrets. Installation is driven entirely by the fullsend CLI (fullsend github setup, fullsend repos). This is unrelated to the public mint Worker, which is a separate deployment provisioned from internal/dispatch/cf/ and served at mint.fullsend.sh (ADR 0068).