Skip to content
Merged
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
3 changes: 3 additions & 0 deletions .github/aixgo.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,3 +13,6 @@ appName: aixgo-code
gate: make check

prDescription: rich

# Dogfood on Vertex Gemini (not Azure OpenAI).
engine: gemini
15 changes: 9 additions & 6 deletions .github/workflows/aixgo-caller.yml
Original file line number Diff line number Diff line change
Expand Up @@ -27,11 +27,12 @@ jobs:
github-app-client-id: ${{ vars.AIXGO_GH_APP_CLIENT_ID }}
issue-number: ${{ github.event.issue.number }}
actor-login: ${{ github.event.sender.login }}
azure-openai-endpoint: ${{ vars.AIXGO_AZURE_OPENAI_ENDPOINT }}
vertex-project: ${{ vars.AIXGO_VERTEX_PROJECT }}
vertex-location: ${{ vars.AIXGO_VERTEX_LOCATION }}
version: ${{ github.sha }}
# model: my-gpt-5-4-deployment
secrets:
azure-openai-api-key: ${{ secrets.AIXGO_AZURE_OPENAI_API_KEY }}
vertex-api-key: ${{ secrets.AIXGO_VERTEX_API_KEY }}
github-app-private-key: ${{ secrets.AIXGO_GH_APP_PRIVATE_KEY }}

address:
Expand All @@ -44,11 +45,12 @@ jobs:
github-app-client-id: ${{ vars.AIXGO_GH_APP_CLIENT_ID }}
pr-number: ${{ github.event.pull_request.number }}
actor-login: ${{ github.event.review.user.login }}
azure-openai-endpoint: ${{ vars.AIXGO_AZURE_OPENAI_ENDPOINT }}
vertex-project: ${{ vars.AIXGO_VERTEX_PROJECT }}
vertex-location: ${{ vars.AIXGO_VERTEX_LOCATION }}
version: ${{ github.sha }}
# model: my-gpt-5-4-deployment
secrets:
azure-openai-api-key: ${{ secrets.AIXGO_AZURE_OPENAI_API_KEY }}
vertex-api-key: ${{ secrets.AIXGO_VERTEX_API_KEY }}
github-app-private-key: ${{ secrets.AIXGO_GH_APP_PRIVATE_KEY }}

comment:
Expand All @@ -70,12 +72,13 @@ jobs:
issue-number: ${{ github.event.issue.pull_request == null && github.event.issue.number || '' }}
pr-number: ${{ github.event.issue.pull_request != null && github.event.issue.number || '' }}
actor-login: ${{ github.event.comment.user.login }}
azure-openai-endpoint: ${{ vars.AIXGO_AZURE_OPENAI_ENDPOINT }}
vertex-project: ${{ vars.AIXGO_VERTEX_PROJECT }}
vertex-location: ${{ vars.AIXGO_VERTEX_LOCATION }}
# Same commit the other two jobs use. Without it this job installs the
# released tag, so the comment path is the one path that cannot catch a
# regression before it ships, and a version default bumped ahead of its
# tag breaks this job alone.
version: ${{ github.sha }}
secrets:
azure-openai-api-key: ${{ secrets.AIXGO_AZURE_OPENAI_API_KEY }}
vertex-api-key: ${{ secrets.AIXGO_VERTEX_API_KEY }}
github-app-private-key: ${{ secrets.AIXGO_GH_APP_PRIVATE_KEY }}
41 changes: 38 additions & 3 deletions .github/workflows/aixgo.yml
Original file line number Diff line number Diff line change
Expand Up @@ -38,9 +38,20 @@ on:
required: true
type: string
azure-openai-endpoint:
description: Azure OpenAI endpoint, for example https://resource.openai.azure.com.
required: true
description: Azure OpenAI endpoint, for example https://resource.openai.azure.com. Required for the default Codex engine; unused when engine is claude or gemini.
required: false
type: string
default: ""
vertex-project:
description: Google Cloud project for Vertex AI. Required when engine is gemini.
required: false
type: string
default: ""
vertex-location:
description: Vertex AI location, for example us-central1.
required: false
type: string
default: us-central1
model:
description: Engine model or deployment name passed to the Aixgo CLI.
default: gpt-5.4
Expand All @@ -53,7 +64,9 @@ on:
type: string
secrets:
azure-openai-api-key:
required: true
required: false
vertex-api-key:
required: false
github-app-private-key:
required: true

