Skip to content

Folders and files

NameName
Last commit message
Last commit date

Latest commit

Β 

History

227 Commits
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

Fordjent

A Forgejo-driven AI agent harness written in Go. Fordjent listens for Forgejo webhook events, spawns per-issue agent sessions, and uses an LLM with tool-calling to autonomously triage, comment, create issues, and submit pull requests.

Features

  • Webhook-driven β€” Receives Forgejo events via HMAC-validated HTTP webhooks
  • Session affinity β€” Events for the same issue/PR route to the same agent session for serial processing
  • Role-based providers β€” Detects agent role (PM, implementer, reviewer, devops, tester) from labels/titles and routes to the best LLM for that role
  • 20 built-in tools β€” Forgejo API, bash, file I/O, git, code search, reactions, branch/hook management, and more
  • PR creation pipeline β€” Multi-gate: stale gate + auto-rebase, merge queue (file overlap), build/test/lint verify, then create PR
  • Dependency scheduler β€” Parses Depends on: #N in issue bodies; auto-unblocks dependents when PRs merge
  • Lifecycle tracking β€” SQLite state machine tracks every session; auto-labels issues on failure
  • Cost tracking + budget β€” Per-session/repo/month cost in SQLite; enforceable spend limits
  • Context compaction β€” Auto-truncates old turns at configurable threshold to avoid context overflow
  • Auto-rebase β€” Stale gate detects when a branch is behind origin/main and rebase + force-pushes automatically
  • Scaffold detection β€” Blocks issues on empty repos; creates a scaffold issue first
  • Loop prevention β€” Multi-layer: <!-- ford --> marker, bot sender filtering, branch protection, commit prefix filtering
  • Observability β€” /metrics (Prometheus), /status (JSON), /activity (HTML), /admin dashboard, /healthz, /readyz
  • Crash recovery β€” Session metadata persisted to SQLite; sessions survive restarts with rebase-abort cleanup
  • Config hot-reload β€” Watcher polls disk and applies changes without restart
  • Single binary β€” Pure Go, no CGO required (SQLite via modernc.org/sqlite)
  • Docker-ready β€” Multi-stage Dockerfile and Compose stack included

Architecture

Event Flow

Event Flow

  1. Ingest β€” Forgejo webhooks are validated via HMAC-SHA256
  2. Filter β€” Agent-originated events (marked with <!-- ford -->) are dropped to prevent loops
  3. Route β€” The session manager maps events to sessions by key (e.g. org/repo/issues/42)
  4. Process β€” Each session runs a serial agent loop; role detection selects the LLM provider
  5. Act β€” The LLM decides which tools to call; results feed back into the conversation
  6. Record β€” Reasoning traces and tool outputs are written to JSONL audit logs

PR Creation Pipeline

PR Pipeline

Before forgejo_create_pr posts to the API, it passes through multiple gates:

  1. Stale gate β€” Detects if the branch is behind origin/main; auto-rebases and force-pushes
  2. Auto-push β€” Ensures the branch is pushed to the remote
  3. Merge queue β€” Checks for file overlap with other open PRs; blocks if conflicts exist
  4. Build/test/lint verify β€” Runs go build, go test, and golangci-lint; blocks on failure

Session Lifecycle

Lifecycle

Every session tracks through a state machine: created β†’ working β†’ pr_created β†’ completed. On failure (max turns or error), the lifecycle system auto-labels the issue fordjent/failed:* + blocked and posts a comment explaining the failure.

Coordination Layer

Coordination

Four coordination subsystems work together around the session manager:

  • Scaffold detection β€” Blocks issues on empty repos; creates a [scaffold] issue first
  • Stale gate β€” Prevents stale-branch PRs; auto-rebases when possible
  • Merge queue β€” File-gate prevents PRs that conflict with open PRs
  • Scheduler β€” On PR merge, scans dependent issues and transitions blocked β†’ ready

Quick Start

Binary

go build -o fordjent ./cmd/fordjent

# Set required secrets
export FORGEJO_TOKEN=your-repo-scoped-token
export OPENAI_API_KEY=your-api-key

# Edit config (set forgejo URL, repository, etc.)
cp fordjent.yaml my-config.yaml

./fordjent -config my-config.yaml

Docker Compose

cp .env.example .env
# Edit .env with your tokens and webhook secret

# Edit fordjent.yaml to point at your Forgejo instance
docker compose up -d

