agent

package module
v0.3.7 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: May 17, 2026 License: MIT Imports: 12 Imported by: 0

README

forge-agent

A minimal Go agent runtime: Anthropic API + tool use loop + MCP client + built-in HTTP tools. Ships with a cron scheduler for running agent jobs 24/7 in the cloud.

forge-agent connects Claude to any MCP server via SSE or Streamable HTTP, dispatches tool calls, and drives the conversation to completion. It ships as a library (forge-cms.dev/forge-agent) and runnable binaries.

Latest: v0.3.5forge-agent/flow (AGPL) · forge-agent (MIT)


Dependencies

Package Purpose
github.com/anthropics/anthropic-sdk-go Anthropic API and tool use protocol
github.com/modelcontextprotocol/go-sdk Official MCP Go SDK (Apache 2.0)
github.com/go-co-op/gocron/v2 Cron scheduler (Apache 2.0)

The MCP SDK is maintained by the MCP organization. Spec changes are tracked automatically — forge-agent does not maintain its own MCP transport layer.

gocron is used instead of stdlib time + goroutines because timezone handling on Alpine/Linux servers is a known failure mode with plain goroutine-based schedulers (forge-social hit this in v0.4.0), and missed job recovery on restart requires non-trivial handling that gocron provides out of the box.


Quick start

export ANTHROPIC_API_KEY=sk-ant-...
export FORGE_MCP_URL=https://your-site.example.com/mcp
export FORGE_TOKEN=your-forge-token

go run ./cmd/agent-forge

For GitHub:

export ANTHROPIC_API_KEY=sk-ant-...
export GITHUB_TOKEN=ghp_...
export GITHUB_REPO=forge-cms/forge

go run ./cmd/agent-github

Config reference

type Config struct {
    MCPURL         string // MCP server endpoint; empty = no MCP tools
    MCPToken       string // Bearer token for the MCP server
    SystemPrompt   string // System message prepended to every conversation
    Model          string // Anthropic model ID (default: "claude-sonnet-4-6")
    MaxTurns       int    // Max tool-use loops before giving up (default: 10)
    StreamableHTTP bool   // Use Streamable HTTP transport instead of SSE
}

Set StreamableHTTP: true when connecting to the GitHub MCP server or any server implementing the 2025-03-26+ spec. Leave it false (the default) for forge-mcp, which uses the SSE transport from the 2024-11-05 spec.


Built-in tools

Two tools are always available in every agent run, alongside MCP tools:

http_get
{
  "name": "http_get",
  "input": { "url": "https://api.example.com/data" }
}

Makes an HTTP GET request. Returns the response body (capped at 32 KB). On non-2xx: returns "HTTP <status>: <body prefix>".

http_post
{
  "name": "http_post",
  "input": {
    "url": "https://ntfy.sh/my-topic",
    "body": "Electricity is cheap between 02:00 and 06:00.",
    "content_type": "text/plain"
  }
}

Makes an HTTP POST request. content_type defaults to "text/plain". Use "application/json" for Discord webhooks and JSON APIs. Returns "HTTP <status>: <response body prefix>".


Scheduler

forge-agent ships a cron scheduler for running agent jobs continuously in the cloud.

Job and Scheduler
type Job struct {
    Schedule string // 5-field cron expression (e.g. "0 6 * * *")
    Timezone string // IANA timezone (e.g. "Europe/Copenhagen"); empty = UTC
    Task     string // prompt passed to Agent.Run on each execution
    Config   Config // agent config for this job
}

s, err := agent.NewScheduler([]agent.Job{
    {
        Schedule: "0 6 * * *",
        Timezone: "Europe/Copenhagen",
        Task:     "Fetch electricity prices and post a recommendation.",
        Config:   agent.Config{SystemPrompt: "..."},
    },
})
if err != nil {
    log.Fatal(err)
}
s.Start()
defer s.Stop()
  • NewScheduler validates all timezones and cron expressions at startup — fail-fast, not at first run.
  • Each job runs in singleton mode: if a job is still running when its next trigger fires, the new run is skipped.
  • Missed jobs on restart are not caught up — the next scheduled run fires as normal.
  • Stop blocks until all in-flight jobs complete (graceful shutdown).
