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
13 changes: 13 additions & 0 deletions .dockerignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
.git
.github
*.md
!forge-*/
LICENSE
Makefile
.goreleaser.yaml
install.sh
forge
forge-cli/forge
docs/
coverage.out
*.test
50 changes: 50 additions & 0 deletions .github/workflows/release.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ on:

permissions:
contents: write
packages: write

jobs:
release:
Expand All @@ -28,3 +29,52 @@ jobs:
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
HOMEBREW_TAP_TOKEN: ${{ secrets.HOMEBREW_TAP_TOKEN }}

docker:
name: Docker Image
runs-on: ubuntu-latest
needs: release
steps:
- uses: actions/checkout@v4

- name: Docker meta
id: meta
uses: docker/metadata-action@v5
with:
images: ghcr.io/${{ github.repository }}
tags: |
type=semver,pattern=v{{version}}
type=semver,pattern=v{{major}}.{{minor}}
type=semver,pattern=v{{major}}
type=raw,value=latest

- uses: docker/setup-qemu-action@v3

- uses: docker/setup-buildx-action@v3

- name: Login to GHCR
uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}

- name: Extract version info
id: version
run: |
echo "version=${GITHUB_REF_NAME#v}" >> "$GITHUB_OUTPUT"
echo "commit=$(git rev-parse --short HEAD)" >> "$GITHUB_OUTPUT"

- name: Build and push
uses: docker/build-push-action@v6
with:
context: .
platforms: linux/amd64,linux/arm64
push: true
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
build-args: |
VERSION=${{ steps.version.outputs.version }}
COMMIT=${{ steps.version.outputs.commit }}
cache-from: type=gha
cache-to: type=gha,mode=max
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ profile.cov

# Binary
/forge
/forge-cli/forge

# Build output
.forge-output/
Expand Down
53 changes: 53 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
# ── Build stage ────────────────────────────────────────────────
FROM --platform=$BUILDPLATFORM golang:1.25-alpine AS builder

ARG TARGETOS
ARG TARGETARCH
ARG VERSION=dev
ARG COMMIT=none

WORKDIR /src

# Copy go.work and all module go.mod/go.sum files first for layer caching
COPY go.work ./
COPY forge-core/go.mod forge-core/go.sum ./forge-core/
COPY forge-cli/go.mod forge-cli/go.sum ./forge-cli/
COPY forge-plugins/go.mod forge-plugins/go.sum ./forge-plugins/
COPY forge-skills/go.mod forge-skills/go.sum ./forge-skills/
COPY forge-ui/go.mod forge-ui/go.sum ./forge-ui/

# Download dependencies (cached unless go.mod/go.sum change)
RUN --mount=type=cache,target=/go/pkg/mod \
go work sync && \
cd forge-core && go mod download && \
cd ../forge-cli && go mod download && \
cd ../forge-plugins && go mod download && \
cd ../forge-skills && go mod download && \
cd ../forge-ui && go mod download

# Copy full source
COPY forge-core/ ./forge-core/
COPY forge-cli/ ./forge-cli/
COPY forge-plugins/ ./forge-plugins/
COPY forge-skills/ ./forge-skills/
COPY forge-ui/ ./forge-ui/

# Build static binary for target platform
RUN --mount=type=cache,target=/go/pkg/mod \
--mount=type=cache,target=/root/.cache/go-build \
CGO_ENABLED=0 GOOS=${TARGETOS} GOARCH=${TARGETARCH} \
go build -ldflags "-s -w -X main.version=${VERSION} -X main.commit=${COMMIT}" \
-o /out/forge ./forge-cli/cmd/forge

# ── Runtime stage ─────────────────────────────────────────────
FROM alpine:3.22.4

RUN apk add --no-cache ca-certificates git tzdata && \
adduser -D -h /home/forge forge

COPY --from=builder /out/forge /usr/local/bin/forge

USER forge
WORKDIR /home/forge

ENTRYPOINT ["forge"]
2 changes: 1 addition & 1 deletion docs/commands.md
Original file line number Diff line number Diff line change
Expand Up @@ -458,7 +458,7 @@ forge key list
Manage agent skills.