Expand Down Expand Up @@ -104,6 +117,9 @@ jobs:
env:
AIXGO_AZURE_OPENAI_ENDPOINT: ${{ inputs['azure-openai-endpoint'] }}
AIXGO_AZURE_OPENAI_API_KEY: ${{ secrets['azure-openai-api-key'] }}
AIXGO_VERTEX_PROJECT: ${{ inputs['vertex-project'] }}
AIXGO_VERTEX_LOCATION: ${{ inputs['vertex-location'] }}
AIXGO_VERTEX_API_KEY: ${{ secrets['vertex-api-key'] }}
AIXGO_GH_APP_CLIENT_ID: ${{ inputs['github-app-client-id'] }}
AIXGO_GH_APP_PRIVATE_KEY: ${{ secrets['github-app-private-key'] }}
run: aixgo-code preflight --repo-dir . --actions
Expand All @@ -128,13 +144,21 @@ jobs:
run: sudo sysctl -w kernel.apparmor_restrict_unprivileged_userns=0

- name: Install Codex CLI
if: ${{ inputs['azure-openai-endpoint'] != '' }}
run: npm install -g @openai/codex@0.146.0

- name: Install Gemini CLI
if: ${{ inputs['vertex-project'] != '' }}
run: npm install -g @google/gemini-cli@0.60.0

- name: Run Aixgo
env:
GH_TOKEN: ${{ steps.app-token.outputs.token }}
AIXGO_AZURE_OPENAI_ENDPOINT: ${{ inputs['azure-openai-endpoint'] }}
AIXGO_AZURE_OPENAI_API_KEY: ${{ secrets['azure-openai-api-key'] }}
AIXGO_VERTEX_PROJECT: ${{ inputs['vertex-project'] }}
AIXGO_VERTEX_LOCATION: ${{ inputs['vertex-location'] }}
AIXGO_VERTEX_API_KEY: ${{ secrets['vertex-api-key'] }}
COMMENT_BODY: ${{ inputs['comment-body'] }}
# --base is explicit because actions/checkout never creates origin/HEAD,
# which is the CLI's default base. The CLI resolves that case on its own
Expand Down Expand Up @@ -198,6 +222,9 @@ jobs:
env:
AIXGO_AZURE_OPENAI_ENDPOINT: ${{ inputs['azure-openai-endpoint'] }}
AIXGO_AZURE_OPENAI_API_KEY: ${{ secrets['azure-openai-api-key'] }}
AIXGO_VERTEX_PROJECT: ${{ inputs['vertex-project'] }}
AIXGO_VERTEX_LOCATION: ${{ inputs['vertex-location'] }}
AIXGO_VERTEX_API_KEY: ${{ secrets['vertex-api-key'] }}
AIXGO_GH_APP_CLIENT_ID: ${{ inputs['github-app-client-id'] }}
AIXGO_GH_APP_PRIVATE_KEY: ${{ secrets['github-app-private-key'] }}
run: aixgo-code preflight --repo-dir . --actions
Expand All @@ -222,13 +249,21 @@ jobs:
run: sudo sysctl -w kernel.apparmor_restrict_unprivileged_userns=0

- name: Install Codex CLI
if: ${{ inputs['azure-openai-endpoint'] != '' }}
run: npm install -g @openai/codex@0.146.0