The Compose stack binds to 127.0.0.1:8080 by default β€” put it behind a reverse proxy (Caddy, Traefik) or expose it directly if Forgejo is on the same host. See docs/deployment.md for systemd and production options.

Forgejo Setup

  1. Go to Repository β†’ Settings β†’ Webhooks β†’ Add Webhook
  2. Set Target URL to http://your-host:8080/acp/v1/events
  3. Set Secret to match webhook.secret in your config
  4. Select events: issues, issue_comment, pull_request, pull_request_review_comment
  5. Create a repository-scoped access token and set it as FORGEJO_TOKEN

Tools

The agent has access to 20 tools exposed via OpenAI function calling:

Forgejo API Tools

Tool Description
forgejo_comment Post comments on issues and pull requests
forgejo_create_issue Create new issues (with dedup check and dependency linking)
forgejo_list_issues List and filter issues in a repository
forgejo_get_issue Get issue or PR details by number
forgejo_create_pr Create pull requests (stale gate β†’ merge queue β†’ verify gate)
forgejo_merge_pr Merge a PR (mergeability check + human approval gate; bot PRs auto-bypass)
forgejo_list_prs List pull requests in a repository
forgejo_search_code Search code within a repository
forgejo_add_reaction Add emoji reactions to issues/comments
forgejo_list_branches List branches with protection status
forgejo_delete_branch Delete a branch (e.g., after merge)
forgejo_list_hooks List webhooks for a repository
forgejo_create_hook Create a webhook
forgejo_delete_hook Delete a webhook
forgejo_list_files List files/directories in a repo
forgejo_pr_files List files changed in a PR
forgejo_list_collabs List repository collaborators
forgejo_version Get the Forgejo server version
forgejo_user Get the currently authenticated user
forgejo_create_token Create an access token

Local Tools

Tool Description
bash Execute shell commands in the repo directory (protected branch push blocked)
read_file Read file contents (with offset/limit; batch mode via paths array)
write_file Create or overwrite files in the repository
git Execute git operations (push blocked; auto-push after commit on feature branches)

All Forgejo API tools sanitize repository names via per-segment url.PathEscape to prevent URL injection. The git tool blocks push to protected branches β€” the agent creates PRs via forgejo_create_pr instead.

Configuration

Configuration is a single YAML file with environment variable expansion via ${VAR} syntax in any field.

server:
  host: "0.0.0.0"
  port: 8080

webhook:
  secret: "${WEBHOOK_SECRET}"

forgejo:
  url: "${FORGEJO_URL}"
  token: "${FORGEJO_TOKEN}"
  admin_token: "${FORGEJO_ADMIN_TOKEN}"   # For admin API calls (optional)
  rate_limit: 60

agent:
  max_sessions: 25
  idle_timeout: "4h"
  workdir: "/var/lib/fordjent/work"
  max_turns: 75                            # Default turn budget per session
  max_turns_pm: 15                         # PM sessions get fewer turns
  max_turns_implementer: 50                # Implementer sessions get more
  commit_prefix: "[agent-automation]"
  context_window: 131072                   # Token window size for compaction
  compaction_threshold: 0.85              # Compact when 85% full
  compaction_keep_turns: 8                # Keep last N turns on compaction
  enable_lifecycle: true                   # Track session state machine
  enable_stale_gate: true                  # Auto-rebase stale branches
  enable_scaffold_detection: true          # Block issues on empty repos
  enable_session_recovery: true            # Resume sessions after crash
  enable_context_injection: true           # Inject issue context into prompt
  enable_auto_collaborator: true           # Auto-add agent as collaborator
  session_timeout: "30m"
  role_providers:                          # Route roles to specific providers
    pm: "openai"
    reviewer: "openai"
    implementer: "openai"

budget:
  enabled: false
  max_session_cost: 0.50                  # USD per session
  max_monthly_cost: 10.00                 # USD per month

providers:
  - name: "openai"
    api_base: "${OPENAI_API_BASE:-https://api.openai.com/v1}"
    api_key: "${OPENAI_API_KEY}"
    model: "${OPENAI_MODEL:-gpt-4o-mini}"
    max_tokens: 16384
    request_timeout: "60s"
    max_retries: 3
    retry_base_delay: "2s"
    retry_max_delay: "30s"
    max_concurrent_llm_calls: 3
    cost_per_1m_input_tokens: 0.30
    cost_per_1m_output_tokens: 1.20

events:
  - "issues"
  - "issue_comment"
  - "pull_request"
  - "pull_request_review_comment"