time/tzdata embed

The cmd/scheduler binary embeds the Go timezone database with import _ "time/tzdata". This is required on Alpine and scratch containers that have no OS-level tzdata. The library itself does not embed it — callers who manage tzdata themselves are not affected.

Quick start — cmd/scheduler
export ANTHROPIC_API_KEY=sk-ant-...
export NTFY_TOPIC=my-ntfy-topic

go run ./cmd/scheduler

The scheduler fires at 06:00 Europe/Copenhagen each day, fetches 48 hours of DK2 electricity spot prices, identifies the cheapest 2-hour window in the next 24 hours and in the following 24 hours, and posts a concise recommendation in Danish to https://ntfy.sh/$NTFY_TOPIC.

Systemd deploy on Hetzner (linux/amd64)

1. Cross-compile

$env:GOOS = "linux"; $env:GOARCH = "amd64"
go build -o forge-agent-scheduler ./cmd/scheduler
$env:GOOS = ""; $env:GOARCH = ""

2. Copy to server

scp forge-agent-scheduler root@your-server:/usr/local/bin/
scp deploy/forge-agent-scheduler.service root@your-server:/etc/systemd/system/

3. Create the env file on the server

mkdir -p /etc/forge-agent
cat > /etc/forge-agent/scheduler.env <<EOF
ANTHROPIC_API_KEY=sk-ant-...
NTFY_TOPIC=my-ntfy-topic
EOF
chmod 600 /etc/forge-agent/scheduler.env

4. Install and start the service

systemctl daemon-reload
systemctl enable forge-agent-scheduler
systemctl start forge-agent-scheduler
systemctl status forge-agent-scheduler
Triggering a manual run

Send SIGUSR1 to trigger an immediate agent run without restarting the service:

systemctl kill -s SIGUSR1 forge-agent-scheduler

The service continues running normally after the triggered run completes.


Forge integration — forge-agent/flow/

forge-cms.dev/forge-agent/flow is an AGPL-3.0-or-later sub-package that wires agent execution into a Forge application. It exposes AgentJob as a Forge content type with full lifecycle management and auto-generated MCP tools.

Quick start
import forgeagent "forge-cms.dev/forge-agent/flow"

// At startup — create table before connecting the module.
forgeagent.CreateTable(db)

agentMod := forgeagent.New(db, forgeagent.Config{
    MCPURL:   "http://localhost:8080/mcp",
    MCPToken: os.Getenv("FORGE_TOKEN"),
})
agentMod.Register(app) // wires MCP tools + signal bus
defer agentMod.Stop()
AgentJob fields
Field Type Description
Name string Human-readable identifier. Used as slug source. Required.
Trigger string 5-field cron expression ("45 13 * * *") or forge signal name ("after_publish"). Required.
ContentTypeFilter string Restrict signal triggers to a content type (e.g. "Post"). Empty = all types. Ignored for cron triggers.
SystemPrompt string System instruction prepended to every run. Required.
Model string Anthropic model ID. Defaults to "claude-sonnet-4-6".
MaxTurns int Max tool-use loops. Defaults to 10.
WebhookURL string If set, agent's task prompt includes an instruction to POST output here via http_post.

Status lifecycle: Draft (job exists, does not run) → Published (active) → Archived (stopped).

MCP tools (auto-generated)

create_agent_job, get_agent_job, list_agent_jobs, update_agent_job, publish_agent_job, archive_agent_job, delete_agent_job. Role: Admin.

Signal triggers

Set Trigger to any forge.Signal string value:

Signal Fires when
after_publish Content transitions to Published
after_create New content item created
after_update Content updated
after_unpublish Content moved out of Published
after_archive Content archived
after_schedule Content scheduled
after_delete Content deleted

Set ContentTypeFilter to restrict to a content type (e.g. "Post"). Leave empty to match all types. Note: AgentJob lifecycle events never trigger other jobs — the module guards against self-activation automatically.