- name: Install Gemini CLI
if: ${{ inputs['vertex-project'] != '' }}
run: npm install -g @google/gemini-cli@0.60.0

- name: Address review feedback
env:
GH_TOKEN: ${{ steps.app-token.outputs.token }}
AIXGO_AZURE_OPENAI_ENDPOINT: ${{ inputs['azure-openai-endpoint'] }}
AIXGO_AZURE_OPENAI_API_KEY: ${{ secrets['azure-openai-api-key'] }}
AIXGO_VERTEX_PROJECT: ${{ inputs['vertex-project'] }}
AIXGO_VERTEX_LOCATION: ${{ inputs['vertex-location'] }}
AIXGO_VERTEX_API_KEY: ${{ secrets['vertex-api-key'] }}
COMMENT_BODY: ${{ inputs['comment-body'] }}
run: |
if [ -n "${COMMENT_BODY}" ]; then
Expand Down
15 changes: 15 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,21 @@
All notable changes are recorded here and summarized again in the matching
GitHub release notes for each tag.

## Unreleased

- **Gemini on Vertex AI** (`engine: gemini`). The loop, roles, and gate are
unchanged. The adapter shells out to the Gemini CLI (`gemini -p`) and reads
`AIXGO_VERTEX_PROJECT`, `AIXGO_VERTEX_LOCATION` (default `us-central1`), and
`AIXGO_VERTEX_API_KEY`. If that last value is a service-account JSON object
(`"type": "service_account"`), it is written to a temp file and exported as
`GOOGLE_APPLICATION_CREDENTIALS`; a plain API key is still passed as
`GOOGLE_API_KEY`. Azure credentials are not required on this path.
- The reusable workflow accepts optional `vertex-project`, `vertex-location`,
and `vertex-api-key`, and the Azure endpoint/key are no longer required
inputs. A Codex adopter who already passes them is unchanged. A Gemini
adopter omits Azure and passes the Vertex values instead. The workflow
installs `@google/gemini-cli@0.60.0` when `vertex-project` is set.

## v0.5.0

**Breaking. The binary is `aixgo-code`, not `aixgo`.** The Aixgo framework's
Expand Down
63 changes: 59 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,20 @@ Aixgo Code is an autonomous coding agent you install into your own GitHub. Your