session_key_template: "{{.Repository}}/issues/{{.IssueNumber}}"

security:
  protected_branches: ["main", "master"]
  require_pr_for_workflows: true
  filter_agent_events: true
  admin_token: "${ADMIN_TOKEN}"            # Protects admin endpoints

memory:
  enabled: true
  compaction_cron: "0 2 * * *"
  compaction_path: "docs/issues"

database:
  path: ""                                # Defaults to sessions.db next to workdir

log_level: "info"

See fordjent.yaml for the full reference with comments.

Emoji Reaction Protocol

The agent uses Forgejo reactions to communicate status:

Reaction Meaning
πŸ‘€ Agent has seen the event
⏳ Agent is processing
βœ… Agent finished successfully
❌ Agent encountered an error

Project Structure

fordjent/
β”œβ”€β”€ cmd/fordjent/main.go              # Entry point β€” wires all components
β”œβ”€β”€ fordjent.yaml                      # Reference configuration
β”œβ”€β”€ Dockerfile                         # Multi-stage Go build (non-root user)
β”œβ”€β”€ docker-compose.yaml                # Compose stack with env secrets
β”œβ”€β”€ .env.example                       # Template for environment variables
β”œβ”€β”€ docs/
β”‚   β”œβ”€β”€ deployment.md                  # Docker, systemd, backup, monitoring
β”‚   └── diagrams/                      # Graphviz architecture diagrams
β”œβ”€β”€ internal/
β”‚   β”œβ”€β”€ agent/
β”‚   β”‚   β”œβ”€β”€ context.go                 # Context window tracking + compaction
β”‚   β”‚   └── turn.go                    # Per-turn execution with cost/latency logging
β”‚   β”œβ”€β”€ config/config.go               # YAML config with env expansion, hot-reload
β”‚   β”œβ”€β”€ cost/cost.go                   # SQLite cost tracker + budget enforcement
β”‚   β”œβ”€β”€ event/event.go                 # Event types, bus (fanout + backpressure)
β”‚   β”œβ”€β”€ forgejo/client.go              # Forgejo REST API client
β”‚   β”œβ”€β”€ lifecycle/lifecycle.go         # Session state machine, failure labeling
β”‚   β”œβ”€β”€ memory/memory.go               # JSONL audit log + git notes memory
β”‚   β”œβ”€β”€ mergequeue/queue.go            # File-gate merge queue
β”‚   β”œβ”€β”€ metrics/metrics.go             # Prometheus counters + JSON snapshot
β”‚   β”œβ”€β”€ provider/
β”‚   β”‚   β”œβ”€β”€ client.go                  # OpenAI-compatible LLM client
β”‚   β”‚   └── retry.go                   # Exponential backoff with jitter
β”‚   β”œβ”€β”€ scaffold/scaffold.go           # Empty-repo protection + scaffold issue
β”‚   β”œβ”€β”€ scheduler/scheduler.go         # Dependency parser, label transitions
β”‚   β”œβ”€β”€ sentinel/sentinel.go          # Typed sentinel errors
β”‚   β”œβ”€β”€ session/
β”‚   β”‚   β”œβ”€β”€ manager.go                 # Session lifecycle, key affinity, idle reaping
β”‚   β”‚   β”œβ”€β”€ store.go                   # SQLite-backed session persistence
β”‚   β”‚   └── agent.go                   # Agent loop β€” LLM turns, tool dispatch
β”‚   β”œβ”€β”€ stalegate/stalegate.go         # Git-plumbing staleness check + auto-rebase
β”‚   β”œβ”€β”€ tool/
β”‚   β”‚   β”œβ”€β”€ registry.go                # Tool interface and registry
β”‚   β”‚   β”œβ”€β”€ adapter.go                 # Session info and agent config adapters
β”‚   β”‚   β”œβ”€β”€ forgejo_tools.go           # 20 Forgejo API tools
β”‚   β”‚   └── local_tools.go             # 4 local tools (bash, read, write, git)
β”‚   β”œβ”€β”€ webhook/router.go              # HTTP server, HMAC, event normalization, /status
β”‚   └── webui/webui.go                # HTML admin dashboard at /admin
└── scripts/
    └── fordjent.service               # systemd unit for bare-metal deploy

Security

Loop Prevention

