Changelog

Every release, every improvement. Track Forge's evolution.

vnext

Rolling build of main @ c9ff6e7. Prerelease (not latest). Pin agent-builder AGENT_FORGE_VERSION=vnext to test unreleased runtime changes.

View on GitHub
v0.19.0

Forge v0.19.0 makes shipping AI agents from existing skill folders faster and safer: importing a SKILL.md folder now infers its metadata.forge build block, a new forge skills validate command catches the silent tool-registration failures that used to only surface at runtime, and the tool→script binding is more forgiving. This release also lands a full documentation sweep, including default-deny Policy Decision Point (PDP) governance guidance for MCP and API tools.

Forge is a secure, portable AI agent runtime — run agents locally, in the cloud, or in enterprise environments without exposing inbound tunnels.

Highlights

  • Import a plain skill folder and get a working Forge agentmetadata.forge is inferred when the SKILL.md has none (#412, #415).
  • forge skills validate — surface invalid **Input:** keys, missing backing scripts, and orphan scripts before runtime, with a non-zero exit for CI (#418, #419).
  • Forgiving tool→script binding — a ## Tool: some_name now binds to either scripts/some_name.* or scripts/some-name.* (#418, #419).
  • Full documentation sweep — MCP/API/PDP schema, audit-event catalog, and default-deny PDP governance for prose skills (#416).

Features

Skill import infers metadata.forge (#412, #415)

forge skills import and forge init --from-skill-dir now generate a metadata.forge block when the imported SKILL.md doesn’t have one — the common case for real-world skill folders that ship only name + description. Forge:

  • derives requires.bins from the script interpreters it finds (.pypython3, .jsnode);
  • reports candidate egress_domains (http(s) hosts referenced in scripts) and requires.env (environment reads — Python os.environ / os.getenv, JavaScript process.env, and shell $VAR / ${VAR}, minus locally-assigned and common shell variables);
  • prints a paste-ready suggested block by default. The high-confidence interpreter bins can be written into the vendored SKILL.md with --write-forge-meta; the egress/env candidates always stay printed for review and are never auto-declared, so an agent’s egress allowlist is never silently widened.

Import writes are atomic with a re-parse guard so a partial or malformed injection can’t corrupt the vendored skill.

# Turn a folder of SKILL.md + scripts into a Forge agent
forge init my-agent --from-skill-dir ./ai-budget-increase-evaluator
# ...or import into an existing project, writing the inferred bins:
forge skills import ./ai-budget-increase-evaluator --write-forge-meta

New: forge skills validate tool-registration lint (#418, #419)

Two skill-registration failures used to be silent — a tool would simply not appear at runtime, with at most one error-log line. forge skills validate now scans the whole skill tree (the main SKILL.md and every skills/*/SKILL.md) and reports each, using Forge’s own parser so the check can never drift from enforcement:

CheckSeverityWhy it matters
**Input:** property key outside ^[a-zA-Z0-9_.-]{1,64}$errorThe LLM provider rejects the entire request, so Forge drops the whole tool at startup.
A ## Tool: with no backing scripts/<name>.{sh,py,js}errorThe tool never registers.
A scripts/*.{sh,py,js} with no ## Tool: headingwarningReachable only via run_skill_script; dead weight if unreferenced.

It exits non-zero on any error, so CI can gate on it — replacing the external lints teams maintained as approximations of Forge’s parser.

forge skills validate   # non-zero exit if any tool would fail to register

More forgiving tool→script binding (#418, #419)

A ## Tool: some_name previously bound only to scripts/some-name.{sh,py,js} (underscores forced to hyphens), so an author who named the file some_name.sh got a silently-missing tool. It now binds to either name form — scripts/some_name.* or scripts/some-name.* — with the hyphenated form winning only if both exist, so every existing skill resolves identically. Resolution priority is otherwise unchanged (skill-local before shared, shell → python → node), and path-traversal hardening is preserved.

Documentation

Full documentation sync sweep + rolling anchor (#416)

A comprehensive sweep audited every feature merged between v0.17.1 and v0.18.1 against the docs and closed the gaps:

  • Reference schema — added the complete mcp:, apis: (per-operation API tools), and pdp: blocks, plus egress allowed_tcp / allowed_private_cidrs and defer approver fields.
  • Audit catalog — documented llm_call_failed and the mcp_auth_required / mcp_auth_resolved / mcp_auth_timeout events, always-on error-field redaction, and channel-sender attribution.
  • Governance — a managed Policy Decision Point (PDP) section, per-operation API tools, and a default-deny PDP example showing how MCP and API calls made from a prose skill are still governed per operation at BeforeToolExec (govern the tool the model picks, not just the ones you listed).
  • Rolling anchor (docs/sync-docs-state.md) so future documentation sweeps run incrementally instead of re-scanning history.

Install / upgrade

# Homebrew
brew upgrade initializ/tap/forge

Prebuilt binaries and checksums.txt for macOS, Linux, and Windows (amd64/arm64) are attached to this release below.

Who should upgrade

Anyone importing existing SKILL.md folders into Forge, anyone authoring skill tools (validate before shipping), and operators governing MCP/API tool calls with a Policy Decision Point. This release is additive and backward-compatible — existing skills and configs are unaffected.

Full changelog: https://github.com/initializ/forge/compare/v0.18.1…v0.19.0

View on GitHub
v0.18.0

Forge v0.18.0 brings delegated per-user MCP authentication with full OAuth 2.1 discovery and dynamic client registration, a one-command forge try onboarding flow, skill-folder→agent import, a config-selectable OpenAI Responses API provider, and hardened egress control and audit attribution — for teams building secure, governed AI agents.

Forge is an open-source runtime for building and shipping AI agents that speak the A2A (Agent2Agent) protocol, call Model Context Protocol (MCP) tools, and run under first-class egress control, policy governance, and tamper-evident audit. This release centers on making MCP tool access safe for real multi-user deployments, and on getting new users to a running agent in seconds.

Upgrade note: additive and backward-compatible. New MCP auth.type values (platform, user) and the openai-responses provider are opt-in. Full diff: https://github.com/initializ/forge/compare/v0.17.1…v0.18.0

Highlights

  • Delegated per-user MCP auth — MCP servers can now authenticate as the requesting user (auth.type: user), with a per-user connection pool, lazy consent, and an auth-required gate that parks a call until the user connects. Works standalone or platform-managed. (#317, #327, #329, #330/#331, #332/#344)
  • MCP OAuth 2.1 discovery + Dynamic Client Registration — zero-config OAuth against any compliant MCP server: RFC 9728 → RFC 8414 metadata discovery and RFC 7591 dynamic client registration at first forge mcp login. (#316/#320)
  • forge try — instant onboarding: scaffold a keyless demo agent and chat in-process, no server or build, credentials auto-resolved. (#350/#353)
  • Skill folder → agentforge skills import <dir> and forge init --from-skill-dir convert a SKILL.md + scripts + reference files into a runnable agent; Python skills are first-class (## Tool: .py/.js), and a skill requirements.txt is pip-installed at build. (#405/#406)
  • OpenAI Responses API provider — point an agent at /v1/responses (or a Responses-first gateway) via config with provider: openai-responses. (#383/#385)

Model Context Protocol (MCP)

Multi-user MCP is the theme of this release: a single agent can now broker each user’s own credentials to remote MCP tools, with consent, tenancy, and audit.

  • Per-user connection lifecycle — pooled per-subject MCP connections + routing seam + materialized type=user servers. (#317 → #327, #329)
  • Auth-required consent gate — a grantless delegated call parks and resumes when consent lands; POST /mcp/consent signals completion. (#330/#331)
  • Standalone delegated consenttype:user works without a platform, via a loopback resolver. (#332/#344)
  • Slack-delivered MCP consent prompts — Forge DMs the user a “Connect” link over Socket Mode; no inbound exposure. (#343/#345)
  • Agent-principal OAuth (2LO)client_credentials grant so an agent authenticates as itself, no user/browser. (#324/#325)
  • Platform + user auth resolvers — read via platform identity, write via delegated user identity. (#326)
  • OAuth discovery + DCR — RFC 9728/8414/7591. (#316/#320)
  • Tenancy headersOrg-Id/Workspace-Id on all platform token requests. (#328/#334)
  • ${VAR} expansion in MCP config fields at load. (#321/#323)
  • Delegated token TTL cap so a disconnect is promptly enforced; call-time ErrNoToken routed through the auth gate. (#376/#377, #380/#381)

CLI & onboarding

  • forge try — zero-to-chat demo agent in one command. (#350/#353)
  • Skill-folder importforge skills import + forge init --from-skill-dir, with reference-file vendoring, egress/env wiring, build-time Python dependency install, and first-class .py/.js tools. (#405/#406)
  • Startup version banner — the running forge binary/runtime version is printed on start. (#335/#336)
  • Windows OAuth launch fix — the login URL opens without cmd’s & truncation. (#312)

Security, governance & egress

  • SOCKS5 raw-TCP egress with a port-aware allowlist — governed non-HTTP egress. (#337/#355)
  • allowed_private_cidrs — narrow the default private-IP block instead of all-or-nothing. (#337/#348)
  • Per-tool DEFER approver allowlist — email-based, fail-closed human-in-the-loop approvals. (#313/#315)
  • Native Slack interactive DEFER approvals — Approve/Reject buttons over Socket Mode (R4c). (#310/#311)
  • Org-wide command denylist (denied_command_patterns) enforcement + emitted approver allowlist in task_deferred. (#403)

LLM providers

  • openai-responses provider — config-selectable OpenAI Responses API with API-key/gateway auth and a disable_store privacy opt-out. (#383/#385)
  • apikey_header_only auth scheme + the invoked LLM URL recorded on llm_call audit events (Kong/gateway support). (#358)

Runtime, PDP & audit attribution

  • Managed PDP decision resolver at BeforeToolExec — the platform can allow/deny/modify each governed tool call. (#399)
  • Per-operation API tool type from admitted OpenAPI entries (<server>__<op>). (#400)
  • Deferrals survive the request — task execution detaches so long human approvals don’t time out with the HTTP connection. (#402)
  • Per-invocation audit attribution — egress-proxy + MCP-consent + subprocess-proxy events attribute to the right task/invocation; channel-originated tasks attribute to the human sender. (#338/#339, #341/#366/#367, #356)

Channels & A2A

  • A2A data parts reach the prompt — a data-part-only message (e.g. a workflow step’s output) no longer executes as an empty prompt; the same projection feeds the guardrail/intent scanners so nothing bypasses them. (#410/#411)
  • Slack long-response fix — stop dumping raw tool JSON; keep chunked replies in-thread. (#384)

Fixes & maintenance

  • Session-recovery dedup only trims a trailing identical user turn. (#378/#379)
  • Remote session store builds independently of the memory-persistence gate. (#372/#373)
  • validate accepts hyphenated MCP tool names in allow/deny lists. (#370)
  • Skill tool schemas parse the platform Input format and validate property keys at registration. (#362)
  • Embedded weather skill switched to the keyless wttr.in endpoint. (#354)
  • Dependency bumps: google.golang.org/grpc 1.81.1 → 1.82.1 (core + cli). (#368, #369)

Full changelog: https://github.com/initializ/forge/compare/v0.17.1…v0.18.0

View on GitHub
v0.18.1

Forge v0.18.1 brings delegated per-user MCP authentication with full OAuth 2.1 discovery and dynamic client registration, a one-command forge try onboarding flow, skill-folder→agent import, a config-selectable OpenAI Responses API provider, and hardened egress control and audit attribution — for teams building secure, governed AI agents.

Forge is an open-source runtime for building and shipping AI agents that speak the A2A (Agent2Agent) protocol, call Model Context Protocol (MCP) tools, and run under first-class egress control, policy governance, and tamper-evident audit. This release centers on making MCP tool access safe for real multi-user deployments, and on getting new users to a running agent in seconds.

Upgrade note: additive and backward-compatible. New MCP auth.type values (platform, user) and the openai-responses provider are opt-in. Full diff: https://github.com/initializ/forge/compare/v0.17.1…v0.18.1

Highlights

  • Delegated per-user MCP auth — MCP servers can now authenticate as the requesting user (auth.type: user), with a per-user connection pool, lazy consent, and an auth-required gate that parks a call until the user connects. Works standalone or platform-managed. (#317, #327, #329, #330/#331, #332/#344)
  • MCP OAuth 2.1 discovery + Dynamic Client Registration — zero-config OAuth against any compliant MCP server: RFC 9728 → RFC 8414 metadata discovery and RFC 7591 dynamic client registration at first forge mcp login. (#316/#320)
  • forge try — instant onboarding: scaffold a keyless demo agent and chat in-process, no server or build, credentials auto-resolved. (#350/#353)
  • Skill folder → agentforge skills import <dir> and forge init --from-skill-dir convert a SKILL.md + scripts + reference files into a runnable agent; Python skills are first-class (## Tool: .py/.js), and a skill requirements.txt is pip-installed at build. (#405/#406)
  • OpenAI Responses API provider — point an agent at /v1/responses (or a Responses-first gateway) via config with provider: openai-responses. (#383/#385)

Model Context Protocol (MCP)

Multi-user MCP is the theme of this release: a single agent can now broker each user’s own credentials to remote MCP tools, with consent, tenancy, and audit.

  • Per-user connection lifecycle — pooled per-subject MCP connections + routing seam + materialized type=user servers. (#317 → #327, #329)
  • Auth-required consent gate — a grantless delegated call parks and resumes when consent lands; POST /mcp/consent signals completion. (#330/#331)
  • Standalone delegated consenttype:user works without a platform, via a loopback resolver. (#332/#344)
  • Slack-delivered MCP consent prompts — Forge DMs the user a “Connect” link over Socket Mode; no inbound exposure. (#343/#345)
  • Agent-principal OAuth (2LO)client_credentials grant so an agent authenticates as itself, no user/browser. (#324/#325)
  • Platform + user auth resolvers — read via platform identity, write via delegated user identity. (#326)
  • OAuth discovery + DCR — RFC 9728/8414/7591. (#316/#320)
  • Tenancy headersOrg-Id/Workspace-Id on all platform token requests. (#328/#334)
  • ${VAR} expansion in MCP config fields at load. (#321/#323)
  • Delegated token TTL cap so a disconnect is promptly enforced; call-time ErrNoToken routed through the auth gate. (#376/#377, #380/#381)

CLI & onboarding

  • forge try — zero-to-chat demo agent in one command. (#350/#353)
  • Skill-folder importforge skills import + forge init --from-skill-dir, with reference-file vendoring, egress/env wiring, build-time Python dependency install, and first-class .py/.js tools. (#405/#406)
  • Startup version banner — the running forge binary/runtime version is printed on start. (#335/#336)
  • Windows OAuth launch fix — the login URL opens without cmd’s & truncation. (#312)

Security, governance & egress

  • SOCKS5 raw-TCP egress with a port-aware allowlist — governed non-HTTP egress. (#337/#355)
  • allowed_private_cidrs — narrow the default private-IP block instead of all-or-nothing. (#337/#348)
  • Per-tool DEFER approver allowlist — email-based, fail-closed human-in-the-loop approvals. (#313/#315)
  • Native Slack interactive DEFER approvals — Approve/Reject buttons over Socket Mode (R4c). (#310/#311)
  • Org-wide command denylist (denied_command_patterns) enforcement + emitted approver allowlist in task_deferred. (#403)

LLM providers

  • openai-responses provider — config-selectable OpenAI Responses API with API-key/gateway auth and a disable_store privacy opt-out. (#383/#385)
  • apikey_header_only auth scheme + the invoked LLM URL recorded on llm_call audit events (Kong/gateway support). (#358)

Runtime, PDP & audit attribution

  • Managed PDP decision resolver at BeforeToolExec — the platform can allow/deny/modify each governed tool call. (#399)
  • Per-operation API tool type from admitted OpenAPI entries (<server>__<op>). (#400)
  • Deferrals survive the request — task execution detaches so long human approvals don’t time out with the HTTP connection. (#402)
  • Per-invocation audit attribution — egress-proxy + MCP-consent + subprocess-proxy events attribute to the right task/invocation; channel-originated tasks attribute to the human sender. (#338/#339, #341/#366/#367, #356)

Channels & A2A

  • A2A data parts reach the prompt — a data-part-only message (e.g. a workflow step’s output) no longer executes as an empty prompt; the same projection feeds the guardrail/intent scanners so nothing bypasses them. (#410/#411)
  • Slack long-response fix — stop dumping raw tool JSON; keep chunked replies in-thread. (#384)

Fixes & maintenance

  • Session-recovery dedup only trims a trailing identical user turn. (#378/#379)
  • Remote session store builds independently of the memory-persistence gate. (#372/#373)
  • validate accepts hyphenated MCP tool names in allow/deny lists. (#370)
  • Skill tool schemas parse the platform Input format and validate property keys at registration. (#362)
  • Embedded weather skill switched to the keyless wttr.in endpoint. (#354)
  • Dependency bumps: google.golang.org/grpc 1.81.1 → 1.82.1 (core + cli). (#368, #369)

Full changelog: https://github.com/initializ/forge/compare/v0.17.1…v0.18.1

View on GitHub
v0.17.1

Forge v0.17.1 — web_fetch and general file tools, org-wide command-denial platform policy (ASI02), and Kong AI Gateway auth

Forge v0.17.1 is a tools-and-governance point release for the open-source AI agent runtime. It gives agents two long-missing file-and-web capabilities — a web_fetch builtin that reads a URL as clean, LLM-readable markdown, and the previously-dormant file_read / file_write / file_edit / file_patch builtins now wired for every general agent — hardens the platform-policy surface with an operator-authored, org-wide command denylist (OWASP ASI02, Tool Misuse), and adds an apikey_header outbound LLM auth scheme so agents work behind Kong AI Gateway and other fixed-header API gateways.

All four ship on the v0.17.0 AARM (Autonomous Action Runtime Management) foundation. Every change is additive; see Upgrade notes for the two new default-on tools.

Highlights

  • web_fetch builtin — read a URL as clean text/markdown (#266). The “read this page” tool agents were missing: web_search returns results and http_request returns raw bytes, but neither reads a doc. web_fetch does a read-only GET over the same egress-controlled path as http_request (allowlist + SSRF protections, no new network path), converts HTML → clean markdown (strips nav/scripts/styling, preserves <pre>/<code>, transcodes non-UTF-8 charsets), enforces a content-type guard + redirect + size caps, and returns readable content plus the final URL. Default-on. See Tools & Builtins.
  • General file tools wired: file_read / file_write / file_edit / file_patch (#268). Forge shipped four fully-implemented file builtins the runtime never registered — dead code reachable only from tests. They’re now registered for every general agent, path-confined to the working directory (the same PathValidator #235 boundary the search tools use), giving a general agent a real file read/edit/overwrite surface instead of just search + file_create. Skipped when the code-agent skill is active (its project-scoped code_agent_* tools are the specialization — skill tools win).
  • Platform-layer denied_command_patterns — org-wide, per-call command denial (#238, ASI02). Operators could deny at tool-name, egress-domain, and channel granularity, but had no argument-level control — so “keep cli_execute, but ban rm -rf / git push --force / kubectl delete across the org” meant banning the whole tool or relying on per-skill SKILL.md. The new platform-policy field applies an operator-authored regex denylist to every tool call by any skill, matched at BeforeToolExec. It’s the first PlatformPolicy field enforced per-invocation rather than once at startup, blocks emit an attributed guardrail_check audit event (source: platform), and an invalid regex fails closed at load. See Platform Policy — Runtime command denial.
  • apikey_header LLM auth scheme for API gateways (#302). Kong AI Gateway’s key-auth plugin reads the consumer key from a fixed apikey header and ignores provider-native headers, so an agent pointed at a Kong-fronted OPENAI_BASE_URL / ANTHROPIC_BASE_URL 401’d every call. model.auth_scheme: apikey_header sends the key in the gateway header in addition to the provider-native one (additive, safe against non-gateway endpoints), with an optional auth_header_name override for custom key_names. forge validate rejects an unknown scheme or a header name that collides with a native auth header. See Runtime Engine.

What’s new

web_fetch builtin (#266)

web_fetch reads a specific page — a linked spec, changelog, or API reference — and returns it as clean markdown, so the model isn’t hand-parsing raw HTML out of http_request.

{ "url": "https://example.com/docs/spec", "max_chars": 50000 }
  • Egress-controlled, read-only GET. Reuses http_request’s exact plumbing — the context egress transport, SafeRedirectPolicy, and the optional R9 JIT credential injector — so the allowlist / SSRF / DNS-rebinding protections and per-domain credentials apply identically. Fails closed: with no egress client installed it refuses rather than falling back to http.DefaultTransport.
  • HTML → clean markdown via golang.org/x/net/html (no new external dependency): drops script / style / nav / footer / aside chrome; keeps the title, headings, list items, and links; preserves <pre> / <code> whitespace so code samples survive; transcodes non-UTF-8 charsets (older RFC/spec pages are often ISO-8859-1).
  • Guards & caps: content-type allowlist (refuses binary blobs — image / pdf / octet-stream), a redirect cap, a byte cap, and a max_chars cap (default 50 000) with a truncation marker. Returns {url, content_type, status, content, truncated?}; a non-2xx still returns the error page’s content with its status.

General file tools (#268)

# Every general agent now has, path-confined to WorkDir:
#   file_read  — read a file (offset/limit) or list a directory
#   file_write — create or overwrite a file
#   file_edit  — exact-string edit with a unified diff
#   file_patch — batch add/update/delete/move in one call

There are two non-colliding file surfaces: the general file_* builtins (WorkDir-scoped, default-on) and the code-agent skill’s project-scoped code_agent_* tools. When the code-agent skill is active the general builtins are skipped so the model never sees two overlapping surfaces. All four enforce the same directory-traversal confinement as file_create and the search tools.

Platform command denial (#238, ASI02)

# platform policy (system / user / workspace layer)
denied_command_patterns:
  - pattern: 'kubectl\s+delete'
    message: "destructive kubectl blocked by org policy"
  - pattern: 'git\s+push\s+--force'
  - pattern: '(^|\s)rm\s+-rf\b'

Unioned across the three policy layers (first layer owns audit attribution). The match target is identical to skill deny_commandscli_execute → the reconstructed command line, any other tool → the tool-input JSON plus its decoded string values (so a JSON-escaped separator like kubectl\tdelete can’t dodge a whitespace pattern). The tool is not stripped (that’s denied_tools); only matching calls are blocked, and a block emits a runtime guardrail_check event with source: platform + the offending pattern, layer, and message. Patterns compile at startup and forge validate --platform-policy rejects an invalid regex before deploy. Off unless a layer declares it.

apikey_header LLM auth (#302)

model:
  provider: openai                                 # or anthropic — symmetric
  name: gpt-4o
  base_url: https://kong-gateway.internal/openai
  auth_scheme: apikey_header
  # auth_header_name: x-gateway-key                # optional; default: apikey

Additive: the provider-native header (Authorization: Bearer / x-api-key) still rides, and the key is also sent in the gateway header, so the scheme is safe to enable against non-gateway endpoints. Extends the same auth_scheme extension point added for AWS Bedrock SigV4 in v0.16.0. Applies to the primary model only.

Upgrade notes

  • Two new default-on tools change the general agent’s tool surface. General (non-code-agent) agents now register file_write / file_edit / file_patch (mutating, path-confined) and web_fetch by default, alongside the existing file_create. Both are confined to the working directory / egress allowlist respectively; action-level approval for mutating tools is tracked separately (#223). If you deny these for a given agent, add them to the policy denied_tools list.
  • All other changes are additive and off by default. denied_command_patterns does nothing unless a platform-policy layer declares it; apikey_header does nothing unless model.auth_scheme is set. No config changes are required to upgrade.

Full changelog

Full changelog: https://github.com/initializ/forge/compare/v0.17.0…v0.17.1

View on GitHub
v0.17.0 BREAKING

Forge v0.17.0 — AARM (Autonomous Action Runtime Management) compliance: intent alignment, step-up & defer authorization, JIT credentials, and tamper-evident audit signing

Forge v0.17.0 is the largest security release yet for the open-source AI agent runtime. It implements AARM — Autonomous Action Runtime Management, an open-source security specification for governing what AI agents are allowed to do at runtime. This release lands the AARM control set end to end: per-call intent-alignment scoring (R3, #245), longitudinal intent-drift detection (R7, #246), tool-wide guardrail enforcement (R4a, #221), RFC 9470 step-up authorization (R4b, #247), pause-and-resume defer authorization (R4c, #248), a tamper-evident audit hash chain with Ed25519 signing (R5/R6, #220, #237), and just-in-time credential dispensing per tool call (R9, #236). Together they let a platform prove what an agent did, why it was allowed, and that the record wasn’t altered.

This release also ships reversible context compression (ctxzip) that cuts 60–95% of tokens on bulky tool outputs losslessly (#241, #279), an opt-in headless-browser tool family driven through the egress proxy (#260), a pluggable session store with a remote backend for stateless Kubernetes replicas (#244), guardrails that now apply to every tool rather than just cli_execute (#221), a platform guardrails policy overlay (#285), and a wave of Skill Builder improvements — structured JSON output (#276), built-in-tool awareness (#297), and skill-first activation routing (#298).

Highlights

  • Intent alignment — every tool call scored against the user’s stated goal (#245, governance R3). Each action is embedded (tool_description + args_json) and compared by cosine similarity to the task’s stated intent. Fail-closed: when enabled and the embedder is unavailable, calls DENY rather than silently bypass. Off by default (security.intent_alignment.enabled), with a recommended warn-only first rollout. Emits an intent_alignment audit event per call. See Intent Alignment.
  • Intent drift detection — catches the “boiling frog” (#246, governance R7). A rolling window watches the trend of intent-alignment scores across a task’s lifetime and trips on either a mean-below-threshold or a monotone-decrease pattern. intent_drift fires only on state changes (enter / recover), so long drift stretches never flood the audit stream.
  • Step-up authorization — RFC 9470 challenges (#247, governance R4b). A high-assurance tool call whose caller lacks a sufficient acr claim returns HTTP 401 with a WWW-Authenticate: Bearer error="step_up_required" challenge instead of running. Optional acr_hierarchy lets a stronger assurance satisfy a weaker requirement. Emits auth_step_up_required. See Step-up Authorization.
  • Defer authorization — pause, approve out-of-band, resume (#248, governance R4c). A high-risk tool call pauses the executor; an external approver POSTs a decision and the executor resumes exactly where it left off. The A2A task status flips to deferred while blocked. Distinct from DENY (refuses) and STEP_UP (re-auths). See Deferred Authorization.
  • JIT credential dispensing — short-lived secrets per tool call (#236, governance R9). A new forge-core/credentials/ package materializes credentials at the moment of a tool call and revokes them after, so long-lived secrets never sit in the agent’s environment. Ships static and sts_assume_role (AWS STS AssumeRole via SDK-free hand-rolled SigV4) providers. Emits credential_issued / credential_revoked / credential_failed — never the credential material itself. See Least-privilege credentials.
  • Tamper-evident audit logging — Ed25519 signatures + hash chain + JWKS (#220, #237). Every audit event now carries a prev_hash link, and — when signing is enabled — a kid + sig. The signature covers prev_hash, so altering the chain breaks both the chain walk and the signature check. A JWKS endpoint publishes the verification key. See Audit signing.
  • Reversible context compression (ctxzip) — 60–95% fewer tokens, losslessly (#241, #279). Bulky tool outputs (JSON arrays, logs, grep, kubectl get -o json) are compressed before reaching the LLM behind an inline <<ctxzip:HASH …>> marker, and recovered on demand via a new context_expand tool. Lossy on the wire, lossless end-to-end. v0.3.0 adds whole-record items[] crushing, categorical drop summaries, and a YAML/describe crusher, plus a keep_patterns learning loop. Off by default (compression.enabled). See Context Compression.
  • Opt-in headless-browser tool family (#260, issue #94). Agents can drive a real headless Chromium (via chromedp) to navigate, read, click, fill, and screenshot — registered only when an active skill declares requires.capabilities: [browser]. The LLM acts on a compact indexed page digest (~1–3 KB), not raw HTML. All traffic is forced through the existing EgressProxy (allowlist / SSRF / DNS-rebinding protection); browser_fill refuses password and payment fields unless the skill opts in. See Browser Tools.
  • Guardrails now apply to every tool, not just cli_execute (#221, governance R4a). deny_commands / deny_output patterns previously no-op’d for web_search, http_request, MCP, and custom tools. That short-circuit is removed. Potentially breaking — see Upgrade notes.
  • Platform guardrails overlay (#285). A platform policy.yaml can now layer additional, most-restrictive-wins guardrails on top of an agent’s guardrails.json, and the unused MongoDB “DB mode” guardrails path is removed. See Platform Policy.
  • Pluggable session store with a remote backend (#244). memory.session_store: remote pushes per-task session snapshots to a platform service over HTTP with conditional GET (If-None-Match) and If-Match CAS commits, so stateless Kubernetes replicas resume any task on any pod without a PVC. Defaults to the local file backend. See Memory System.
  • Audit event correlation + quieter export heartbeat (#300, #301). Auth events are now attributed to their invocation via an ingress-minted correlation id (#278), and the audit_export_status heartbeat moved to a hybrid cadence — it emits immediately on a sink health change and otherwise a slow keepalive, cutting ~1,440 rows/agent/day to a handful (#280).

What’s new

AARM — Autonomous Action Runtime Management

AARM (Autonomous Action Runtime Management) is an open-source security specification for governing what autonomous AI agents are allowed to do at runtime. v0.17.0 implements its control set, keyed by requirement ID (R3–R9); see the security architecture overview for how these layers fit together. At the center is the R4 policy-decision taxonomyAllow < Modify < StepUp < Defer < Deny, ordered by restrictiveness — with the R3/R7 intent surface and R9 credentials around it. Every AARM control is off by default and fail-closed when enabled.

AARM controlRequirementPR
Intent alignment (per-call semantic gate)R3#245
Guardrail MODIFY/DENY across all toolsR4a#221
Step-up authorization (RFC 9470)R4b#247
Defer authorization (pause / approve / resume)R4c#248
Audit hash chainR5#237
Ed25519 audit signing + JWKSR6#220, #237
Intent drift detection (longitudinal)R7#246
JIT credential dispensingR9#236

Intent alignment (#245) & drift (#246)

security:
  intent_alignment:
    enabled: true              # off by default; recommend warn-only first
    provider: openai           # openai | gemini | ollama
    model: text-embedding-3-small
    threshold: 0.5             # soft: below → warn
    hard_threshold: 0.3        # hard: below → deny
    cache_size: 1024           # LRU size for action-side embeddings

Intent is anchored on the tool description (not its name — MCP names like fn_42 carry no meaning) plus the arguments JSON. Both sides are embedded; the action side hits an LRU keyed on sha256(action_text) so repeated identical calls skip the round-trip. R7 drift layers longitudinal telemetry on top: it trips on a rolling-window mean below the drift threshold or a monotone decrease of the last N scores.

Step-up (#247) & defer (#248) authorization

security:
  step_up:                     # R4b — require stronger auth for risky tools
    enabled: true
    tools:
      cli_execute: acr:mfa     # per-tool required acr value
    acr_hierarchy: [acr:password, acr:mfa, acr:hardware]  # optional: stronger satisfies weaker
  defer:                       # R4c — pause for out-of-band approval
    enabled: true
    tools:
      cli_execute:
        to: channel:slack:#oncall
        timeout: 10m
        context_template: "Agent wants to run {tool} with args: {args}"

Step-up returns an RFC 9470 challenge on insufficient assurance; defer blocks the executor goroutine on a decision channel and flips the A2A task to deferred until an approver POSTs a decision (or the timeout auto-denies). Both hooks run after guardrails so a guardrail-denied call never produces an unnecessary challenge or deferral.

JIT credentials (#236)

credentials:
  - tool: cli_execute
    binary: aws
    provider: sts_assume_role
    spec:
      role_arn: arn:aws:iam::123456789012:role/forge-skill-read
      external_id: skill-alpha
      session_name: forge-agent-jit
      duration: 15m

Credentials are materialized on every Execute, merged into the subprocess environment, and revoked on completion. The sts_assume_role provider uses Forge’s SDK-free SigV4 signer (no aws-sdk-go-v2), enforces AWS 15m–12h duration bounds, and supports external_id, session_name, and an inline session_policy.

Tamper-evident audit logging (#220, #237)

Every event carries prev_hash unconditionally; enabling FORGE_AUDIT_SIGNING_KEY_B64 adds kid + sig (Ed25519 over the JCS-canonicalized event, covering prev_hash). Because the signature commits to the previous hash, editing or reordering any line breaks both the chain walk and the per-event signature. A JWKS endpoint publishes the public key for offline verification. See Audit signing and the audit env-var reference (#254).

Reversible context compression (#241, #279)

compression:
  enabled: true

ctxzip compresses bulky tool outputs once at production time (an AfterToolExec seam), so the conversation prefix stays byte-stable and provider prompt caches keep hitting. Dropped content is stored in a durable local bbolt store behind an inline <<ctxzip:HASH …>> marker and retrieved via the context_expand tool. v0.3.0 (#279) routes list-API shapes like kubectl get -o json through whole-record items[] crushing — anomalous records survive intact while the rest offload as a retrievable array — and adds categorical drop summaries ([offloaded: 139 running, 24 batch completed]) plus a YAML/describe crusher. A keep_patterns learning loop mines context_expand retrievals so frequently-needed fields stop being offloaded.

Opt-in headless-browser tools (#260)

The browser_navigate / browser_state / browser_click / browser_fill / browser_extract / browser_screenshot family registers only when a skill declares requires.capabilities: [browser] and a Chromium binary is present. Every request routes through the EgressProxy (same allowlist / SSRF / DNS-rebinding protection as in-process tools); the analyzer scores the capability high-risk and flags a browser + trust_hints.network: false contradiction as a Critical violation. forge build installs Chromium into the image only for browser agents.

Skill Builder & skills runtime

  • Structured {message, skill} JSON output (#276, #252 part 2) — the Skill Builder now returns a machine-parseable envelope so the dashboard reliably separates chat from the drafted skill, with a hardened parser that rejects wrapper objects and incidental JSON.
  • Built-in-tool awareness (#297, closes #270) — the builder knows Forge’s built-in tools (read_file, write_file, edit_file, glob, grep, file_create, scheduling, …), authors rather than role-plays, and gates scheduling behind an explicit ask.
  • Skill-first activation routing (#298, closes #271) — the runtime checks whether a request matches an attached skill before answering from model defaults.
  • Interview convergence + custom-binary install recipes (#258, #252 part 1), robust draft extraction so the preview always populates (#253), a persistently-visible Edit button (#262, closes #261), and skill-catalog names aligned with read_skill resolution (#250).
  • Skill-relative file reads + multi-language script execution (#257, closes #251) and env_required / env_optional surfaced in build-manifest.json (#259). forge build no longer purges curl when a skill needs it at runtime (#242).

Platform & operations

  • Guardrails overlay in policy.yaml (#285, #288) — most-restrictive-wins platform layering over an agent’s guardrails.json; the unused MongoDB DB-mode path is removed.
  • Remote session store for stateless replicas (#244) — resume any task on any Kubernetes pod without a PVC; 412 CAS conflicts yield to the newer state instead of clobbering it.
  • Rolling vnext prerelease on every merge to main (#249) — a continuously-built prerelease of the runtime for early adopters.
  • Dependency hardeninggolang.org/x/crypto bumped to 0.52.0 across forge-core and forge-cli (#274, #275).

Upgrade notes

  • Guardrail MODIFY / DENY now apply to every tool (#221 / governance R4a) — potentially breaking. The SkillGuardrailEngine no longer short-circuits on non-cli_execute tools, so deny_commands / deny_output patterns that previously no-op’d for web_search, http_request, MCP calls, and custom tools now fire. Review your guardrail patterns before upgrading.
    • Match-target asymmetry: for cli_execute the match target is still the reconstructed shell command line (binary arg1 arg2 …), so existing shell-style patterns keep working. For every other tool the match target is the raw tool-input JSON as the LLM produced it — write patterns accordingly.
    • API change: GuardrailChecker.CheckInbound / CheckOutbound now return (PolicyResult, error). The new PolicyDecision enum is ordered by restrictiveness (Allow < Modify < StepUp < Defer < Deny). Callers today read only Allow / Modify / Deny; StepUp / Defer are emitted by the R4b (#210) / R4c (#211) hooks. The guardrail_check audit wire shape is unchanged.
    • Migration guidance: docs/security/policy-decisions.md.
  • Guardrails MongoDB “DB mode” removed (#285). If you set FORGE_GUARDRAILS_DB, migrate to guardrails.json plus the platform policy.yaml overlay. See Platform Policy.
  • All governance features are off by default. Intent alignment, drift, step-up, defer, and JIT credentials ship disabled and fail-closed when enabled — turn them on deliberately, and roll intent alignment out in warn-only mode first.

Full changelog

Full changelog: https://github.com/initializ/forge/compare/v0.16.0…v0.17.0

View on GitHub
v0.16.0

Forge v0.16.0 — AWS Bedrock SigV4 outbound, platform admission control, W3C trace propagation into subprocess skills

Forge v0.16.0 ships four substantial additions to the open-source AI agent runtime: AWS Bedrock SigV4 outbound signing with symmetric OpenAI + Anthropic wire-format support, an opt-in platform admission hook that lets a control plane gate new invocations on per-agent cost / quota ceilings, W3C trace-context propagation into skill subprocesses so OTel-instrumented CLIs nest their spans under the agent’s tool.<name> span, and workflow correlation split into definition + execution IDs matching the GitHub Actions / Tekton / Argo industry pattern.

This release also adds three new runtime spans (auth.verify, channel.<adapter>.deliver, schedule.fire), a Skill Builder edit mode for iterating on attached skills from the web dashboard, opt-in workflow-header auto-propagation on allow-listed hosts, fail-loud DB mode for guardrails, an operator-visible surface for FWS-8 audit payload capture, and a consolidated redact pass that unifies the vendor-secret scrub across audit, guardrail evidence, and OTel span content.

Highlights

  • AWS Bedrock SigV4 outbound (#202). New model.auth_scheme: aws_sigv4 + model.aws_region fields on ModelRef. A hand-rolled ~250 LOC stdlib-only SigV4 RoundTripper wraps the LLM client’s http.Transport and skips the default Authorization: Bearer / x-api-key headers. Symmetric across the openai and anthropic providers — pick whichever wire format the Bedrock endpoint speaks. Credentials from AWS_ACCESS_KEY_ID / AWS_SECRET_ACCESS_KEY / AWS_SESSION_TOKEN. Matches the inbound-auth posture (forge-core/auth/providers/aws_sigv4) — same no-aws-sdk-go-v2 policy that keeps the binary flat.
  • Anthropic-format Custom URLs (#202 Phase 1). forge init wizard’s “Custom URL” option now asks whether the endpoint speaks OpenAI Chat Completions or Anthropic Messages wire format and scaffolds the matching provider + base-URL env. forge.yaml never carries provider: "custom". Covers Bedrock’s Anthropic passthrough and Anthropic-compatible proxies alongside the existing OpenRouter / vLLM / litellm / Together.ai path.
  • Platform admission hook (#201). Opt-in pre-dispatch gate that asks a platform API whether to admit each new inbound A2A invocation. Engaged when both FORGE_ADMISSION_URL and FORGE_PLATFORM_TOKEN are set. At most one call per agent per 5s (baked cache); denies return HTTP 402 Payment Required with a Retry-After derived from the platform’s reset_at. Fail-open everywhere — network / 4xx / 5xx / parse errors log one warn line and admit, so a platform outage never blocks traffic. Distinct from 401 (auth failed) and 429 (rate limiter). Emits task_admission_denied audit events and an admission.check OTel span sibling of auth.verify.
  • Subprocess W3C trace-context propagation (#182). Skill and tool subprocesses now receive TRACEPARENT, TRACESTATE, and BAGGAGE env vars derived from the parent agent’s active span, plus a curated OTEL_* SDK config subset. OTel-instrumented binaries (infil, an LLM CLI, a Python service, any tool that reads TRACEPARENT) nest their spans under the agent’s tool.<name> span instead of starting a fresh root. OTEL_EXPORTER_OTLP_HEADERS is deliberately excluded — collector auth tokens must be declared via SKILL.md env.optional.
  • Binary-runtime skills (#182). New metadata.forge.runtime: binary in the SKILL.md schema. Binary skills exec the first metadata.forge.requires.bins entry directly (resolved via exec.LookPath) — no bash fork, no materialized script. runtime: script (or empty) keeps the legacy path.
  • Workflow correlation split (#185). X-Workflow-Id now carries the workflow DEFINITION id (stable across every run); new X-Workflow-Execution-Id carries the per-run instance id. Both surface as top-level audit fields (workflow_id, workflow_execution_id). SIEM consumers answer both “every event in this specific run” (join on workflow_execution_id) AND “top failing workflows over time” (group by workflow_id) without joining on opaque ids. Industry precedent: GitHub Actions (workflow + workflow_run_id), Tekton (Pipeline + PipelineRun), Argo (Workflow + WorkflowRun).
  • Workflow header auto-propagation on allow-listed hosts (#186). New workflow_propagation.allowed_hosts block in forge.yaml. Hosts on the allow-list automatically receive X-Workflow-Id / X-Workflow-Execution-Id / X-Workflow-Stage-Id / X-Workflow-Step-Id / X-Invocation-Caller from the current request context on every built-in HTTP tool call — no per-tool code change. Non-allow-listed hosts stay opt-in so workflow identity never leaks to third-party APIs.
  • Three new runtime spans (#187). auth.verify wraps the auth chain’s Verify call so JWKS / STS / IAP / Graph HTTP calls nest under it instead of appearing as orphan roots. channel.<adapter>.deliver wraps every channel adapter’s per-message handler (Slack / Telegram / Teams); the internal A2A POST injects traceparent so the a2a.tasks/send span nests under deliver — operators finally answer “Slack→agent latency” from the flame graph alone. schedule.fire wraps the file-backend scheduler.
  • Skill Builder edit mode (#193). The web dashboard’s Skill Builder can now iterate on an already-attached custom skill instead of only creating new ones. New “Skills attached to this agent” panel lists each skills/<name>/SKILL.md; Edit loads current SKILL.md + helper scripts, primes the chat, and switches the LLM to an edit-mode prompt (preserves ## Tool: <name> headings, defaults to minimal patches, emits a **Changed:** summary). Monaco diff modal shows editor-state vs disk side-by-side before save.
  • Guardrails DB fail-loud + operator surface (#166). New FORGE_GUARDRAILS_DB_REQUIRED=true env var: when DB mode is selected and the Mongo connect fails, the runner returns a non-nil startup error instead of silently downgrading to file mode. New forge guardrails seed-defaults subcommand prints DefaultStructuredGuardrails as JSON suitable for piping into MongoDB. New forge guardrails validate-db subcommand reports on baseline coverage (PII, jailbreak threshold, secret-pattern rule count, gate enablement) and exits non-zero on missing document. One-shot warning when both FORGE_GUARDRAILS_DB and guardrails.json are present.
  • Audit payload capture: operator surface + consolidated redact pass (#163). FWS-8 raw-payload capture is now configurable from forge.yaml and FORGE_AUDIT_CAPTURE_* env vars, closing the gap that previously required a code change to enable capture in a container deployment. New shared PrepareCapturedContent helper — the FWS-8 hooks, guardrail evidence pipeline, and OTel span content pipeline now all delegate to one vendor-secret regex scrub instead of maintaining three copies.

What’s new

AWS Bedrock SigV4 outbound (#202)

Bedrock uses AWS SigV4 signing instead of a static API key, so model.auth_scheme: aws_sigv4 swaps the default header for a hand-rolled SigV4 signature over the outbound HTTP request. Symmetric across the openai and anthropic providers:

model:
  provider: anthropic                                        # or openai, for Bedrock's OpenAI compat
  name: anthropic.claude-sonnet-4-20250514-v1:0
  base_url: https://bedrock-runtime.us-east-1.amazonaws.com
  auth_scheme: aws_sigv4
  aws_region: us-east-1

Credentials come from the standard AWS env chain: AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY, optional AWS_SESSION_TOKEN. Region falls back to AWS_REGION env when aws_region is unset. The signer matches the inbound-auth posture in forge-core/auth/providers/aws_sigv4 — same stdlib-only crypto, no aws-sdk-go-v2 dependency (which would have added ~5 MB to a binary that recently hit 88 MB). The signing algorithm is ~250 LOC of crypto/hmac + crypto/sha256 + encoding/hex.

When AuthScheme is aws_sigv4:

  • Anthropic client skips x-api-key, keeps anthropic-version.
  • OpenAI client skips Authorization: Bearer, keeps OpenAI-Organization.
  • http.Client.Transport is wrapped with the SigV4 signer.
  • Configured APIKey is ignored on this path.

Empty AuthScheme preserves the pre-#202 contract byte-for-byte — all existing Anthropic and OpenAI deployments see zero behavior change.

Deferred (tracked separately):

  • Web identity / IRSA / EC2 metadata credential resolution. Today the credential getter reads env only; most Bedrock deployments set AWS env vars at the platform layer (IRSA env injection, sidecar credential fetcher, AWS_PROFILE resolution before launch).
  • Bedrock-specific URL / body rewriting (POST /model/<id>/invoke path + anthropic_version body field + event-stream framing) is tracked under #205. Operators today front Bedrock with a Bedrock-compat proxy that handles protocol translation (litellm) when the model doesn’t expose the OpenAI or Anthropic wire shape.
  • forge init “AWS Bedrock” first-class wizard option (Phase 3 in the issue). The Custom path with shape-picker covers the configuration; the dedicated Bedrock option is pure UX polish.

Reference: docs/core-concepts/runtime-engine.md, docs/reference/forge-yaml-schema.md.

Platform admission hook (#201)

Forge measures LLM token usage per call and per invocation (FWS-3) so a platform can compute a spend ceiling externally. The admission hook lets the platform tell the agent process to stop accepting new invocations when that ceiling is hit — distinct from auth (HTTP 401) and from the per-IP rate limiter (HTTP 429).

The hook is off by default. Self-hosted deploys see no change. Engaged when both env vars are set:

Env varBehavior
FORGE_ADMISSION_URLPlatform admission endpoint. Unset → admission off.
FORGE_PLATFORM_TOKENBearer token sent on every admission call. Unset → admission off + warn at startup.

Existing tenancy env vars from #157 are forwarded as request headers when set (FORGE_ORG_IDOrg-Id, FORGE_WORKSPACE_IDWorkspace-Id).

Wire contract:

GET /v1/admission?agent_id=my-agent HTTP/1.1
Authorization: Bearer <FORGE_PLATFORM_TOKEN>
Org-Id: <FORGE_ORG_ID>
Workspace-Id: <FORGE_WORKSPACE_ID>
{
  "decision": "admit" | "deny",
  "reason": "cost_limit_exceeded",
  "scope": "agent" | "workspace" | "org",
  "window": "daily",
  "reset_at": "2026-06-28T14:00:00Z"
}

Deny → HTTP 402 Payment Required with Retry-After derived from reset_at and a structured body. Fail-open everywhere: network errors, 4xx, 5xx, body parse failure, unknown decision value → log one warn line + admit + cache the admit for the full 5s TTL (so a platform outage produces one call per agent per 5s, not a flood).

Emits task_admission_denied audit events and opens an admission.check OTel span as a sibling of auth.verify. The forge.admission.fallback=true span attribute marks admits forced by a call failure — alerts on that attribute surface platform outage rate even though no caller observes a deny.

Reference: docs/security/admission.md.

Subprocess W3C trace-context propagation + binary-runtime skills (#182)

Every skill invocation is now a span nested under the agent’s tool.<name> span. Skill and tool subprocesses receive the parent span’s TRACEPARENT / TRACESTATE / BAGGAGE env vars plus a curated OTEL_* SDK config subset (OTEL_SERVICE_NAME, OTEL_EXPORTER_OTLP_ENDPOINT, OTEL_EXPORTER_OTLP_PROTOCOL, OTEL_TRACES_SAMPLER, OTEL_TRACES_SAMPLER_ARG, OTEL_RESOURCE_ATTRIBUTES).

OTel-instrumented binaries (infil, an LLM CLI, a Python service, any tool that reads TRACEPARENT) nest their spans under the agent’s tool.<name> span instead of starting a fresh root. OTEL_EXPORTER_OTLP_HEADERS is deliberately excluded from the passthrough — collector auth tokens are treated as secrets and must be declared via SKILL.md env.optional like every other credential.

New metadata.forge.runtime: binary in the SKILL.md schema. Binary skills exec the first metadata.forge.requires.bins entry directly (resolved via exec.LookPath) — no bash fork, no materialized script file. runtime: script (or empty) keeps the legacy behavior.

When tracing is off the global propagator is a no-op composite — subprocess env is byte-identical to pre-#182 deploys.

Reference: docs/core-concepts/observability-tracing.md, docs/skills/writing-custom-skills.md.

Workflow definition vs execution split (#185)

The previously-overloaded X-Workflow-ID header now carries the workflow DEFINITION id (stable across every run); a new X-Workflow-Execution-ID header carries the per-run instance id. Both surface as top-level fields on every audit event under a workflow run (workflow_id, workflow_execution_id), so SIEM consumers answer both “show me every event in this specific run” (join on workflow_execution_id) AND “top failing workflows” / “latency by workflow definition” (group by workflow_id) without joining on opaque ids.

Industry precedent: GitHub Actions (workflow + workflow_run_id), Tekton (Pipeline + PipelineRun), Argo (Workflow + WorkflowRun).

Both fields use omitempty — direct A2A invocations without orchestrator headers continue to emit byte-identical JSON to pre-#185 consumers. AuditSchemaVersion remains "1.0".

Workflow header auto-propagation on allow-listed hosts (#186)

Adds a workflow_propagation.allowed_hosts block to forge.yaml. Hosts matching the allow-list automatically receive the workflow correlation headers from the current request context when invoked from any built-in HTTP tool — no per-tool code change needed.

workflow_propagation:
  allowed_hosts:
    - "*.internal.example.com"
    - "agents.example.com"

Wildcard semantics mirror the egress allow-list (exact + *.suffix.com, port-stripped, lowercase-normalized). Wildcards match strictly-deeper subdomains and refuse the apex — *.agents.internal does NOT match the bare agents.internal. Empty config short-circuits and returns the underlying transport identity-equal, zero overhead per request on the default-deploy path.

Hosts not on the list keep the pre-#186 opt-in behavior; the headers stay off so workflow identity never leaks to third-party APIs.

Three new runtime spans: auth.verify, channel.<adapter>.deliver, schedule.fire (#187)

All three use the existing global Tracer() and respect the off-by-default tracing posture — when tracing is disabled the no-op tracer makes them zero-allocation.

  • auth.verify wraps the Provider.Chain.Verify call. Provider outbound HTTP calls (JWKS / STS / IAP / Graph) now nest under it instead of appearing as orphan roots. Attributes: forge.auth.provider, forge.auth.token_kind, forge.auth.decision, forge.auth.user_id / org_id on success, forge.auth.fail_reason on failure.
  • channel.<adapter>.deliver wraps the per-message handler in every channel adapter (Slack / Telegram / Teams). The internal A2A POST injects the W3C traceparent via the global propagator, so the downstream a2a.tasks/send span nests under the deliver span. Attributes: forge.channel.adapter, forge.channel.target, forge.channel.message_id, forge.channel.user_id. Highest user-visible payoff of the three — operators can now answer “Slack→agent latency” from the flame graph alone.
  • schedule.fire wraps Scheduler.fire in the file backend. K8s-backend dispatch is out of scope for this pass — the trigger Pod is a separate curl-based Pod and needs traceparent injected into the rendered CronJob YAML at forge package time (follow-up).

Status=Error on the failure path keeps error-rate dashboards consistent across span types.

Reference: docs/core-concepts/observability-tracing.md.

Skill Builder edit mode (#193)

The dashboard’s Skill Builder can now iterate on an already-attached custom skill instead of only creating new ones. A new Skills attached to this agent panel lists each skills/<name>/SKILL.md discovered on disk; clicking Edit loads its current SKILL.md and helper scripts into the editor, primes the chat with the existing content, and switches the LLM call to an edit-mode prompt that instructs it to preserve ## Tool: <name> headings, default to minimal patches, and emit a **Changed:** summary.

A Preview changes Monaco diff modal shows editor-state vs disk side-by-side before save. Confirm save overwrites the existing skill directory in place; helper scripts dropped during the edit are removed from disk so the runtime stops discovering them. A Restart agent banner appears after a successful edit-mode save because the running agent’s tool registry is captured at startup and not live-mutated.

New endpoints:

  • GET /api/agents/{id}/skill-builder/skills[]CustomSkillSummary
  • GET /api/agents/{id}/skill-builder/skills/{name}CustomSkillContent
  • POST /api/agents/{id}/skill-builder/chat accepts mode: "edit" + editing_name
  • POST /api/agents/{id}/skill-builder/save accepts overwrite: true with matching editing_name

All endpoints reject path-traversal, slashes, backslashes, and non-kebab-case names with 400. The script loader rejects symlinks whose resolved target escapes the skill’s own directory so a malicious link to /etc/passwd never reaches the editor or LLM context.

Reference: docs/reference/web-dashboard.md, docs/skills/writing-custom-skills.md.

Guardrails DB mode fail-loud + operator surface (#166)

Three quiet behaviors in the BuildGuardrailChecker resolution ladder that mismatched what operators expect from a “production-grade guardrails” deploy are now addressable from the operator surface:

  • FORGE_GUARDRAILS_DB_REQUIRED=true — when DB mode is selected (FORGE_GUARDRAILS_DB set) and the Mongo connect fails, the runner logs an Error and returns a non-nil startup error instead of silently downgrading. Off by default for back-compat; recommended ON for platform deployments where DB-mode guardrails are security-critical.
  • forge guardrails seed-defaults — prints DefaultStructuredGuardrails as JSON suitable for piping into MongoDB. Round-trips through models.StructuredGuardrails so the output is library-consumable verbatim. Closes the “DB mode bypasses built-in defaults” footgun.
  • forge guardrails validate-db — connects to FORGE_GUARDRAILS_DB, fetches the agent’s AgentConfig document, and reports on baseline coverage (PII config, jailbreak / prompt-injection / command-injection thresholds, secret-pattern rule count, core gate enablement). Warns when fewer than 5 secret-pattern rules are present or PII config is missing. Exits non-zero on missing document so CI / deployment hooks can fail rollout.

One-shot startup warning when both FORGE_GUARDRAILS_DB is set AND a guardrails.json is present in the workdir — repo readers previously saw the file and assumed it was active; in DB-mode deploys it was dead config that drifted. Warning fires exactly once per process, pointing at the specific ignored path.

DB connect timeout in NewDBGuardrailEngine trimmed from 10s to 3s — short enough that a misconfigured URI surfaces during startup, long enough to absorb DNS jitter + TLS on a healthy cluster.

Reference: docs/security/guardrails.md.

Audit payload capture: operator surface + consolidated redact pass (#163)

FWS-8 raw-payload capture (AuditPayloadCapture) is now configurable from forge.yaml and FORGE_AUDIT_CAPTURE_* env vars in addition to the existing programmatic RunnerConfig path — closing the gap that previously made the feature unusable from a container deployment without a code change.

New env vars: FORGE_AUDIT_CAPTURE_TOOL_ARGS, FORGE_AUDIT_CAPTURE_TOOL_RESULT, FORGE_AUDIT_CAPTURE_LLM_MESSAGES, FORGE_AUDIT_CAPTURE_LLM_RESPONSE, FORGE_AUDIT_CAPTURE_REDACT (default true), FORGE_AUDIT_CAPTURE_MAX_BYTES (single-knob 16 KiB default).

New forge.yaml block under audit.capture: with per-field *bool semantics (nil = fall through to env). Precedence: forge.yaml > env > default.

New Redact field on AuditPayloadCapture. ON by default. When on, captured fields run through the shared vendor-secret regex scrub (PrepareCapturedContent) before truncation, so a token an LLM glued into a cli_execute command surfaces in audit as [REDACTED], not verbatim. Set false only when a downstream sink runs its own scrubber.

New shared helper coreruntime.PrepareCapturedContent(s, redact, maxBytes). The FWS-8 capture hooks, guardrail evidence pipeline, and OTel span content pipeline now all delegate to this one helper — a fix to the regex set propagates to every content-capture path. Removes 3 independent copies of the vendor-secret regex set.

Reference: docs/security/audit-logging.md.

CLI

  • forge guardrails seed-defaults — print DefaultStructuredGuardrails as JSON suitable for piping into MongoDB
  • forge guardrails validate-db — validate an agent’s AgentConfig document against baseline coverage; exits non-zero when the document is missing so CI / deployment hooks can fail rollout
  • The forge init interactive wizard’s “Custom URL” option gains a shape picker (OpenAI Chat Completions vs Anthropic Messages) between the URL and model phases

forge.yaml

  • New model.auth_scheme field ("" | x_api_key | bearer | aws_sigv4) and model.aws_region (required when auth_scheme: aws_sigv4) — issue #202
  • New top-level workflow_propagation.allowed_hosts block — issue #186
  • New audit.capture: block with per-field *bool semantics for tool_args, tool_result, llm_messages, llm_response, redact, max_bytes — issue #163

Environment variables

  • FORGE_ADMISSION_URL + FORGE_PLATFORM_TOKEN — engage the platform admission hook (both required)
  • FORGE_GUARDRAILS_DB_REQUIRED=true — fail-loud when the guardrails Mongo connect fails
  • FORGE_AUDIT_CAPTURE_TOOL_ARGS, FORGE_AUDIT_CAPTURE_TOOL_RESULT, FORGE_AUDIT_CAPTURE_LLM_MESSAGES, FORGE_AUDIT_CAPTURE_LLM_RESPONSE, FORGE_AUDIT_CAPTURE_REDACT, FORGE_AUDIT_CAPTURE_MAX_BYTES — audit payload capture surface (all opt-in; capture stays off by default)

Fixed

  • K8s scheduler backend no longer hard-errors when scheduler.kubernetes.service_url is unset (#179). Pre-fix, an agent deployed in-cluster with scheduler.backend: auto and no explicit service_url aborted startup with kubernetes scheduler backend: scheduler.kubernetes.service_url is required, even though the build-time schedule_manifest_stage already knew how to default the same field. Runtime now mirrors the build-time default: when ServiceURL is empty, the constructor derives the in-cluster Service DNS using agent_id + resolved namespace + the runner’s listen port. Explicit service_url overrides pass through untouched.
  • forge init scaffolds scheduler.backend: file explicitly. Previously the omitted field left the K8s-vs-file resolution to runtime auto-detection, which surprised operators reading the scaffolded yaml.
  • forge init always emits the env secret provider in the scaffolded forge.yaml. Previously the block was omitted when no secrets were declared, which meant a later forge secret set had no provider to write into and silently no-op’d.
  • Audit SequenceCounter installs before auth so auth_verify lands seq=1 and session_start lands seq=2 (#174). Previously the counter was installed inside the auth middleware, so auth_verify events emitted with an empty seq field.
  • tool_exec + session_end audit events now stamp seq. Previously the EmitToolExec and EmitSessionEnd paths bypassed EmitFromContext and skipped the counter increment; consumers detecting gaps saw phantom holes in the sequence.

Audit-event schema

AuditSchemaVersion remains "1.0". All new fields are additive:

  • workflow_execution_id — top-level, omitempty — from X-Workflow-Execution-ID
  • task_admission_denied — new event with fields.reason / scope / window / reset_at / cached
  • FWS-8 capture fields (args, result, prompt_messages, completion_text) surface on tool_exec / llm_call when the corresponding capture flag is on; absent otherwise

Consumers ignoring unknown keys continue to work unchanged.

Documentation

Closed issues

  • #163 — audit payload capture operator surface (env + yaml) + consolidated redact pass
  • #166 — guardrails DB fail-loud + seed-defaults / validate-db + exclusivity warning
  • #174SequenceCounter installed before auth; tool_exec / session_end stamp seq
  • #179 — K8s scheduler service_url default at runtime
  • #182 — subprocess W3C trace-context propagation + binary-runtime skills
  • #185 — split X-Workflow-ID into definition + execution ids
  • #186 — opt-in workflow-header auto-propagation on allow-listed hosts
  • #187auth.verify, channel.<adapter>.deliver, schedule.fire runtime spans
  • #193 — Skill Builder edit mode for iterating on attached skills
  • #201 — platform admission hook (opt-in pre-dispatch quota gate)
  • #202 — Anthropic-format Custom URLs + AWS Bedrock SigV4 outbound

Upgrading from v0.15.x

Forge v0.16.0 is backwards-compatible with v0.15.x deployments. The new admission hook, workflow-execution ID, subprocess trace propagation, workflow-header auto-propagation, audit payload capture surface, and Bedrock SigV4 support are all opt-in — deployments that don’t set the corresponding env vars / yaml fields keep emitting the pre-v0.16 shape verbatim.

To opt into the platform admission hook on an existing deployment:

  1. Deploy the platform-side admission endpoint (GET /v1/admission?agent_id=<id> returning {decision, reason, scope, window, reset_at}).
  2. Set FORGE_ADMISSION_URL and FORGE_PLATFORM_TOKEN on the agent process. Both must be set — one without the other logs a warn and disables the hook.
  3. Optionally set FORGE_ORG_ID / FORGE_WORKSPACE_ID so the outbound admission call carries Org-Id / Workspace-Id headers.
  4. No forge.yaml change required.

To point Forge at AWS Bedrock:

  1. Set model.provider to anthropic or openai depending on which wire format the Bedrock endpoint speaks.
  2. Set model.base_url: https://bedrock-runtime.<region>.amazonaws.com, model.auth_scheme: aws_sigv4, model.aws_region: <region>.
  3. Set AWS_ACCESS_KEY_ID + AWS_SECRET_ACCESS_KEY (+ AWS_SESSION_TOKEN for temporary credentials) in the environment.
  4. For Bedrock models that don’t expose the OpenAI or Anthropic wire shape, front Bedrock with a compat proxy (e.g. litellm) until #205 lands native URL/body rewriting.

Full changelog

git log v0.15.0..v0.16.0 --oneline in this repository, or browse v0.15.0...v0.16.0 on GitHub.

View on GitHub
v0.15.0

Forge v0.15.0 — Kubernetes-native scheduling, end-to-end guardrails audit, multi-tenant stamping

Forge v0.15.0 ships three substantial additions to the open-source AI agent runtime: a hybrid scheduler that deploys agent cron jobs as native Kubernetes CronJob resources, a complete guardrails audit pipeline covering all five library gates with optional OpenTelemetry span parity, and tenant + entity identifiers stamped on every audit event so SIEM consumers can filter the export stream by org, workspace, and agent without joining against authentication rows.

Forge agents now deploy to Kubernetes with one command: forge build && forge package && kubectl apply -k. The scheduler’s CronJob controller takes over timing, the cluster’s etcd persists state, and the manifests forge package emits are byte-identical to what the runtime backend reconciles — no drift between build-time and runtime.

This release also publishes forge-core, forge-skills, and forge-plugins as importable Go modules under their own path-prefixed tags so platform teams can embed Forge’s runtime, registry, and channel adapters directly.

Highlights

  • scheduler.Backend interface with two implementations: a file-backed scheduler (the existing 30s ticker + markdown persistence, unchanged behavior) and a Kubernetes backend using k8s.io/client-go for native CronJob CRUD. Selected at startup via scheduler.backend: auto | file | kubernetes in forge.yaml. In-cluster detection probes the projected ServiceAccount token at /var/run/secrets/kubernetes.io/serviceaccount/token.
  • forge package Kubernetes manifest generation. For every forge.yaml schedules[] entry, the build pipeline emits one cronjob-<id>.yaml, a credential-less Secret template, and a Role/RoleBinding scoped to the agent’s namespace. The Secret manifest deliberately ships without a data field — the credential never lands in version control; operators populate via forge auth secret-yaml | kubectl apply -f -, ExternalSecrets, Sealed Secrets, SOPS, or Vault Agent Injector.
  • forge auth subcommand (show-token, mint-token, secret-yaml) for the internal bearer token the runtime mints at startup. Same token channel adapters use for A2A callbacks, now reused by Kubernetes scheduler CronJob trigger pods.
  • guardrail_check audit event emitted at all five library gates (input, context, tool_call, output, stream) with opt-in evidence capture controlled by FORGE_GUARDRAIL_CAPTURE_EVIDENCE. The PII/secret content the library produces is captured post-mask by default; redact-then-truncate uses the same regex set as the OpenTelemetry content-capture pipeline.
  • OpenTelemetry guardrail spans (guardrail.<gate>) shipped symmetrically to the audit emission. Block decisions stamp OTel Error status with the violation summary — operators see blocked invocations as red bars in the trace UI without writing custom attribute queries.
  • Tenancy stamping (org_id, workspace_id) and entity stamping (entity_id, entity_type) added as top-level audit fields. Sourced from FORGE_ORG_ID / FORGE_WORKSPACE_ID / FORGE_AGENT_ID (or forge.yaml’s agent_id) with per-request X-Forge-Org-ID / X-Forge-Workspace-ID headers overriding the tenancy stamp. Field names match the guardrails library’s MongoDB GuardrailAuditEvent columns 1:1.
  • OpenTelemetry span content capture opt-in (issue #130). Prompts and completions stamp gen_ai.input.messages + gen_ai.output.messages (current OTel GenAI semantic conventions, not the deprecated flat-string keys); tool calls stamp forge.tool.args + forge.tool.result. Capture is off by default; redaction is on by default when capture is enabled.
  • Go library modules publishedforge-core, forge-skills, forge-plugins are now importable via path-prefixed tags (forge-core/v0.15.0, etc.). The Initializ platform and other embedders can pull just the runtime, registry, or channel layers without the CLI.

What’s new

Kubernetes-native scheduler (#162)

The default auto backend resolves to file when running on a laptop or CI and to Kubernetes when the agent is running as a pod. The K8s backend persists schedules as CronJob resources keyed on forge.agent.id / forge.schedule.id labels; the cluster’s controller handles timing, etcd provides durability, and concurrencyPolicy: Forbid enforces overlap prevention natively.

# forge.yaml
schedules:
  - id: daily-summary
    cron: "0 9 * * *"
    task: "Generate the daily ops summary and post to #ops"
    channel: slack
    channel_target: "C12345"

scheduler:
  backend: auto
  kubernetes:
    namespace: ""           # defaults to the agent pod's own namespace
    service_url: ""         # CronJob trigger pods POST here (in-cluster Service DNS)
    allow_dynamic: false    # whether the LLM (schedule_set) can create CronJobs
    trigger_image: ""       # default: curlimages/curl:8.10.1
    auth_secret_name: ""    # default: <agent_id>-internal-token

End-to-end deploy:

forge build && forge package                       # emits k8s/cronjob-*.yaml + Secret template + RBAC
forge auth mint-token > /dev/null                  # first-deploy bootstrap
forge auth secret-yaml | kubectl apply -f -        # populates the Secret out-of-band
kubectl apply -k .forge-output/k8s/                # brings up the agent + CronJobs

Sync() reconciles cluster CronJob resources against the declared yaml entries: creates missing, updates on spec drift, prunes dropped yaml entries, and preserves LLM-sourced (forge.schedule.source=llm) entries unconditionally. AllowDynamic: false (the default) keeps the LLM from creating CronJob resources at runtime; forge package materializes the yaml entries at build time instead. The RBAC Role forge package emits grants get / list / watch by default, with create / update / delete only when allow_dynamic: true.

Reference: docs/deployment/scheduler-kubernetes.md, docs/core-concepts/scheduling.md.

Guardrails audit emission and OTel span parity (#155, #159, #161)

Every mask / block / warn decision now emits a guardrail_check audit event on the configured sink (stderr, Unix socket, HTTP fallback) carrying the library gate, decision, guardrail, category, violation_count, and optional tool. With FORGE_GUARDRAIL_CAPTURE_EVIDENCE=true, the post-mask content lands in fields.evidence (or the pre-mask original on block / warn), scrubbed through a vendor-secret redact pass with a 4 KiB byte cap.

OpenTelemetry receives the same information as guardrail.<gate> child spans with forge.guardrail.gate / decision / type / category / violation_count attributes. Block decisions stamp OTel Error status with the violation summary — operators get red bars in the trace UI without custom attribute queries.

The direction field shipped in earlier drafts is replaced by gate (sourced from Result.Gate), matching the library vocabulary 1:1.

{
  "ts": "2026-06-15T10:00:00Z",
  "event": "guardrail_check",
  "schema_version": "1.0",
  "correlation_id": "fd111edd27c20101",
  "task_id": "slack-...",
  "fields": {
    "gate": "input",
    "decision": "masked",
    "guardrail": "pii",
    "category": "ssn",
    "violation_count": 1
  }
}

Reference: docs/security/guardrails.md, docs/security/audit-logging.md.

Tenancy and entity stamping (#157, #164)

Audit events now carry top-level org_id, workspace_id, entity_id, and entity_type fields. SIEM consumers filter the audit stream by (org_id, workspace_id, entity_id) as a complete deploy identifier without joining against auth_verify rows. Field names match the guardrails library’s MongoDB GuardrailAuditEvent columns 1:1, so consumers reading both streams join on the same column names.

FieldSourcePer-request header override
org_idFORGE_ORG_IDX-Forge-Org-ID
workspace_idFORGE_WORKSPACE_IDX-Forge-Workspace-ID
entity_idFORGE_AGENT_ID or forge.yaml agent_id
entity_typehardcoded "agent" (future-extensible to workflow / assistant)

Reference: docs/security/tenancy.md.

OpenTelemetry content capture and current GenAI semantic conventions (#130)

The observability.tracing.capture_content and observability.tracing.redact config knobs are now wired through Phase 3 span instrumentation. With capture on, the executor stamps gen_ai.input.messages (structured JSON array per current OTel GenAI semconv, superseding the deprecated gen_ai.prompt) and gen_ai.output.messages on llm.completion spans, plus forge.tool.args and forge.tool.result on tool.<name> spans.

Captured values pass through a vendor-secret redactor (Anthropic, OpenAI, GitHub, AWS, Slack, RSA/EC/OpenSSH/Private keys, Telegram bot tokens) and a 4 KiB byte cap with a …[truncated:N] marker byte-identical to the audit payload-capture marker — operators grepping [truncated: across spans and audit rows see aligned output.

Library modules

forge-core, forge-skills, and forge-plugins are now importable Go modules:

import (
  "github.com/initializ/forge/forge-core/runtime"
  "github.com/initializ/forge/forge-skills/parser"
  "github.com/initializ/forge/forge-plugins/channels/slack"
)

Tagged independently via path-prefixed tags: forge-core/v0.15.0, forge-skills/v0.15.0, forge-plugins/v0.15.0. The Initializ platform and other embedders pull just the runtime, registry, or channel layers without the CLI binary.

Reference: docs/reference/library-modules.md.

CLI

  • forge auth show-token — print the runtime token at <root>/.forge/runtime.token
  • forge auth mint-token — generate, store, and print a fresh token (first-deploy bootstrap)
  • forge auth secret-yaml — print a Kubernetes Secret manifest holding the token, ready for kubectl apply -f -
  • forge build --policy <path> — override the security analyzer policy at build time
  • forge package — now also emits k8s/cronjob-<id>.yaml, k8s/internal-token-secret.yaml, k8s/scheduler-role.yaml, k8s/scheduler-rolebinding.yaml when forge.yaml schedules[] is populated

forge.yaml

  • New top-level scheduler block (backend, kubernetes.namespace, kubernetes.service_url, kubernetes.allow_dynamic, kubernetes.trigger_image, kubernetes.auth_secret_name)
  • New top-level security.policy_path for the security analyzer override

Fixed

  • forge build no longer fails the bundled code-review skill on a fresh agent (#145). The security analyzer’s trustedDomains map now includes GitHub-owned content endpoints (raw.githubusercontent.com, patch-diff.githubusercontent.com, gist.githubusercontent.com, objects.githubusercontent.com) and chatgpt.com. The env category score is capped at 25 points so multi-purpose skills declaring many config knobs aren’t penalized linearly. DefaultPolicy.MaxRiskScore raised from 75 → 90 so vetted bundled skills clear the default; operators wanting a stricter posture can lower the ceiling via security.policy_path or forge build --policy <path>.
  • forge build no longer leaks dead compiled/ artifacts into the agent image, and the Dockerfile context is tighter (#147). The compiled/skills.json and compiled/prompt.txt files generated by the old skills stage are no longer written; the integration test asserts their absence. The .dockerignore now excludes k8s/, build-manifest.json, compiled/, Dockerfile, .dockerignore, and .local-bins/. The checksums.json path mismatch the runner experienced when the WorkDir was the .forge-output/ directory is fixed by a fallback probe.
  • forge build no longer drops runtime apt-installed binaries from the final image (#149). The bins stage was unconditionally copying /usr/local/bin/ from the bins stage to the runtime stage, silently overwriting any apt packages installed in the previous layer. Replaced with per-binary COPY directives that target only the bins the agent declared in requires.bins.
  • Mask-decision evidence no longer leaks the pre-mask raw PII in the audit stream. The guardrail_check event’s fields.evidence carries the post-mask content for decision: masked (the same payload the LLM saw downstream); decision: warned / decision: blocked carry the original triggering text because the library produces no masked variant in those paths.
  • Pinned k8s.io/client-go to v0.34.1 so the Go directive stays on 1.25 — v0.36 requires Go 1.26 which broke CI compilation.

Audit-event schema

AuditSchemaVersion remains "1.0". The new top-level fields (org_id, workspace_id, entity_id, entity_type) and the new event (guardrail_check) are additive — consumers ignoring unknown keys continue to work unchanged.

Documentation

Closed issues

  • #130 — OpenTelemetry span content capture (capture_content + redact)
  • #145 — security analyzer false positives on bundled code-review skill
  • #147 — dead compiled/ artifacts and Dockerfile context bloat
  • #149 — bins stage wholesale /usr/local/bin/ copy
  • #155 — guardrail_check audit emission
  • #157 — tenancy stamping (org_id / workspace_id)
  • #159 — wire all five library gates + replace direction with gate
  • #161 — OpenTelemetry guardrail spans symmetric to audit
  • #162 — hybrid scheduler backend (file + Kubernetes) end-to-end
  • #164 — entity_id + entity_type audit stamping

Upgrading from v0.14.x

Forge v0.15.0 is backwards-compatible with v0.14.x deployments. The new audit fields (org_id, workspace_id, entity_id, entity_type, the guardrail_check event) are additive — deployments that don’t set the corresponding env vars keep emitting the pre-v0.15 JSON shape verbatim.

To opt into Kubernetes-native scheduling on an existing deployment:

  1. Add the scheduler block to forge.yaml (or rely on the auto default — Forge auto-detects the in-cluster ServiceAccount token).
  2. Re-run forge package. The new k8s/cronjob-*.yaml, k8s/internal-token-secret.yaml, k8s/scheduler-role.yaml, and k8s/scheduler-rolebinding.yaml files appear in the output directory.
  3. Populate the Secret out-of-band via forge auth secret-yaml | kubectl apply -f - (or your ExternalSecrets / Sealed Secrets pipeline).
  4. kubectl apply -k .forge-output/k8s/.

The file backend remains the default outside of clusters — no change required for forge run on a laptop or CI.

Full changelog

git log v0.14.1..v0.15.0 --oneline in this repository, or browse v0.14.1...v0.15.0 on GitHub.

View on GitHub
v0.14.1 BREAKING

Forge v0.14.1 — Bug fixes for OpenAI-compatible providers (Together.ai, OpenRouter, Groq) and session persistence

Published: 2026-06-09 · Tag: v0.14.1 · Previous release: v0.14.0 (2026-06-09, OpenTelemetry Tracing v1)

Forge v0.14.1 is a patch release focused on operators running agents against OpenAI-compatible LLM providers — Together.ai, OpenRouter, Groq, Fireworks, Anyscale, vLLM, llama.cpp’s server, and others — plus a class of session-persistence bugs that broke multi-turn channel conversations on OpenAI reasoning models (gpt-5-nano, o1, o3) and strict OpenAI-compatible gateways.

Seven bug fixes, no new features, no breaking changes. Backward-compatible upgrade from v0.14.0. 18 files changed, +1,683 / −38 lines, 7 issues closed, 7 PRs merged.

If you saw "something went wrong while processing your request, please try again" on Slack thread followups, Anthropic API returned status 401 despite configuring OpenAI, Unsupported parameter: 'max_tokens', or NetworkPolicy blocking calls to api.together.ai after forge package — this release fixes all of them.

The shipped fixes

1. Session persistence no longer poisons followup turns

Issue #131 · PR #132

Long-running Slack threads (and any session-persistent channel) succeeded on the first message and then failed every followup with "something went wrong while processing your request, please try again". The executor unconditionally wrote the LLM’s assistant message to memory before checking content. When the provider hit finish_reason: length (output token cap) and returned an assistant turn with empty content AND no tool_calls, that invalid-per-OpenAI-spec shape landed in memory. The in-loop empty-response recovery papered over it for the current task, but persistSession wrote the polluted memory to .forge/sessions/<task_id>.json. The next request recovered the bad shape and strict OpenAI-spec providers (Moonshot, hosted OpenRouter, OpenAI strict mode) returned HTTP 400.

Fix:

  • Substitute a placeholder content string ("(continuing — previous response was truncated by output token limit)") when the LLM returns the bad shape, so newly-written sessions never contain it.
  • Extend sanitizeMessages on LoadFromStore with a new stripEmptyAssistantTurns pass that rescues sessions already on disk — no manual rm <agent-dir>/.forge/sessions/<task-id>.json required after upgrade.

2. Duplicate user message at session start no longer trips strict-mode providers

Issue #143 · PR #144

Same surface symptom as #131 — "something went wrong" on Slack thread followups — different root cause. The runner pre-appended params.Message to task.History before calling Execute so SSE clients could observe the inbound message in the in-flight task. The executor’s !recovered first-interaction path then iterated task.History AND appended *msg separately, producing two consecutive identical user turns at the start of every fresh conversation. OpenAI reasoning models (gpt-5-nano, o1, o3) and strict OpenAI-compatible gateways (Together’s Kimi) reject consecutive same-role messages with HTTP 400.

Fix:

  • Strip the trailing task.History entry when it equals *msg before iterating (new a2aMessagesEqual helper).
  • Extend sanitizeMessages with a collapseConsecutiveDuplicates pass on LoadFromStore. Surgical by design — only EXACT same-role + same-content + tool-call-free pairs collapse; workflow nudges ("Your response was empty...") and tool-bearing assistant turns survive.

3. code-review skill no longer routes Anthropic-first when both API keys are set

Issue #133 · PR #134

The skill’s code-review-diff.sh and code-review-file.sh picked Anthropic whenever ANTHROPIC_API_KEY was non-empty, even when the operator’s forge.yaml pointed at an OpenAI-compatible provider (Together.ai, OpenRouter, Groq, Fireworks, Anyscale, vLLM) via OPENAI_BASE_URL and REVIEW_MODEL was clearly a non-Anthropic model. Operators with a stale ANTHROPIC_API_KEY co-resident with a live OPENAI_API_KEY in .forge/secrets.enc got Anthropic API returned status 401 and assumed the skill was broken.

Fix: new REVIEW_PROVIDER env var (values anthropic or openai) that wins always. When unset, auto-detected from REVIEW_MODEL prefix (claude-* or anthropic/* → Anthropic; anything else → OpenAI), then by sole API key, then defaults to OpenAI when both keys exist with no other signal.

The same PR also fixes a separate bug: the OPENAI_BASE_URL-as-Responses-API-toggle was exactly backwards — Together, OpenRouter, Groq, Fireworks, Anyscale all implement /chat/completions only, not OpenAI’s proprietary /responses. Setting OPENAI_BASE_URL=https://api.together.ai/v1 silently POSTed to https://api.together.ai/v1/responses → 404. Decoupled into a separate OPENAI_USE_RESPONSES_API=1 opt-in for the Codex/OAuth flow.

4. code-review skill uses max_completion_tokens (not deprecated max_tokens)

Issue #141 · PR #142

OpenAI deprecated max_tokens in favor of max_completion_tokens for Chat Completions. Reasoning models (o1, o1-preview, o3, gpt-5) and strict OpenAI-compatible providers (Together.ai’s Kimi-K2.6 series, Moonshot) reject the legacy field with HTTP 400:

"Unsupported parameter: 'max_tokens' is not supported with this model. Use 'max_completion_tokens' instead."

Fix: one-token edit per script. The Anthropic branch keeps max_tokens (correct field for Anthropic’s API). The Responses API branch uses no max-tokens field (auto-sizes).

5. Skill subprocesses inherit OPENAI_BASE_URL and the other standard SDK base-URL env vars

Issue #137 · PR #138

SkillCommandExecutor built a whitelist-only env for skill subprocesses where OPENAI_ORG_ID was always-passed but the standard SDK base-URL pointers (OPENAI_BASE_URL, ANTHROPIC_BASE_URL, OLLAMA_BASE_URL, GEMINI_BASE_URL) were not — unless each SKILL.md author remembered to declare each variable in env.optional. Every LLM-calling skill that forgot silently broke for OpenAI-compatible deployments. The operator’s main agent loop hit Together.ai correctly; the same agent’s skill subprocess hit api.openai.com with the Together.ai key and 401d.

Fix: four standard SDK variables special-cased alongside OPENAI_ORG_ID. Every skill that uses industry-standard env conventions just works — present, future, in-tree, and third-party.

6. CLI wizard recognizes encrypted one_of keys and stops writing first-key placeholders

Issue #135 · PR #136

forge skills add did not validate one_of groups at all — operators got no confirmation that their encrypted key (e.g. OPENAI_API_KEY in .forge/secrets.enc) was detected. forge init’s fallback wrote opts.EnvVars[OneOfEnv[0]] = "" (the first key in the list — ANTHROPIC_API_KEY for code-review), producing an empty .env line that misled operators into thinking they needed an Anthropic key when they had configured OpenAI.

Fix:

  • forge skills add now mirrors RequiredEnv’s three-source check for one_of groups: os.Getenv / .env / loadSecretPlaceholders. Output now shows OPENAI_API_KEY (secrets) — ok regardless of list order.
  • forge init no longer pre-writes a placeholder. The runtime resolver already surfaces missing one_of groups clearly at forge run if neither key is set.
  • .env writer no longer emits empty placeholder lines for every one_of member — only keys with non-empty values get written.

7. forge package and forge run auto-add the LLM provider’s custom base URL to the egress allowlist

Issue #139 · PR #140

Operators configuring an OpenAI-compatible provider via OPENAI_BASE_URL=https://api.together.ai/v1 shipped a NetworkPolicy that blocked the provider’s hostname — deployed agents 401d or timed out depending on which side noticed first. Same trap Phase 6 of OTel Tracing v1 fixed for the OTLP collector (#107), but for the LLM provider.

Fix: added ModelRef.BaseURL and ModelFallback.BaseURL fields to the forge.yaml schema, plus two new helpers:

  • security.LLMProviderDomains(cfg) — extracts hostnames from cfg.Model.BaseURL + cfg.Model.Fallbacks[].BaseURL. Cfg-driven; used by both build and runtime.
  • security.LLMProviderEnvDomains(envVars) — extracts hostnames from the four canonical SDK base-URL env vars in the resolved env. Runtime safety net for deployments that haven’t migrated to the schema field.

Both wired into egress_stage.go and runner.go alongside the existing AuthDomains / MCPDomains / OTelDomain merges. Operators going forward declare the URL once in forge.yaml:

model:
  provider: openai
  name: moonshotai/Kimi-K2.6
  base_url: https://api.together.ai/v1

…and forge build && forge package && kubectl apply produces a NetworkPolicy that admits api.together.ai automatically.

Compatibility-restored deployment matrix

SetupPre-v0.14.1Post-v0.14.1
Agent on Together.ai / OpenRouter / Groq with OPENAI_BASE_URL setSkill subprocess 401’d against OpenAI✓ hits the configured provider
gpt-5-nano or other OpenAI reasoning modelFollowup turns 400’d on duplicate user message✓ multi-turn conversations work
code-review skill with stale Anthropic key in .forge/secrets.encProvider mis-routed to Anthropic, 401✓ explicit REVIEW_PROVIDER override
code-review against Moonshot / Kimi via Together.aimax_tokens rejected, HTTP 400max_completion_tokens accepted
forge package with OPENAI_BASE_URL=together.aiNetworkPolicy blocked the provider✓ host auto-added
Long-running Slack thread where LLM hit finish_reason: lengthEmpty-assistant-turn poison broke followups✓ placeholder substitution + LoadFromStore sanitization
forge skills add with OPENAI_API_KEY encryptedWizard prompted for missing key✓ shows OPENAI_API_KEY (secrets) — ok

Upgrade guide

Backward-compatible. No forge.yaml schema break. No code changes required for existing agents.

Three small things changed defaults; if you depended on them, you can override:

  1. code-review skill provider selection — previously preferred Anthropic when both keys were set. Now prefers OpenAI. To preserve the old behavior: add REVIEW_PROVIDER=anthropic to your env.

  2. code-review OPENAI_BASE_URL no longer triggers the Responses API. Previously the variable was both a base URL pointer AND a Responses API toggle. Now decoupled: setting OPENAI_BASE_URL always uses /chat/completions. If you relied on the Responses API: add OPENAI_USE_RESPONSES_API=1.

  3. forge init no longer pre-writes empty one_of placeholders to .env. If your CI scripts grep .env for ANTHROPIC_API_KEY= or OPENAI_API_KEY= to detect “user hasn’t filled this in,” migrate to checking forge skills add’s install output instead — it now prints the satisfaction state for one_of groups.

Sessions corrupted by issues #131 or #143 on a pre-v0.14.1 build do not require a rm. Both fixes ship defense-in-depth on LoadFromStore that sanitizes the bad shape on load. Just upgrade and send the next message in the affected channel thread.

Installation

Download a pre-built binary from the assets attached below: forge-Darwin-arm64.tar.gz, forge-Darwin-x86_64.tar.gz, forge-Linux-arm64.tar.gz, forge-Linux-x86_64.tar.gz, forge-Windows-arm64.zip, forge-Windows-x86_64.zip. Verify with checksums.txt.

Container image: ghcr.io/initializ/forge:v0.14.1.

Homebrew: brew upgrade initializ/tap/forge.

From source:

git clone --branch v0.14.1 https://github.com/initializ/forge
cd forge
go install ./forge-cli/cmd/forge

What’s new since v0.14.0 — per-PR changelog

  • #132 fix(runtime): stop persisting empty assistant turns on finish_reason=length (closes #131)
  • #134 fix(skills/code-review): provider routing + OpenAI-compatible endpoint (closes #133)
  • #136 fix(skills): one_of env validation visibility + stop writing first-key placeholder (closes #135)
  • #138 fix(skills): always pass OPENAI_BASE_URL / ANTHROPIC_BASE_URL / OLLAMA_BASE_URL / GEMINI_BASE_URL to skill subprocesses (closes #137)
  • #140 fix(security): auto-add LLM provider base URL to egress allowlist at build + runtime (closes #139)
  • #142 fix(skills/code-review): max_completion_tokens for OpenAI Chat Completions (closes #141)
  • #144 fix(runtime): stop duplicating the inbound user message at the start of every fresh session (closes #143)

Full per-PR detail in CHANGELOG.md.

Compatibility

  • Go: 1.25+
  • A2A: 0.3.0
  • Forge agent schema: unchanged from v0.14.0 (additive only — new ModelRef.BaseURL and ModelFallback.BaseURL fields)
  • LLM providers verified: OpenAI (Chat Completions + Responses API), Anthropic, Ollama, Gemini, Together.ai, OpenRouter, Groq, Fireworks, Anyscale, Moonshot (Kimi-K2.6 series), Bedrock proxy patterns
  • Container image: ghcr.io/initializ/forge:v0.14.1

Documentation

Acknowledgements

All seven fixes landed within hours of being reported by operators running real production-shaped agents on Slack-channel-backed deployments. The session-poison bug (#131) was caught on a long-running PR-review thread; the duplicate-user bug (#143) was caught on a gpt-5-nano agent; the egress / wizard / subprocess-env bugs (#135, #137, #139) were caught in sequence on the same Together.ai-backed deployment, with each fix exposing the next layer. The 7-PR plan landed with the same discipline that delivered OpenTelemetry Tracing v1 in v0.14.0.

View on GitHub
v0.14.0

Forge v0.14.0 — End-to-end OpenTelemetry distributed tracing for AI agents

Published: 2026-06-09 · Tag: v0.14.0 · Previous release: v0.13.0 (2026-06-07)

Forge v0.14.0 ships OpenTelemetry Tracing v1 — end-to-end distributed tracing for AI agents covering A2A dispatch, the executor loop, every LLM completion, every tool call, and every outbound HTTP request. Traces export over OTLP to Tempo, Jaeger, Honeycomb, Datadog, Grafana Cloud, or any other OTLP-compatible backend. Multi-hop A2A flows display as one connected trace. Audit events carry the active span’s trace_id and span_id so operators pivot from compliance row to performance trace with a single copy-paste. The OTLP collector hostname auto-injects into the build’s egress allowlist so Kubernetes deployments need no second NetworkPolicy edit.

This release closes initiative #108 across seven phases (one PR per phase, #122#128), plus a bug-2 fix for A2A tasks/send message validation and the docs sync across the operator-facing surface.

45 files changed, +5,494 / −42 lines, 8 issues closed, 10 PRs merged since v0.13.0. Full per-PR detail in CHANGELOG.md.

OpenTelemetry tracing — what it covers

When observability.tracing.enabled: true is set in forge.yaml, Forge produces one OTLP span tree per inbound A2A request:

a2a.<method>                          [SpanKindServer; dispatcher]
└── agent.execute                     [outer loop; root for the task]
    ├── llm.completion (× N turns)    [per LLM provider call — Anthropic / OpenAI / Ollama]
    │   └── http.client (× outbound)  [auto via otelhttp on egress transport]
    └── tool.<tool_name> (× M calls)  [per tool invocation]
        └── http.client (if HTTP)

Every llm.completion carries OpenTelemetry GenAI semantic-convention attributes: gen_ai.system, gen_ai.request.model, gen_ai.response.model, gen_ai.usage.input_tokens, gen_ai.usage.output_tokens, gen_ai.response.finish_reasons. Cost-attribution dashboards in any OTLP backend (Tempo + Grafana, Jaeger, Honeycomb, Datadog APM, Grafana Cloud) light up immediately.

Forge-specific attributes use the forge.* namespace: forge.task.id, forge.task.final_state (completed / failed / canceled), forge.tool.name, forge.workflow.id / .stage.id / .step.id (from FWS-2 X-Workflow-* headers), forge.correlation_id, forge.loop.iteration.

How to enable

In forge.yaml:

observability:
  tracing:
    enabled: true
    endpoint: https://otel-collector.monitoring.svc.cluster.local:4318/v1/traces
    sampler: parentbased_always_on

Or via standard OTEL_* environment variables:

export OTEL_EXPORTER_OTLP_TRACES_ENDPOINT=http://localhost:4318/v1/traces
export OTEL_TRACES_SAMPLER=always_on
forge run --otel-enabled

Or per-command via nine new --otel-* CLI flags on forge run and forge serve start:

forge run --otel-enabled \
  --otel-endpoint http://localhost:4318/v1/traces \
  --otel-sampler always_on \
  --otel-service-name my-agent-staging

Resolution precedence: CLI flags > OTEL_* env vars > observability.tracing in forge.yaml > defaults. A set-but-empty env var does not wipe a non-empty yaml value (absence-of-value is “no override,” not “unset”).

All 10 standard OTEL_* SDK env vars are honored: OTEL_SDK_DISABLED, OTEL_EXPORTER_OTLP_TRACES_ENDPOINT, OTEL_EXPORTER_OTLP_ENDPOINT, OTEL_EXPORTER_OTLP_PROTOCOL, OTEL_EXPORTER_OTLP_HEADERS, OTEL_EXPORTER_OTLP_TIMEOUT, OTEL_SERVICE_NAME, OTEL_RESOURCE_ATTRIBUTES, OTEL_TRACES_SAMPLER, OTEL_TRACES_SAMPLER_ARG.

End-to-end propagation across multi-hop A2A flows

Forge installs the W3C tracecontext + baggage composite propagator on the OpenTelemetry global at startup. The JSON-RPC dispatcher extracts inbound traceparent headers before opening its own span, and outbound HTTP through the egress-enforced transport re-injects traceparent automatically via otelhttp. Multi-hop flows — orchestrator → agent A → agent B → external API — show as one connected trace in the backend:

orchestrator
    │  traceparent: 00-T-S1-01

┌───────────────┐
│  forge agent  │  span_id=S2, parent=S1   (a2a.tasks/send)
│      ▼        │  span_id=S3, parent=S2   (agent.execute)
│      ▼        │  span_id=S4, parent=S3   (llm.completion)
└──────│────────┘
       ▼  traceparent: 00-T-S2-01  ← otelhttp re-injects on outbound
                                      via the egress-enforced transport
┌───────────────┐
│  downstream   │  span_id=S5, parent=S2   (a2a.tasks/send)
│  forge agent  │
└───────────────┘

All spans share trace_id = T and chain by parent_span_id. The operator sees one flame graph across every hop.

baggage (the other half of the composite propagator) flows through to the handler context so application-level identifiers — tenant_id, user_id, A/B-test bucket — travel with the trace.

Forge has shipped a structured audit pipeline since FWS-1 (NDJSON to stderr + opt-in Unix Domain Socket sink). As of v0.14.0, every audit event emitted from a request-scoped context carries the active span’s trace_id and span_id:

{
  "event": "llm_call",
  "task_id": "t-1234",
  "trace_id": "4a8f95a0e1bedda42c9dd5350fb3b33a",
  "span_id":  "ad8b2c91e44f0a72",
  "model": "claude-sonnet-4",
  "input_tokens": 1240,
  "output_tokens": 187,
  "duration_ms": 2310
}

Two new pivot directions for operators:

  • Audit row → trace: paste the trace_id into Tempo / Jaeger / Honeycomb to land on the matching trace; paste the span_id to jump directly to the llm.completion child carrying the matching gen_ai.usage.* tokens.
  • Trace → audit row: copy the trace_id from a trace browser, grep the audit log for the corresponding row, get the FWS-8 payload metadata the trace doesn’t carry.

Format: lowercase hex matching W3C traceparent semantics — 32-char (128-bit) trace_id, 16-char (64-bit) span_id.

Backward compatible by construction: both fields use omitempty. When tracing is disabled (the default), audit JSON is byte-identical to pre-v0.14.0. The audit schema version ("1.0") is not bumped — the documented policy treats adding optional fields as schema-compatible.

Egress-enforced OTLP transport + build-time auto-merge

The OTLP HTTP exporter rides through the same egress enforcer every other in-process Forge HTTP client uses. The operator’s egress allowlist bounds where Forge can send spans — a misconfigured collector URL cannot exfiltrate span content to an unapproved destination. The post-DNS IP guard applies to OTLP too.

No second egress edit needed. forge package and forge run both extract the OTLP endpoint hostname and auto-inject it into egress_allowlist.json. The Kubernetes NetworkPolicy generated by forge package therefore admits OTLP traffic automatically:

# forge.yaml — this is sufficient. The collector is added to the allowlist automatically.
egress:
  mode: allowlist
  allowed_domains:
    - api.anthropic.com    # operator-declared
observability:
  tracing:
    enabled: true
    endpoint: https://otel-collector.monitoring.svc.cluster.local:4318/v1/traces

# generated egress_allowlist.json:
# all_domains = [api.anthropic.com, otel-collector.monitoring.svc.cluster.local]

Disabled tracing produces no allowlist entry — turning tracing off in yaml does not leave a stale entry punched through the NetworkPolicy.

Tracing-off safety: nothing crashes the agent

Off by default per the initiative ruling. When tracing is off, enabled: false, or endpoint is empty:

  • coreruntime.Tracer() returns a non-recording no-op tracer; spans cost near zero (one interface dispatch).
  • EmitFromContext does not stamp trace_id / span_id; audit JSON is byte-identical to pre-Phase-4.
  • OTelDomain returns nil; no entry in egress_allowlist.json.
  • observability.WrapHTTPTransport is a near pass-through.

Telemetry failures never crash the agent. A misconfigured endpoint, a malformed traceparent header, an unreachable collector — every failure mode falls through to the no-op tracer with a warning in the ops log. The CLI resolver is the single place that fails loudly on bad config at startup.

Also in this release

A2A message validation at tasks/send entry points (#119 / PR #121). The most common failure was a client sending the pre-A2A-0.3.0 "type": "text" discriminator instead of the spec-correct "kind": "text"encoding/json silently dropped the unknown field, the executor produced a confused reply (“It looks like your message didn’t come through”). Now rejected at the dispatcher with a clear InvalidParams error naming the spec divergence: “parts[0]: part kind is required (A2A 0.3.0); got empty kind with non-empty content — did you send \"type\" instead of \"kind\"? \"type\" is from the pre-0.3.0 dialect and is silently ignored by the decoder.” Sentinel errors (ErrPartKindMissing, ErrPartKindUnknown, ErrMessageRoleMissing, ErrMessagePartsEmpty) exposed for callers that branch on the cause.

FWS-3 X-Forge-* response headers stamped on JSON-RPC tasks/send (PR #120). Closes a bug where the workflow correlation and usage headers were present on the REST endpoint but missing from the JSON-RPC tasks/send path.

Documentation sync across the operator-facing surface (PR #129). New comprehensive reference at docs/core-concepts/observability-tracing.md. Updates to forge.yaml schema, CLI reference, environment-variables reference, audit-logging schema, egress-control, and deployment monitoring. The .claude/skills/forge.md knowledge skill includes a new § 12.9 Observability subsection covering all seven phases.

Per-phase issue + PR mapping

PhaseIssuePRWhat shipped
0#101#122Tracer seam in forge-core/runtime/tracing.go (no-op default) + composite W3C tracecontext + baggage propagator installed on OTel global
1#102#123Real OTLP SDK tracer provider in forge-core/observability — HTTP/protobuf + gRPC, batch processor, semconv 1.26.0 resource
2#103#124Config resolver (yaml < env < flags), nine --otel-* CLI flags, runner wiring
3#104#125Span instrumentation across A2A dispatcher, executor loop, LLM provider calls, tool invocations, outbound HTTP
4#105#126Audit ↔ trace cross-link: trace_id + span_id on every audit event via EmitFromContext
5#106#127Inbound traceparent extraction at the JSON-RPC dispatcher boundary — multi-hop A2A flows form one trace
6#107#128Build-time + runtime egress auto-merge of the OTLP collector hostname into egress_allowlist.json

Upgrade guide

Backward-compatible. Default behavior is unchanged: tracing is off unless observability.tracing.enabled: true is set in forge.yaml, OTEL_SDK_DISABLED=false is set in env with an endpoint, or --otel-enabled is passed on the command line.

If you have an OTel collector deployed, the minimum config is:

# forge.yaml
observability:
  tracing:
    enabled: true
    endpoint: <YOUR_OTLP_HTTP_ENDPOINT>

Then forge build && forge package && kubectl apply -f ... — the generated NetworkPolicy will admit OTLP traffic automatically. No second egress edit needed. No code changes required to existing agents.

If your collector listens on localhost (e.g. via kubectl port-forward) during local development, add egress.allow_private_ips: true to forge.yaml so the egress enforcer’s post-DNS IP guard admits the loopback address.

Documentation

Installation

Download a pre-built binary from the assets attached below (forge-Darwin-arm64.tar.gz, forge-Darwin-x86_64.tar.gz, forge-Linux-arm64.tar.gz, forge-Linux-x86_64.tar.gz, forge-Windows-arm64.zip, forge-Windows-x86_64.zip). Verify with checksums.txt.

Container image: ghcr.io/initializ/forge:v0.14.0.

Homebrew: brew install initializ/tap/forge.

From source:

git clone --branch v0.14.0 https://github.com/initializ/forge
cd forge
go install ./forge-cli/cmd/forge

Compatibility

  • Go: 1.25+
  • A2A: 0.3.0
  • OpenTelemetry SDK: 1.44.0 (semconv 1.26.0)
  • OTLP: HTTP/protobuf (recommended) and gRPC, against any OTLP-compatible backend including Grafana Tempo, Jaeger, Honeycomb, Datadog APM, Grafana Cloud, New Relic, and the OpenTelemetry Collector

Acknowledgements

OpenTelemetry Tracing v1 was shipped as a strict 7-PR plan across two days, one PR per phase. Implementation followed an audit-pipeline mirror pattern: every design decision that produced a usable audit signal in earlier work (FWS-1 through FWS-10) was applied to the new tracing pipeline. Failure-domain isolation, backward-compatible schema additions, off-by-default posture, and the egress-enforced transport contract carry forward verbatim.

View on GitHub
v0.13.0

Forge v0.13.0 — A2A 0.3.0 conformance, audit pipeline hardening, and configurable platform policy

Published: 2026-06-07

Forge v0.13.0 is a feature release covering every commit merged to main since v0.12.0 on 2026-05-25. 119 files changed, +16,324 / −566 lines, 13 issues closed, 10 numbered workstreams (FWS-1 through FWS-10) plus the new Claude knowledge skills.

If you operate Forge in production, this release lands the verifiable version of the security and observability claims the project has made since v0.10: an A2A 0.3.0 spec-conformant Agent Card, a documented schema-versioned audit contract with monotonic sequence numbers and a metadata-only invariant pinned by regression tests, a dedicated Unix Domain Socket sink for audit export, a three-layer platform policy (system / user / workspace) with first-match-wins attribution, configurable per-IP rate limiting with sensible orchestration-friendly defaults, and a one-line stream split so SIEM pipelines can route ops logs and audit NDJSON without parsing payloads.

The full per-PR detail lives in CHANGELOG.md.


Highlights

  • A2A 0.3.0 spec-conformant Agent Card at the canonical /.well-known/agent-card.json path. Every required field populated from forge.yaml and SKILL.md. Legacy /.well-known/agent.json alias preserved with a Deprecation: true header per RFC 8594.
  • Hardened audit emission: every event carries schema_version: "1.0" and a monotonic per-invocation seq so consumers detect gaps and reordering by grouping (correlation_id, task_id). The default audit posture is metadata-only (token counts, sizes, durations — never raw prompt or completion text or tool args/results), and a TestNoPayloadByDefault_LLMCall regression test pins the invariant.
  • Audit event export via Unix Domain Socket sink (preferred) or localhost HTTP fallback. Fire-and-forget, 50ms per-event timeout, exponential reconnect backoff, periodic audit_export_status event every 60s carrying per-sink health.
  • Three-layer platform policy. The single-layer FORGE_PLATFORM_POLICY env from v0.12 is now joined by a system layer (/etc/forge/policy.yaml, sysadmin) and a user layer (~/.forge/policy.yaml, developer). Deny lists union; max-bounds use smallest non-zero; audit attribution credits the first layer to deny.
  • Channel deny is first-class. A new denied_channels field in the platform policy schema skips named adapters at startup with a channel_denied_by_policy audit event. forge channel disable / enable edit the user layer; --system retargets to the system layer.
  • Per-IP rate limit is configurable end-to-end via server.rate_limit: in forge.yaml, matching --rate-limit-* CLI flags, and FORGE_RATE_LIMIT_* env vars. Resolution order: CLI > env > yaml > defaults. Defaults bumped for orchestrated workloads — write side now 60/min sustained with burst 20 (was 10/min, burst 3). tasks/cancel is exempt from the write bucket by default so a cost-ceiling cancel burst never throttles the recovery mechanism.
  • Workflow correlationX-Workflow-ID, X-Workflow-Stage-ID, X-Workflow-Step-ID, and X-Invocation-Caller headers are extracted on every request and stamped on every audit event for multi-agent flow tracing.
  • Token usage in audit and response headers. Every llm_call event carries input_tokens / output_tokens / duration_ms / model / provider; every A2A response carries X-Forge-Tokens-In/Out, X-Forge-Duration-Ms, X-Forge-Model, X-Forge-Provider for cost-enforcement pipelines.
  • tasks/cancel actually cancels. The JSON-RPC tasks/cancel method now signals an in-flight invocation via context.CancelCauseFunc with a typed reason. The agent loop honors cancellation at iteration and tool-call boundaries. An invocation_cancelled audit event closes the cancelled invocation with the reason + partial token totals.
  • Ops logs to stdout, audit to stderr. Container log collectors and SIEM pipelines can now split the two at the stream level — no payload parsing.
  • New Claude-side reference: .claude/skills/forge.md ships a single self-contained knowledge file for Forge, and .claude/skills/forge-skill-builder.md ports the Web UI Skill Builder’s prompt for use in any Claude chat. The sync-docs workflow keeps both current with the code.

What’s new

1. A2A 0.3.0 Agent Card conformance (FWS-1, #85)

Forge’s Agent Card now conforms to the A2A 0.3.0 spec end-to-end: canonical path, required-field shape, security schemes derived from the configured auth chain, and SKILL.md skills bridged into the published card at both forge build and forge run time.

WhatWhere
Canonical pathGET /.well-known/agent-card.json
Legacy aliasGET /.well-known/agent.json — same body + Deprecation: true + Link: rel=successor-version per RFC 8594. Removable one release after this ships.
Required fieldsname, description, url, version (from forge.yaml), protocolVersion: "0.3.0", defaultInputModes, defaultOutputModes, skills[], capabilities, securitySchemes, security
Skills mappingSKILL.md frontmatter (name, description, category, tags) → A2A AgentSkill objects with deterministic ID-sorted output so card bytes are stable across restarts
Security schemesDerived from auth.providersoidc/azure_adopenIdConnect with discovery URL; static_token/http_verifierhttp+bearer; gcp_iapapiKey in header; aws_sigv4http+bearer with bearerFormat: "forge-aws-v1"
New audit eventagent_card_published at startup + hot-reload, carrying name, version, protocol_version, url, skill_count, capabilities, security_schemes, card_size_bytes, card_sha256 — consumers detect config drift across deploys

See docs/reference/a2a-agent-card.md.

2. Workflow correlation ID threading (FWS-2, #86)

Orchestrators (initializ Command, custom controllers, GitOps tooling) that fire multi-agent workflows now get end-to-end audit traceability without any per-agent code change. Inbound requests carrying X-Workflow-ID / X-Workflow-Stage-ID / X-Workflow-Step-ID / X-Invocation-Caller headers have those values extracted into the request context, threaded through the agent loop, and stamped on every audit event the invocation produces — invocation_complete, llm_call, tool_exec, etc.

The emitted JSON is byte-for-byte identical to the pre-FWS-2 shape for direct (non-orchestrated) calls, so consumers reading the v0.12 audit stream continue to work unchanged.

See docs/security/workflow-correlation.md.

3. Token usage + execution duration in audit (FWS-3, #87)

Every llm_call audit event now carries input_tokens, output_tokens, model, provider, duration_ms, and request_id captured directly from provider response metadata (Anthropic, OpenAI, Ollama via the OpenAI-compatible path, OpenAI Responses). Field naming aligns with OTel GenAI semantic conventions (gen_ai.usage.input_tokens / gen_ai.usage.output_tokens) so audit consumers correlate to OTel traces without a translation table.

When a provider returns no usage (some self-hosted Ollama setups), the event flags tokens_unavailable: true rather than silent zeros.

Each tool_exec event gains duration_ms plus structured arg-shape metadata (args_size, result_size). Raw arg values are not emitted (that’s FWS-8’s payload-stripping invariant, not FWS-3’s).

A new invocation_complete audit event closes every A2A invocation with the wall-clock duration and aggregated input_tokens_total / output_tokens_total / llm_call_count. Response headers X-Forge-Tokens-In, X-Forge-Tokens-Out, X-Forge-Duration-Ms, X-Forge-Model, X-Forge-Provider carry the same totals so cost-enforcement layers act without parsing the body.

4. Cancellation signal handling (FWS-4, #88)

The A2A tasks/cancel JSON-RPC method now actually cancels in-flight invocations instead of merely flipping the stored task state. A per-Runner CancellationRegistry tracks every active invocation by task ID; the cancel handler signals the registered context.CancelCauseFunc with a typed reason (workflow_failure / cost_limit_exceeded / timeout / external_signal).

The agent loop honors cancellation at the iteration boundary and between tool calls within an iteration, so cancellation latency is bounded by the current LLM call or tool exec.

A new invocation_cancelled audit event closes every cancelled invocation with the classified reason, duration_ms up to cancellation, and partial token totals consumed before the signal. The A2A response carries state canceled plus a cancelled: <reason> message so the orchestrator can react. Cancel-after-complete is idempotent.

5. Three-layer platform policy + channel scope (FWS-5 / FWS-6, #89 + #90)

The single-layer FORGE_PLATFORM_POLICY env from v0.12.0 is now joined by two more layers so different operators with different trust contexts can each express their bounds independently.

LayerPathSet by
system/etc/forge/policy.yaml (override FORGE_SYSTEM_POLICY)Sysadmin (MDM, corporate image, Ansible)
user~/.forge/policy.yamlDeveloper (via forge channel disable or Web UI chip)
workspacepath at FORGE_PLATFORM_POLICYOperator (Initializ Command, GitOps tooling); forge package pre-wires this env into generated Kubernetes manifests

Resolution semantics:

  • Deny lists (denied_egress_domains, denied_tools, forbidden_models, denied_channels) → union across layers
  • Max bounds (max_egress_allowlist_size, max_tool_count) → smallest non-zero value wins
  • Audit attributionfirst layer to deny in load order (system → user → workspace) takes credit, so operators grepping layer=system see every sysadmin-enforced violation without false positives from per-user overrides

Channel deny is now first-class: denied_channels in any layer skips the named adapter at startup with a channel_denied_by_policy audit event. The runner continues with the remaining channels — channel skip is non-fatal, unlike egress / tool / model violations which abort startup.

CLI:

forge channel disable slack             # edits ~/.forge/policy.yaml (user layer)
forge channel disable slack --system    # edits /etc/forge/policy.yaml (system layer; warns when not root)
forge channel enable slack              # removes from the user layer

The Web UI surfaces all three layers via new GET/PUT /api/user-policy endpoints. The agent card renders denied channels as locked / dimmed chips with a tooltip naming the enforcing layer.

See docs/security/platform-policy.md and examples/platform-policy.yaml.

6. Audit event export — UDS sink + HTTP fallback (FWS-7, #95)

Audit events can now be exported to a local Unix Domain Socket (preferred) or localhost HTTP endpoint in addition to the existing NDJSON-to-stderr stream. An in-pod sidecar (the initializ platform receiver or a customer SIEM) can consume audit at low latency while stderr stays as the safety-net fallback.

Configure via:

FlagEnvPurpose
--audit-socketFORGE_AUDIT_SOCKETUnix Domain Socket path (preferred)
--audit-http-endpointFORGE_AUDIT_HTTP_ENDPOINTlocalhost HTTP POST endpoint (fallback)
--audit-write-timeoutFORGE_AUDIT_WRITE_TIMEOUTper-event timeout (default 50ms)

Behavior:

  • Lazy connect — the socket need not exist at agent startup; the first emit dials. Sidecar deploys that come up later pick up future events without restarting the agent.
  • Per-event timeout — 50ms default. Beyond that the event drops and increments drops_timeout. A slow sidecar cannot back-pressure the agent.
  • Exponential backoff between failed dials — 100ms → 200ms → … → 5s cap. During backoff, writes drop without dialing.
  • No buffering — the sink is fire-and-forget; buffering is the sidecar’s job.
  • Byte parity — events leaving the export sink are byte-identical to events leaving stderr. No sink transforms the payload.

A periodic audit_export_status event fires every 60s carrying per-sink writes_ok, drops_timeout, drops_dial, and connected counters so operators tail the audit stream itself to confirm export health.

See docs/security/audit-logging.md § Audit Event Export.

7. Hardened audit emission — schema, sequence, payload (FWS-8, #91)

The audit event schema is now a stable, versioned contract.

  • Every event carries schema_version: "1.0". The schema is additive-by-default; the version bumps only on removals or semantic changes.
  • Every event emitted on behalf of an A2A invocation carries a monotonic seq field starting at 1. Consumers detect gaps (lost events) and reordering by grouping (correlation_id, task_id). Startup events (policy_loaded, agent_card_published, audit_export_status) omit seq (no invocation scope).
  • The default audit posture stays metadata-only — token counts, sizes, durations, tool names. No prompt text, no completion text, no raw tool args / results. A TestNoPayloadByDefault_LLMCall regression test pins the invariant — any future caller that smuggles raw user content into a default event fails the test.
  • Customers who need raw payloads (debugging incidents, supervised-learning corpora, compliance replay) opt in field by field via AuditPayloadCapture on the runner config: LLMMessages / LLMResponse / ToolArgs / ToolResult. Captured strings are truncated to a per-field byte cap (default 16 KiB) with a …[truncated:N] marker so a runaway prompt cannot bloat one audit event.

Audit-event signing is deferred per the issue’s architectural recommendation — sequence numbers cover gap detection in the meantime, and schema_version gives signing a stable place to land when added.

See docs/security/audit-logging.md § Schema contract.

8. Ops logger output to stdout (FWS-9, #100)

forge run / forge serve use the OS streams as a stream-level audit-vs-ops split so container log collectors and SIEM pipelines can route the two concerns separately without parsing any payload.

StreamCarriesConsumer
stdoutOps logs — startup banner, request lines, runtime errors via r.logger.Info/Warn/Error (JSONLogger)Container log collector / local debugging
stderrAudit NDJSON — every event constant. After FWS-7, also lands on the dedicated UDS / HTTP sink in parallel (stderr stays as the safety-net fallback)SIEM pipeline today / FWS-7 sink primary

Operator migration: if you previously captured ops logs via forge run 2> ops.log, switch to forge run > ops.log (and 2> audit.log for audit). Container deployments that capture both streams via the runtime’s standard log collector are unaffected.

Interactive CLI commands (forge init, forge build, forge channel) keep writing user-facing warnings + errors to stderr — those are UX messages, not server ops logs, and the stream-split policy doesn’t apply.

9. Rate limit configurability + cancel exemption (FWS-10, #110)

The per-IP A2A rate limiter from #31 is now configurable end-to-end via a new top-level server.rate_limit: block in forge.yaml, matching --rate-limit-* CLI flags, and FORGE_RATE_LIMIT_* env vars.

FieldNew defaultPrevious
read_rps1.0 (60/min)unchanged
read_burst10unchanged
write_rps1.0 (60/min)10/60 ≈ 10/min
write_burst203
cancel_exempttrue(didn’t exist)

Why the bump. The old defaults predated parallel workflow execution and cron bursts — a 10-step parallel stage was serialized after the 3rd dispatch. The new 60/min sustained + burst 20 absorbs orchestrator dispatch without throttling and still bounds anonymous public-internet DoS at 1 task/sec sustained.

Why cancel exemption. The cost-ceiling cancel-burst case (orchestrator firing N parallel tasks/cancel when a workflow budget trips) was hitting -32603: rate limit exceeded at exactly the moment cancellation matters most. DoS via cancel-spam is naturally bounded by the registry’s O(1) unknown-task lookup, so exempting cancel from the write bucket is safe by default. Configurable via cancel_exempt: false for stricter threat models.

Resolution per field is CLI > env > yaml > defaults.

See docs/reference/forge-yaml-schema.md § server.rate_limit.

10. Workspace-level Skill Builder LLM config (#92)

The Web UI Skill Builder now resolves its LLM from a workspace-level .forge/ui.yaml (with ~/.forge/ui.yaml as fallback), decoupled from any agent’s runtime model. Operator picks the model verbatim — no hardcoded “upgrade” to gpt-4.1 or claude-opus-4-6. Credentials are threaded as request-scoped values; the pre-#92 os.Setenv leak that caused cross-agent credential stomping when switching agents in the UI is removed.

See docs/ui/skill-builder-llm.md.

11. Claude knowledge skills (PR #118)

Two new Markdown files under .claude/skills/ make Forge knowledge loadable into any Claude conversation:

  • .claude/skills/forge.md — single comprehensive knowledge skill. Architecture, security model, A2A protocol, CLI surface, forge.yaml schema, audit pipeline, how to build agents + skills, FWS-1–FWS-10 recap, docs map, recipes. ~600 lines, table of contents, every claim cross-links to the canonical doc.
  • .claude/skills/forge-skill-builder.md — byte-identical port of the Web UI Skill Builder’s system prompt (forge-ui/skill_builder_context.go:skillBuilderSystemPrompt). Author a valid SKILL.md in any Claude chat without running forge ui.

The sync-docs workflow keeps both current with the code; the byte-identical invariant for the skill-builder file is documented as a rule in .claude/commands/sync-docs.md.


New audit events

Seven new event constants land in this release. All carry the FWS-8 schema_version and seq fields when emitted in an invocation scope.

EventWhen
agent_card_publishedAgent Card finalized at startup + hot-reload. card_size_bytes, card_sha256 for drift detection. (FWS-1)
invocation_completeA2A invocation closed; duration_ms, token totals, llm_call_count. (FWS-3)
llm_call_cancelledStreaming LLM call aborted mid-flight; partial usage counts. (FWS-3 / FWS-4)
invocation_cancelledA2A invocation cancelled via tasks/cancel; classified reason + partial token totals. (FWS-4)
policy_loadedOne per non-empty platform-policy layer at startup; layer, source, per-list size counters. (FWS-5 / FWS-6)
policy_violation_at_build_timeforge.yaml conflicts with a policy layer at startup. violation_kind, offending_value, forge_yaml_field, layer, source. (FWS-5 / FWS-6)
channel_denied_by_policyChannel adapter skipped at startup. channel, layer, source. (FWS-6)
audit_export_statusEvery 60s when an export sink is configured. Per-sink writes_ok, drops_timeout, drops_dial, connected. (FWS-7)

See the full event-type table in docs/security/audit-logging.md § Event Types.


New CLI flags and env vars

Flag (on forge run / forge serve start)Env varPurpose
--audit-socketFORGE_AUDIT_SOCKETUnix Domain Socket export path (FWS-7)
--audit-http-endpointFORGE_AUDIT_HTTP_ENDPOINTLocalhost HTTP fallback (FWS-7)
--audit-write-timeoutFORGE_AUDIT_WRITE_TIMEOUTPer-event sink timeout, default 50ms (FWS-7)
--rate-limit-read-rpsFORGE_RATE_LIMIT_READ_RPSPer-IP read RPS (FWS-10)
--rate-limit-read-burstFORGE_RATE_LIMIT_READ_BURSTPer-IP read burst (FWS-10)
--rate-limit-write-rpsFORGE_RATE_LIMIT_WRITE_RPSPer-IP write RPS (FWS-10)
--rate-limit-write-burstFORGE_RATE_LIMIT_WRITE_BURSTPer-IP write burst (FWS-10)
--rate-limit-cancel-exemptFORGE_RATE_LIMIT_CANCEL_EXEMPTExempt tasks/cancel from the write bucket (FWS-10)

forge serve start forwards each flag to the forked forge run; env vars flow through os.Environ() regardless.


forge.yaml schema additions

New top-level server: block:

server:
  rate_limit:
    read_rps: 1.0
    read_burst: 10
    write_rps: 1.0
    write_burst: 20
    cancel_exempt: true

All five fields are optional; omitting them inherits the new defaults. See docs/reference/forge-yaml-schema.md § server.rate_limit.


Removed

  • Per-agent disabled_channels: field on forge.yaml. FWS-6 shipped this in its first cut, then replaced it with the three-layer policy’s denied_channels. Migration: move any disabled_channels: [X] from forge.yaml into ~/.forge/policy.yaml’s denied_channels: (developer scope), /etc/forge/policy.yaml (laptop-wide), or the workspace ConfigMap (deployed-agent). forge channel disable <name> does this automatically.
  • channel_disabled_by_config audit event. channel_denied_by_policy (with layer attribution) carries every skip now.

Upgrade notes

  1. No action required for existing deployments that don’t use the new audit export sinks, platform policy, or server.rate_limit. Defaults are backward-compatible and metadata-only audit is unchanged on the wire.

  2. If you previously captured ops logs via forge run 2> ops.log, switch to forge run > ops.log (and 2> audit.log for audit). The FWS-9 stream split routes ops to stdout. Container deployments that capture both streams via the runtime’s standard log collector are unaffected.

  3. If your SIEM grouped audit by correlation_id alone, you can now add seq for gap detection. The schema is additive — no existing field changed shape.

  4. If you carry a per-agent disabled_channels: block in your forge.yaml, migrate it to denied_channels: in ~/.forge/policy.yaml (per-developer) or the workspace policy ConfigMap (per-deployment). forge channel disable <name> does this for you.

  5. Rate-limit defaults are more permissive. If you previously relied on the WriteBurst=3 ceiling for DoS protection, set server.rate_limit.write_burst: 3 in your forge.yaml to restore the prior behavior. The new 20-burst default targets orchestration-friendly workloads.

  6. Audit-event signing is deferred to a future release per the FWS-8 architectural recommendation. Sequence numbers cover gap detection in the meantime.


Merged pull requests

PRTitle
#118docs(claude): ship the forge knowledge skill + skill-builder skill (+ sync-docs hook)
#117feat(server): rate-limit configurability + orchestration-friendly defaults + cancel exemption (FWS-10)
#116chore(runtime): ops logs to stdout — stream separation from audit (FWS-9)
#115feat(runtime): hardened audit emission — sequence numbers, schema version, opt-in payload capture (FWS-8)
#114feat(runtime): audit event export — UDS sink + HTTP fallback (FWS-7)
#113feat(security): three-layer platform policy + channel scope (FWS-6)
#112feat(security): platform policy enforcement at runtime (FWS-5)
#111feat(runtime): cancellation signal handling (FWS-4)
#109feat(runtime): token usage + execution duration emission (FWS-3)
#108feat(runtime): workflow correlation ID threading (FWS-2)
#107feat(a2a): A2A 0.3.0 Agent Card conformance (FWS-1)
#84feat(ui): workspace-level skill-builder LLM config

(PR numbers above the v0.12 release reflect the per-feature PRs merged since then. The CHANGELOG entry for each FWS workstream lives in CHANGELOG.md.)


Issues closed

  • FWS-1 → #85
  • FWS-2 → #86
  • FWS-3 → #87
  • FWS-4 → #88
  • FWS-5 → #89
  • FWS-6 → #90
  • FWS-7 → #95
  • FWS-8 → #91
  • FWS-9 → #100
  • FWS-10 → #110
  • Custom-provider regression → #83
  • Skill Builder LLM workspace config → #92

Documentation


About Forge

Forge is an open-source runtime for building, packaging, and operating LLM-backed agents that do real work. The design commitment is three properties at once — atomicity (explicit skills, declared tools, declared dependencies), security (restricted egress, encrypted secrets, end-to-end audit), and portability (the same agent runs locally, in a container, or in Kubernetes with the same forge.yaml). The runtime speaks A2A 0.3.0 over JSON-RPC and REST, integrates with multiple LLM providers (Anthropic, OpenAI, Ollama, OpenAI-compatible) behind a common interface, and ships with a pluggable channel system (Slack / Telegram / MS Teams) plus an MCP client for external tool servers.


Full diff: v0.12.0...v0.13.0

View on GitHub
v0.12.0

Forge v0.12.0 — Cloud-native auth providers, Microsoft Teams channel, and Model Context Protocol (MCP) HTTP client

This is a feature release covering work merged to main since v0.10.1. It lands three substantial capability tracks: a pluggable authentication chain with cloud-native providers for AWS IAM, Microsoft Entra ID, and Google Cloud IAP; a Microsoft Teams channel adapter; and Phase 1 of Model Context Protocol (MCP) client support.

If you operate Forge on Kubernetes, this release removes the need to stand up a parallel OIDC issuer in front of your agent and gives you a structured way to integrate hosted MCP servers without writing per-tool adapter code.

The full per-PR detail lives in CHANGELOG.md.


Highlights

  • Six auth providers ship in-box: static_token, oidc, http_verifier, aws_sigv4, gcp_iap, azure_ad. Pluggable chain — first-match-wins, fail-closed on transient errors, structured auth_verify / auth_fail audit events with stable reason codes.
  • AWS IAM authentication via STS pre-signed URLs — the same pattern aws-iam-authenticator uses for EKS. No aws-sdk-go-v2 dependency.
  • Microsoft Entra ID (Azure AD) with tenant lock-in, optional Microsoft Graph group enrichment, and an explicit allowed_tenants allow-list for multi-tenant deployments.
  • GCP Identity-Aware Proxy support for Forge instances behind a GCP HTTPS Load Balancer with IAP enabled.
  • Microsoft Teams channel adapter built on Microsoft Graph polling, with inline device-code OAuth in the init TUI.
  • MCP HTTP client (Phase 1) — declare MCP servers in forge.yaml; discovered tools register as namespaced <server>__<tool> first-class tools with per-call audit, OAuth 2.1 PKCE, and per-server lifecycle.
  • Three new embedded skills: linear, code-plan, and ticket-driven variants of code-agent and github.
  • Web UI authentication wizard and matching non-interactive forge init flags for every new provider.

What’s new

1. Pluggable authentication chain with cloud-native providers

Forge’s authentication is now a chain of Provider implementations registered via a database/sql-style factory pattern. The chain is configured under a new auth: block in forge.yaml; existing --auth-url deployments continue to work via backward-compatible flag mapping.

Built-in providers:

ProviderToken formatUse case
static_tokenAuthorization: Bearer <static-token>Shared secret, dev/test
http_verifierBearer; verifier endpoint returns identityDrop-in for the old --auth-url flow
oidcBearer JWTAny OIDC issuer (Okta, Auth0, Cognito, Keycloak)
aws_sigv4Authorization: Bearer forge-aws-v1.<base64-of-presigned-sts-url>AWS IAM callers (Lambda, EC2, EKS, IRSA)
gcp_iapX-Goog-Iap-Jwt-Assertion: <jwt>Forge behind GCP LB + IAP
azure_adAuthorization: Bearer <aad-jwt>Microsoft Entra ID (humans, CI, Azure workloads)

Putting Forge behind any of the three major cloud identity providers previously required standing up a parallel OIDC issuer (Cognito for AWS, Workspace SAML, etc.). The Phase 2 providers remove that requirement — operators authenticate to Forge using the identity they already have in their cloud, and Forge never holds the IdP’s secrets.

AWS IAM via STS pre-signed URLs

aws_sigv4 follows the aws-iam-authenticator pattern used by EKS. The caller’s AWS SDK mints a pre-signed GetCallerIdentity URL, base64-encodes it, and sends it as a Bearer token. Forge validates the URL host (SSRF guard), GETs it, and stamps the canonical caller ARN onto the request.

Client-side, in any AWS-SDK-bearing language:

import boto3, base64, requests
from botocore.auth import SigV4QueryAuth
from botocore.awsrequest import AWSRequest

creds = boto3.Session().get_credentials().get_frozen_credentials()
req = AWSRequest(method="GET",
                 url="https://sts.us-east-1.amazonaws.com/?Action=GetCallerIdentity&Version=2011-06-15")
SigV4QueryAuth(creds, "sts", "us-east-1", expires=900).add_auth(req)
token = "forge-aws-v1." + base64.urlsafe_b64encode(req.url.encode()).rstrip(b"=").decode()

requests.post(forge_url, headers={"Authorization": f"Bearer {token}"}, data=msg)

A reference client lives at scripts/forge-aws-sign.py.

forge.yaml:

auth:
  providers:
    - type: aws_sigv4
      region: us-east-1
      allowed_principals:
        - "arn:aws:iam::ACCOUNT:role/forge-ci"
      # or, for a whole account:
      allowed_accounts:
        - "ACCOUNT"   # expands to user/* role/* assumed-role/*/* federated-user/*

Microsoft Entra ID (Azure AD)

azure_ad composes the OIDC provider for signature verification and layers AAD-specific concerns: tenant lock-in via the tid claim, optional Microsoft Graph group enrichment for users with many group memberships (AAD’s “groups overage” case), and an allowed_tenants allow-list for multi-tenant deployments.

auth:
  providers:
    - type: azure_ad
      audience: "api://forge"
      tenant_id: "11111111-2222-3333-4444-555555555555"
      groups_mode: graph     # or "claim"

Multi-tenant requires explicit opt-in (allow_multi_tenant: true). When combined with an empty allowed_tenants, the configuration accepts tokens from any Entra tenant — startup logs a warning so the trade-off is loud.

GCP Identity-Aware Proxy

gcp_iap consumes the X-Goog-Iap-Jwt-Assertion header that GCP’s IAP forwards to your backend. The IAP issuer and JWKS URL are hardcoded per Google’s stable contract.

auth:
  providers:
    - type: gcp_iap
      audience: "/projects/PROJECT_NUMBER/global/backendServices/BACKEND_ID"

Security posture

  • Algorithm whitelist before key lookup on every JWT path (RS/PS/ES variants only; HMAC and none rejected).
  • JWKS cache with TTL refresh, exponential backoff, and stale-grace fallback.
  • All outbound auth-provider HTTP clients pin CheckRedirect to refuse 3xx so the parser-side host gate is not silently undone by a redirect.
  • aws_sigv4 parser rejects URLs with userinfo and enforces X-Amz-Date freshness plus X-Amz-Expires cap before any STS call.
  • AAD SkipIssuerCheck field is unreachable from YAML (yaml:"-") — only settable from Go by azure_ad itself.

2. Microsoft Teams channel adapter

A new Teams channel adapter polls Microsoft Graph for messages in configured chats and dispatches them to your agent. Inline device-code OAuth in the forge init TUI auto-opens the browser; tokens persist via the existing encrypted credential store.

channels:
  - type: msteams
    chat_ids:
      - "19:abc123...@thread.v2"

CLI helper: forge channel msteams-login runs the device-code flow standalone. Recent chat history is prepended to each dispatched event so the agent has context for the conversation it’s joining.


3. Model Context Protocol (MCP) HTTP client — Phase 1

Forge agents can now consume MCP servers as first-class tool sources. Configure servers under a new top-level mcp: block; discovered tools register as namespaced <server>__<tool> and flow through the existing LLM executor.

mcp:
  servers:
    - name: linear
      transport: http
      url: https://mcp.linear.app/sse
      auth:
        type: oauth
        client_id: ${LINEAR_OAUTH_CLIENT_ID}
        authorize_url: https://linear.app/oauth/authorize
        token_url: https://api.linear.app/oauth/token
        scopes: [read, write]
      tools:
        allow: [create_issue, list_issues, update_issue]
      required: false

    - name: internal-tools
      transport: http
      url: http://internal-mcp.default.svc.cluster.local:8080/mcp
      auth:
        type: bearer
        token_env: INTERNAL_MCP_TOKEN
      tools:
        deny: [delete_database, drop_table]    # default deny-all if both empty
      required: true

New CLI subcommands:

forge mcp list                            # show every configured server, its state, tool count
forge mcp test <name> --call <tool>       # connect, list tools, optionally invoke one
forge mcp login <name>                    # laptop-time OAuth 2.1 PKCE flow
forge mcp logout <name>                   # remove stored OAuth tokens

Per-server lifecycle: each MCP server runs its own state machine in its own goroutine — parallel startup, Required=true failure cancels the agent context (Kubernetes observes CrashLoopBackOff); Required=false failure leaves the rest of the agent intact with that server’s tools absent.

Audit events (NDJSON to stderr, no args/result byte payload): mcp_server_started, mcp_server_failed, mcp_server_degraded, mcp_tool_call, mcp_tool_result, mcp_tool_conflict, mcp_token_refresh.

Phase 1 supports HTTP transport only. Stdio MCP servers are reached via the documented sidecar bridge pattern using projects like mcp-proxy. transport: stdio is rejected at forge validate time with "stdio is on the roadmap; Phase 1 supports HTTP transport only".

MCP protocol version pinned to 2025-06-18 — the handshake hard-fails on mismatch; version negotiation is intentionally absent.

Migration: the previous mcp_call adapter tool is removed in this release. The new mcp: block is strictly more capable (typed schemas per tool, per-call audit, conflict detection). See docs/mcp/configuration.md for the migration recipe.


4. Embedded skills: code-plan, linear, ticket-driven code-agent and github

  • code-plan — produces a structured implementation plan from a ticket; intended as a precondition for code-agent’s execution.
  • linear — read/write Linear tickets, comments, and status transitions.
  • Ticket-driven mode for code-agent and github — plan-first execution that creates branches with deterministic naming from ticket IDs.

Configuration changes

  • New top-level auth: block with providers: [].
  • New top-level mcp: block with servers: [] and optional token_store_path.
  • egress_hosts is auto-extended per configured auth provider and per MCP server URL — operators don’t have to maintain provider-specific egress entries by hand.
  • The forge init TUI runs the Authentication step before Egress so auth-provider hosts are present in the proposed egress allowlist when the operator confirms it.

No forge.yaml changes are required for callers continuing to use static_token, oidc, or http_verifier — the file is backward-compatible.


Removed

  • mcp_call adapter tool — superseded by the new mcp: configuration block. A compile-time guard in the adapter test package ensures the deprecation doesn’t regress.

Upgrade notes

  1. No action required for existing auth setups. static_token / oidc / http_verifier configurations in existing forge.yaml files continue to work unmodified. The new providers are additive.

  2. --auth-url flag still works. It maps internally to the http_verifier provider chain. New deployments are encouraged to configure under auth.providers: directly for clarity.

  3. If you used mcp_call: remove it from tools: in forge.yaml and configure the same server under mcp.servers: instead. The LLM no longer needs to know about a mcp_call(url, method, params) wrapper — each discovered MCP tool is registered with its own schema. See the migration guide in docs/mcp/configuration.md.

  4. MCP OAuth is a laptop-time operation. For Kubernetes deployments, run forge mcp login <name> locally, mount the resulting credential file as a Secret, and point MCP_TOKEN_STORE_PATH at it.

  5. Multi-tenant Azure AD is a deliberate security trade-off. If you set allow_multi_tenant: true without allowed_tenants, Forge accepts tokens from any Entra tenant in the world — startup logs a warning. Add the tenant GUIDs you intend to trust.


Merged pull requests

PRTitle
#81feat(mcp): Phase 1 — MCP HTTP client
#79feat(auth): Phase 2 — AWS Sigv4, GCP IAP, Azure AD providers
#77refactor(auth): introduce pluggable Provider chain (PR1 foundation)
#76feat(channels): Microsoft Teams adapter via Graph polling
#74feat(skills/code-agent): ticket-driven mode (plan-first carve-out)
#72docs: add linear and code-plan to embedded-skills index
#71feat(skills/github): ticket-driven PR creation + branch-name helper
#69feat(skills): add code-plan embedded skill
#67feat(skills): add linear embedded skill
#65feat(channels): LLM-generated summary for large responses

Documentation


Full diff: v0.10.1...v0.12.0

View on GitHub
v0.10.1 BREAKING

Forge v0.10.1 — Windows Agent Start Fix, Documentation Link Cleanup

Release Date: May 19, 2026 Full Changelog: https://github.com/initializ/forge/compare/v0.10.0…v0.10.1

Forge v0.10.1 is a patch release containing one user-facing reliability fix on Windows and a comprehensive documentation link cleanup. Three pull requests, 24 files changed, no breaking changes — drop-in upgrade from v0.10.0.


Highlights

#AreaChangeIssue / PR
1Windowsforge ui no longer falsely reports agent failed to start for healthy daemons. Cause: Signal(0) is unsupported on Windows; the cross-platform process-liveness check is now centralized.#59 / #60
2DocumentationAll 21 broken doc links in the main README.md are fixed. New CI workflow gates future README link breakage.#58 / #61
3DocumentationAll 34 broken in-doc cross-references across 14 doc files are fixed. The CI link check is extended to cover docs/**.#62 / #63

What’s Fixed

Windows: forge ui Recognizes Healthy Daemons (PR #60, closes #59)

Symptom: On Windows, starting an agent from forge ui produced a red banner reading agent failed to start: REST: http://localhost:<port>/tasks/send ... — but the backend agent was actually running successfully. The same flow worked correctly on macOS and Linux.

Cause: forge-ui/process.go’s pidAlive function used os.Process.Signal(syscall.Signal(0)) — the standard Unix idiom for liveness checking. On Windows, Go’s stdlib only translates os.Interrupt and os.Kill; every other signal (including Signal(0)) returns "operating system does not support signal". So pidAlive returned false for every PID on Windows regardless of whether the process was alive.

The waitForPort poll loop interpreted that as “child crashed”, fast-failed, and surfaced whatever was already in serve.log as the supposed error — which by that point contained the daemon’s normal startup banner.

A second occurrence of the same broken check in forge-ui/discovery.go was actively deleting .forge/serve.json for still-running daemons on every page-load — orphaning them from the dashboard. Both are fixed.

Fix: New shared helper forge-core/util/process/{process_unix.go, process_windows.go} with IsAlive(pid int) bool. Unix path uses FindProcess + Signal(0); Windows path uses OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION) + CloseHandle — the same pattern forge-cli/cmd/serve_windows.go already shipped for daemon-lifecycle checks. The duplicate implementations in forge-cli and forge-ui are deleted in favor of the single shared helper.

// One implementation, build-tag split, consumed by both forge-cli and forge-ui.
import "github.com/initializ/forge/forge-core/util/process"

if !process.IsAlive(pid) {
    // accurate on every supported platform
}

There is now exactly one implementation of “is this PID alive” in the repo. CI runs GOOS=windows go build to catch future build-tag drift.

The top-level README.md referenced 25 relative .md files. 21 of them were 404s — the README linked to canonical flat names (docs/runtime.md, docs/security/secrets.md, …) but the actual docs lived under subdirectories with different filenames (docs/core-concepts/runtime-engine.md, docs/security/secret-management.md, …). Every newcomer clicking “Quick Start”, “Installation”, “Architecture”, “Skills”, “Runtime”, “Memory”, “Channels”, or any of 12 other top-level doc links from the README landed on a GitHub 404.

After the fix:

before: BROKEN 21, OK 4
after:  BROKEN 0,  OK 25

A new .github/workflows/docs-links.yaml runs on every push/PR touching README.md, docs/**, or the workflow itself — fails the build if any relative .md link doesn’t resolve. Prevents this from regressing.

In-Doc Cross-References Fixed (PR #63, closes #62)

The same pattern that broke the README also broke 34 in-doc cross-references across 14 doc files. The heaviest hitter was docs/security/overview.md (10 broken links — the file pre-dated the rename of egress.md → egress-control.md, secrets.md → secret-management.md, signing.md → build-signing.md). Other files used paths like security/guardrails.md from within docs/core-concepts/ which resolved to docs/core-concepts/security/guardrails.md (does not exist) instead of docs/security/guardrails.md.

All 34 fixed via mechanical substitution with #anchor preservation. The CI link check is extended to walk docs/**/*.md in addition to README.md:

before: 34 broken in-doc links across 14 files
after:   0 broken; 37 files checked (1 README + 36 docs)

Upgrade Notes

Drop-in patch upgrade — no forge.yaml changes, no API changes, no behavior changes for healthy installs.

  • Windows users on v0.10.0: this release fixes the “agent failed to start” red banner. Highly recommended.
  • forge-core/util/process is a new package. If you import forge-core directly in your own Go projects, the new package is available but doesn’t change any existing API.

Install

# Homebrew
brew upgrade initializ/tap/forge

# One-line install/upgrade
curl -sSL https://raw.githubusercontent.com/initializ/forge/main/install.sh | bash

# Docker
docker pull ghcr.io/initializ/forge:v0.10.1
docker pull ghcr.io/initializ/forge:latest

Reference

View on GitHub
v0.10.0

Forge v0.10.0 — Dynamic Secret Overlay, Configurable Security Scoring, K8s Channel Manifests, Slack Bot Admission

Release Date: May 18, 2026 Full Changelog: https://github.com/initializ/forge/compare/v0.9.2…v0.10.0

Forge v0.10.0 makes encrypted secrets, security policy, channel deployment, and inter-bot communication first-class operator concerns. Skill-declared env vars now flow from the encrypted store to the agent process without code changes; security audits can be downgraded via YAML policy with full auditability; Kubernetes manifests finally include channel env vars from the same source as docker-compose; and Slack agents can now accept @-mentions from operator-allowlisted bots while staying immune to self-loops. Two latent bugs in the secret provider chain are also fixed. 62 files changed, 2,718 insertions, 728 deletions across forge-cli, forge-core, forge-plugins, and forge-skills.


Highlights

#AreaChangeIssue / PR
1SecretsSkill-declared env vars in encrypted store reach the agent’s OS env via dynamic provider.List() discovery — no hardcoded key list#48 / #51
2Skills auditNew forge skills audit --policy <path> overrides scoring and policy checks; downgrades carry (via policy) / (acknowledged by policy) annotations#49 / #53
3K8s + channelsChannel env vars from <channel>-config.yaml now flow into k8s/secrets.yaml, k8s/deployment.yaml, and docker-compose.yaml from a single canonical source#50 / #54
4SecretsStale global ~/.forge/secrets.enc with a different passphrase no longer poisons the chain — eager-load validation drops it with a clear warning#52 / #57
5SlackNew allow_bot_ids setting admits @-mentions from operator-trusted bots; the agent’s own bot_id is unconditionally dropped (self-loop guard)#55 / #56

What’s New

Dynamic Secret Overlay for Skill-Declared Env Vars (PR #51, closes #48)

Before: the runtime overlaid only a hardcoded set of LLM/channel keys (OPENAI_API_KEY, SLACK_BOT_TOKEN, etc.) from the encrypted secrets store. A skill declaring metadata.forge.requires.env.required: [ACME_API_TOKEN] would see an empty value at runtime, even after forge secret set ACME_API_TOKEN ....

Now: a new secretOverlayKeys(provider) helper unions the builtin set with every key the provider exposes via Provider.List(). Custom skill-declared keys flow through the encrypted store to the OS env automatically — no code change required when adding a new skill.

# skills/my-skill/SKILL.md
metadata:
  forge:
    requires:
      env:
        required: [ACME_API_TOKEN]
forge secret --local set ACME_API_TOKEN my-secret-value
forge run   # ACME_API_TOKEN is now in the agent's environment

Cross-category secret-reuse detection continues to operate on the builtin set only — custom keys have no defined purpose category and are not part of the reuse check.

Policy-Configurable Security Scoring (PR #53, closes #49)

forge skills audit now accepts a --policy <path> flag that loads a YAML SecurityPolicy. The policy affects both policy-violation checks (existing) and scoring (new). Three new scoring overrides let operators down-weight builtin classifications:

# policy.yaml
script_policy: allow
max_risk_score: 90

trusted_domains:    [internal.example.com]   # +2 instead of +10 (unknown)
acknowledged_bins:  [python]                 # +3 instead of +15 (builtin high-risk)
acknowledged_env:   [DB_PASSWORD]            # +5 instead of +10 (builtin sensitive)
forge skills audit --dir skills --policy policy.yaml

Every override is auditable. Affected factors carry (via policy) or (acknowledged by policy) in their description, visible in both --format text and --format json:

egress   +2   trusted domain (via policy): internal.example.com
binary   +3   high-risk binary (acknowledged by policy): python
env      +5   sensitive variable (acknowledged by policy): DB_PASSWORD

Scoring overrides can only down-weight builtin classifications — a binary not in the high-risk set stays at +3 even if listed in acknowledged_bins. This prevents accidental score inflation.

API change: AnalyzeSkillDescriptor and AnalyzeSkillEntry now take a SecurityPolicy parameter. Pass SecurityPolicy{} to reproduce historical default scoring exactly.

Channel Env Vars in Kubernetes Manifests (PR #54, closes #50)

Before this release, forge build generated k8s/deployment.yaml and k8s/secrets.yaml containing only skill-declared env vars. Channel env vars (SLACK_BOT_TOKEN, TELEGRAM_BOT_TOKEN, etc.) were silently dropped — even though forge package --with-channels injected them into docker-compose via a hardcoded switch statement. The two output paths disagreed from the same forge.yaml.

A new ChannelsStage (forge-cli/build/channels_stage.go) reads each <channel>-config.yaml and unions every _env-suffixed setting value into Spec.Requirements.EnvRequired. EnvVarsFromConfig in forge-cli/channels/env.go is the canonical helper; generateDockerCompose in cmd/package.go calls the same function, so both output paths produce a consistent env-var set.

# slack-config.yaml — the canonical source
adapter: slack
settings:
  app_token_env: SLACK_APP_TOKEN
  bot_token_env: SLACK_BOT_TOKEN
  custom_env: MY_PROJECT_SLACK_OVERRIDE   # ← appears in K8s + docker-compose

After forge build:

# .forge-output/k8s/secrets.yaml
apiVersion: v1
kind: Secret
metadata:
  name: my-agent-secrets
stringData:
  SLACK_APP_TOKEN: ""
  SLACK_BOT_TOKEN: ""
  MY_PROJECT_SLACK_OVERRIDE: ""
  TELEGRAM_BOT_TOKEN: ""

Adding a new channel adapter requires zero build-pipeline edits. Operators (and channel-adapter authors) just declare env vars via the _env suffix convention in their channel YAML.

Behavior change worth calling out: SLACK_SIGNING_SECRET is no longer injected. The Slack adapter is Socket Mode and never reads it; the previous hardcoded map included it erroneously. Operators using a custom Slack adapter that does need it can add signing_secret_env: SLACK_SIGNING_SECRET to their slack-config.yaml.

Slack Bot @-Mention Admission with Self-Loop Guard (PR #56, closes #55)

The Slack adapter previously dropped every event whose bot_id was non-empty — silently ignoring @-mentions from scheduler bots, monitoring tools, workflow steps, and CI bots even when an operator explicitly wired them up.

A new optional allow_bot_ids setting admits specific bots:

# slack-config.yaml
adapter: slack
settings:
  app_token_env: SLACK_APP_TOKEN
  bot_token_env: SLACK_BOT_TOKEN
  allow_bot_ids: B0123ABC,B0456DEF   # comma-separated bot_ids

Three coordinated rules keep loops bounded:

RuleBehavior
Self-loop guard (always on)The agent’s own bot_id is dropped before the allowlist is consulted. No opt-out — even if its own ID is misconfigured into allow_bot_ids, the guard short-circuits.
Allowlist gate (opt-in)Bots not in allow_bot_ids stay dropped. Empty/absent setting preserves today’s behavior (no other bots admitted).
Mention requirement (unchanged)Admitted bots still must include <@FORGE_AGENT> in the message text. Chatter without a tag is ignored.

resolveBotID now captures both user_id and bot_id from Slack’s auth.test response — user_id drives mention matching; bot_id powers the self-loop guard. Drop reasons are emitted to stderr with the bot_id and a pointer to slack-config.yaml allow_bot_ids so operators can self-diagnose.

Stale Global Secrets File No Longer Poisons the Chain (PR #57, closes #52)

A latent bug in OverlaySecretsToEnv and Runner.buildSecretProvider — exposed during the validation of #51 — caused a stale ~/.forge/secrets.enc from an unrelated project (encrypted with a different passphrase) to break ChainProvider.List() for everyone. Local file’s keys would silently disappear from the agent’s env.

A new viableEncryptedFileProviders helper eagerly validates each candidate file at chain-build time:

Candidate stateBehavior
File missing (os.IsNotExist)Silently skipped — common case, no log noise
Decryption succeedsAdmitted; cleartext is cached for subsequent reads (no extra Argon2id)
Decryption fails (wrong passphrase, corruption)Dropped from the chain with a clear warning naming the file path
forge: skipping secrets provider that failed to load
  (label=global, path=/Users/.../.forge/secrets.enc,
   error=decrypting secrets file: ... wrong passphrase?)

ChainProvider semantics stay strict — non-NotFound errors from Get / List still propagate. The fix is at the chain-construction site: we don’t admit providers we already know will fail.


Documentation

Each PR shipped with synced docs. Notable additions:


Upgrade Notes

For most users this is a drop-in upgrade — no forge.yaml changes needed.

  • forge-skills/analyzer API: AnalyzeSkillDescriptor and AnalyzeSkillEntry now take a third SecurityPolicy parameter. Pass SecurityPolicy{} for the historical default. Affects only direct importers of the analyzer package.
  • Slack docker-compose: SLACK_SIGNING_SECRET is no longer auto-injected. If you depended on it (you likely didn’t — Socket Mode never reads it), add signing_secret_env: SLACK_SIGNING_SECRET to your slack-config.yaml.
  • Skill audit CLI: forge skills audit --dir <path> is required for subdirectory-style skills (skills/<name>/SKILL.md). The default still targets a flat SKILL.md.

Install

# Homebrew
brew upgrade initializ/tap/forge

# One-line install/upgrade
curl -sSL https://raw.githubusercontent.com/initializ/forge/main/install.sh | bash

# Docker
docker pull ghcr.io/initializ/forge:v0.10.0
docker pull ghcr.io/initializ/forge:latest

Contributors

Thanks to the operators who reported the channel env / Slack bot / encrypted secrets issues that drove this release.


Reference

View on GitHub
v0.9.2

Forge v0.9.2 — Skill Environment Injection, Docker Images, Agent Reliability Fixes

Release Date: April 17, 2026 Full Changelog: https://github.com/initializ/forge/compare/v0.9.1…v0.9.2


Highlights

Forge v0.9.2 delivers automatic environment variable and egress domain injection when saving skills, pre-built multi-architecture Docker images on GHCR, and critical agent reliability fixes for port allocation, stale daemon detection, and OAuth credential handling. This release modifies 25 files with over 1,200 lines of new code across all five modules.


What’s New

Skill Environment & Egress Auto-Configuration (#46)

When saving a skill via the UI Skill Builder or forge skills add, Forge now automatically configures the agent’s environment — no manual .env or forge.yaml editing required.

How it works:

  1. SKILL.md frontmatter declares metadata.forge.requires.env and metadata.forge.egress_domains
  2. On save, Forge parses requirements and:
    • Writes env vars to .env (deduplicated, skips existing keys and encrypted placeholders)
    • Merges egress domains into forge.yaml security.egress.allowed_domains (deduplicated, sorted, YAML formatting preserved)
    • Reports missing env vars back to the UI for user input
  3. Frontend displays what was configured and prompts for any remaining required variables
# CLI: forge skills add now also merges egress domains
forge skills add github
# ✓ Added egress domains: api.github.com, github.com
# ✓ Wrote GITHUB_TOKEN to .env

New shared utilities in forge-cli/cmd/skill_env.go:

  • ParseSkillRequirements() — extracts env requirements and egress domains from SKILL.md
  • MergeEgressDomains() — adds domains to forge.yaml allowlist with text-based YAML insertion
  • AppendEnvVars() — appends key=value pairs to .env with deduplication
  • CheckMissingEnv() — checks OS env + .env + secret placeholders for missing entries

SkillSaveFunc now returns *SkillSaveResult with structured response fields: path, egress_added, env_configured, env_missing.

Pre-Built Docker Images on GHCR (#46)

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

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

# Pin to a specific version
docker pull ghcr.io/initializ/forge:v0.9.2

# 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

Image details:

  • Tags: v0.9.2, v0.9, v0, latest (semver hierarchy)
  • Base: alpine:3.22.4 with ca-certificates, git, tzdata
  • Build: Multi-stage with golang:1.25-alpine, BuildKit cache mounts, CGO_ENABLED=0 static binary
  • Security: Non-root forge user, minimal attack surface
  • CI: QEMU for cross-platform emulation, GitHub Actions cache for layer reuse

LLM Agent Loop Robustness (#46)

Two fixes to the core agent loop that prevent subtle failures across providers:

Tool call execution fix: The loop now terminates based solely on len(ToolCalls) == 0, ignoring FinishReason. Some providers (notably certain OpenAI models) return FinishReason: "stop" even when tool calls are present — previously this caused the agent to stop mid-execution.

Session recovery deduplication: When a session is recovered from disk after a crash or timeout, the executor checks whether the conversation already ends with an identical user message and skips the duplicate. This prevents the same prompt from appearing twice in the context window on retry.

Q&A nudge suppression: When only explore-phase tools were invoked (e.g., web_search, file_read), the agent no longer sends a continuation nudge (“You stopped…”), correctly treating the response as a final answer to an informational query.


Bug Fixes

Port Collision on Agent Start (forge-ui)

Before: When the UI restarted, PortAllocator lost its in-memory state and always tried port 9100 first — colliding with agents already running on that port from a previous session.

After: Allocate() now does a net.Listen TCP probe to verify the port is actually free before assigning it. Occupied ports are automatically skipped.

Stale Agent Status After UI Restart (forge-ui)

Before: detectExternalAgent() only checked if a port was listening via TCP probe. If serve.json was stale (agent crashed) but another process happened to be on the same port, the dead agent appeared as “running”.

After: detectExternalAgent() now checks PID liveness via pidAlive() (signal 0) before the TCP probe. If the PID from serve.json is dead, the stale file is cleaned up and the agent is correctly reported as stopped.

OAuth Silent 401 in Skill Builder and Runner (forge-cli)

Before: When OpenAI OAuth credentials were missing or unloadable (no ~/.forge/credentials/openai.json), both the Skill Builder llmStreamFunc and the Runner createProviderClient silently fell through to create a regular OpenAI client with no API key — resulting in a cryptic 401 Unauthorized error.

After: When the API key is empty/__oauth__ and oauth.LoadCredentials() fails, a clear error is returned: “no OpenAI API key or OAuth credentials found; run ‘forge init’ with OAuth or set OPENAI_API_KEY”.


Files Changed

AreaFilesDescription
Skill env utilitiesforge-cli/cmd/skill_env.goParseSkillRequirements, MergeEgressDomains, AppendEnvVars, CheckMissingEnv
Skill env testsforge-cli/cmd/skill_env_test.go12 unit tests for shared utilities
CLI skills addforge-cli/cmd/skills.goEgress domain merge in runSkillsAdd
UI save funcforge-cli/cmd/ui.goEnhanced skill save with env/egress handling, OAuth error surfacing
Runner OAuthforge-cli/runtime/runner.goOAuth error surfacing in createProviderClient
Parser exportforge-skills/parser/parser.goExport ExtractForgeReqs for reuse
LLM loopforge-core/runtime/loop.goIgnore FinishReason, session dedup, Q&A nudge suppression
Loop testsforge-core/runtime/loop_test.goRegression tests for stop+tool_calls
UI typesforge-ui/types.goSkillSaveResult, SkillEnvEntry, updated SkillSaveFunc signature
UI handlerforge-ui/handlers_skill_builder.goPass env vars, return SkillSaveResult
Port allocatorforge-ui/process.gonet.Listen probe in Allocate(), portFree() helper
Agent discoveryforge-ui/discovery.goPID liveness check, stale serve.json cleanup
Frontendforge-ui/static/dist/app.jsEnv var inputs, egress/env status display
DockerDockerfile, .dockerignoreMulti-stage alpine build
CI.github/workflows/release.yamlGHCR multi-arch build+push job
Docsdocs/{runtime,skills,dashboard,deployment,commands}.mdSynced with code changes

25 files changed, 1,201 insertions, 79 deletions


Installation

# Homebrew
brew upgrade initializ/tap/forge

# One-line install/upgrade
curl -sSL https://raw.githubusercontent.com/initializ/forge/main/install.sh | bash

# Docker
docker pull ghcr.io/initializ/forge:v0.9.2

Contributors

Built with Forge — turn SKILL.md into portable, secure, runnable AI agents.

View on GitHub
v0.9.1 BREAKING

Forge v0.9.1 — Container Packaging, Local Binary Overrides, External Auth, and Guardrails Library

Release Date: April 13, 2026
Full Changelog: https://github.com/initializ/forge/compare/v0.9.0…v0.9.1


Highlights

Forge v0.9.1 delivers container packaging improvements, local binary injection, external authentication, inline KUBECONFIG support, and a full guardrails library integration. This release modifies 65 files with over 3,600 lines of new code, making container-based agent deployments significantly more flexible and production-ready.


What’s New

Container Packaging: Local Binary Overrides (#44)

Inject host-compiled binaries directly into container images, bypassing remote resolution entirely. This enables air-gapped builds, custom binary versions, and rapid iteration without waiting for upstream releases.

# Inject a locally-built forge binary into the container
forge package --local-bin forge=/path/to/linux/forge

# Combine with image optimization
forge package --local-bin forge=./bin/forge --alpine --slim
  • --local-bin name=/path/to/file flag on both forge build and forge package (repeatable)
  • --slim flag to minimize image size by skipping heavy/optional binaries
  • --alpine flag to prefer Alpine base image
  • Configurable in forge.yaml via package.bin_overrides.<name>.local
  • Smart Dockerfile generation with multi-stage bin resolution: local override → skill override → config override → image registry → apt/apk
  • Automatic ca-certificates installation for TLS support

External Authentication Provider (#44)

Delegate token validation to an external auth endpoint for enterprise deployments. The internal loopback token ensures channel adapters (Slack, Telegram) continue to work without needing a valid external token.

# Via CLI flag
forge run --auth-url https://auth.example.com/verify

# Via environment variable (containers)
docker run -e FORGE_AUTH_URL=https://auth.example.com/verify my-agent
  • Two-layer auth middleware: internal token accepted first (channel loopback), then external provider
  • FORGE_AUTH_URL and FORGE_AUTH_ORG_ID environment variables
  • Org ID extracted from X-Org-ID, org-id, or org_id request headers

KUBECONFIG Materialization (#44)

Pass kubeconfig content directly as an environment variable — the runtime automatically writes it to a file and updates KUBECONFIG to the file path. No volume mounts needed.

docker run -e KUBECONFIG="$(cat ~/.kube/config)" my-agent
  • Detects inline YAML via newlines, apiVersion: markers, and certificate-authority-data: presence
  • kubectl and helm receive the materialized path via env passthrough

Channel Adapter Fixes (#44)

  • Entrypoint now includes --with flag — container entrypoint automatically becomes ["forge", "run", "--host", "0.0.0.0", "--with", "slack,telegram"] when channels are configured in forge.yaml
  • Channel config files packagedslack-config.yaml, telegram-config.yaml, etc. are automatically copied into the Docker build context
  • Auth loopback — channel adapters authenticate to the A2A server using an internal token, bypassing external auth providers

Guardrails Library Integration (#45)

Replaced the hand-rolled GuardrailEngine (435 lines of hardcoded patterns) with the external github.com/initializ/guardrails library, supporting dual-mode operation.

  • File-based mode (guardrails.json) for local development — zero external dependencies
  • MongoDB-backed mode for platform deployments with centralized policy management and audit logging
  • Inbound PII masking fixedCheckInbound now correctly masks PII (e.g., SSNs) before they reach the LLM
  • Session recovery crash fixed — orphaned tool calls (assistant tool_calls without matching tool results) are stripped on save and recovery, preventing API rejection errors

Skills Build Stage Fix (#45)

  • SkillsStage now always scans the skills/ subdirectory even without a root SKILL.md, restoring binary installation (e.g., kubectl) for subdirectory-only skill projects

Root SKILL.md Removed (#44)

  • forge init no longer generates a root-level SKILL.md with placeholder content
  • Skills live exclusively in skills/<skill-name>/SKILL.md subdirectories
  • Removed skills.path from forge.yaml template
  • Build pipeline already scans skills/ automatically

OpenAI API Fix (#44)

  • Fixed "Invalid value for 'content': expected a string, got null" error by omitting content field for assistant messages with tool_calls per OpenAI spec

New Configuration

forge.yaml — Package Section

package:
  alpine: false                     # Prefer Alpine base image
  slim: false                       # Minimize image size
  bin_overrides:
    forge:
      local: "/path/to/linux/forge" # Host path to local binary
    jq:
      apt: "jq"                     # APT package name
    custom-tool:
      url: "https://example.com/tool.tar.gz"
      dest: "/usr/local/bin/custom-tool"
      chmod: "0755"

New Environment Variables

VariableDescription
FORGE_AUTH_URLExternal auth provider URL for token validation
FORGE_AUTH_ORG_IDOrganization ID sent to external auth provider
FORGE_GUARDRAILS_DBMongoDB connection string for centralized guardrails

New CLI Flags

CommandFlagDescription
forge build--local-binLocal binary override as name=/path/to/file (repeatable)
forge build--slimMinimize image size
forge build--alpinePrefer Alpine base image
forge package--local-binLocal binary override (same as build)
forge package--slimMinimize image size
forge package--alpinePrefer Alpine base image
forge run--auth-urlExternal auth provider URL
forge serve--auth-urlExternal auth provider URL

Breaking Changes

  • Root SKILL.md no longer generated by forge init. Existing projects with a root SKILL.md are unaffected — the build pipeline still reads it if present, but new projects should use skills/ subdirectories exclusively.
  • skills.path removed from forge.yaml template. Existing configs with skills.path still work at runtime.

Pull Requests

  • #44 — feat: container packaging, local binary overrides, and external auth
  • #45 — feat: integrate guardrails library with dual-mode support

Stats

  • 65 files changed, 3,652 insertions, 1,031 deletions
  • 5 new CLI flags across build, package, run, and serve
  • 3 new environment variables for auth and guardrails
  • 1 new forge.yaml section (package)

Installation:

# Homebrew
brew upgrade initializ/tap/forge

# Binary (Linux/macOS)
curl -fsSL https://github.com/initializ/forge/releases/download/v0.9.1/forge-$(uname -s)-$(uname -m).tar.gz | tar xz -C /usr/local/bin forge

Documentation: docs/

View on GitHub
v0.9.0 BREAKING

Forge v0.9.0 — Security Hardening, GitHub Skills, and Bug Fixes

Release Date: April 4, 2026
Full Changelog: https://github.com/initializ/forge/compare/v0.8.0…v0.9.0


Highlights

Forge v0.9.0 is a security-focused release that delivers two full phases of security hardening (17 fixes total), a new GitHub API skill, a critical secret decryption bug fix, and improvements across the CLI, TUI, and channel plugins. This release modifies 78 files with over 4,100 lines of new code and hardened tests.


What’s New

Security: Phase 1 — Critical Fixes (C-1 through C-7)

  • SSRF protection — new IP validator blocks requests to private/loopback/link-local ranges (#34)
  • Safe dialer — all outbound HTTP connections routed through a secure dialer with DNS rebinding protection
  • Redirect validation — HTTP redirects are checked against the egress allowlist before following
  • bash_execute removed — eliminated the high-risk shell execution tool from the code-agent skill (#29)
  • Egress enforcer hardened — stricter domain matching and proxy enforcement

Security: Phase 2 — High-Priority Fixes (H-1 through H-10)

  • Scoped environment variablesKUBECONFIG, NO_PROXY, and GH_CONFIG_DIR are now injected only into their target binaries (kubectl, helm, gh), not the global environment (#39, #42)
  • A2A server hardened — added input validation, rate limiting, and auth improvements to the Agent-to-Agent server
  • Custom tool sandboxing — external tool execution now enforces stricter argument validation
  • Channel plugin hardening — Slack and Telegram adapters received input sanitization and error-handling improvements
  • Guardrails loader hardened — runtime guardrail loading now validates schema before application

New Feature: GitHub API Skill

  • Query GitHub users, pull requests, forks, and stargazers directly from within an agent (#38)
  • Includes six new scripts: github-get-user, github-list-prs, github-list-forks, github-list-stargazers, github-pr-author-profiles, github-stargazer-profiles
  • Per-tool PII exemptions — tools that need GitHub usernames can bypass PII redaction on a per-tool basis

Bug Fixes

  • Secret decryption — fixed a bug where decryption failed even with the correct passphrase (#40, #41)
  • Q&A nudge suppression — resolved unwanted nudge prompts during agent conversations
  • UI agent start errors — fixed errors when starting agents from the skill builder UI
  • Chat streaming — resolved streaming interruption issues in the TUI
  • File attachmentcli_execute now correctly handles file attachment behavior
  • Errcheck lint — fixed unchecked error returns in test files

Documentation

  • Updated security docs covering egress enforcement, guardrails, and the new IP validator
  • Synced architecture, channels, runtime, skills, and tools documentation with code changes (#43)

Breaking Changes

  • bash_execute tool removed — agents using the bash_execute builtin tool must migrate to cli_execute or custom tool definitions. This tool was removed for security reasons.

Upgrade Guide

# Update via Homebrew
brew upgrade initializ/tap/forge

# Or pull the latest binary
curl -sSL https://raw.githubusercontent.com/initializ/forge/main/install.sh | bash

No configuration changes required. Existing agents and skills are fully compatible with v0.9.0.


Stats

MetricValue
Files changed78
Insertions+4,126
Deletions−632
Net new lines+3,494
PRs merged6
Contributors2

Pull Requests Included

  • #43 — docs: sync documentation for Phase 2 fixes and UI improvements (@initializ-mk)
  • #42 — security: Phase 2 high-priority fixes and UI improvements (@initializ-mk)
  • #41 — [Bug]: Secret decryption fails with correct passphrase #40 (@pandey03muskan)
  • #39 — security: Phase 2 high-priority fixes (H-1 through H-10) (@initializ-mk)
  • #38 — feat: add GitHub API query tools and per-tool PII exemptions (@initializ-mk)
  • #34 — security: Phase 1 critical fixes (C-1 through C-7) (@initializ-mk)

Contributors

  • @initializ-mk
  • @pandey03muskan

Forge is a secure, portable AI agent runtime. Build, run, and deploy AI agents from a single SKILL.md file.
Learn more at github.com/initializ/forgeDocumentation

View on GitHub
v0.8.0

Forge v0.8.0 — Code Agent, Skill Guardrails, and Telegram Reliability

Forge v0.8.0 introduces the Code Agent skill for autonomous code generation, a multi-layer skill guardrails system for fine-grained security policy enforcement, Kubernetes cost visibility, and critical Telegram reliability fixes — making Forge the most secure open-source AI agent runtime for enterprise deployments.


Code Agent Skill

New embedded skill (code-agent) that enables autonomous code generation, modification, and project scaffolding across multiple frameworks.

  • 7 builtin tools: file_read, file_write, file_edit, file_patch, glob_search, grep_search, directory_tree — all confined to the agent’s working directory via PathValidator
  • Multi-framework scaffolding: Vite + React, Express, FastAPI, Go, Spring Boot, and more via code_agent_scaffold
  • Surgical code editing: Exact string matching with unified diff output via code_agent_edit
  • Batch operations: Atomic multi-file add/update/delete/move in a single call via file_patch
  • Smart search: Uses ripgrep when available, with Go-based fallback for grep_search
  • Layered registration: Skills request only the capabilities they need — search-only, read-only, or full read-write
forge skills add code-agent

GitHub Skill — Now Script-Backed

The github skill has been upgraded from binary-backed to script-backed with 6 shell scripts and 8 tools:

ToolPurpose
github_cloneClone a repository and create a feature branch
github_checkoutSwitch to or create a branch
github_statusShow git status
github_commitStage and commit changes
github_pushPush feature branch to remote
github_create_prCreate a pull request
github_create_issueCreate a GitHub issue
github_list_issuesList open issues

Multi-Layer Skill Guardrails

Skills can now declare domain-specific security policies in their SKILL.md frontmatter, enforced at four interception points in the agent loop:

GuardrailHook PointPurpose
deny_commandsBefore tool executionBlock dangerous CLI commands (e.g., kubectl get secrets)
deny_outputAfter tool executionBlock or redact sensitive tool output (e.g., Secret manifests, tokens)
deny_promptsBefore LLM callIntercept capability enumeration probes
deny_responsesAfter LLM callPrevent binary name disclosure in LLM responses
  • Declarative YAML config in SKILL.md frontmatter — no code changes needed
  • Pattern aggregation across multiple active skills with deduplication
  • Runtime fallback — guardrails fire during forge run without requiring forge build
  • file:// protocol blocking in cli_execute to prevent filesystem traversal via curl file:///etc/passwd

Kubernetes Cost Visibility Skill

New embedded skill (k8s-cost-visibility) that estimates cluster infrastructure costs:

  • Four cost dimensions: Compute (CPU + memory), Storage (PVC/PV), LoadBalancer, and Waste (unbound PVs)
  • Multiple grouping modes: namespace, workload, node, label, annotation
  • Auto-detect cloud pricing: AWS, GCP, Azure, or static/custom rates
  • Strictly read-only — only kubectl get commands, never mutating operations
forge skills add k8s-cost-visibility

Telegram Reliability Fix

Resolved a critical context cancellation bug that killed in-flight agent tasks during polling restarts:

  • Context isolation: Each handler goroutine now runs with an independent context (10-minute timeout), detached from the polling lifecycle
  • Interim messaging: After 15 seconds of processing, Telegram sends “Working on it — I’ll send the result when ready” — matching Slack’s existing behavior
  • Shared handler logic: Extracted handleEvent() method eliminates duplication between polling and webhook code paths

PII Detection Improvements

Reduced false positives in the guardrail engine with structural validators:

PatternValidatorWhat It Checks
SSNvalidateSSNRejects area=000/666/900+, group=00, serial=0000, known test SSNs
Credit CardvalidateLuhnLuhn checksum, 13-19 digit length
PhoneRegexArea code 2-9, requires separators (prevents matching version numbers)

Outbound messages are now always redacted rather than blocked — even in enforce mode — to avoid discarding useful agent responses over false positives in source code.

Additional Changes

  • TUI: Updated OpenAI provider description to show current model names (GPT 5.4, GPT 5 Mini, GPT 5 Nano)
  • UI: Fixed auth and channel config propagation when starting agents from the web dashboard
  • Docs: Comprehensive documentation sync across tools, skills, channels, and security guardrails

Upgrade

# macOS / Linux
curl -fsSL https://raw.githubusercontent.com/initializ/forge/main/install.sh | bash

# Or build from source
git clone https://github.com/initializ/forge.git && cd forge && make build

Contributors

Built by the Initializ team.


Full Changelog: https://github.com/initializ/forge/compare/v0.7.0…v0.8.0

View on GitHub
v0.7.0

What’s New in Forge v0.7.0

Forge v0.7.0 brings Kubernetes cost optimization, frontend code generation, an interactive skill builder UI, and OpenAI Enterprise support — plus infrastructure improvements that make adding new skills a single-directory operation.


Kubernetes Pod Rightsizer Skill

New embedded skill (k8s-pod-rightsizer) that analyzes real workload metrics and produces deterministic CPU/memory rightsizing recommendations — no LLM guessing.

  • Three modes: dry-run (report only), plan (generate patch YAMLs), apply (execute with automatic rollback bundles)
  • Prometheus p95 metrics with metrics-server fallback
  • Policy model with per-namespace and per-workload overrides (safety factors, min/max bounds, step constraints)
  • Workload classification: over-provisioned, under-provisioned, right-sized, limit-bound, insufficient-data
  • Safety: apply mode requires explicit i_accept_risk: true confirmation; generates rollback bundle before patching

Code Generation Skills

Two new embedded skills for scaffolding and iterating on frontend applications:

  • codegen-react — Vite + React 19 with Tailwind CSS. Four tools: scaffold, run (npm install + dev server with browser auto-open), read, write. Vite hot-reloads on file changes.
  • codegen-html — Preact + HTM with zero local dependencies. Three tools: scaffold (single-file or multi-file mode), read, write. No Node.js, no build step — just open index.html in a browser.

Both skills include Forge dark theme defaults, path traversal prevention, and LLM guardrails to prevent common errors.

Skill Builder UI

Interactive LLM-powered skill creation wizard in the web dashboard:

  • Real-time chat for generating skills from natural language descriptions
  • YAML frontmatter validation with inline error display
  • One-click save to agent configuration
  • Daemon lifecycle — agents now run as forge serve daemon processes via exec.Command, surviving UI shutdown and auto-detected on restart

file_create Builtin Tool

New tool for creating downloadable files that are both written to disk and uploaded to the user’s channel (Slack/Telegram):

  • Files written to agent-scoped .forge/files/ directory (not system temp) via FilesDir context injection
  • Returns a path field so other tools (e.g., kubectl apply -f <path>) can reference created files
  • Supports .json, .yaml, .yml, .txt, .md, .csv, .xml, .html, .sh, .py, .go, .js, .ts

OpenAI Enterprise Organization ID

Full-stack support for routing OpenAI API requests to the correct enterprise organization:

  • organization_id in forge.yaml and OPENAI_ORG_ID env var
  • OpenAI-Organization header on all OpenAI API calls (chat, embeddings, responses)
  • Fallback inheritance with per-fallback override
  • Audit logging includes organization_id field
  • TUI wizard and web dashboard org ID configuration

Skill Metadata Improvements

All 11 embedded skills now declare icon, category, and tags in their SKILL.md frontmatter:

  • Icons flow from SKILL.md → parser → scanner → registry → TUI (no hardcoded map)
  • Adding a new skill never requires touching forge-cli — all metadata is in the SKILL.md
  • Enforced by tests: TestEmbeddedRegistry_AllSkillsHaveIcons and TestEmbeddedRegistry_AllSkillsHaveCategoryAndTags
  • Category and tag filtering: forge skills list --category sre --tags kubernetes

Additional Improvements

  • TUI scroll fix — MultiSelect and SingleSelect components now use viewport-based scrolling with ▲ N more above / ▼ N more below indicators
  • Update notifications — Dashboard checks GitHub Releases API (30-min cache) and shows an animated banner when a newer Forge version is available
  • Slack file handling — Preserves raw content for typed files (JSON, YAML) instead of unwrapping as markdown

Embedded Skills (11 total)

SkillIconCategoryType
github🐙developerbinary-backed
weather🌤️utilitiesbinary-backed
tavily-search🔍researchscript-backed
tavily-research🔬researchscript-backed
k8s-incident-triage☸️srebinary-backed
k8s-pod-rightsizer⚖️srebinary-backed
code-review🔎developerscript-backed
code-review-standards📏developertemplate-based
code-review-github🐙developerbinary-backed
codegen-react⚛️developerscript-backed
codegen-html🌐developerscript-backed

Pull Requests

  • #22 — feat: skill builder UI, daemon lifecycle, update notifications
  • #21 — feat: add k8s-pod-rightsizer skill, file_create tool, and skill metadata improvements
  • #20 — feat: OpenAI Enterprise org ID support and TUI scroll fix
  • #19 — feat: add codegen-react and codegen-html embedded skills

Full Changelog: https://github.com/initializ/forge/compare/v0.6.0…v0.7.0

View on GitHub
v0.6.0

What’s New

Code Review Skill Suite

Three new embedded skills for AI-powered code review:

  • code-review — Script-backed skill with code_review_diff and code_review_file tools. Supports GitHub PR URLs and local git diffs, Anthropic/OpenAI LLM routing, large diff detection, untracked file inclusion, ~ path expansion, and org standards via .forge-review/ directory.
  • code-review-standards — Standards discovery and initialization with templates for config.yaml, ignore patterns, and language-specific rules (Go, Python, TypeScript, security, testing).
  • code-review-github — PR workflow orchestration: list PRs, post inline review comments, apply labels, and guarded auto-merge.

Bug Fix

  • Fixed OneOf env vars not being passed to skill script executor, causing skills with one_of requirements (e.g., ANTHROPIC_API_KEY/OPENAI_API_KEY) to silently fail at runtime.

Full Changelog: https://github.com/initializ/forge/compare/v0.5.0…v0.6.0

View on GitHub