Imported from akillness/jeo-skills (
.agent-skills/goalflow/SKILL.md). Install upstream withnpx skills add akillness/jeo-skills --skill goalflow. Copyright stays with the author.
goalflow — Graph-Orchestrated Agent Loop
goalflow gives you two ways to build LLM applications on LangGraph and lets
you combine them: visual-first (design in Dify, transpile the exported DSL
into a LangGraph Python file you own) and code-first (ReAct/Deep agent
loops via the vendored agent_kit). A graph node can host an agent loop,
and an agent can call sub-workflows as tools.
Two things shape almost every answer about this project:
- It is extracted from an internal production system. The generalizable core is real; the defaults are opinionated (Qwen/DashScope, Alibaba OSS).
- Its own docs open with a CAUTION: live credentials remain in git history. That is a blocker before any public push, not a footnote.
When to use this skill
- Converting a Dify flow into version-controlled LangGraph code without staying on Dify's runtime
- Choosing or authoring workflow nodes, edges, branch routing, or iteration/loop subgraphs
- Building an agent loop with
agent_kit— graph builder, middleware chain, harness/model router, executable skills - Wiring the serving layer: data adapters, SSE streaming, HITL interrupts, Redis/MySQL, and workflow registration
- Authoring
SKILL.mdcapabilities that get LLM-matched and injected into prompts - Auditing the repo before publishing it or deploying it anywhere real
When not to use this skill
- Plain LangGraph graph/state/checkpointer API questions →
langgraph-fundamentals,langgraph-workflow,langgraph-persistence - Generic LangChain chains, retrievers, or RAG design →
langchain-fundamentals,langchain-rag - Generic HITL patterns not tied to this engine →
langgraph-human-in-the-loop deepagentsused directly, outsideagent_kit's wrapper →deepagents- Authoring skills for this repo's agent catalog rather than goalflow's
runtime →
skill-standardization,write-a-skill - LLM tracing/eval platform selection →
langsmith,opik
Instructions
Step 1: Capture the intake packet
Four facts decide the mode:
- Starting artifact — a Dify DSL export, an empty repo, an existing
BaseWorkflowsubclass, or a running deployment - Target shape — a workflow graph, an agent loop, or a graph node that hosts a loop
- Client protocol — Dify-native (default), OpenAI-compatible, or custom
- Environment reality — is Redis and MySQL actually available? MySQL backs the checkpointer, and the checkpointer is what makes stop/resume and HITL work at all
Step 2: Pick exactly one mode
| Mode | Use when | Produces |
|---|---|---|
orient |
It is unclear goalflow is the right tool | A fit verdict, or an honest route-out to plain LangGraph |
transpile |
A Dify DSL export exists | A generated BaseWorkflow subclass, registered and runnable |
build |
Authoring or fixing the graph itself | Node choices, edge/branch routing, error strategy |
agent |
The job is an open-ended tool-use loop | An Agent/AgentBaseNode with builder, middleware, harness |
serve |
Wiring the runtime to clients | Adapter, endpoints, streaming, HITL, storage, registration |
harden |
Before publishing or deploying | A blocker list from the pre-publish gate |
Run harden before any push to a public or shared remote, regardless of which
mode the user asked for.
Step 3: Check the environment read-only
bash .agent-skills/goalflow/scripts/goalflow.sh doctor
bash .agent-skills/goalflow/scripts/goalflow.sh doctor /path/to/goal-flow
doctor reports Python version, the core packages (langgraph, fastapi,
redis, pymysql, sqlalchemy, langchain-openai), whether goalflow and
agent_kit import, and which .env keys are set — by name only, never by
value. It installs nothing and starts no server.
Step 4: Transpile before hand-writing a graph
The transformer is a two-stage pipeline: parse the DSL into an internal graph model, then emit Python via a double-dispatch visitor.
python -m goalflow.tool.dify_transformer.wf_transformer_tool \
--dsl path/to/my_flow.yml \
--out my_flow_workflow.py \
--class MyFlowWorkflow
Two facts that prevent the common mistakes:
- The parse is read-only. Host-portability rewrites happen on an in-memory copy; your export file is never modified.
- The substitution table is
DifyDslParser.DEFAULT_HOST_SUBSTITUTIONS. It encodes the original authors' internal hostnames. Pass your ownhost_substitutions=, or{}to disable it — do not inherit theirs.
Generation is only half the job. A generated class does nothing until it is registered (Step 6). Details in references/dify-transpile.md.
Step 5: Author nodes against the real BaseNode contract
You implement exactly one method, call(self, state), and its return type
encodes both state update and routing:
| Return | Meaning |
|---|---|
dict |
merge into state, continue to next_node_ids |
Command(update=, goto=) |
update and jump |
List[str] |
branch routing — pick outgoing handles |
Sequence[Send] |
fan out, one parallel branch per Send |
None |
no update |
The __call__ wrapper gives you timing, logging, and a fan-in barrier for
free: a node with multiple pre_node_ids re-queues itself until its
topological depth is reached, so it runs once after all upstream branches
arrive. Do not hand-roll that.
Node catalog, error strategies, and the iteration/loop caps live in references/nodes-and-graph.md.
Step 6: Register the workflow, and say what registration really is
# src/goalflow/api/auth_validator.py
apikey_workflow_def_map = {
"2999a65aa67e37253623075d60796f9a": MyWorkflow, # md5(api_key)
}
This is a static in-code map keyed by the MD5 of the API key. Upstream documents it as the current design and the first thing to replace for a real deployment. MD5 is unsuitable for hashing secrets. When a user asks how to register a workflow, answer the question and flag this — do not present it as a production auth story.
Step 7: Prefer AgentBaseNode for agent work
AgentBaseNode multiply-inherits the workflow BaseNode and agent_kit's
Agent. Subclass it and implement output_schema, build_prompt, and
build_command. It supersedes DeepAgentBaseNode, CreateAgentBaseNode, and
StateGraphBaseNode, and it is the preferred path over the older manual
AgentNode ReAct loop.
The builder is auto-selected: DeepGraphBuilder when subagents are present,
ReactGraphBuilder otherwise. Middleware runs in list order.
references/agent-kit.md has the hooks, the middleware catalog, and the harness/model-router contract.
Step 8: Keep protocol changes in the adapter layer
The engine emits protocol-neutral semantic events; a DataAdapter is the last
hop that serializes them. To add a protocol, implement generate() (streaming)
and execute() (blocking) — you never touch the engine, nodes, or graph.
DifyDataAdapter is an identity passthrough, because the internal format
already is the Dify protocol. OpenAIDataAdapter backs /v1/chat/completions.
Streaming is branch-aware: tokens from an untaken if-else or classifier
branch are pruned before they ever reach the client. Details in
references/serving-and-protocols.md.
Step 9: Run the pre-publish gate before any push
python3 .agent-skills/goalflow/scripts/preflight_audit.py /path/to/goal-flow
Stdlib-only. It runs the upstream checklist deterministically: tracked
.env*/key/log files, .env* blobs still reachable in git history,
hard-coded internal IPs and endpoints, .gitignore coverage, LICENSE
presence, the open-CORS-with-credentials combination, the MD5 auth map, and
CodeNode's exec path. It prints one ```review fenced JSON block and
exits 1 on a blocker.
The non-negotiable rule it encodes: untracking .env does not remove it
from history. Rotate every credential and scrub history with
git filter-repo, then push to a fresh remote — never one that already
carries the secrets.
The published wanmol/goal-flow history is already scrubbed (only
.env.example survives), so this check is usually clean there. It matters for
internal forks, mirrors, and clones that predate the scrub, which is where
the original credentials still live.
Full checklist in references/security-gate.md.
Step 10: Author runtime skills with the injection cost in mind
goalflow has two skill systems. The main-project engine
(src/goalflow/skill/) matches a SKILL.md to a query with an LLM (not
keywords; default qwen-turbo, threshold 0.3, top_k 1) and injects the
Markdown body verbatim into the system prompt. agent_kit's engine adds
executable skills (module:func as a LangChain tool) and hybrid mode.
python3 .agent-skills/goalflow/scripts/check_goalflow_skill.py skills/weather_query
Because bodies are injected verbatim, body length is prompt cost on every
matched turn — the checker warns on oversized bodies and on the description
being too vague for the matcher to reason over.
references/skills-engine.md covers frontmatter, matching, and the choice between the two engines.
Best practices
- Transpile, then own the code. The point is escaping Dify's runtime; do not keep round-tripping through the DSL as the source of truth.
- Replace the host-substitution table. Inheriting the upstream authors' internal hostnames silently rewrites your URLs.
- Never present the MD5 API-key map as production auth. Answer the registration question, then flag it.
- Treat
CodeNodeinput as trusted-only. Itexecs DSL/model-provided Python and the ASTsafe_check()guard is disabled upstream. Do not accept untrusted DSLs without re-enabling and strengthening sandboxing. - Do not promise HITL or stop/resume without MySQL. The checkpointer is the backbone of both; no checkpointer means no durable pause.
- Prefer
AgentBaseNodeoverAgentNodeand over the three deprecated bases. - Put protocol work in an adapter, never in the engine. If a change requires touching nodes to support a client, the design has drifted.
- Say when a default is Alibaba-shaped. Qwen/DashScope, OSS, and Hologres defaults come from the project's origin, not from a recommendation.
- Run
preflight_audit.pybefore any push to a shared remote, and treat history-resident secrets as compromised rather than merely untracked.
Examples
Example 1: "I designed a flow in Dify, now what?"
transpile mode: run the transformer, override host_substitutions, register
the generated class in the API-key map, and flag that the map is a demo
mechanism.
Example 2: "My branch's tokens leak to the client"
They should not — the chunk processor prunes untaken if-else/classifier
branches. Check whether the node provably reaches an answer/end node given
branches already taken, rather than patching the adapter.
Example 3: "Can I run this without MySQL?"
Partially, and you must say which part breaks: MySQL backs the LangGraph checkpointer, so stop/resume and HITL stop being durable.
Example 4: "We're about to open-source our fork"
harden mode first. Run preflight_audit.py against your checkout — a
clean git status says nothing about what a clone can still recover, and a
fork that predates upstream's scrub carries the original credentials.
Example 5: "Should I use goalflow or just LangGraph?"
orient mode. If there is no Dify design to transpile, no need for a
swappable wire protocol, and no graph-hosting-a-loop requirement, plain
LangGraph is lighter — route out and say so.
References
- references/dify-transpile.md — two-stage pipeline, CLI flags, host substitutions, generated anatomy
- references/nodes-and-graph.md —
BaseNodecontract,NodeOutputunion, fan-in, error strategies, full node catalog - references/agent-kit.md —
Agenthooks, graph builders, middleware catalog, harness/model router, executable skills - references/skills-engine.md —
SKILL.mdfrontmatter, LLM matching, prompt injection, main vsagent_kitengines - references/serving-and-protocols.md — endpoints, data adapters, three-layer streaming, HITL, storage and config
- references/security-gate.md — the pre-publish checklist and what each finding actually costs
- scripts/goalflow.sh — read-only
doctor, plusauditandcheck-skillpassthroughs - scripts/preflight_audit.py — stdlib-only pre-publish security gate
- scripts/check_goalflow_skill.py — stdlib-only runtime-
SKILL.mdfrontmatter and injection-cost check - goalflow GitHub Repository
- Project standards:
.agent-skills/skill-standardization/SKILL.md