```bash
# Add a skill from the registry
# Add a skill from the registry (prompts for env vars, merges egress domains)
forge skills add <skill-name>

# List available skills
Expand Down
7 changes: 5 additions & 2 deletions docs/dashboard.md
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,8 @@ The Skill Builder uses the agent's own LLM provider to power a chat conversation
| Script preview | View generated helper scripts alongside the SKILL.md |
| Validation | Server-side validation checks name format, required fields, egress domain declarations, and name uniqueness |
| One-click save | Save the validated skill directly to the agent's `skills/` directory |
| Egress injection | Automatically merges declared `egress_domains` into `forge.yaml` allowlist on save |
| Env var handling | Writes user-provided env vars to `.env`; prompts for missing required vars after save |

### Workflow

Expand All @@ -125,7 +127,8 @@ The Skill Builder uses the agent's own LLM provider to power a chat conversation
3. **Iterate** — the AI asks about requirements, security constraints, and env vars
4. **Review** — inspect the generated SKILL.md and scripts in the preview panel
5. **Validate** — check for errors and warnings before saving
6. **Save** — writes `skills/{name}/SKILL.md` and `skills/{name}/scripts/` to the agent directory
6. **Save** — writes `skills/{name}/SKILL.md` and `skills/{name}/scripts/` to the agent directory, merges egress domains into `forge.yaml`, and writes env vars to `.env`
7. **Configure** — if the skill requires env vars that aren't set, the UI shows input fields to provide them and re-save

### Validation Rules

Expand All @@ -150,7 +153,7 @@ The validator enforces the [SKILL.md format](skills.md):
| `GET` | `/api/agents/{id}/skill-builder/context` | Returns the system prompt used for skill generation |
| `POST` | `/api/agents/{id}/skill-builder/chat` | Streams an LLM conversation via SSE (accepts `messages` array) |
| `POST` | `/api/agents/{id}/skill-builder/validate` | Validates a SKILL.md and optional scripts |
| `POST` | `/api/agents/{id}/skill-builder/save` | Saves a validated skill to `skills/{name}/` |
| `POST` | `/api/agents/{id}/skill-builder/save` | Saves a validated skill to `skills/{name}/`, merges egress domains, writes env vars; returns `SkillSaveResult` with `path`, `egress_added`, `env_configured`, `env_missing` |

## Architecture

Expand Down
23 changes: 22 additions & 1 deletion docs/deployment.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,28 @@

Forge agents can be packaged as container images and deployed to Docker, Kubernetes, or air-gapped environments.

## Building Container Images
## Pre-built Docker Image

Forge publishes multi-architecture Docker images (linux/amd64, linux/arm64) to GitHub Container Registry on every release:

```bash
# Pull the latest release
docker pull ghcr.io/initializ/forge:latest

# Pin to a specific version
docker pull ghcr.io/initializ/forge:v1.2.3

# Run with your agent directory mounted
docker run -v /path/to/agent:/home/forge/agent -w /home/forge/agent \
-e OPENAI_API_KEY=sk-... \
ghcr.io/initializ/forge:latest run --host 0.0.0.0
```

Tags follow the pattern `v1.2.3`, `v1.2`, `v1`, and `latest`.

The image is built from a multi-stage Dockerfile in the repository root — `golang:1.25-alpine` for the build stage (static binary, `CGO_ENABLED=0`) and `alpine:3.21` for the runtime with `ca-certificates`, `git`, and `tzdata`. The container runs as a non-root `forge` user.

## Building Agent Container Images

```bash
# Build a container image (auto-detects Docker/Podman/Buildah)
Expand Down
6 changes: 5 additions & 1 deletion docs/runtime.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,11 @@ The core agent loop follows a simple pattern:
User message → Memory → LLM → tool_calls? → Execute tools → LLM → ... → text → Done
```

The loop terminates when `len(ToolCalls) == 0`. Tool calls are always executed even if `FinishReason` is `"stop"` — this prevents orphaned function calls that would cause API rejection on session recovery.
The loop terminates when `len(ToolCalls) == 0`. `FinishReason` is intentionally ignored — some providers return `"stop"` even when tool calls are present. Only the tool call list determines whether execution continues.

### Session Recovery Deduplication

When a session is recovered from disk (e.g., after a premature loop exit), the executor checks whether the recovered conversation already ends with an identical user message. If so, the duplicate is skipped to prevent the same message from appearing twice in the context window. This handles the common case where a user retries the same prompt after a crash or timeout.

### Q&A Nudge Suppression

Expand Down
8 changes: 6 additions & 2 deletions docs/skills.md
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,7 @@ forge skills validate
forge skills audit --embedded
```

`forge skills add` copies the skill's SKILL.md and any associated scripts into your project's `skills/` directory. It validates binary and environment requirements, checks for existing values in your environment, `.env` file, and encrypted secrets, and prompts only for truly missing values with a suggestion to use `forge secrets set` for sensitive keys.
`forge skills add` copies the skill's SKILL.md and any associated scripts into your project's `skills/` directory. It validates binary and environment requirements, checks for existing values in your environment, `.env` file, and encrypted secrets, and prompts only for truly missing values with a suggestion to use `forge secrets set` for sensitive keys. If the skill declares `egress_domains`, they are automatically merged into the `forge.yaml` `egress.allowed_domains` list (deduplicated and sorted).

## Skills as First-Class Tools

Expand Down Expand Up @@ -516,7 +516,11 @@ forge build

## Skill Builder (Web UI)

The [Web Dashboard](dashboard.md#skill-builder) includes an AI-powered Skill Builder that generates valid SKILL.md files and helper scripts through a conversational interface. It uses the agent's own LLM provider and includes server-side validation before saving to the agent's `skills/` directory.
The [Web Dashboard](dashboard.md#skill-builder) includes an AI-powered Skill Builder that generates valid SKILL.md files and helper scripts through a conversational interface. It uses the agent's own LLM provider and includes server-side validation before saving to the agent's `skills/` directory. On save, the builder automatically parses the skill's requirements and:

- **Merges egress domains** into `forge.yaml` `egress.allowed_domains` (deduplicated)
- **Writes user-provided env vars** to `.env` (skipping keys already present)
- **Reports missing env vars** so the user can provide values and re-save

---
← [Architecture](architecture.md) | [Back to README](../README.md) | [Tools](tools.md) →
Loading
Loading