Four layers prevent the agent from triggering itself in an infinite loop:

  1. <!-- ford --> marker β€” Every comment, PR, and issue body created by the agent includes a hidden HTML marker. The webhook router detects this marker and drops the event.
  2. Bot sender filter β€” Events from fordjent[bot] are ignored
  3. Branch protection β€” The bash and git tools block pushes to main/master; agent uses feature branches + forgejo_create_pr
  4. Commit prefix filter β€” Events from commits with [agent-automation] prefix are dropped

Input Sanitization

  • Repository names from LLM tool calls are sanitized via per-segment url.PathEscape before URL construction
  • File paths in read_file/write_file are validated against path traversal (must stay inside repo root)
  • Shell arguments in the bash tool are passed via exec.Command argument vector (no shell injection)
  • Dangerous commands (rm -rf /, mkfs, dd, shutdown) are blocked by pattern
  • Tool output is capped at a configurable byte limit (default 64KB) to prevent context bloat

Secret Management

Secret Scope Purpose
FORGEJO_TOKEN Repository-scoped Forgejo API calls
FORGEJO_ADMIN_TOKEN Admin-scoped Admin API calls (optional)
Provider API key LLM provider Model inference
webhook.secret Shared HMAC Webhook authenticity
security.admin_token Fordjent admin Protects /admin, /status endpoints

Secrets are injected via environment variables and expanded in config with ${VAR} syntax. Never commit secrets to the repository.

Monitoring & Operations

Fordjent exposes HTTP endpoints on the configured port:

Endpoint Purpose
/healthz Liveness probe β€” returns ok
/readyz Readiness probe β€” returns ready
/metrics Prometheus text-format metrics
/status JSON snapshot: costs, lifecycle, metrics
/activity HTML activity feed (recent sessions)
/admin HTML admin dashboard

Prometheus Metrics

fordjent_events_total          β€” webhook events received
fordjent_sessions_total        β€” cumulative sessions created
fordjent_sessions_active       β€” gauge of current sessions
fordjent_tool_calls_total      β€” all tool executions
fordjent_llm_calls_total       β€” all LLM calls
fordjent_llm_retries_total    β€” cumulative LLM retries
fordjent_tokens_total{type}    β€” input/output token counts
fordjent_cost_total_total      β€” cumulative spend in USD

Deployment

See docs/deployment.md for Docker Compose with reverse-proxy, systemd installation, backup strategy, and monitoring.

Recovery: If sessions become corrupted, restart with ./fordjent -config my-config.yaml -clean to wipe all session state.

Development

Prerequisites

  • Go 1.25+
  • A running Forgejo instance (for integration testing)
  • An OpenAI-compatible LLM endpoint

Building & Testing

go build -o fordjent ./cmd/fordjent

# Run all tests with race detector
go test -race -count=1 ./...

# Run a specific package
go test -v -race ./internal/session/...

The test suite includes 112+ tests covering all packages with -race clean.

Adding a Tool

  1. Create a struct implementing the tool.Tool interface:
type myTool struct {
    adapter *tool.ForgejoAdapter
}

func (t *myTool) Name() string         { return "my_tool" }
func (t *myTool) Description() string  { return "Does something useful" }
func (t *myTool) Parameters() map[string]interface{} { /* JSON Schema */ }
func (t *myTool) Execute(ctx context.Context, args json.RawMessage) (string, error) {
    // Implementation
}
  1. Register it in tool.NewRegistry() (in registry.go)
  2. The tool is automatically exposed to the LLM via function calling

Running with Docker

The image is built from a pinned golang:1.25-alpine base, runs as a non-root fordjent user, and stores state in /var/lib/fordjent.

# Build and start
docker compose up -d

# View logs
docker compose logs -f

# Health check
curl http://localhost:8080/healthz

# Status dashboard
curl http://localhost:8080/status | jq .

Roadmap

  • Observability β€” Prometheus metrics, /readyz, /status, /admin dashboard
  • Persistence β€” SQLite-backed session state for crash recovery
  • Intelligence β€” Role-based provider routing, context compaction, dependency scheduling
  • Coordination β€” Merge queue, stale gate + auto-rebase, scaffold detection, lifecycle tracking
  • Cost tracking β€” Per-session/repo/month cost with budget enforcement
  • Multi-node β€” Redis event bus for horizontal scaling
  • CI integration β€” Forgejo Actions runner integration for test-gated merges
  • Summarization compaction β€” LLM-based context summarization instead of truncation
  • Subagent orchestration β€” Spawn explore/research agents for complex tasks

License

MIT

About

A forgejo-driven agent

Resources

Stars

4 stars

Watchers

1 watching

Forks

Releases

Packages

Contributors

Languages