UC1 — devlog-social-drafts example
1. create_agent_job — name: "devlog-social-drafts",
                      trigger: "after_publish",
                      content_type_filter: "Post",
                      system_prompt: "Draft LinkedIn and X posts for this content."
2. publish_agent_job slug="devlog-social-drafts" — activates the job
3. Publish a Post — signal fires, agent runs, creates scheduled social posts
4. archive_agent_job slug="devlog-social-drafts" — deactivates the job

Architecture note

The MCP client in forge-agent is generic. It speaks to any SSE or Streamable HTTP MCP server — not just forge-mcp. The cmd/agent-github binary demonstrates this: it connects to the GitHub MCP server using Streamable HTTP while cmd/agent-forge connects to forge-mcp using SSE. Same agent loop, different transport, different tool catalog.


License

MIT — see LICENSE.

Documentation

Overview

Package agent provides a minimal agent execution runtime: Anthropic API + tool use loop + MCP client + built-in HTTP tools.

Index

Constants

This section is empty.

Variables

View Source
var ErrMaxTurns = errors.New("agent: max turns reached")

ErrMaxTurns is returned when the agent exhausts MaxTurns without reaching end_turn.

Functions

This section is empty.

Types

type Agent

type Agent struct {
	// contains filtered or unexported fields
}

Agent drives an Anthropic tool-use loop with optional MCP and built-in tools.

func New

func New(cfg Config) *Agent

New creates a new Agent with defaults applied.

func (*Agent) Run

func (a *Agent) Run(ctx context.Context, task string) (string, error)

Run connects to the MCP server (if MCPURL is set), then drives a tool-use loop until the model returns end_turn or MaxTurns is reached. The ANTHROPIC_API_KEY environment variable must be set.

type Config

type Config struct {
	// MCPURL is the MCP server endpoint. If empty, no MCP tools are available.
	MCPURL string
	// MCPToken is the Bearer token sent to the MCP server.
	MCPToken string
	// SystemPrompt is prepended to every conversation as the system message.
	SystemPrompt string
	// Model is the Anthropic model ID. Defaults to "claude-sonnet-4-6".
	Model string
	// MaxTurns is the maximum number of tool-use loops before giving up. Defaults to 10.
	MaxTurns int
	// StreamableHTTP instructs the MCP client to use Streamable HTTP transport
	// instead of SSE. Use this when connecting to GitHub MCP or any 2025-03-26+ server.
	StreamableHTTP bool
}

Config holds the settings for an Agent run.

type Job added in v0.2.0

type Job struct {
	// Schedule is a 5-field cron expression (e.g. "0 6 * * *").
	Schedule string
	// Timezone is the IANA timezone for the cron schedule (e.g. "Europe/Copenhagen").
	// Empty defaults to UTC.
	Timezone string
	// Task is the prompt passed to Agent.Run on each scheduled execution.
	Task string
	// Config is the agent configuration for this job.
	Config Config
}

Job defines a single scheduled agent task.

type Scheduler added in v0.2.0

type Scheduler struct {
	// contains filtered or unexported fields
}

Scheduler wraps gocron and runs registered Jobs on their cron schedules. Jobs sharing a timezone share a gocron.Scheduler instance.

func NewScheduler added in v0.2.0

func NewScheduler(jobs []Job) (*Scheduler, error)

NewScheduler creates a Scheduler from a slice of Jobs. Returns an error if any job has an invalid timezone or cron expression.

func (*Scheduler) Start added in v0.2.0

func (s *Scheduler) Start()

Start begins scheduling all jobs. Non-blocking.

func (*Scheduler) Stop added in v0.2.0

func (s *Scheduler) Stop()

Stop gracefully shuts down all schedulers, waiting for in-flight jobs to complete.

Directories

Path Synopsis
cmd
agent-forge command
agent-forge connects to a forge-mcp server and summarises published posts.
agent-forge connects to a forge-mcp server and summarises published posts.
agent-github command
agent-github connects to the GitHub MCP server and summarises open issues.
agent-github connects to the GitHub MCP server and summarises open issues.
scheduler command
Package forgeagent wires forge-agent into a Forge application.
Package forgeagent wires forge-agent into a Forge application.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL