feat: deep-agent canvas, live observability, and multi-environment tooling

Self-hosted platform for building, testing, and shipping LangChain/LangGraph agents. Deep-agent sub-agents on the canvas, a live tracing/observability timeline, auto-provisioned built-in tools with import/export, per-environment tool variables, streamed evaluations, and per-user auth token forwarding.
This commit is contained in:
nihalashetty
2026-07-28 01:49:19 +05:30
commit ae67bff5a3
350 changed files with 58244 additions and 0 deletions
+117
View File
@@ -0,0 +1,117 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "forge/auth_provider",
"type": "object",
"required": ["name", "kind"],
"properties": {
"name": { "$ref": "forge/common#/$defs/Identifier" },
"kind": { "type": "string", "enum": ["csrf_session", "oauth2_client_credentials", "oauth2_authorization_code", "bearer", "basic", "api_key", "custom_script"], "x-ui": { "widget": "segmented" } },
"credentials_ref": { "$ref": "forge/common#/$defs/SecretRef" },
"cache_ttl_seconds": { "type": "integer", "default": 1800 },
"refresh_on": { "type": "array", "items": { "type": "integer" }, "default": [401, 403] },
"per_user_context_keys": { "type": "array", "items": { "type": "string" }, "description": "Context keys (e.g. csrf, session from the widget) that vary the cache key and feed templates as {{ctx.*}}." },
"token_ctx_key": { "type": "string", "description": "Per-user bearer/api_key only: a run-context key (X-Forge-Context) whose value is used as the token when present, taking precedence over the stored per-user connection. Lets a server-to-server caller (e.g. a web chat) forward the acting user's token inline while MCP/console fall back to the stored credential." },
"extra_headers": { "type": "object", "additionalProperties": { "type": "string" }, "x-ui": { "widget": "rows-table", "section": "Extra headers" }, "description": "Fixed headers stamped on every outbound call in addition to the primary auth header. Each value is a literal OR a secret:// ref (resolved from the secret store). Define shared service/attestation headers (e.g. X-Forge-Client-Id, a service token) once here instead of hardcoding them per tool." }
},
"allOf": [
{ "if": { "properties": { "kind": { "const": "csrf_session" } } }, "then": { "$ref": "#/$defs/FetchExtractInject" } },
{ "if": { "properties": { "kind": { "const": "oauth2_client_credentials" } } }, "then": { "$ref": "#/$defs/OAuth2" } },
{ "if": { "properties": { "kind": { "const": "oauth2_authorization_code" } } }, "then": { "$ref": "#/$defs/OAuth2AuthCode" } },
{ "if": { "properties": { "kind": { "const": "bearer" } } }, "then": { "$ref": "#/$defs/Bearer" } },
{ "if": { "properties": { "kind": { "const": "basic" } } }, "then": { "$ref": "#/$defs/Basic" } },
{ "if": { "properties": { "kind": { "const": "api_key" } } }, "then": { "$ref": "#/$defs/ApiKey" } },
{ "if": { "properties": { "kind": { "const": "custom_script" } } }, "then": { "$ref": "#/$defs/CustomScript" } }
],
"$defs": {
"ExtractRule": {
"type": "object",
"required": ["name", "from"],
"properties": {
"name": { "type": "string" },
"from": { "type": "string", "enum": ["header", "cookie", "json"] },
"header": { "type": "string" },
"cookie": { "type": "string" },
"json_path": { "type": "string" },
"kind": { "type": "string", "enum": ["value", "ttl"], "default": "value" }
}
},
"InjectRule": {
"type": "object",
"required": ["to", "name", "value"],
"properties": {
"to": { "type": "string", "enum": ["header", "cookie", "query"] },
"name": { "type": "string" },
"value": { "$ref": "forge/common#/$defs/TemplateString", "x-ui": { "widget": "token-template", "tokens": ["extracted"] } }
}
},
"FetchExtractInject": {
"required": ["token_fetch", "extract", "inject"],
"properties": {
"token_fetch": {
"type": "object",
"required": ["method", "url"],
"x-ui": { "section": "Token fetch" },
"properties": {
"method": { "type": "string", "enum": ["GET", "POST"] },
"url": { "type": "string" },
"headers": { "type": "object", "x-ui": { "widget": "token-template", "tokens": ["cred", "ctx"] } },
"body": { "type": "object", "x-ui": { "widget": "token-template", "tokens": ["cred", "ctx"] } }
}
},
"extract": { "type": "array", "items": { "$ref": "#/$defs/ExtractRule" }, "x-ui": { "widget": "rows-table", "section": "Extract" } },
"inject": { "type": "array", "items": { "$ref": "#/$defs/InjectRule" }, "x-ui": { "widget": "rows-table", "section": "Inject" } }
}
},
"OAuth2": {
"required": ["token_url"],
"properties": {
"token_url": { "type": "string" },
"scope": { "type": "string" },
"client_id_ref": { "$ref": "forge/common#/$defs/SecretRef" },
"client_secret_ref": { "$ref": "forge/common#/$defs/SecretRef" },
"audience": { "type": "string" }
}
},
"OAuth2AuthCode": {
"required": ["authorize_url", "token_url"],
"properties": {
"authorize_url": { "type": "string", "x-ui": { "help": "Provider's authorization endpoint (the user is redirected here to grant access)." } },
"token_url": { "type": "string" },
"scope": { "type": "string" },
"client_id_ref": { "$ref": "forge/common#/$defs/SecretRef" },
"client_secret_ref": { "$ref": "forge/common#/$defs/SecretRef" },
"redirect_uri": { "type": "string", "description": "Optional override; defaults to {oauth_redirect_base}/v1/oauth/callback." },
"header_name": { "type": "string", "default": "Authorization" },
"prefix": { "type": "string", "default": "Bearer " },
"token_bundle_ref": { "$ref": "forge/common#/$defs/SecretRef", "description": "Where the connected access/refresh tokens are stored (set by the connect flow)." }
}
},
"Bearer": {
"properties": {
"token_ref": { "$ref": "forge/common#/$defs/SecretRef" },
"header_name": { "type": "string", "default": "Authorization" },
"prefix": { "type": "string", "default": "Bearer " }
}
},
"Basic": {
"properties": {
"username_ref": { "$ref": "forge/common#/$defs/SecretRef" },
"password_ref": { "$ref": "forge/common#/$defs/SecretRef" }
}
},
"ApiKey": {
"required": ["in", "name"],
"properties": {
"in": { "type": "string", "enum": ["header", "query"] },
"name": { "type": "string" },
"value_ref": { "$ref": "forge/common#/$defs/SecretRef" }
}
},
"CustomScript": {
"required": ["source"],
"properties": {
"source": { "type": "string", "x-ui": { "widget": "code", "language": "python", "help": "RestrictedPython returning {headers, cookies, ttl}. Advanced - audited." } }
}
}
}
}
+68
View File
@@ -0,0 +1,68 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "forge/common",
"$defs": {
"IOType": {
"type": "string",
"enum": ["messages", "text", "json", "tool", "embedding", "vector", "any", "control"],
"description": "Port data type. Connection valid iff source/target compatible; 'any' matches all; 'control' only connects to 'control'."
},
"Port": {
"type": "object",
"required": ["id", "io_type", "direction"],
"properties": {
"id": { "type": "string", "pattern": "^[a-z0-9_]+$" },
"label": { "type": "string" },
"io_type": { "$ref": "forge/common#/$defs/IOType" },
"direction": { "type": "string", "enum": ["in", "out"] },
"required": { "type": "boolean", "default": true },
"many": { "type": "boolean", "default": false, "description": "Accepts/produces multiple connections (e.g. fanout/join control ports)." }
}
},
"ModelRef": {
"type": "string",
"description": "Provider-prefixed model id, e.g. 'anthropic:claude-sonnet-4-6', 'openai:gpt-5.4', 'google_genai:gemini-3.1-pro-preview', or a gateway 'litellm:...'/'openrouter:...'.",
"x-ui": { "widget": "model-picker" }
},
"ContextSize": {
"type": "array",
"minItems": 2,
"maxItems": 2,
"prefixItems": [
{ "type": "string", "enum": ["tokens", "messages", "fraction"] },
{ "type": "number" }
],
"description": "LangChain ContextSize tuple, e.g. ['tokens',4000] or ['fraction',0.8]."
},
"TemplateString": {
"type": "string",
"description": "May contain {{...}} token references resolved at runtime."
},
"Identifier": {
"type": "string",
"pattern": "^[a-zA-Z0-9_-]+$",
"description": "snake_case/kebab; no spaces (provider compatibility)."
},
"SecretRef": {
"type": "string",
"pattern": "^(secret|vault)://.+",
"description": "Reference to a stored secret; never a plaintext value.",
"x-ui": { "widget": "secret-ref" }
},
"Expression": {
"type": "string",
"description": "RestrictedPython expression evaluated over the run state/context. No imports, no attribute escapes."
},
"RetryPolicy": {
"type": "object",
"properties": {
"max_retries": { "type": "integer", "minimum": 0, "default": 2 },
"backoff_factor": { "type": "number", "default": 2.0 },
"initial_delay": { "type": "number", "default": 1.0 },
"max_delay": { "type": "number", "default": 60.0 },
"jitter": { "type": "boolean", "default": true },
"retry_on": { "type": "array", "items": { "type": "string" }, "description": "Failure kinds to retry: exception, timeout, connection, http_error, value_error, key_error, runtime_error. Empty = retry any exception." }
}
}
}
}
+205
View File
@@ -0,0 +1,205 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "forge/middleware",
"$defs": {
"MiddlewareEntry": {
"type": "object",
"required": ["type"],
"properties": {
"type": { "type": "string", "description": "Middleware type key (see config_schemas)." },
"enabled": { "type": "boolean", "default": true },
"config": { "type": "object", "description": "Validated against the per-type schema in config_schemas." }
},
"x-ui": {
"category-map": {
"summarization": "context", "context_editing": "context", "todo": "context",
"pii": "safety", "guardrail_regex": "safety", "openai_moderation": "safety",
"tool_retry": "reliability", "model_retry": "reliability", "model_fallback": "reliability",
"model_call_limit": "cost", "tool_call_limit": "cost", "tenant_budget": "cost", "llm_tool_selector": "cost",
"semantic_cache": "cost",
"human_in_the_loop": "human",
"anthropic_prompt_caching": "provider",
"tool_emulator": "testing",
"dynamic_model_by_state": "advanced", "tool_filter_by_context": "advanced"
}
}
}
},
"config_schemas": {
"summarization": {
"type": "object",
"properties": {
"model": { "$ref": "forge/common#/$defs/ModelRef" },
"trigger": { "oneOf": [{ "$ref": "forge/common#/$defs/ContextSize" }, { "type": "array", "items": { "$ref": "forge/common#/$defs/ContextSize" } }] },
"keep": { "$ref": "forge/common#/$defs/ContextSize" },
"summary_prompt": { "type": "string", "x-ui": { "widget": "textarea", "advanced": true } }
},
"x-ui": { "help": "Summarize older messages when the conversation grows past the trigger; keep the most recent." }
},
"human_in_the_loop": {
"type": "object",
"required": ["interrupt_on"],
"properties": {
"interrupt_on": {
"type": "object",
"additionalProperties": {
"oneOf": [
{ "type": "boolean" },
{ "type": "object", "properties": { "allowed_decisions": { "type": "array", "items": { "type": "string", "enum": ["approve", "edit", "reject"] } } } }
]
},
"x-ui": { "widget": "rows-table", "columns": ["tool", "allowed_decisions"] }
}
},
"x-ui": { "help": "Pause for human approval before chosen tools run. Requires a checkpointer." }
},
"model_call_limit": {
"type": "object",
"properties": {
"thread_limit": { "type": "integer" },
"run_limit": { "type": "integer" },
"exit_behavior": { "type": "string", "enum": ["end", "error"], "default": "end" }
}
},
"tool_call_limit": {
"type": "object",
"properties": {
"tool_name": { "type": "string", "x-ui": { "widget": "reference", "refType": "tool" } },
"thread_limit": { "type": "integer" },
"run_limit": { "type": "integer" },
"exit_behavior": { "type": "string", "enum": ["continue", "error", "end"], "default": "continue" }
}
},
"model_fallback": {
"type": "object",
"required": ["models"],
"properties": {
"models": { "type": "array", "items": { "$ref": "forge/common#/$defs/ModelRef" }, "minItems": 1, "x-ui": { "widget": "chips", "help": "Tried in order if the primary model fails." } }
}
},
"pii": {
"type": "object",
"required": ["pii_type"],
"properties": {
"pii_type": { "type": "string", "description": "email|credit_card|ip|mac_address|url|<custom>" },
"strategy": { "type": "string", "enum": ["block", "redact", "mask", "hash"], "default": "redact" },
"detector": { "type": "string", "description": "Regex pattern for custom types.", "x-ui": { "widget": "mono", "advanced": true } },
"apply_to_input": { "type": "boolean", "default": true },
"apply_to_output": { "type": "boolean", "default": false },
"apply_to_tool_results": { "type": "boolean", "default": false }
}
},
"todo": {
"type": "object",
"properties": { "system_prompt": { "type": "string", "x-ui": { "widget": "textarea", "advanced": true } } }
},
"llm_tool_selector": {
"type": "object",
"properties": {
"model": { "$ref": "forge/common#/$defs/ModelRef" },
"max_tools": { "type": "integer" },
"always_include": { "type": "array", "items": { "type": "string" }, "x-ui": { "widget": "chips", "refType": "tool" } }
},
"x-ui": { "help": "Pre-select relevant tools before the main model call (saves tokens when many tools exist)." }
},
"tool_retry": {
"allOf": [{ "$ref": "forge/common#/$defs/RetryPolicy" }],
"properties": {
"tools": { "type": "array", "items": { "type": "string" }, "x-ui": { "widget": "chips", "refType": "tool" } },
"on_failure": { "type": "string", "enum": ["return_message", "raise", "continue"], "default": "return_message" }
}
},
"model_retry": {
"allOf": [{ "$ref": "forge/common#/$defs/RetryPolicy" }],
"properties": { "on_failure": { "type": "string", "enum": ["continue", "error"], "default": "continue" } }
},
"tool_emulator": {
"type": "object",
"properties": {
"tools": { "type": "array", "items": { "type": "string" }, "x-ui": { "widget": "chips", "refType": "tool" } },
"model": { "$ref": "forge/common#/$defs/ModelRef" }
},
"x-ui": { "help": "Emulate tool outputs with an LLM (no real calls). For testing." }
},
"context_editing": {
"type": "object",
"properties": {
"edits": {
"type": "array",
"items": {
"type": "object",
"properties": {
"trigger": { "type": "integer", "default": 100000 },
"keep": { "type": "integer", "default": 3 },
"clear_at_least": { "type": "integer", "default": 0 },
"clear_tool_inputs": { "type": "boolean", "default": false },
"exclude_tools": { "type": "array", "items": { "type": "string" } },
"placeholder": { "type": "string", "default": "[cleared]" }
}
}
}
},
"x-ui": { "help": "Clear older tool outputs once the conversation passes the trigger; keep the most recent N." }
},
"anthropic_prompt_caching": {
"type": "object",
"properties": { "ttl": { "type": "string", "enum": ["5m", "1h"], "default": "5m" } },
"x-ui": { "help": "Cache the system prompt block on Anthropic models (cost/latency).", "showWhen": "model.startsWith(anthropic)" }
},
"openai_moderation": {
"type": "object",
"properties": {
"apply_to_input": { "type": "boolean", "default": true },
"apply_to_output": { "type": "boolean", "default": true }
}
},
"dynamic_model_by_state": {
"type": "object",
"required": ["rules"],
"properties": {
"rules": {
"type": "array",
"items": { "type": "object", "properties": { "when": { "$ref": "forge/common#/$defs/Expression" }, "use": { "$ref": "forge/common#/$defs/ModelRef" } } },
"x-ui": { "widget": "rows-table" }
},
"default": { "$ref": "forge/common#/$defs/ModelRef" }
},
"x-ui": { "help": "Switch model at runtime based on state (compiles to @wrap_model_call)." }
},
"tool_filter_by_context": {
"type": "object",
"properties": {
"expose_when": { "type": "object", "description": "e.g. {role:['admin']} matched against runtime.context." },
"tools": { "type": "array", "items": { "type": "string" }, "x-ui": { "widget": "chips", "refType": "tool" } }
},
"x-ui": { "help": "Show/hide tools at runtime by context (auth state, role, flags)." }
},
"guardrail_regex": {
"type": "object",
"properties": {
"patterns": { "type": "array", "items": { "type": "string" }, "x-ui": { "widget": "chips" } },
"on_match": { "type": "string", "enum": ["block", "redact", "flag"], "default": "block" },
"apply_to": { "type": "string", "enum": ["input", "output", "both"], "default": "output" }
}
},
"tenant_budget": {
"type": "object",
"properties": {
"max_usd_per_thread": { "type": "number" },
"max_tokens_per_run": { "type": "integer" },
"on_exceed": { "type": "string", "enum": ["end", "error"], "default": "end" }
},
"x-ui": { "help": "Stop the run when accumulated cost/tokens exceed the cap (before_model)." }
},
"semantic_cache": {
"type": "object",
"properties": {
"threshold": { "type": "number", "minimum": 0, "maximum": 1, "default": 0.95, "description": "Minimum cosine similarity for a cached answer to be reused. High by design - a wrong cache hit is worse than a miss." },
"ttl": { "type": "integer", "default": 3600, "description": "Seconds a cached answer stays valid." },
"scope": { "type": "string", "default": "default", "description": "Cache partition (e.g. per-locale/per-tenant-segment); questions only match within the same scope." },
"min_question_chars": { "type": "integer", "default": 8, "description": "Skip caching for questions shorter than this (too generic to key on)." }
},
"x-ui": { "help": "Reuse a previous answer when a semantically-equivalent question was already answered - skips the model/tool loop entirely on a hit." }
}
}
}
+173
View File
@@ -0,0 +1,173 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "forge/nodes/agent",
"type": "object",
"anyOf": [{ "required": ["model"] }, { "required": ["agent_ref"] }],
"properties": {
"agent_ref": { "type": "string", "description": "Id of a saved Agent preset this node mirrors. When set, the saved agent's config drives this node (live - edits in the Agents tab apply).", "x-ui": { "widget": "reference", "refType": "agent", "section": "Flavor", "order": 0 } },
"flavor": { "type": "string", "enum": ["agent", "deep_agent"], "default": "agent", "x-ui": { "widget": "segmented", "order": 1, "section": "Flavor" } },
"name": { "$ref": "forge/common#/$defs/Identifier", "x-ui": { "section": "Flavor" } },
"description": { "type": "string", "description": "When this agent is used as a deep-agent sub-agent, the supervisor reads this to decide when to call it (like a tool description).", "x-ui": { "section": "Flavor", "widget": "textarea" } },
"model": { "$ref": "forge/common#/$defs/ModelRef", "x-ui": { "section": "Model", "order": 2 } },
"model_params": {
"type": "object",
"x-ui": { "section": "Model", "advanced": true },
"properties": {
"temperature": { "type": "number" },
"max_tokens": { "type": "integer" },
"timeout": { "type": "integer" },
"base_url": { "type": "string" }
}
},
"dynamic_model": {
"type": "object",
"x-ui": { "section": "Model", "advanced": true },
"description": "Compiles to a @wrap_model_call middleware.",
"properties": {
"enabled": { "type": "boolean", "default": false },
"rules": {
"type": "array",
"items": { "type": "object", "required": ["when", "use"], "properties": { "when": { "$ref": "forge/common#/$defs/Expression" }, "use": { "$ref": "forge/common#/$defs/ModelRef" } } },
"x-ui": { "widget": "rows-table" }
},
"default": { "$ref": "forge/common#/$defs/ModelRef" }
}
},
"system_prompt": { "type": "string", "x-ui": { "widget": "textarea", "section": "Instructions", "order": 3, "modelView": true } },
"dynamic_prompt": {
"type": "object",
"x-ui": { "section": "Instructions", "advanced": true },
"properties": {
"enabled": { "type": "boolean" },
"rules": {
"type": "array",
"items": { "type": "object", "properties": { "when": { "$ref": "forge/common#/$defs/Expression" }, "prompt": { "type": "string" } } },
"x-ui": { "widget": "rows-table" }
}
}
},
"tools": { "type": "array", "items": { "type": "string" }, "description": "Tool ids (incl. MCP tool ids) available to the agent.", "x-ui": { "widget": "chips", "refType": "tool", "section": "Tools", "order": 4 } },
"toolsets": { "type": "array", "items": { "type": "string" }, "description": "Tool set ids granted to the agent - every tool in each set becomes available (in addition to any individually-listed tools).", "x-ui": { "widget": "chips", "refType": "tool_set", "section": "Tools", "order": 4.5 } },
"knowledge": {
"type": "object",
"description": "Built-in knowledge-base access for the agent (no separate Tool needed). Compiles to agent-callable tools: RAG over documents and/or curated Q&A lookup, each scoped by folders/kinds. Prefer this over a fixed retrieval node for conversational or multi-part agents.",
"x-ui": { "section": "Knowledge", "order": 5 },
"properties": {
"rag": {
"type": "object",
"description": "Document search (RAG) as a tool the agent calls per sub-question.",
"properties": {
"enabled": { "type": "boolean", "default": false },
"folders": { "type": "array", "items": { "type": "string" }, "x-ui": { "widget": "chips", "refType": "kb_folder" }, "description": "Restrict to these knowledge folders (empty = all)." },
"top_k": { "type": "integer", "default": 4, "description": "Document chunks returned per search." },
"min_score": { "type": "number", "default": 0.18, "description": "Drop chunks below this cosine similarity so off-topic queries surface nothing. Not applied when rerank is on (the cross-encoder score is on a different scale)." },
"hybrid": { "type": "boolean", "default": false, "description": "Blend lexical BM25 keyword ranking with vector search (RRF) so exact terms - codes, names, SKUs - aren't missed." },
"rerank": { "type": "boolean", "default": false, "description": "Two-stage retrieval: a local cross-encoder re-scores the shortlist and keeps only the best matches. Big accuracy boost; adds some latency. Runs offline on CPU (no extra cost)." },
"rerank_top_n": { "type": "integer", "description": "How many chunks to pull into the rerank shortlist before the cross-encoder trims them to top_k (only when rerank is on). Leave unset to auto-size (roughly 6× top_k, minimum 25)." }
}
},
"qa": {
"type": "object",
"description": "FAQ / Q&A lookup as a tool the agent calls for common questions.",
"properties": {
"enabled": { "type": "boolean", "default": false },
"kinds": { "type": "array", "items": { "type": "string" }, "x-ui": { "widget": "chips", "refType": "qa_kind" }, "description": "Restrict to these Q&A kinds/categories (empty = all)." },
"threshold": { "type": "number", "default": 0.3, "description": "Minimum similarity for a Q&A pair to be returned." },
"top_k": { "type": "integer", "default": 3, "description": "Q&A pairs returned per lookup." }
}
}
}
},
"mcp_servers": {
"type": "array",
"items": { "type": "string" },
"description": "Registered MCP servers (by id) this agent can use - each server's enabled tools become agent-callable.",
"x-ui": { "widget": "chips", "refType": "mcp_client", "section": "MCP Servers", "order": 5.5 }
},
"response_format": { "$ref": "#/$defs/ResponseFormat", "x-ui": { "section": "Output", "order": 6 } },
"middleware": { "type": "array", "items": { "$ref": "forge/middleware#/$defs/MiddlewareEntry" }, "x-ui": { "widget": "middleware-stack", "section": "Middleware", "order": 6 } },
"memory": {
"type": "object",
"x-ui": { "section": "Memory", "order": 7 },
"properties": {
"long_term": { "type": "boolean", "default": false, "x-ui": { "effect": "Persist memory across conversations" } },
"store_namespace": { "type": "string" },
"state_extensions": {
"type": "array",
"items": { "type": "object", "properties": { "name": { "type": "string" }, "type": { "type": "string" }, "reducer": { "type": "string" } } },
"x-ui": { "widget": "rows-table" }
}
}
},
"planning": { "type": "boolean", "default": true, "x-ui": { "section": "Deep Agent", "order": 8, "effect": "Adds write_todos planning tool", "showWhen": "flavor=deep_agent" } },
"subagents": { "type": "array", "items": { "$ref": "#/$defs/SubAgent" }, "x-ui": { "section": "Deep Agent", "order": 9, "showWhen": "flavor=deep_agent" } },
"filesystem": { "$ref": "#/$defs/FilesystemBackend", "x-ui": { "section": "Deep Agent", "order": 10, "showWhen": "flavor=deep_agent" } },
"sandbox": { "$ref": "#/$defs/SandboxConfig", "x-ui": { "section": "Deep Agent", "order": 11, "showWhen": "flavor=deep_agent" } },
"skills": { "type": "array", "items": { "type": "string" }, "x-ui": { "section": "Deep Agent", "showWhen": "flavor=deep_agent" } },
"permissions": {
"type": "array",
"items": { "type": "object", "properties": { "path": { "type": "string" }, "access": { "type": "string", "enum": ["read", "write", "none"] } } },
"x-ui": { "widget": "rows-table", "section": "Deep Agent", "showWhen": "flavor=deep_agent" }
}
},
"$defs": {
"ResponseFormat": {
"oneOf": [
{ "type": "object", "properties": { "mode": { "const": "freeform" } } },
{ "type": "object", "required": ["mode", "schema"], "properties": { "mode": { "const": "structured" }, "strategy": { "type": "string", "enum": ["auto", "provider", "tool"], "default": "auto" }, "schema": { "type": "object", "description": "JSON Schema of the desired structured output." } } }
],
"x-ui": { "widget": "kind-switcher", "discriminator": "mode" }
},
"SubAgent": {
"oneOf": [
{
"type": "object",
"required": ["name", "description"],
"properties": {
"name": { "$ref": "forge/common#/$defs/Identifier" },
"description": { "type": "string", "x-ui": { "modelView": true } },
"system_prompt": { "type": "string", "x-ui": { "widget": "textarea" } },
"tools": { "type": "array", "items": { "type": "string" }, "x-ui": { "widget": "chips", "refType": "tool" } },
"toolsets": { "type": "array", "items": { "type": "string" }, "x-ui": { "widget": "chips", "refType": "tool_set" } },
"model": { "$ref": "forge/common#/$defs/ModelRef" },
"middleware": { "type": "array", "items": { "$ref": "forge/middleware#/$defs/MiddlewareEntry" } }
}
},
{
"type": "object",
"required": ["name", "workflow_ref"],
"properties": {
"name": { "$ref": "forge/common#/$defs/Identifier" },
"description": { "type": "string" },
"workflow_ref": { "type": "object", "properties": { "workflow_id": { "type": "string" }, "version": { "type": "integer" } }, "description": "Wrap a compiled workflow as CompiledSubAgent.", "x-ui": { "widget": "reference", "refType": "workflow" } }
}
}
]
},
"FilesystemBackend": {
"type": "object",
"properties": {
"kind": { "type": "string", "enum": ["state", "disk", "store", "composite"], "default": "state" },
"routes": { "type": "object", "additionalProperties": { "type": "string", "enum": ["state", "disk", "store"] }, "description": "For composite, e.g. {'/memories/':'store'}." }
}
},
"SandboxConfig": {
"type": "object",
"properties": {
"kind": { "type": "string", "enum": ["none", "docker", "remote"], "default": "none" },
"provider": { "type": "string", "enum": ["e2b", "modal", "daytona", "runloop", "agentcore"], "x-ui": { "showWhen": "kind=remote" } },
"pattern": { "type": "string", "enum": ["agent_in_sandbox", "sandbox_as_tool"], "default": "sandbox_as_tool" },
"limits": { "$ref": "#/$defs/Limits" }
}
},
"Limits": {
"type": "object",
"properties": {
"cpu_seconds": { "type": "integer", "default": 10 },
"memory_mb": { "type": "integer", "default": 512 },
"timeout_seconds": { "type": "integer", "default": 30 },
"egress_allowlist": { "type": "array", "items": { "type": "string" } }
}
}
}
}
@@ -0,0 +1,14 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "forge/nodes/classifier",
"type": "object",
"required": ["labels"],
"properties": {
"labels": { "type": "array", "items": { "type": "string" }, "minItems": 1, "description": "The intent labels to classify into (e.g. return_item, cancel_subscription, get_information)." },
"output_key": { "type": "string", "default": "intent", "description": "State field the chosen label is written to - a Router downstream branches on it." },
"multi_label": { "type": "boolean", "default": false, "x-ui": { "effect": "Write EVERY applicable label (a list) instead of exactly one - pair with a Router set to 'multi' so a question with several intents fans out to every matching branch in parallel." } },
"model": { "$ref": "forge/common#/$defs/ModelRef", "description": "Empty = project default model." },
"instructions": { "type": "string", "description": "Optional extra guidance for the classification (domain hints, tie-breaking rules)." }
},
"$comment": "ports: in:text, out:text. Routing pattern: classifier -> router(expression=output_key, cases per label). multi_label writes list[str]."
}
@@ -0,0 +1,11 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "forge/nodes/emit_event",
"type": "object",
"required": ["channel"],
"properties": {
"channel": { "type": "string" },
"payload": { "type": "object" }
},
"$comment": "ports: in:any, out:any (pushes a custom SSE frame)"
}
+8
View File
@@ -0,0 +1,8 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "forge/nodes/end",
"type": "object",
"properties": {},
"additionalProperties": false,
"$comment": "ports: in:control"
}
@@ -0,0 +1,9 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "forge/nodes/handoff",
"type": "object",
"properties": {
"reason": { "type": "string", "default": "Escalated to a human agent.", "x-ui": { "help": "Why the conversation is being handed off (shown in the agent inbox)." } },
"ack_message": { "type": "string", "x-ui": { "help": "Optional message sent to the customer while they wait for a human." } }
}
}
@@ -0,0 +1,15 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "forge/nodes/human_input",
"type": "object",
"required": ["prompt"],
"properties": {
"prompt": { "type": "string", "x-ui": { "widget": "textarea" } },
"schema": { "type": "object", "description": "Optional structured input schema requested from the human." },
"allowed_decisions": { "type": "array", "items": { "type": "string", "enum": ["approve", "edit", "reject"] }, "default": ["approve", "reject"] },
"timeout_seconds": { "type": "integer", "minimum": 1, "description": "Optional approval deadline in seconds (overrides the global HITL timeout)." },
"timeout_default": { "type": "string", "enum": ["approve", "edit", "reject"], "description": "Decision used to resume automatically when the approval deadline expires. Leave empty to fail the run instead." },
"output_key": { "type": "string", "description": "Optional state key the decision string is written to, so a Router can branch approve → continue / reject → end." }
},
"$comment": "ports: in:any, out:any (calls interrupt(); requires checkpointer)"
}
+11
View File
@@ -0,0 +1,11 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "forge/nodes/join",
"type": "object",
"properties": {
"reducer": { "type": "string", "enum": ["concat", "merge", "first", "last"], "default": "concat" },
"input_key": { "type": "string", "description": "State key holding the fan-out results to re-aggregate. Omit to use the join purely as a convergence marker (results are then aggregated by that key's own add-reducer)." },
"output_key": { "type": "string", "description": "State key to write the aggregated result to (defaults to input_key). Point at a 'last'-reducer field to overwrite cleanly rather than append." }
},
"$comment": "ports: in:control[], out:json"
}
+13
View File
@@ -0,0 +1,13 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "forge/nodes/llm",
"type": "object",
"required": ["model", "prompt"],
"properties": {
"model": { "$ref": "forge/common#/$defs/ModelRef" },
"prompt": { "$ref": "forge/common#/$defs/TemplateString", "x-ui": { "widget": "textarea", "modelView": true } },
"model_params": { "type": "object" },
"response_format": { "$ref": "forge/nodes/agent#/$defs/ResponseFormat" }
},
"$comment": "ports: in:[text,json], out:[text,json]"
}
+11
View File
@@ -0,0 +1,11 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "forge/nodes/loop",
"type": "object",
"required": ["condition", "max_iter"],
"properties": {
"condition": { "$ref": "forge/common#/$defs/Expression" },
"max_iter": { "type": "integer", "default": 10 }
},
"$comment": "ports: in:any, out:any (allows_cycle = true)"
}
@@ -0,0 +1,15 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "forge/nodes/parallel_fanout",
"type": "object",
"required": ["over", "child_node", "item_key"],
"properties": {
"over": { "type": "string", "description": "State key holding a list." },
"child_node": { "type": "string", "description": "Node id to run per item via Send." },
"item_key": { "type": "string", "description": "State key the item is written to for each child." },
"index_key": { "type": "string", "default": "_fanout_index", "description": "Key each child receives its 0-based input index under (also gets _fanout_total), so results can be reordered deterministically despite parallel completion order." },
"on_item_error": { "type": "string", "enum": ["fail", "skip"], "default": "fail", "description": "fail: one item's error aborts the whole batch (default). skip: isolate it - log and drop that item's contribution, keep the rest. (Also enabled by workflow error_policy 'continue'.)" },
"item_timeout_seconds": { "type": "number", "description": "Optional per-item timeout. Applies to async children / subgraphs (sync children can't be preempted). A timed-out item follows on_item_error." }
},
"$comment": "ports: in:json, out:control[]"
}
@@ -0,0 +1,25 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "forge/nodes/retrieval",
"type": "object",
"properties": {
"include_docs": { "type": "boolean", "default": true, "x-ui": { "effect": "Search the knowledge-base DOCUMENTS (RAG). Turn off for a Q&A-only node that pulls only curated FAQ pairs." } },
"top_k": { "type": "integer", "default": 5, "description": "How many knowledge-base document chunks to retrieve (only when include_docs is true)." },
"hybrid": { "type": "boolean", "default": false, "x-ui": { "effect": "Hybrid search: blend lexical BM25 keyword matching with semantic vectors (RRF). Catches exact terms - codes, names, SKUs - that pure vector search can miss." } },
"rerank": { "type": "boolean", "default": false, "x-ui": { "effect": "Two-stage retrieval: after the initial search, a local cross-encoder re-scores the shortlist and keeps only the best matches. Big accuracy boost; adds some latency. Runs offline on CPU (no extra cost)." } },
"rerank_top_n": { "type": "integer", "description": "How many chunks to pull into the rerank shortlist before the cross-encoder trims them to top_k (only when rerank is true). Leave unset to auto-size the shortlist (roughly 6× top_k, minimum 25)." },
"rerank_min_score": { "type": "number", "default": 0.3, "description": "Drop reranked chunks below this cross-encoder relevance (0-1 sigmoid scale) so an off-topic query still yields nothing when rerank is on. Only applied when rerank is true (min_score is a cosine floor on a different scale and is skipped then)." },
"mmr": { "type": "boolean", "default": false, "x-ui": { "effect": "Diversify results (Maximal Marginal Relevance): trade a little relevance to avoid returning several near-duplicate chunks. Useful when one topic dominates the knowledge base." } },
"mmr_lambda": { "type": "number", "default": 0.5, "description": "MMR relevance/diversity balance (0-1): 1.0 = pure relevance, lower = more diverse. Only applied when mmr is true." },
"include_qa": { "type": "boolean", "default": false, "x-ui": { "effect": "Also pull matching Q&A pairs into context - retrieval then grounds the agent on BOTH KB docs and Q&A." } },
"qa_threshold": { "type": "number", "default": 0.3, "description": "Minimum similarity for a Q&A pair to be included (only when include_qa is true)." },
"qa_top_k": { "type": "integer", "default": 3, "description": "How many Q&A pairs to retrieve (only when include_qa is true)." },
"qa_kinds": { "type": "array", "items": { "type": "string" }, "x-ui": { "widget": "chips", "refType": "qa_kind" }, "description": "Only include Q&A pairs of these kinds/categories (empty = all; only when include_qa is true)." },
"min_score": { "type": "number", "default": 0.6, "description": "Drop document chunks below this cosine similarity so off-topic queries surface nothing. Calibrated for the default local BGE embedder (related pairs ~0.75+, unrelated ~0.4-0.5); lower it for a hosted model on a smaller cosine scale. In hybrid mode it is applied to the underlying cosine (not the fused rank). Not applied when rerank is on (see rerank_min_score)." },
"announce_empty": { "type": "boolean", "default": false, "x-ui": { "effect": "When nothing relevant is found, inject a note so a grounded agent says it doesn't know instead of guessing." } },
"source_filter": { "type": "array", "items": { "type": "string" }, "x-ui": { "widget": "chips", "refType": "kb_source" }, "description": "Restrict retrieval to specific knowledge sources." },
"folders": { "type": "array", "items": { "type": "string" }, "x-ui": { "widget": "chips", "refType": "kb_folder" }, "description": "Restrict retrieval to sources in these knowledge folders (empty = all)." },
"route_key": { "type": "string", "description": "Optional state key set to 'yes'/'no' (anything found?) so a Router can branch found → agent / not-found → escalation." }
},
"$comment": "ports: in:text, out:json. Fields mirror forge/nodes/rag.py retrieval_factory."
}
+13
View File
@@ -0,0 +1,13 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "forge/nodes/router",
"type": "object",
"required": ["expression", "cases"],
"properties": {
"expression": { "$ref": "forge/common#/$defs/Expression", "x-ui": { "help": "Evaluated over state; result matched against cases." } },
"cases": { "type": "object", "additionalProperties": { "type": "string" }, "x-ui": { "widget": "rows-table", "columns": ["value", "target"] } },
"default": { "type": "string", "description": "Fallback target node id." },
"multi": { "type": "boolean", "default": false, "x-ui": { "effect": "When the expression value is a LIST (multi-label Classifier output), route to EVERY matching case in parallel instead of one." } }
},
"$comment": "ports: in:any, out: one control port per case + default"
}
+8
View File
@@ -0,0 +1,8 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "forge/nodes/start",
"type": "object",
"properties": {},
"additionalProperties": false,
"$comment": "ports: out:control"
}
@@ -0,0 +1,13 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "forge/nodes/subworkflow",
"type": "object",
"required": ["workflow_id"],
"properties": {
"workflow_id": { "type": "string", "x-ui": { "widget": "reference", "refType": "workflow" } },
"version": { "type": "integer" },
"input_mapping": { "type": "object", "x-ui": { "widget": "rows-table" } },
"output_mapping": { "type": "object", "x-ui": { "widget": "rows-table" } }
},
"$comment": "ports: in:[messages,json], out:[messages,json]"
}
@@ -0,0 +1,12 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "forge/nodes/tool_call",
"type": "object",
"required": ["tool_id"],
"properties": {
"tool_id": { "type": "string", "x-ui": { "widget": "reference", "refType": "tool" } },
"input_mapping": { "type": "object", "description": "Maps state/inputs -> tool args (JMESPath/templates).", "x-ui": { "widget": "rows-table" } },
"handle_tool_errors": { "oneOf": [{ "type": "boolean" }, { "type": "string" }], "default": true }
},
"$comment": "ports: in:json, out:json"
}
@@ -0,0 +1,13 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "forge/nodes/transform",
"type": "object",
"required": ["expression"],
"properties": {
"engine": { "type": "string", "enum": ["jmespath", "jq"], "default": "jmespath" },
"expression": { "type": "string", "x-ui": { "widget": "jmespath", "tokenMeter": false } },
"input_key": { "type": "string", "description": "State key to read as input (omit to use whole state)." },
"output_key": { "type": "string", "default": "data", "description": "State key to write the result to (must be a declared state field)." }
},
"$comment": "ports: in:json, out:json"
}
@@ -0,0 +1,14 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "forge/nodes/trigger_app_event",
"type": "object",
"required": ["poll_url"],
"properties": {
"poll_url": { "type": "string", "x-ui": { "help": "URL polled on each interval; new items fire the workflow." } },
"method": { "type": "string", "enum": ["GET", "POST"], "default": "GET" },
"interval_minutes": { "type": "integer", "minimum": 1, "default": 5 },
"items_path": { "type": "string", "x-ui": { "help": "JMESPath to the list of items in the response (empty = the whole body)." } },
"dedupe_key": { "type": "string", "x-ui": { "help": "JMESPath within each item for its unique id (so each item fires once)." } },
"message_path": { "type": "string", "x-ui": { "help": "JMESPath within an item for the message text (empty = the whole item as JSON)." } }
}
}
@@ -0,0 +1,10 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "forge/nodes/trigger_email",
"type": "object",
"properties": {
"mailbox": { "type": "string", "x-ui": { "help": "Address/label this trigger handles (informational; routing is per inbound-mailbox config)." } },
"include_subject": { "type": "boolean", "default": true },
"reply": { "type": "boolean", "default": true, "x-ui": { "help": "Send the workflow's answer back as a threaded email reply." } }
}
}
@@ -0,0 +1,10 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "forge/nodes/trigger_schedule",
"type": "object",
"properties": {
"every_minutes": { "type": "integer", "minimum": 1, "x-ui": { "help": "Run every N minutes. Use this OR cron." } },
"cron": { "type": "string", "x-ui": { "help": "Cron expression (min hour dom mon dow). Requires croniter; otherwise use every_minutes." } },
"message": { "type": "string", "x-ui": { "help": "The input message sent into the workflow on each fire." } }
}
}
@@ -0,0 +1,10 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "forge/nodes/trigger_webhook",
"type": "object",
"properties": {
"message_path": { "type": "string", "x-ui": { "help": "JMESPath into the webhook JSON body to use as the user message (empty = the whole body)." } },
"require_signature": { "type": "boolean", "default": false, "x-ui": { "help": "Require a valid HMAC-SHA256 signature (X-Forge-Signature) computed with the secret." } },
"secret_ref": { "$ref": "forge/common#/$defs/SecretRef" }
}
}
@@ -0,0 +1,15 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "forge/nodes/webhook_out",
"type": "object",
"required": ["url", "method"],
"properties": {
"url": { "$ref": "forge/common#/$defs/TemplateString" },
"method": { "type": "string", "enum": ["GET", "POST", "PUT", "PATCH", "DELETE"] },
"auth_provider_id": { "type": "string", "x-ui": { "widget": "reference", "refType": "auth_provider" } },
"body": { "type": "object" },
"headers": { "type": "object" },
"output_key": { "type": "string", "default": "webhook_result", "description": "State key to write the response to (must be a declared state field)." }
},
"$comment": "ports: in:json, out:json"
}
+52
View File
@@ -0,0 +1,52 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "forge/project",
"type": "object",
"properties": {
"default_model": { "$ref": "forge/common#/$defs/ModelRef" },
"allowed_models": { "type": "array", "items": { "$ref": "forge/common#/$defs/ModelRef" }, "x-ui": { "widget": "chips" } },
"provider_credentials": {
"type": "object",
"additionalProperties": { "$ref": "forge/common#/$defs/SecretRef" },
"description": "provider -> secret ref, e.g. {openai:'secret://proj/openai_key'}"
},
"budgets": {
"type": "object",
"properties": {
"max_usd_per_run": { "type": "number" },
"max_tokens_per_run": { "type": "integer" },
"monthly_usd_cap": { "type": "number" },
"alert_at_fraction": { "type": "number", "default": 0.8 }
}
},
"default_middleware": { "type": "array", "items": { "$ref": "forge/middleware#/$defs/MiddlewareEntry" }, "x-ui": { "widget": "middleware-stack" } },
"data_region": { "type": "string" },
"rag_defaults": {
"type": "object",
"properties": {
"embedding_model": { "type": "string", "default": "fastembed:BAAI/bge-small-en-v1.5", "x-ui": { "effect": "Embedder for RAG. Default 'fastembed:BAAI/bge-small-en-v1.5' is local, open-source, and free (runs offline). 'openai:text-embedding-3-small|large' is billed per token at ingest AND per query. Changing the model changes the vector dimension, so existing sources must be re-embedded." } },
"chunk_size": { "type": "integer", "default": 1000 },
"chunk_overlap": { "type": "integer", "default": 200 },
"chunking_strategy": { "type": "string", "enum": ["recursive", "section", "sentence", "semantic"], "default": "recursive", "x-ui": { "effect": "Default chunking when a source doesn't specify one: recursive (general), section (Markdown headings), sentence (sentence-aware), semantic (splits where meaning shifts - uses the embedder, slower to ingest)." } },
"retrieval_mode": { "type": "string", "enum": ["chunk", "parent_child"], "default": "chunk", "x-ui": { "effect": "chunk: search and return the same chunks (default). parent_child: embed small child chunks for precise matching but feed the agent the larger parent passage for context. Changing this requires re-ingesting existing sources." } },
"child_chunk_size": { "type": "integer", "default": 300, "description": "Size (chars) of the small child chunks that get embedded in parent_child mode. The parent window uses chunk_size. Ignored in chunk mode." },
"reranker_model": { "type": "string", "default": "Xenova/ms-marco-MiniLM-L-6-v2", "x-ui": { "effect": "Local cross-encoder used when a retrieval node has rerank on. Default 'Xenova/ms-marco-MiniLM-L-6-v2' is small and CPU-fast; 'BAAI/bge-reranker-base' is heavier and more accurate. Runs offline, no API cost." } }
}
},
"features": {
"type": "object",
"properties": {
"code_nodes": { "type": "boolean", "default": false },
"remote_sandbox": { "type": "boolean", "default": false },
"advanced_scripts": { "type": "boolean", "default": false }
}
},
"tracing": {
"type": "object",
"properties": {
"retention_days": { "type": "integer", "default": 30 },
"langfuse_otlp_url": { "type": "string" }
}
}
}
}
+114
View File
@@ -0,0 +1,114 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "forge/tool",
"type": "object",
"required": ["name", "description", "kind"],
"properties": {
"name": { "$ref": "forge/common#/$defs/Identifier", "x-ui": { "section": "Identity", "modelView": false } },
"display_name": { "type": "string", "x-ui": { "section": "Identity", "modelView": false, "help": "Human-readable name shown in chat/streaming activity. The model still calls the tool by its identifier above; leave blank to show the identifier." } },
"description": { "type": "string", "x-ui": { "section": "Identity", "widget": "textarea", "modelView": true, "help": "This is what the model reads to decide when to use the tool." } },
"kind": { "type": "string", "enum": ["rest_api", "graphql", "code", "sql", "builtin", "mcp"], "x-ui": { "section": "Identity", "widget": "segmented" } },
"auth_provider_id": { "type": "string", "x-ui": { "section": "Auth", "widget": "reference", "refType": "auth_provider" } },
"rate_limit": { "type": "object", "properties": { "per_minute": { "type": "integer" } }, "x-ui": { "section": "Reliability" } },
"timeout_seconds": { "type": "integer", "default": 30, "x-ui": { "section": "Reliability" } },
"retry": { "$ref": "forge/common#/$defs/RetryPolicy", "x-ui": { "section": "Reliability", "advanced": true } },
"cache": { "type": "object", "properties": { "ttl_seconds": { "type": "integer", "default": 0 } }, "x-ui": { "section": "Reliability", "advanced": true } }
},
"allOf": [
{ "if": { "properties": { "kind": { "const": "rest_api" } } }, "then": { "$ref": "#/$defs/RestConfig" } },
{ "if": { "properties": { "kind": { "const": "graphql" } } }, "then": { "$ref": "#/$defs/GraphQLConfig" } },
{ "if": { "properties": { "kind": { "const": "code" } } }, "then": { "$ref": "#/$defs/CodeConfig" } },
{ "if": { "properties": { "kind": { "const": "sql" } } }, "then": { "$ref": "#/$defs/SqlConfig" } },
{ "if": { "properties": { "kind": { "const": "builtin" } } }, "then": { "$ref": "#/$defs/BuiltinConfig" } }
],
"$defs": {
"FieldSpec": {
"type": "object",
"required": ["path", "type", "in"],
"properties": {
"path": { "type": "string", "x-ui": { "widget": "mono" } },
"type": { "type": "string", "enum": ["string", "integer", "number", "boolean", "object", "array"] },
"in": { "type": "string", "enum": ["path", "query", "header", "body", "cookie"] },
"required": { "type": "boolean", "default": false },
"llm_visible": { "type": "boolean", "default": true, "x-ui": { "effect": "Model can set this argument" } },
"description": { "type": "string", "x-ui": { "modelView": true } },
"default": {}
}
},
"ResponseFieldSpec": {
"type": "object",
"required": ["path"],
"properties": {
"path": { "type": "string", "x-ui": { "widget": "mono" } },
"description": { "type": "string", "x-ui": { "modelView": true } },
"include_in_llm": { "type": "boolean", "default": true }
}
},
"RestConfig": {
"required": ["request"],
"properties": {
"request": {
"type": "object",
"required": ["method", "url_template"],
"x-ui": { "section": "Request" },
"properties": {
"method": { "type": "string", "enum": ["GET", "POST", "PUT", "PATCH", "DELETE"] },
"url_template": { "$ref": "forge/common#/$defs/TemplateString" },
"body_encoding": { "type": "string", "enum": ["json", "form", "multipart", "raw"], "x-ui": { "section": "Request", "help": "How the request body is encoded. Unset (auto): a structured body is JSON, a body template is sent as-is. json: application/json. form: application/x-www-form-urlencoded — in:body fields (and JSON body templates) are URL-encoded and the Content-Type is set for you; use this for classic HTML form posts, including multi-line values and repeated keys. multipart: multipart/form-data — in:body fields become form parts (a value shaped [filename, content] or {content, filename?, content_type?} is sent as a file part); the Content-Type + boundary are set for you. raw: send the body template string verbatim." } },
"body_template": { "$ref": "forge/common#/$defs/TemplateString", "x-ui": { "section": "Request", "widget": "textarea", "help": "Free-form body interpolating {{input.*}} (validated tool args) and {{ctx.*}} (run context). Parsed as JSON when possible, else sent as raw content. For form posts prefer in:body fields with body_encoding=form (auto URL-encoded)." } },
"fields": { "type": "array", "items": { "$ref": "#/$defs/FieldSpec" }, "x-ui": { "widget": "rows-table" } },
"headers": { "type": "array", "items": { "type": "object", "properties": { "name": { "type": "string" }, "value": { "$ref": "forge/common#/$defs/TemplateString" } } }, "x-ui": { "widget": "rows-table" } },
"follow_redirects": { "type": "boolean", "default": false, "x-ui": { "help": "Follow 3xx redirects to the target URL. Each hop is re-checked by the SSRF guard. When off, the redirect's target URL is still reported to the model so it can act on it." } },
"tls_skip_verify": { "type": "boolean", "default": false, "x-ui": { "help": "Skip TLS certificate verification. Honored ONLY when the target host is on the server's FORGE_EGRESS_ALLOW_PRIVATE_HOSTS allow-list (a trusted internal/dev target); ignored for any other host. Use for internal services with self-signed certs - never for public endpoints." } }
}
},
"response": {
"type": "object",
"x-ui": { "section": "Response shaping" },
"properties": {
"fields": { "type": "array", "items": { "$ref": "#/$defs/ResponseFieldSpec" }, "x-ui": { "widget": "rows-table" } },
"projection_jmespath": { "type": "string", "x-ui": { "widget": "jmespath", "tokenMeter": true, "help": "Cut the payload before it reaches the model. Compare Raw vs Projected tokens." } }
}
}
}
},
"GraphQLConfig": {
"required": ["endpoint", "query"],
"properties": {
"endpoint": { "type": "string" },
"query": { "type": "string", "x-ui": { "widget": "code", "language": "graphql" } },
"operation_name": { "type": "string", "x-ui": { "section": "Request", "help": "operationName to run when the query document defines more than one operation. Leave blank for a single-operation document." } },
"variables": { "type": "array", "items": { "$ref": "#/$defs/FieldSpec" }, "x-ui": { "widget": "rows-table" } },
"follow_redirects": { "type": "boolean", "default": false, "x-ui": { "help": "Follow 3xx redirects to the target endpoint (each hop re-checked by the SSRF guard)." } },
"tls_skip_verify": { "type": "boolean", "default": false, "x-ui": { "help": "Skip TLS certificate verification. Honored ONLY when the endpoint host is on the server's FORGE_EGRESS_ALLOW_PRIVATE_HOSTS allow-list (a trusted internal/dev target); ignored otherwise. Use for internal services with self-signed certs - never for public endpoints." } },
"response": { "$ref": "#/$defs/RestConfig/properties/response" }
}
},
"CodeConfig": {
"required": ["language", "source"],
"properties": {
"language": { "type": "string", "enum": ["python"], "default": "python" },
"source": { "type": "string", "x-ui": { "widget": "code", "language": "python", "help": "Define def main(**kwargs): return ... (RestrictedPython sandbox; imports limited to pure stdlib)." } },
"args_schema": { "type": "object", "description": "JSON Schema of the tool's LLM-visible args." },
"timeout_seconds": { "type": "integer", "default": 5 }
}
},
"SqlConfig": {
"required": ["query", "connection_ref"],
"properties": {
"connection_ref": { "type": "string", "x-ui": { "widget": "reference", "refType": "secret", "help": "Secret holding the SQLAlchemy DB URL (e.g. postgresql+psycopg://…)." } },
"query": { "type": "string", "x-ui": { "widget": "code", "language": "sql", "help": "Parameterized SELECT using :named binds. Read-only is enforced by default." } },
"args_schema": { "type": "object", "description": "JSON Schema of the query's :named parameters (the LLM-visible args)." },
"read_only": { "type": "boolean", "default": true },
"max_rows": { "type": "integer", "default": 100 }
}
},
"BuiltinConfig": {
"required": ["builtin"],
"properties": {
"builtin": { "type": "string", "enum": ["web_search", "web_fetch", "current_time", "calculator", "knowledge_search", "remember", "recall"] },
"options": { "type": "object" }
}
}
}
}
+72
View File
@@ -0,0 +1,72 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "forge/workflow",
"type": "object",
"required": ["id", "version", "state", "entry_node", "nodes", "edges"],
"properties": {
"id": { "$ref": "forge/common#/$defs/Identifier" },
"version": { "type": "integer", "minimum": 1 },
"state": {
"type": "object",
"description": "Map of state field name -> StateFieldSpec. Compiled to a TypedDict (NOT pydantic).",
"additionalProperties": { "$ref": "#/$defs/StateFieldSpec" },
"x-ui": { "widget": "rows-table", "section": "State schema", "columns": ["__key__", "type", "reducer"] }
},
"entry_node": { "type": "string" },
"global_middleware": {
"type": "array",
"items": { "$ref": "forge/middleware#/$defs/MiddlewareEntry" },
"description": "Prepended to every agent node's stack at compile time.",
"x-ui": { "widget": "middleware-stack", "section": "Global middleware" }
},
"error_policy": { "type": "string", "enum": ["halt", "continue", "route_to_error_node"], "default": "halt" },
"on_error": {
"type": "object",
"description": "Graceful fallback when the run errors: send this message instead of failing silently, and optionally open a human handoff.",
"properties": {
"message": { "type": "string", "x-ui": { "help": "Customer-facing fallback reply when the workflow errors." } },
"escalate": { "type": "boolean", "default": false, "x-ui": { "help": "Also open a human-handoff queue item on error." } }
}
},
"timeout_seconds": { "type": "integer", "default": 600 },
"max_concurrency": { "type": "integer", "default": 8 },
"nodes": { "type": "array", "items": { "$ref": "#/$defs/NodeInstance" }, "minItems": 1 },
"edges": { "type": "array", "items": { "$ref": "#/$defs/Edge" } }
},
"$defs": {
"StateFieldSpec": {
"type": "object",
"required": ["type", "reducer"],
"properties": {
"type": { "type": "string", "enum": ["list[message]", "list[str]", "list[json]", "str", "int", "float", "bool", "json"] },
"reducer": {
"type": "string",
"enum": ["add_messages", "add", "last", "merge"],
"description": "add_messages for messages; add for accumulating lists; last for overwrite; merge for dict."
}
}
},
"NodeInstance": {
"type": "object",
"required": ["id", "type", "config"],
"properties": {
"id": { "$ref": "forge/common#/$defs/Identifier" },
"type": { "type": "string", "description": "Must exist in the Node Type Registry." },
"config": { "type": "object", "description": "Validated against the node type's own schema (Section 3)." },
"position": { "type": "object", "properties": { "x": { "type": "number" }, "y": { "type": "number" } } }
}
},
"Edge": {
"type": "object",
"required": ["source", "target"],
"properties": {
"source": { "type": "string" },
"source_handle": { "type": "string" },
"target": { "type": "string", "description": "Target node id or 'END'." },
"target_handle": { "type": "string" },
"condition": { "$ref": "forge/common#/$defs/Expression", "description": "If present, becomes a conditional edge." },
"branches": { "type": "object", "additionalProperties": { "type": "string" }, "description": "value -> target-node-id map for routers." }
}
}
}
}