> Beta. Current release: `v0.5.0`. Product overview: [aixgo.dev/code](https://aixgo.dev/code?utm_source=github&utm_medium=readme&utm_campaign=code). See [Status](#status).


## Contents

- [Product overview](#product-overview)
- [Why teams use it](#why-teams-use-it)
- [Installation](#installation)
- [How it works](#how-it-works)
- [Configuration](#configuration) — `.github/aixgo.yml` options
- [Engines](#engines) — Codex (Azure), Claude, Gemini (Vertex): required env vars
- [Deployment model](#deployment-model)
- [Status](#status)
- [Security](#security)
- [Full setup walkthrough](docs/setup.md)

## Product overview

Aixgo Code turns a GitHub issue into a proposed code change inside your own environment. Your team keeps the repository, runners, secrets, and branch protection rules. The agent does the implementation work, but it never merges its own pull requests.
Expand Down Expand Up @@ -145,7 +159,9 @@ That runs the whole loop, including the model and your own gate. It makes no Git

## Configuration

Configuration lives in `.github/aixgo.yml`. A minimal file looks like this:
Configuration lives in `.github/aixgo.yml`. Engine credentials are **environment variables or Actions secrets**, never committed — see [Engines](#engines) for the table, and [docs/setup.md](docs/setup.md) for the full walkthrough.

A minimal file looks like this:

```yaml
labelPrefix: ax
Expand Down Expand Up @@ -185,17 +201,55 @@ Getting the gate right is where first runs stall: it has to be green on your own

## Engines

The model that writes the code sits behind a pluggable `Runner` interface, so you bring your own provider.
The model that writes the code sits behind a pluggable `Runner` interface, so you bring your own provider. Set `engine:` in [`.github/aixgo.yml`](#configuration). The loop, roles, and gate are identical for every engine.

| Engine | Config | Local CLI | GitHub Actions | Credentials |
| --- | --- | --- | --- | --- |
| **codex** (default) | omit `engine:` or `engine: codex` | yes | yes | `AIXGO_AZURE_OPENAI_ENDPOINT`, `AIXGO_AZURE_OPENAI_API_KEY` |
| **claude** | `engine: claude` | yes | not yet (workflow does not install Claude CLI) | your existing `claude` CLI auth |
| **gemini** | `engine: gemini` | yes | yes | `AIXGO_VERTEX_PROJECT`, `AIXGO_VERTEX_API_KEY`, optional `AIXGO_VERTEX_LOCATION` (default `us-central1`) |

Full install steps and Actions wiring: [docs/setup.md](docs/setup.md) (see *Local CLI* / *GitHub Actions* under engine choice).

Codex on Azure is the engine the reusable GitHub Actions workflow can run today. `engine: claude` selects the Claude Code adapter, which runs headless (`claude -p`) against whatever credentials that CLI is already configured with. That path now works for local CLI runs and does not need Azure variables, but the reusable workflow still installs only the Codex CLI and still requires Azure inputs and secrets, so GitHub Actions cannot use Claude yet. The loop, the roles, and the gate are identical either way; the engine is the only thing that changes.
### Codex on Azure (default)

```yaml
gate: make check
# engine: codex
```

```sh
export AIXGO_AZURE_OPENAI_ENDPOINT="https://<resource>.openai.azure.com"
export AIXGO_AZURE_OPENAI_API_KEY="<key>"
```

Optional model override defaults to `gpt-5.4` if unset.

### Claude (local CLI)

```yaml
gate: make check
engine: claude
```

The first engine adapter targets the Codex CLI running against Azure OpenAI. Today the shipped GitHub Actions setup needs an Azure endpoint, an API key, and optionally a deployment name override if you are not using the default `gpt-5.4`. The Claude Code adapter is written and tested behind `engine: claude`, and today is reachable from a local CLI run only. The `Runner` interface is the seam where other engines plug in.
Uses headless `claude -p` with whatever credentials that CLI already has. No Azure variables.

### Gemini on Vertex AI

```yaml
gate: make check
engine: gemini
```

```sh
export AIXGO_VERTEX_PROJECT="<gcp-project>"
export AIXGO_VERTEX_LOCATION="us-central1" # optional
# Plain Vertex API key, OR the full contents of a service-account JSON key
# (JSON with "type":"service_account" is written to a temp file as ADC).
export AIXGO_VERTEX_API_KEY="<key-or-sa-json>"
```

In GitHub Actions, pass reusable-workflow inputs `vertex-project`, `vertex-location`, and secret `vertex-api-key` instead of Azure. The workflow installs `@google/gemini-cli` when `vertex-project` is set. Default model is `gemini-2.5-pro` (override with `--model` / workflow `model:`).

## Deployment model

Expand Down Expand Up @@ -236,6 +290,7 @@ Roadmap, roughly in order:
- Wiring the read-only reviewer role into the loop so a diff is reviewed before it reaches a human.
- The self-onboarding flow via `init` and `init --workflow`. **Done.**
- The Codex on Azure OpenAI engine adapter. **Done.**
- Gemini on Vertex AI (`engine: gemini`). **Done** on this branch / upcoming release.
- Self-hosted models on Hugging Face.

If you are evaluating it now, read that as beta software rather than a polished product. The core loops work; reviewer wiring is still in progress, and self-onboarding shipped as `init` and `init --workflow`.
Expand Down
40 changes: 35 additions & 5 deletions cmd/aixgo-code/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ import (
"github.com/aixgo-dev/code/internal/engine"
"github.com/aixgo-dev/code/internal/engine/claude"
"github.com/aixgo-dev/code/internal/engine/codex"
"github.com/aixgo-dev/code/internal/engine/gemini"
forge2 "github.com/aixgo-dev/code/internal/forge"
"github.com/aixgo-dev/code/internal/forge/dryrun"
forgegh "github.com/aixgo-dev/code/internal/forge/gh"
Expand Down Expand Up @@ -115,8 +116,11 @@ flags (both commands):
--state-dir where worktrees and the generated config live

environment (engine settings; never committed to a repo):
AIXGO_AZURE_OPENAI_ENDPOINT e.g. https://<resource>.openai.azure.com
AIXGO_AZURE_OPENAI_ENDPOINT e.g. https://<resource>.openai.azure.com (codex)
AIXGO_AZURE_OPENAI_API_KEY the key, read by name and never written to disk
AIXGO_VERTEX_PROJECT Google Cloud project (gemini / Vertex)
AIXGO_VERTEX_LOCATION Vertex location, default us-central1
AIXGO_VERTEX_API_KEY Vertex API key, read by name and never written to disk
`, buildinfo.Version)
}

Expand Down Expand Up @@ -165,10 +169,30 @@ var selftestWorkflowTemplate string
// Azure endpoint when the engine needs one. It is the single implementation:
// `prepare` calls it before a run, and `preflight` calls it so a workflow can
// fail early without a second copy of these rules written in shell.
func usesAzure(cfg *config.Config) bool {
if cfg == nil {
return true
}
switch cfg.Engine {
case "claude", "gemini":
return false
}
return true
}

func engineEnv(cfg *config.Config) (string, error) {
if cfg != nil && cfg.Engine == "claude" {
return "", nil
}
if cfg != nil && cfg.Engine == "gemini" {
if err := requireSet("AIXGO_VERTEX_PROJECT", sectionVariable); err != nil {
return "", err
}
if err := requireSet("AIXGO_VERTEX_API_KEY", sectionSecret); err != nil {
return "", err
}
return "", nil
}
if err := requireSet("AIXGO_AZURE_OPENAI_ENDPOINT", sectionVariable); err != nil {
return "", err
}
Expand Down Expand Up @@ -246,9 +270,15 @@ func newVCS(self string) *vcsgit.Git {
// the sandbox start rather than widening it. A run that still cannot sandbox
// stops and a human finishes the work; it never falls back to running
// unconfined. See docs/faq.md and the self-test.
func newRunner(cfg *config.Config, codexHome string) engine.Runner {
func newRunner(cfg *config.Config, codexHome, model string) engine.Runner {
if cfg.Engine == "claude" {
return claude.New()
return &claude.Runner{Model: model}
}
if cfg.Engine == "gemini" {
if model == "" || model == "gpt-5.4" {
model = "gemini-2.5-pro"
}
return &gemini.Runner{Model: model}
}
r := codex.New(codexHome)
if mode := os.Getenv("AIXGO_SANDBOX"); mode != "" {
Expand Down Expand Up @@ -602,7 +632,7 @@ func prepare(name string, argv []string) (*commonFlags, []string, error) {
}

codexHome := filepath.Join(*stateDir, "codex-home")
if cfg.Engine != "claude" {
if usesAzure(cfg) {
if _, err := codex.WriteConfig(codexHome, codex.ProviderConfig{
Model: *model,
BaseURL: endpoint + "/openai/v1",
Expand Down Expand Up @@ -638,7 +668,7 @@ func prepare(name string, argv []string) (*commonFlags, []string, error) {
}

deps := app.Deps{
Runner: newRunner(cfg, codexHome),
Runner: newRunner(cfg, codexHome, *model),
// Self, when set, filters the agent's own review feedback out of the fix
// loop. Empty for local runs, where the operator is a human, not the bot.
Forge: forgeForLoop,
Expand Down
Loading
Loading