Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion .github/scripts/select-eval-agents-test.sh
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,6 @@ YAML
cat > "$tmpdir/harness/code.yaml" << 'YAML'
agent: agents/code.md
doc: docs/code.md
policy: policies/code.yaml
pre_script: scripts/pre-code.sh
post_script: scripts/post-code.sh
host_files:
Expand Down
2 changes: 1 addition & 1 deletion FEATURES.md
Original file line number Diff line number Diff line change
Expand Up @@ -162,7 +162,7 @@ agent prompt:
If the new option requires the agent to reach a new external service
from the sandbox:

- [ ] Update `policies/<agent>.yaml` to allow the new host/port
- [ ] Update `policies/<forge>/<agent>.yaml` to allow the new host/port
- [ ] This is rare — most configuration changes don't need network
changes

Expand Down
14 changes: 12 additions & 2 deletions LOCAL.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,10 +38,18 @@ GitHub issue.
Export the variables the agent needs:

```bash
# GitHub:
export GITHUB_ISSUE_URL="https://github.com/your-org/test-repo/issues/25"
export GH_TOKEN="$(gh auth token)"
export FULLSEND_FORGE="github"

# GitLab (alternative — set these INSTEAD of the GitHub vars above;
# only one forge's vars should be set at a time, and FULLSEND_FORGE
# must match the chosen forge):
# export GITLAB_ISSUE_URL="https://gitlab.com/your-group/test-project/-/issues/25"
# export GITLAB_TOKEN="glpat-xxxxxxxxxxxxxxxxxxxx"
# export FULLSEND_FORGE="gitlab"

# GCP/Vertex AI credentials — required by most agents via
# common/env/gcp-vertex.env and the host_files GOOGLE_APPLICATION_CREDENTIALS
# mount in harness YAML.
Expand All @@ -56,9 +64,11 @@ If you're testing a new env var, export it here too. You can also use

### 2. Clone the target repo

`GITHUB_ISSUE_URL` above points at an issue in a separate repo (e.g.
The issue URL above points at an issue in a separate repo (e.g.
`your-org/test-repo`) — clone it to its own local path so `--target-repo`
has real content to work against:
has real content to work against. The harness maps `GITHUB_ISSUE_URL` or
`GITLAB_ISSUE_URL` to a generic `ISSUE_URL` via the per-forge env file
(`env/github/*.env` or `env/gitlab/*.env`):

```bash
git clone git@github.com:your-org/test-repo /tmp/target-repo
Expand Down
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,9 +34,9 @@ common/ Shared configuration (GCP Vertex AI auth)

Agents run inside sandboxed containers with strict filesystem, network, and binary restrictions. Each agent follows a three-phase pipeline:

1. **Pre-script** — runs on the GitHub Actions runner to validate inputs and prepare the environment
1. **Pre-script** — runs on the CI runner to validate inputs and prepare the environment
2. **Sandbox** — runs the agent with restricted permissions; the agent writes code and produces structured JSON output
3. **Post-script** — runs on the runner with elevated permissions to perform GitHub mutations (pushing branches, creating PRs, posting comments, applying labels)
3. **Post-script** — runs on the runner with elevated permissions to perform forge mutations (pushing branches, creating PRs/MRs, posting comments, applying labels)

The agent never has direct write access to the repository. All mutations flow through post-scripts.

Expand Down
4 changes: 2 additions & 2 deletions agents/code.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
---
name: code
description: >-
Implementation specialist for GitHub issues. Reads triaged issues, implements
Implementation specialist for issues. Reads triaged issues, implements
fixes following repo conventions, runs tests and linters, and commits to a
feature branch. Use when implementing a fix or feature from a triaged issue.
model: opus
Expand All @@ -11,7 +11,7 @@ skills:

# Code Agent

You are an implementation specialist. Your purpose is to read a triaged GitHub
You are an implementation specialist. Your purpose is to read a triaged
issue, implement a fix or feature following the target repository's conventions,
verify it passes tests and linters, and commit the result to a local feature
branch. You do not triage issues, review PRs, push branches, create PRs, or
Expand Down
38 changes: 31 additions & 7 deletions docs/code.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

![Code agent icon](icons/coder.png)

Implementation specialist that reads triaged GitHub issues, implements fixes or features following repository conventions, runs tests and linters, and commits to a local feature branch.
Implementation specialist that reads triaged issues, implements fixes or features following repository conventions, runs tests and linters, and commits to a local feature branch.

## Setup

Expand Down Expand Up @@ -45,8 +45,9 @@ See [Customizing with AGENTS.md](https://fullsend.sh/docs/guides/user/customizin

| Variable | Description | Default | Valid values |
|----------|-------------|---------|--------------|
| `CODE_ALLOWED_TARGET_BRANCHES` | Restricts which branches the code agent can target when pushing. The post-code script validates the agent's chosen target branch against this variable before pushing. Set via `env.runner` in `harness/code.yaml` (never injected into the sandbox). | Repo default branch (auto-detected via GitHub API; falls back to `main`) | Comma-separated branch names (e.g. `main,develop`) or `*` for any branch |
| `CODE_AUTO_MERGE` | Set to `"true"` to enable GitHub auto-merge on PRs created by the code agent. Requires branch protection with required reviews or status checks on the target branch. Read directly from the runner environment (not declared in `env.runner`). | `""` (disabled) | `"true"` to enable |
| `CODE_ALLOWED_TARGET_BRANCHES` | Restricts which branches the code agent can target when pushing. The post-code script validates the agent's chosen target branch against this variable before pushing. Set via `env.runner` in `harness/code.yaml` (never injected into the sandbox). | Repo default branch (auto-detected via forge API; falls back to `main`) | Comma-separated branch names (e.g. `main,develop`) or `*` for any branch |
| `FULLSEND_FORGE` | Forge platform. Set automatically by the harness `forge.<platform>.env` section. | (set by harness) | `"github"`, `"gitlab"` |
| `CODE_AUTO_MERGE` | Set to `"true"` to enable auto-merge on PRs/MRs created by the code agent. On GitHub, uses `gh pr merge --auto`; on GitLab, uses `merge_when_pipeline_succeeds`. Requires branch protection with required reviews or status checks on the target branch. Read directly from the runner environment (not declared in `env.runner`). | `""` (disabled) | `"true"` to enable |
| `CODE_AUTO_MERGE_METHOD` | Merge method for auto-merge: `"squash"`, `"rebase"`, or `"merge"`. When unset, auto-detected from the repo's allowed merge methods (prefers squash). Omitted automatically when the target branch uses a merge queue. Ignored unless `CODE_AUTO_MERGE` is `"true"`. | Auto-detected (prefers squash) | `"squash"`, `"rebase"`, `"merge"` |

## How the agent works
Expand Down Expand Up @@ -80,15 +81,16 @@ need a custom image.
### Image requirements

A custom image must work within the constraints enforced by the sandbox
policy ([`policies/code.yaml`](../policies/code.yaml)):
policy ([`policies/github/code.yaml`](../policies/github/code.yaml) or
[`policies/gitlab/code.yaml`](../policies/gitlab/code.yaml)):

| Requirement | Detail |
|-------------|--------|
| **Base image** | Extend from `ghcr.io/fullsend-ai/fullsend-code:latest` to inherit the agent runtime, pre-installed tools, and security scanning binaries. |
| **User/group** | The sandbox runs as `sandbox:sandbox`. Installed tools must be executable by this user. |
| **Filesystem layout** | The working directory is `/sandbox/workspace`. Read-write access is limited to `/sandbox` and `/tmp`. System paths (`/usr`, `/lib`, `/etc`) are read-only at runtime — install packages at build time, not in an entrypoint. |
| **Network access** | The sandbox restricts outbound network to specific hosts and binaries (Vertex AI, GitHub API, package registries). Arbitrary HTTP access is blocked. Tools that phone home at startup may fail. |
| **Required binaries** | `git`, `gh`, `scan-secrets`, `pre-commit` must remain on `PATH`. Do not remove or shadow them. |
| **Network access** | The sandbox restricts outbound network to specific hosts and binaries (Vertex AI, forge API, package registries). Arbitrary HTTP access is blocked. Tools that phone home at startup may fail. |
| **Required binaries** | `git`, `scan-secrets`, `pre-commit` must remain on `PATH`. On GitHub, `gh` is also required; on GitLab, `curl` is used instead. Do not remove or shadow them. |

### How to build

Expand All @@ -110,7 +112,7 @@ RUN curl -fsSL https://deb.nodesource.com/setup_24.x | bash - \
```

Build and push the image to a container registry accessible from your
GitHub Actions runners:
CI runners:

```bash
docker build -t ghcr.io/<org>/<repo>-code:latest .
Expand Down Expand Up @@ -165,6 +167,28 @@ The precedence is as follows:
**Note**: bots are filtered (`*[bot]`, `app/*`, `dependabot`). The resolution logic lives in
[`scripts/lib/pr-assignee.lib.sh`](../scripts/lib/pr-assignee.lib.sh).

## Multi-forge support

The code agent supports both GitHub and GitLab. The harness
`forge.<platform>` sections configure platform-specific policies,
skills, env vars, and scripts. Key differences from single-forge
setup:

- **`FULLSEND_FORGE`** is required. Set automatically by the harness
`forge.<platform>.env` section (`"github"` or `"gitlab"`).
- **`ISSUE_URL`** replaces `GITHUB_ISSUE_URL` in scripts. The
per-forge env file (`env/github/code.env` or `env/gitlab/code.env`)
maps the platform-specific variable to `ISSUE_URL`.
- **Policy** is per-forge: `policies/github/code.yaml` or
`policies/gitlab/code.yaml`. Custom harnesses using `base:`
composition should override at the forge level if needed.
- **GitLab uses `curl`** instead of `gh` for API access. The GitLab
sandbox policy allows `curl` for `gitlab_api` endpoints only.
- **GitLab host allowlist** — `forge_validate_issue_url` in
`scripts/lib/gitlab-code-ops.lib.sh` and the network policy in
`policies/gitlab/code.yaml` both maintain an allowlist of GitLab
hosts. To support a self-hosted GitLab instance, update both files.

## Custom network policy

If this agent needs to reach hosts beyond the defaults, see the
Expand Down
17 changes: 9 additions & 8 deletions docs/network-policy.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,11 +19,11 @@ access with injected credentials. The GitLab forge policy allows `curl`
because GitLab API access requires it (the `gh` CLI is not available).

Each agent has its own default policy under
[`policies/`](../policies/). The defaults cover Vertex AI, the GitHub
API, package registries, and gitleaks releases; other agents have
smaller subsets (for example, the scribe agent only needs Vertex AI).
See the individual policy files for the full list of allowed hosts and
binaries.
[`policies/`](../policies/). The defaults cover Vertex AI, the forge
API (GitHub or GitLab), package registries, and gitleaks releases;
other agents have smaller subsets (for example, the scribe agent only
needs Vertex AI). See the individual policy files for the full list of
allowed hosts and binaries.

## Before you start

Expand All @@ -40,7 +40,7 @@ binaries.
### 1. Create a policy file

Copy the default policy for the agent you are configuring (for example,
[`policies/code.yaml`](../policies/code.yaml) for the code agent) to
[`policies/github/code.yaml`](../policies/github/code.yaml) for the code agent) to
`.fullsend/policies/<agent>.yaml` in your repository and append a
block for the host you need:

Expand Down Expand Up @@ -97,8 +97,9 @@ Each agent has its own policy file with similar but not identical
defaults. If multiple agents need the same custom hosts, create a
separate override for each one. For example, the
[code](code.md) and [fix](fix.md) agents use
[`policies/code.yaml`](../policies/code.yaml) and
[`policies/fix.yaml`](../policies/fix.yaml) respectively.
[`policies/github/code.yaml`](../policies/github/code.yaml) and
[`policies/fix.yaml`](../policies/fix.yaml) respectively
(code agent also has a GitLab equivalent under `policies/gitlab/`).

## Troubleshooting

Expand Down
13 changes: 2 additions & 11 deletions env/code.env
Original file line number Diff line number Diff line change
@@ -1,16 +1,7 @@
export ISSUE_NUMBER=${ISSUE_NUMBER}
export GITHUB_ISSUE_URL=${GITHUB_ISSUE_URL}

# GH_TOKEN in the sandbox is a READ-ONLY scoped app installation token
# (contents:read, issues:read, pull_requests:read). Set by
# setup-agent-env.sh from CODE_GH_TOKEN. This token CANNOT push code
# or create PRs — token scoping is the primary enforcement mechanism.
# The separate write-enabled PUSH_TOKEN (env.runner) never enters the sandbox.
export GH_TOKEN=${GH_TOKEN}

# Git identity — uses the GitHub App bot user's noreply email so GitHub
# links commits to the bot account (author.type === "Bot"). This makes
# the Probot DCO app auto-exempt agent commits. The GIT_BOT_EMAIL var
# Git identity — uses the bot user's noreply email so the platform
# links commits to the bot account. The GIT_BOT_EMAIL var
# is resolved at runtime by the "Resolve bot identity" workflow step.
export GIT_AUTHOR_NAME="fullsend-code"
export GIT_AUTHOR_EMAIL="${GIT_BOT_EMAIL}"
Expand Down
3 changes: 3 additions & 0 deletions env/github/code.env
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export ISSUE_URL="${GITHUB_ISSUE_URL}"
export GH_TOKEN=${GH_TOKEN}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[low] env-file-quoting-inconsistency

GH_TOKEN is exported without quotes while ISSUE_URL and FULLSEND_FORGE are quoted. Pre-existing pattern in env/code.env, not a regression.

export FULLSEND_FORGE="github"
4 changes: 4 additions & 0 deletions env/gitlab/code.env
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
export ISSUE_URL="${GITLAB_ISSUE_URL}"
export GITLAB_TOKEN="${GITLAB_TOKEN}"
export GITLAB_HOST="${GITLAB_HOST}"

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[low] gitlab-host-injection-into-sandbox

GITLAB_HOST is injected into the sandbox. Pre/post scripts on the runner validate it against ISSUE_URL and an allowlist. Network policy endpoint restrictions are the enforcement mechanism inside the sandbox.

export FULLSEND_FORGE="gitlab"
47 changes: 42 additions & 5 deletions harness/code.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -4,17 +4,16 @@
# Flow: pre_script → sandbox (agent) → post_script
# pre_script : validates inputs on the runner BEFORE sandbox creation
# agent : reads the issue, implements, tests, scans, commits locally
# post_script : secret scan, pre-commit, push branch, create PR, assign human
# post_script : secret scan, pre-commit, push branch, create PR/MR, assign human
#
# The agent NEVER pushes or creates PRs. Enforcement: the sandbox
# GH_TOKEN is read-only scoped, PUSH_TOKEN never enters the sandbox,
# and network policy restricts git/gh binary access.
# The agent NEVER pushes or creates PRs/MRs. Enforcement: the sandbox
# GH_TOKEN/GITLAB_TOKEN is read-only scoped, PUSH_TOKEN never enters the
# sandbox, and network policy restricts binary access per forge.
# Only the post-script, running on the runner with PUSH_TOKEN, can write.
agent: agents/code.md
doc: docs/code.md
model: opus
image: ghcr.io/fullsend-ai/fullsend-code@sha256:de3ecbd7719a1927c983142ada96475f3314d2505d0f258bcf19c31411856eb6
policy: policies/code.yaml

role: coder
slug: fullsend-ai-coder
Expand Down Expand Up @@ -56,11 +55,49 @@ timeout_minutes: 35

forge:
github:
policy: policies/github/code.yaml
pre_script: scripts/pre-code.sh
post_script: scripts/post-code.sh
skills:
- skills/github-forge
host_files:
- src: env/github/code.env
dest: /sandbox/workspace/.env.d/forge-code.env
expand: true
env:
runner:
PUSH_TOKEN: "${PUSH_TOKEN}"
PUSH_TOKEN_SOURCE: "${PUSH_TOKEN_SOURCE}"
REPO_FULL_NAME: "${REPO_FULL_NAME}"
ISSUE_NUMBER: "${ISSUE_NUMBER}"
ISSUE_URL: "${GITHUB_ISSUE_URL}"
GH_TOKEN: "${GH_TOKEN}"
FULLSEND_FORGE: github
sandbox:
ISSUE_URL: "${GITHUB_ISSUE_URL}"
GH_TOKEN: "${GH_TOKEN}"
FULLSEND_FORGE: github
gitlab:
policy: policies/gitlab/code.yaml
pre_script: scripts/pre-code.sh
post_script: scripts/post-code.sh
skills:
- skills/gitlab-forge
host_files:
- src: env/gitlab/code.env
dest: /sandbox/workspace/.env.d/forge-code.env
expand: true
env:
runner:
PUSH_TOKEN: "${PUSH_TOKEN}"
PUSH_TOKEN_SOURCE: "${PUSH_TOKEN_SOURCE}"
REPO_FULL_NAME: "${REPO_FULL_NAME}"
ISSUE_NUMBER: "${ISSUE_NUMBER}"
ISSUE_URL: "${GITLAB_ISSUE_URL}"
GITLAB_TOKEN: "${GITLAB_TOKEN}"
GITLAB_HOST: "${GITLAB_HOST}"
FULLSEND_FORGE: gitlab
sandbox:
ISSUE_URL: "${GITLAB_ISSUE_URL}"
GITLAB_TOKEN: "${GITLAB_TOKEN}"
FULLSEND_FORGE: gitlab
File renamed without changes.
Loading
Loading