This is an automated email from the ASF dual-hosted git repository.
xintongsong pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/flink-agents.git
The following commit(s) were added to refs/heads/main by this push:
new 1628a2b7 [docs][examples] Add Agent Skills documentation and
quickstart example (#707)
1628a2b7 is described below
commit 1628a2b74f31c8f717b0c9525a44fba161a595c6
Author: Wenjin Xie <[email protected]>
AuthorDate: Thu May 28 00:02:27 2026 +0800
[docs][examples] Add Agent Skills documentation and quickstart example
(#707)
Co-authored-by: Claude Opus 4.7 (1M context) <[email protected]>
---
docs/content/docs/development/skills.md | 222 +++++++++++++
.../docs/get-started/quickstart/skills_agent.md | 360 +++++++++++++++++++++
.../flink/agents/examples/SkillsAgentExample.java | 71 ++++
.../flink/agents/examples/agents/MathAgent.java | 101 ++++++
.../main/resources/skills/math-calculator/SKILL.md | 43 +++
.../examples/quickstart/agents/math_agent.py | 99 ++++++
.../resources/skills/math-calculator/SKILL.md | 43 +++
.../examples/quickstart/skills_agent_example.py | 77 +++++
python/pyproject.toml | 2 +-
9 files changed, 1017 insertions(+), 1 deletion(-)
diff --git a/docs/content/docs/development/skills.md
b/docs/content/docs/development/skills.md
new file mode 100644
index 00000000..d07c84ed
--- /dev/null
+++ b/docs/content/docs/development/skills.md
@@ -0,0 +1,222 @@
+---
+title: Skills
+weight: 10
+type: docs
+---
+<!--
+Licensed to the Apache Software Foundation (ASF) under one
+or more contributor license agreements. See the NOTICE file
+distributed with this work for additional information
+regarding copyright ownership. The ASF licenses this file
+to you under the Apache License, Version 2.0 (the
+"License"); you may not use this file except in compliance
+with the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing,
+software distributed under the License is distributed on an
+"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+KIND, either express or implied. See the License for the
+specific language governing permissions and limitations
+under the License.
+-->
+
+## Overview
+
+A **Skill** is a self-contained package of instructions, and optionally
scripts and reference files, that teaches the agent how to perform a
specialized task. A skill is just a directory containing a `SKILL.md` file.
Flink Agents discovers the skills you declare, lets the agent decide which one
is relevant to the current request, and loads its full instructions only when
needed.
+
+Skills follow a **progressive disclosure** model, so that providing the agent
with many capabilities does not bloat every request:
+
+1. **Discovery** — at startup, only each skill's `name` and `description` are
injected into the system prompt (a few dozen tokens per skill), so the agent
knows what is available.
+2. **Activation** — when the agent judges a skill relevant, it calls the
built-in `load_skill` tool to read the full `SKILL.md` instructions into the
context.
+3. **Execution** — the agent follows the loaded instructions, running any
bundled scripts or shell commands through the built-in `bash` tool, and reading
additional reference files only on demand.
+
+Skills are a good fit when a capability is best described as a procedure (a
runbook the agent follows) rather than a single function call. For a single,
well-typed operation, prefer a [tool]({{< ref "docs/development/tool_use" >}})
instead.
+
+## Skill Format
+
+A skill is a directory whose name matches the skill, containing a `SKILL.md`
file with YAML frontmatter and a Markdown body:
+
+````markdown
+---
+name: math-calculator
+description: Calculate mathematical expressions using shell commands. Use when
the user asks to perform arithmetic like addition, subtraction, multiplication,
division, or powers.
+license: Apache-2.0
+compatibility: Requires bash with bc (basic calculator)
+---
+
+# Math Calculator Skill
+
+## When to Use
+Use this skill whenever the user asks to evaluate a numeric expression.
+
+## Method
+Evaluate expressions with the `bc` calculator:
+
+```bash
+echo "(2 + 3) * 4" | bc
+# Output: 20
+```
+````
+
+**Frontmatter fields:**
+
+| Field | Required | Description |
+|-------|----------|-------------|
+| `name` | Yes | Skill identifier. 1–64 characters, lowercase letters, numbers
and hyphens only (no leading/trailing hyphen). Must match the value referenced
in the chat model's `skills` list. |
+| `description` | Yes | 1–1024 characters. Loaded at discovery time — write it
so the agent can decide *when* to use the skill. State both what it does and
when to use it. |
+| `license` | No | License of the skill. |
+| `compatibility` | No | Free-text note on runtime requirements (e.g. required
commands), up to 500 characters. |
+
+The Markdown body is the full instruction set loaded on activation. It may
reference bundled files using paths relative to the skill directory (for
example `python scripts/gen_joke.py`); those scripts and reference files are
loaded only when the agent actually needs them.
+
+A skill source is a directory holding one or more such skill subdirectories
(or a `.zip` of that layout):
+
+```
+skills/
+├── math-calculator/
+│ └── SKILL.md
+└── joke-generator/
+ ├── SKILL.md
+ └── scripts/
+ └── gen_joke.py
+```
+
+## Declare Skills in an Agent
+
+Declare where to load skills from with the `@skills`/`@Skills`
decorator/annotation. The method returns a `Skills` resource built with one of
its factory methods.
+
+{{< tabs "Declare Skills in Agent" >}}
+
+{{< tab "Python" >}}
+```python
+from flink_agents.api.agents.agent import Agent
+from flink_agents.api.decorators import skills
+from flink_agents.api.skills import Skills
+
+
+class MathAgent(Agent):
+
+ @skills
+ @staticmethod
+ def my_skills() -> Skills:
+ # Load all skill subdirectories under a local directory.
+ return Skills.from_local_dir("/path/to/skills")
+```
+{{< /tab >}}
+
+{{< tab "Java" >}}
+```java
+import org.apache.flink.agents.api.agents.Agent;
+// The @Skills annotation and the Skills resource share a simple name but live
+// in different packages, so fully-qualify the annotation when importing the
class.
+import org.apache.flink.agents.api.skills.Skills;
+
+public class MathAgent extends Agent {
+
+ @org.apache.flink.agents.api.annotation.Skills
+ public static Skills mySkills() {
+ // Load all skill subdirectories from a classpath resource (packaged
in the jar).
+ return Skills.fromClasspath("skills");
+ }
+}
+```
+{{< /tab >}}
+
+{{< /tabs >}}
+
+**Key points:**
+- Use the decorator/annotation to declare a skill source.
+ - In Python, use `@skills`.
+ - In Java, use `@Skills`.
+- Declare more than one `@skills`/`@Skills` method on the same agent to
combine sources; the runtime merges them and de-duplicates identical entries.
+- Declaring a skill source only makes the skills *available*. A skill is
exposed to a chat model only when that model lists it in its `skills` (see
[Enable Skills on a Chat Model](#enable-skills-on-a-chat-model)).
+
+### Skill Sources
+
+Each factory method creates a source with a different scheme:
+
+| Factory method (Python / Java) | Scheme | Description |
+|--------------------------------|--------|-------------|
+| `Skills.from_local_dir(*paths)` / `Skills.fromLocalDir(String...)` | `local`
| One or more local directories, or `.zip` files, holding skill subdirectories.
The path must be resolvable on the Flink TaskManager that runs the agent. |
+| `Skills.from_url(*urls)` / `Skills.fromUrl(String...)` | `url` | One or more
`http(s)` URLs, each pointing to a `.zip` whose top level holds the skill
subdirectories. |
+| `Skills.from_package(*pairs)` | `package` | **Python only.** One or more
`(package, resource)` tuples locating skills inside an installed Python
package. |
+| `Skills.fromClasspath(String...)` | `classpath` | **Java only.** One or more
classpath resource paths (e.g. under `src/main/resources/skills`). When
packaged into a jar, the resource is materialized to a temp directory at
runtime. |
+
+{{< hint info >}}
+The `package` scheme is Python-only and the `classpath` scheme is Java-only. A
plan written in one language using the other language's scheme deserializes
fine, but fails fast at load time. Use `local` or `url` for cross-language
skill sources.
+{{< /hint >}}
+
+## Enable Skills on a Chat Model
+
+A declared skill becomes usable only when a chat model opts in by listing it
in `skills`. When `skills` is set, the framework automatically:
+
+- injects the discovery prompt (the names and descriptions of the listed
skills) into the system messages, and
+- adds the two built-in tools the agent needs — `load_skill` (to read a
skill's full instructions) and `bash` (to run its commands and scripts).
+
+{{< tabs "Enable Skills on a Chat Model" >}}
+
+{{< tab "Python" >}}
+```python
+@chat_model_setup
+@staticmethod
+def math_model() -> ResourceDescriptor:
+ return ResourceDescriptor(
+ clazz=ResourceName.ChatModel.OLLAMA_SETUP,
+ connection="ollama_server",
+ model="qwen3.5:9b",
+ prompt="system_prompt",
+ # Expose declared skills to this model by name.
+ skills=["math-calculator"],
+ # Whitelist the shell commands the bash tool is allowed to run.
+ allowed_commands=["echo", "bc"],
+ )
+```
+{{< /tab >}}
+
+{{< tab "Java" >}}
+```java
+@ChatModelSetup
+public static ResourceDescriptor mathModel() {
+ return
ResourceDescriptor.Builder.newBuilder(ResourceName.ChatModel.OLLAMA_SETUP)
+ .addInitialArgument("connection", "ollamaChatModelConnection")
+ .addInitialArgument("model", "qwen3.5:9b")
+ .addInitialArgument("prompt", "systemPrompt")
+ // Expose declared skills to this model by name.
+ .addInitialArgument("skills", List.of("math-calculator"))
+ // Whitelist the shell commands the bash tool is allowed to run.
+ .addInitialArgument("allowed_commands", List.of("echo", "bc"))
+ .build();
+}
+```
+{{< /tab >}}
+
+{{< /tabs >}}
+
+**Key points:**
+- `skills` lists the skill names (matching the `name` field in each
`SKILL.md`) the agent may use.
+- `allowed_commands` is a whitelist of shell command names the built-in `bash`
tool may execute. Any command not on the list is rejected, so keep it as narrow
as the skills require (for example `echo` and `bc` for arithmetic).
+- The `load_skill` and `bash` tools are added automatically — you do not
declare them in `tools`. They are added alongside any tools you do declare.
+- Make sure the system prompt instructs the agent to load the relevant skill
before acting, for example: *"You must load the skill first and strictly follow
its instructions."* Without this nudge, smaller models may answer directly
instead of consulting the skill.
+
+Skills work with both the [Workflow Agent]({{< ref
"docs/development/workflow_agent" >}}) (configure the chat model via
`@chat_model_setup`/`@ChatModelSetup` as above) and the [ReAct Agent]({{< ref
"docs/development/react_agent" >}}) (set `skills` and `allowed_commands` on the
`ReActAgent`'s chat model descriptor, and register the `Skills` resource on the
execution environment with `add_resource(..., ResourceType.SKILLS, ...)`).
+
+## How Skills Work
+
+Once enabled, a request flows through the three progressive-disclosure stages:
+
+1. **Discovery.** The discovery prompt lists each available skill's `name` and
`description` and explains how to load one. This is the only skill content the
agent sees by default:
+ ```text
+ ## Available Skills
+ <available_skills>
+ <skill>
+ <name>math-calculator</name>
+ <description>Calculate mathematical expressions using shell commands. Use
when ...</description>
+ </skill>
+ </available_skills>
+ ```
+2. **Activation.** When the agent decides a skill applies, it calls
`load_skill(name="math-calculator")`. The framework returns the full `SKILL.md`
body, including the skill's base directory and the absolute paths of its
bundled resources.
+3. **Execution.** Following the loaded instructions, the agent invokes `bash`
to run commands (e.g. `echo "(2 + 3) * 4" | bc`) or bundled scripts (e.g.
`python scripts/gen_joke.py`), and loads additional reference files only when
an instruction points to them.
+
+This keeps each request lean — a skill the agent never activates costs only
its one-line description.
diff --git a/docs/content/docs/get-started/quickstart/skills_agent.md
b/docs/content/docs/get-started/quickstart/skills_agent.md
new file mode 100644
index 00000000..04eb0461
--- /dev/null
+++ b/docs/content/docs/get-started/quickstart/skills_agent.md
@@ -0,0 +1,360 @@
+---
+title: 'Skills'
+weight: 3
+type: docs
+---
+<!--
+Licensed to the Apache Software Foundation (ASF) under one
+or more contributor license agreements. See the NOTICE file
+distributed with this work for additional information
+regarding copyright ownership. The ASF licenses this file
+to you under the Apache License, Version 2.0 (the
+"License"); you may not use this file except in compliance
+with the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing,
+software distributed under the License is distributed on an
+"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+KIND, either express or implied. See the License for the
+specific language governing permissions and limitations
+under the License.
+-->
+
+## Overview
+
+A [Skill]({{< ref "docs/development/skills" >}}) is a self-contained package
of instructions, and optionally scripts and reference files, that teaches the
agent how to perform a specialized task. Skills are loaded with *progressive
disclosure*: only each skill's name and description are shown to the agent up
front, and the full instructions are pulled in on demand when the agent decides
a skill is relevant.
+
+This quickstart builds a small streaming agent that answers arithmetic
questions. Rather than letting the LLM compute by itself, the agent exposes a
`math-calculator` skill; for each question the agent loads the skill and
follows its instructions to compute the result with the `bc` calculator through
the built-in `bash` tool. It demonstrates the full skill lifecycle — discovery,
activation, and execution — in a Flink streaming job.
+
+## Code Walkthrough
+
+### Define the Skill
+
+A skill is a directory containing a `SKILL.md` file with YAML frontmatter
(loaded at discovery time) and a Markdown body (loaded on activation). Here is
`skills/math-calculator/SKILL.md`:
+
+````markdown
+---
+name: math-calculator
+description: Calculate mathematical expressions using shell commands. Use when
the user asks to perform arithmetic calculations like addition, subtraction,
multiplication, division, or powers.
+license: Apache-2.0
+compatibility: Requires bash with bc (basic calculator)
+---
+
+# Math Calculator Skill
+
+## When to Use
+Use this skill when the user asks to evaluate a numeric expression.
+
+## Method
+Pipe the expression into the `bc` (basic calculator) command:
+
+```bash
+echo "(2 + 3) * 4" | bc
+# Output: 20
+```
+````
+
+### Create the Agent
+
+The agent declares where to load skills from with the `@skills`/`@Skills`
decorator/annotation, and enables the skill on its chat model by listing it in
`skills` together with the `allowed_commands` whitelist for the `bash` tool.
For more details, please refer to the [Skills]({{< ref
"docs/development/skills" >}}) documentation.
+
+{{< tabs "Create the Agent" >}}
+
+{{< tab "Python" >}}
+```python
+class MathAgent(Agent):
+ """An agent that answers arithmetic questions using the math-calculator
skill."""
+
+ @skills
+ @staticmethod
+ def my_skills() -> Skills:
+ """Declare where to load skills from."""
+ # Skills are bundled under this example package, loaded by package
name.
+ return Skills.from_package(
+ ("flink_agents.examples.quickstart", "resources/skills")
+ )
+
+ @prompt
+ @staticmethod
+ def system_prompt() -> Prompt:
+ """System prompt instructing the agent to use the skill."""
+ return Prompt.from_messages(
+ messages=[
+ ChatMessage(
+ role=MessageRole.SYSTEM,
+ content="You are a helpful math assistant. Use the "
+ "math-calculator skill when asked to evaluate an
expression. "
+ "You must load the skill first and strictly follow its "
+ "instructions. Reply with only the final numeric result.",
+ )
+ ],
+ )
+
+ @chat_model_setup
+ @staticmethod
+ def math_model() -> ResourceDescriptor:
+ """ChatModel with the math-calculator skill enabled."""
+ return ResourceDescriptor(
+ clazz=ResourceName.ChatModel.OLLAMA_SETUP,
+ connection="ollama_server",
+ model="qwen3:8b",
+ prompt="system_prompt",
+ # Expose the declared skill to this model by name.
+ skills=["math-calculator"],
+ # Whitelist the shell commands the built-in bash tool may run.
+ allowed_commands=["echo", "bc"],
+ )
+
+ @action(InputEvent.EVENT_TYPE)
+ @staticmethod
+ def process_input(event: Event, ctx: RunnerContext) -> None:
+ """Process input event and send a chat request to evaluate the
question."""
+ question: str = InputEvent.from_event(event).input
+ ctx.send_event(
+ ChatRequestEvent(
+ model="math_model",
+ messages=[ChatMessage(role=MessageRole.USER,
content=question)],
+ )
+ )
+
+ @action(ChatResponseEvent.EVENT_TYPE)
+ @staticmethod
+ def process_chat_response(event: Event, ctx: RunnerContext) -> None:
+ """Process chat response event and send the answer as output."""
+ chat_response = ChatResponseEvent.from_event(event)
+ ctx.send_event(OutputEvent(output=chat_response.response.content))
+```
+{{< /tab >}}
+
+{{< tab "Java" >}}
+```java
+/** An agent that answers arithmetic questions using the math-calculator
skill. */
+public class MathAgent extends Agent {
+
+ /** Load skills from the skills/ directory packaged on the classpath. */
+ @org.apache.flink.agents.api.annotation.Skills
+ public static Skills mySkills() {
+ return Skills.fromClasspath("skills");
+ }
+
+ /** System prompt instructing the agent to use the skill. */
+ @Prompt
+ public static org.apache.flink.agents.api.prompt.Prompt systemPrompt() {
+ return org.apache.flink.agents.api.prompt.Prompt.fromMessages(
+ Collections.singletonList(
+ new ChatMessage(
+ MessageRole.SYSTEM,
+ "You are a helpful math assistant. Use the
math-calculator skill "
+ + "when asked to evaluate an
expression. You must load the "
+ + "skill first and strictly follow its
instructions. Reply "
+ + "with only the final numeric
result.")));
+ }
+
+ /** ChatModel with the math-calculator skill enabled. */
+ @ChatModelSetup
+ public static ResourceDescriptor mathModel() {
+ return
ResourceDescriptor.Builder.newBuilder(ResourceName.ChatModel.OLLAMA_SETUP)
+ .addInitialArgument("connection", "ollamaChatModelConnection")
+ .addInitialArgument("model", "qwen3:8b")
+ .addInitialArgument("prompt", "systemPrompt")
+ // Expose the declared skill to this model by name.
+ .addInitialArgument("skills", List.of("math-calculator"))
+ // Whitelist the shell commands the built-in bash tool may run.
+ .addInitialArgument("allowed_commands", List.of("echo", "bc"))
+ .build();
+ }
+
+ /** Process input event and send a chat request to evaluate the question.
*/
+ @Action(listenEventTypes = {InputEvent.EVENT_TYPE})
+ public static void processInput(InputEvent event, RunnerContext ctx) {
+ ctx.sendEvent(
+ new ChatRequestEvent(
+ "mathModel",
+ Collections.singletonList(
+ new ChatMessage(MessageRole.USER, (String)
event.getInput()))));
+ }
+
+ /** Process chat response event and send the answer as output. */
+ @Action(listenEventTypes = {ChatResponseEvent.EVENT_TYPE})
+ public static void processChatResponse(ChatResponseEvent event,
RunnerContext ctx) {
+ ctx.sendEvent(new OutputEvent(event.getResponse().getContent()));
+ }
+}
+```
+{{< /tab >}}
+
+{{< /tabs >}}
+
+**Key points:**
+- `@skills`/`@Skills` declares a skill source. `Skills.from_package` (Python)
loads skills bundled inside an installed package by `(package, resource)`;
`Skills.fromClasspath` (Java) loads them from a classpath resource packaged in
the jar.
+- A declared skill is exposed to a model only when the model lists it in
`skills`. The `load_skill` and `bash` tools are then added automatically.
+- `allowed_commands` whitelists the shell commands the `bash` tool may run —
keep it as narrow as the skill requires.
+
+### Integrate the Agent with Flink
+
+Register the Ollama chat model connection, create a stream of questions, and
apply the agent.
+
+{{< tabs "Integrate the Agent with Flink" >}}
+
+{{< tab "Python" >}}
+```python
+# Set up the Flink streaming environment and the Agents execution environment.
+env = StreamExecutionEnvironment.get_execution_environment()
+agents_env = AgentsExecutionEnvironment.get_execution_environment(env)
+
+# Add Ollama chat model connection to be used by the MathAgent.
+agents_env.add_resource(
+ "ollama_server", ResourceType.CHAT_MODEL_CONNECTION,
ollama_server_descriptor
+)
+
+# A small stream of arithmetic questions to answer.
+question_stream = env.from_collection(
+ ["What is (2 + 3) * 4?", "Compute 2 ^ 10.", "What is 144 divided by 12?"]
+)
+
+# Use the MathAgent to answer each question with the math-calculator skill.
+answer_stream = (
+ agents_env.from_datastream(input=question_stream, key_selector=lambda x: x)
+ .apply(MathAgent())
+ .to_datastream()
+)
+
+# Print the answers to stdout, then execute the Flink pipeline.
+answer_stream.print()
+agents_env.execute()
+```
+{{< /tab >}}
+
+{{< tab "Java" >}}
+```java
+// Set up the Flink streaming environment and the Agents execution environment.
+StreamExecutionEnvironment env =
StreamExecutionEnvironment.getExecutionEnvironment();
+env.setParallelism(1);
+AgentsExecutionEnvironment agentsEnv =
+ AgentsExecutionEnvironment.getExecutionEnvironment(env);
+
+// Add Ollama chat model connection to be used by the MathAgent.
+agentsEnv.addResource(
+ "ollamaChatModelConnection",
+ ResourceType.CHAT_MODEL_CONNECTION,
+ CustomTypesAndResources.OLLAMA_SERVER_DESCRIPTOR);
+
+// A small stream of arithmetic questions to answer.
+DataStream<String> questionStream =
+ env.fromData("What is (2 + 3) * 4?", "Compute 2 ^ 10.", "What is 144
divided by 12?");
+
+// Use the MathAgent to answer each question with the math-calculator skill.
+DataStream<Object> answerStream =
+ agentsEnv.fromDataStream(questionStream).apply(new
MathAgent()).toDataStream();
+
+// Print the answers to stdout, then execute the Flink pipeline.
+answerStream.print();
+agentsEnv.execute();
+```
+{{< /tab >}}
+
+{{< /tabs >}}
+
+## Run the Example
+
+### Prerequisites
+
+* Unix-like environment (we use Linux, Mac OS X, Cygwin, WSL)
+* Git
+* Java 11+
+* Python 3.10, 3.11 or 3.12
+* `bc` (basic calculator), used by the `math-calculator` skill — preinstalled
on most Unix-like systems
+
+### Preparation
+
+#### Prepare Flink and Flink Agents
+
+Follow the [installation]({{< ref "docs/get-started/installation" >}})
instructions to setup Flink and the Flink Agents.
+
+#### Clone the Flink Agents Repository (if not done already)
+
+```bash
+git clone https://github.com/apache/flink-agents.git
+cd flink-agents
+```
+
+{{< hint info >}}
+For python examples, you can skip this step and submit the python file in
installed flink-agents wheel.
+{{< /hint >}}
+
+#### Deploy a Standalone Flink Cluster
+
+You can deploy a standalone Flink cluster in your local environment with the
following command.
+
+{{< tabs "Deploy a Standalone Flink Cluster" >}}
+
+{{< tab "Python" >}}
+```bash
+export PYTHONPATH=$(python -c 'import sysconfig;
print(sysconfig.get_paths()["purelib"])')
+$FLINK_HOME/bin/start-cluster.sh
+```
+{{< /tab >}}
+
+{{< tab "Java" >}}
+1. Build Flink Agents from source to generate example jar. See
[installation]({{< ref "docs/get-started/installation" >}}) for more details.
+2. Start the Flink cluster
+ ```bash
+ $FLINK_HOME/bin/start-cluster.sh
+ ```
+
+{{< hint info >}}
+To run example on JDK 21+, append jvm option
`--add-exports=java.base/jdk.internal.vm=ALL-UNNAMED` to
[env.java.opts.all](https://nightlies.apache.org/flink/flink-docs-stable/docs/deployment/config/#env-java-opts-all)
in `$FLINK_HOME/conf/config.yaml` before starting the Flink cluster.
+{{< /hint >}}
+{{< /tab >}}
+
+{{< /tabs >}}
+You can refer to the [local
cluster](https://nightlies.apache.org/flink/flink-docs-release-1.20/docs/try-flink/local_installation/#starting-and-stopping-a-local-cluster)
instructions for more detailed steps.
+
+{{< hint info >}}
+If you can't navigate to the web UI at [localhost:8081](localhost:8081), you
can find the reason in `$FLINK_HOME/log`. If the reason is port conflict, you
can change the port in `$FLINK_HOME/conf/config.yaml`.
+{{< /hint >}}
+
+#### Prepare Ollama
+
+Download and install Ollama from the official
[website](https://ollama.com/download).
+
+{{< hint info >}}
+Ollama server **0.9.0** or higher is required.
+{{< /hint >}}
+
+Then pull the qwen3:8b model, which is required by the quickstart examples.
+
+```bash
+ollama pull qwen3:8b
+```
+
+### Submit Flink Agents Job to Standalone Flink Cluster
+
+#### Submit to Flink Cluster
+
+{{< tabs "Submit to Flink Cluster" >}}
+
+{{< tab "Python" >}}
+```bash
+export PYTHONPATH=$(python -c 'import sysconfig;
print(sysconfig.get_paths()["purelib"])')
+
+# Run the agent skills example
+$FLINK_HOME/bin/flink run -py
./flink-agents/python/flink_agents/examples/quickstart/skills_agent_example.py
+# or submit the example python file in installed flink-agents wheel
+$FLINK_HOME/bin/flink run -py
$PYTHONPATH/flink_agents/examples/quickstart/skills_agent_example.py
+```
+{{< /tab >}}
+
+{{< tab "Java" >}}
+```bash
+$FLINK_HOME/bin/flink run -c
org.apache.flink.agents.examples.SkillsAgentExample
./flink-agents/examples/target/flink-agents-examples-$VERSION.jar
+```
+{{< /tab >}}
+
+{{< /tabs >}}
+
+Now you should see a Flink job submitted to the Flink Cluster in Flink web UI
[localhost:8081](localhost:8081).
+
+After a few minutes, you can check for the output in the TaskManager output
log.
diff --git
a/examples/src/main/java/org/apache/flink/agents/examples/SkillsAgentExample.java
b/examples/src/main/java/org/apache/flink/agents/examples/SkillsAgentExample.java
new file mode 100644
index 00000000..8335dec0
--- /dev/null
+++
b/examples/src/main/java/org/apache/flink/agents/examples/SkillsAgentExample.java
@@ -0,0 +1,71 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.flink.agents.examples;
+
+import org.apache.flink.agents.api.AgentsExecutionEnvironment;
+import org.apache.flink.agents.api.agents.AgentExecutionOptions;
+import org.apache.flink.agents.api.resource.ResourceType;
+import org.apache.flink.agents.examples.agents.CustomTypesAndResources;
+import org.apache.flink.agents.examples.agents.MathAgent;
+import org.apache.flink.streaming.api.datastream.DataStream;
+import org.apache.flink.streaming.api.environment.StreamExecutionEnvironment;
+
+/**
+ * Java example demonstrating Agent Skills with Flink Agents.
+ *
+ * <p>A stream of arithmetic questions is processed by {@link MathAgent},
whose chat model has a
+ * {@code math-calculator} skill enabled. For each question the model loads
the skill and follows
+ * its instructions to compute the answer with the {@code bc} calculator, then
the result is printed
+ * to stdout. This serves as a minimal, end-to-end example of integrating
skill-powered agents with
+ * Flink streaming jobs.
+ */
+public class SkillsAgentExample {
+
+ /** Runs the example pipeline. */
+ public static void main(String[] args) throws Exception {
+ // Set up the Flink streaming environment and the Agents execution
environment.
+ StreamExecutionEnvironment env =
StreamExecutionEnvironment.getExecutionEnvironment();
+ env.setParallelism(1);
+ AgentsExecutionEnvironment agentsEnv =
+ AgentsExecutionEnvironment.getExecutionEnvironment(env);
+
+ // limit async request to avoid overwhelming ollama server
+ agentsEnv.getConfig().set(AgentExecutionOptions.NUM_ASYNC_THREADS, 2);
+
+ // Add Ollama chat model connection to be used by the MathAgent.
+ agentsEnv.addResource(
+ "ollamaChatModelConnection",
+ ResourceType.CHAT_MODEL_CONNECTION,
+ CustomTypesAndResources.OLLAMA_SERVER_DESCRIPTOR);
+
+ // A small stream of arithmetic questions to answer.
+ DataStream<String> questionStream =
+ env.fromData(
+ "What is (2 + 3) * 4?", "Compute 2 ^ 10.", "What is
144 divided by 12?");
+
+ // Use the MathAgent to answer each question with the math-calculator
skill.
+ DataStream<Object> answerStream =
+ agentsEnv.fromDataStream(questionStream).apply(new
MathAgent()).toDataStream();
+
+ // Print the answers to stdout.
+ answerStream.print();
+
+ // Execute the Flink pipeline.
+ agentsEnv.execute();
+ }
+}
diff --git
a/examples/src/main/java/org/apache/flink/agents/examples/agents/MathAgent.java
b/examples/src/main/java/org/apache/flink/agents/examples/agents/MathAgent.java
new file mode 100644
index 00000000..1056f877
--- /dev/null
+++
b/examples/src/main/java/org/apache/flink/agents/examples/agents/MathAgent.java
@@ -0,0 +1,101 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.flink.agents.examples.agents;
+
+import org.apache.flink.agents.api.InputEvent;
+import org.apache.flink.agents.api.OutputEvent;
+import org.apache.flink.agents.api.agents.Agent;
+import org.apache.flink.agents.api.annotation.Action;
+import org.apache.flink.agents.api.annotation.ChatModelSetup;
+import org.apache.flink.agents.api.annotation.Prompt;
+import org.apache.flink.agents.api.chat.messages.ChatMessage;
+import org.apache.flink.agents.api.chat.messages.MessageRole;
+import org.apache.flink.agents.api.context.RunnerContext;
+import org.apache.flink.agents.api.event.ChatRequestEvent;
+import org.apache.flink.agents.api.event.ChatResponseEvent;
+import org.apache.flink.agents.api.resource.ResourceDescriptor;
+import org.apache.flink.agents.api.resource.ResourceName;
+import org.apache.flink.agents.api.skills.Skills;
+
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * An agent that answers arithmetic questions using the {@code
math-calculator} skill.
+ *
+ * <p>Instead of relying on the LLM to compute by itself, this agent exposes a
{@code
+ * math-calculator} skill. When asked to evaluate an expression, the model
loads the skill ({@code
+ * load_skill}) and follows its instructions to compute the result with the
{@code bc} calculator
+ * through the built-in {@code bash} tool.
+ */
+public class MathAgent extends Agent {
+
+ /**
+ * Load skills from the {@code skills/} directory packaged on the
classpath.
+ *
+ * <p>The {@code @Skills} annotation and the {@link Skills} resource share
a simple name but
+ * live in different packages, so the annotation is fully-qualified here.
+ */
+ @org.apache.flink.agents.api.annotation.Skills
+ public static Skills mySkills() {
+ return Skills.fromClasspath("skills");
+ }
+
+ /** System prompt instructing the model to use the skill. */
+ @Prompt
+ public static org.apache.flink.agents.api.prompt.Prompt systemPrompt() {
+ return org.apache.flink.agents.api.prompt.Prompt.fromMessages(
+ Collections.singletonList(
+ new ChatMessage(
+ MessageRole.SYSTEM,
+ "You are a helpful math assistant. Use the
math-calculator skill "
+ + "when asked to evaluate an
expression. You must load the "
+ + "skill first and strictly follow its
instructions. Reply "
+ + "with only the final numeric
result.")));
+ }
+
+ /** ChatModel with the math-calculator skill enabled. */
+ @ChatModelSetup
+ public static ResourceDescriptor mathModel() {
+ return
ResourceDescriptor.Builder.newBuilder(ResourceName.ChatModel.OLLAMA_SETUP)
+ .addInitialArgument("connection", "ollamaChatModelConnection")
+ .addInitialArgument("model", "qwen3.5:9b")
+ .addInitialArgument("prompt", "systemPrompt")
+ // Expose the declared skill to this model by name.
+ .addInitialArgument("skills", List.of("math-calculator"))
+ // Whitelist the shell commands the built-in bash tool may run.
+ .addInitialArgument("allowed_commands", List.of("echo", "bc"))
+ .build();
+ }
+
+ /** Process input event and send a chat request to evaluate the question.
*/
+ @Action(listenEventTypes = {InputEvent.EVENT_TYPE})
+ public static void processInput(InputEvent event, RunnerContext ctx) {
+ ctx.sendEvent(
+ new ChatRequestEvent(
+ "mathModel",
+ Collections.singletonList(
+ new ChatMessage(MessageRole.USER, (String)
event.getInput()))));
+ }
+
+ /** Process chat response event and send the answer as output. */
+ @Action(listenEventTypes = {ChatResponseEvent.EVENT_TYPE})
+ public static void processChatResponse(ChatResponseEvent event,
RunnerContext ctx) {
+ ctx.sendEvent(new OutputEvent(event.getResponse().getContent()));
+ }
+}
diff --git a/examples/src/main/resources/skills/math-calculator/SKILL.md
b/examples/src/main/resources/skills/math-calculator/SKILL.md
new file mode 100644
index 00000000..c4f1071b
--- /dev/null
+++ b/examples/src/main/resources/skills/math-calculator/SKILL.md
@@ -0,0 +1,43 @@
+---
+name: math-calculator
+description: Calculate mathematical expressions using shell commands. Use when
the user asks to perform arithmetic calculations like addition, subtraction,
multiplication, division, or powers.
+license: Apache-2.0
+compatibility: Requires bash with bc (basic calculator)
+---
+
+# Math Calculator Skill
+
+This skill calculates mathematical expressions using shell commands.
+
+## When to Use
+
+Use this skill when the user asks to evaluate a numeric expression, such as:
+
+- Basic arithmetic (add, subtract, multiply, divide)
+- Expressions with parentheses
+- Powers and square roots
+
+## Method
+
+Pipe the expression into the `bc` (basic calculator) command:
+
+```bash
+echo "(2 + 3) * 4" | bc
+# Output: 20
+
+echo "2 ^ 10" | bc
+# Output: 1024
+```
+
+For decimal results, set the scale first:
+
+```bash
+echo "scale=2; 10 / 3" | bc
+# Output: 3.33
+```
+
+## Instructions
+
+1. Translate the user's question into a single `bc` expression.
+2. Run it with the `bash` tool: `echo "<expression>" | bc`.
+3. Report the number printed by `bc` as the answer.
diff --git a/python/flink_agents/examples/quickstart/agents/math_agent.py
b/python/flink_agents/examples/quickstart/agents/math_agent.py
new file mode 100644
index 00000000..20e3b8ac
--- /dev/null
+++ b/python/flink_agents/examples/quickstart/agents/math_agent.py
@@ -0,0 +1,99 @@
+################################################################################
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#################################################################################
+from flink_agents.api.agents.agent import Agent
+from flink_agents.api.chat_message import ChatMessage, MessageRole
+from flink_agents.api.decorators import action, chat_model_setup, prompt,
skills
+from flink_agents.api.events.chat_event import ChatRequestEvent,
ChatResponseEvent
+from flink_agents.api.events.event import Event, InputEvent, OutputEvent
+from flink_agents.api.prompts.prompt import Prompt
+from flink_agents.api.resource import ResourceDescriptor, ResourceName
+from flink_agents.api.runner_context import RunnerContext
+from flink_agents.api.skills import Skills
+
+
+class MathAgent(Agent):
+ """An agent that answers arithmetic questions using the math-calculator
skill.
+
+ Instead of relying on the LLM to compute by itself, this agent exposes a
+ ``math-calculator`` skill. When asked to evaluate an expression, the model
+ loads the skill (``load_skill``) and follows its instructions to compute
the
+ result with the ``bc`` calculator through the built-in ``bash`` tool.
+ """
+
+ @skills
+ @staticmethod
+ def my_skills() -> Skills:
+ """Declare where to load skills from.
+
+ The skills are bundled under this example package's
``resources/skills``
+ directory and loaded by package name, so the agent resolves them the
+ same way whether run from the source tree or the installed wheel.
+ """
+ return Skills.from_package(
+ ("flink_agents.examples.quickstart", "resources/skills")
+ )
+
+ @prompt
+ @staticmethod
+ def system_prompt() -> Prompt:
+ """System prompt instructing the model to use the skill."""
+ return Prompt.from_messages(
+ messages=[
+ ChatMessage(
+ role=MessageRole.SYSTEM,
+ content="You are a helpful math assistant. Use the "
+ "math-calculator skill when asked to evaluate an
expression. "
+ "You must load the skill first and strictly follow its "
+ "instructions. Reply with only the final numeric result.",
+ )
+ ],
+ )
+
+ @chat_model_setup
+ @staticmethod
+ def math_model() -> ResourceDescriptor:
+ """ChatModel with the math-calculator skill enabled."""
+ return ResourceDescriptor(
+ clazz=ResourceName.ChatModel.OLLAMA_SETUP,
+ connection="ollama_server",
+ model="qwen3.5:9b",
+ prompt="system_prompt",
+ # Expose the declared skill to this model by name.
+ skills=["math-calculator"],
+ # Whitelist the shell commands the built-in bash tool may run.
+ allowed_commands=["echo", "bc"],
+ )
+
+ @action(InputEvent.EVENT_TYPE)
+ @staticmethod
+ def process_input(event: Event, ctx: RunnerContext) -> None:
+ """Process input event and send a chat request to evaluate the
question."""
+ question: str = InputEvent.from_event(event).input
+ ctx.send_event(
+ ChatRequestEvent(
+ model="math_model",
+ messages=[ChatMessage(role=MessageRole.USER,
content=question)],
+ )
+ )
+
+ @action(ChatResponseEvent.EVENT_TYPE)
+ @staticmethod
+ def process_chat_response(event: Event, ctx: RunnerContext) -> None:
+ """Process chat response event and send the answer as output."""
+ chat_response = ChatResponseEvent.from_event(event)
+ ctx.send_event(OutputEvent(output=chat_response.response.content))
diff --git
a/python/flink_agents/examples/quickstart/resources/skills/math-calculator/SKILL.md
b/python/flink_agents/examples/quickstart/resources/skills/math-calculator/SKILL.md
new file mode 100644
index 00000000..c4f1071b
--- /dev/null
+++
b/python/flink_agents/examples/quickstart/resources/skills/math-calculator/SKILL.md
@@ -0,0 +1,43 @@
+---
+name: math-calculator
+description: Calculate mathematical expressions using shell commands. Use when
the user asks to perform arithmetic calculations like addition, subtraction,
multiplication, division, or powers.
+license: Apache-2.0
+compatibility: Requires bash with bc (basic calculator)
+---
+
+# Math Calculator Skill
+
+This skill calculates mathematical expressions using shell commands.
+
+## When to Use
+
+Use this skill when the user asks to evaluate a numeric expression, such as:
+
+- Basic arithmetic (add, subtract, multiply, divide)
+- Expressions with parentheses
+- Powers and square roots
+
+## Method
+
+Pipe the expression into the `bc` (basic calculator) command:
+
+```bash
+echo "(2 + 3) * 4" | bc
+# Output: 20
+
+echo "2 ^ 10" | bc
+# Output: 1024
+```
+
+For decimal results, set the scale first:
+
+```bash
+echo "scale=2; 10 / 3" | bc
+# Output: 3.33
+```
+
+## Instructions
+
+1. Translate the user's question into a single `bc` expression.
+2. Run it with the `bash` tool: `echo "<expression>" | bc`.
+3. Report the number printed by `bc` as the answer.
diff --git a/python/flink_agents/examples/quickstart/skills_agent_example.py
b/python/flink_agents/examples/quickstart/skills_agent_example.py
new file mode 100644
index 00000000..c9dd8f16
--- /dev/null
+++ b/python/flink_agents/examples/quickstart/skills_agent_example.py
@@ -0,0 +1,77 @@
+################################################################################
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#################################################################################
+from pyflink.datastream import StreamExecutionEnvironment
+
+from flink_agents.api.core_options import AgentExecutionOptions
+from flink_agents.api.execution_environment import AgentsExecutionEnvironment
+from flink_agents.api.resource import ResourceType
+from flink_agents.examples.quickstart.agents.custom_types_and_resources import
(
+ ollama_server_descriptor,
+)
+from flink_agents.examples.quickstart.agents.math_agent import MathAgent
+
+
+def main() -> None:
+ """Main function for the agent skills quickstart example.
+
+ This example demonstrates how to use Agent Skills with Flink Agents. A
stream
+ of arithmetic questions is processed by ``MathAgent``, whose chat model
has a
+ ``math-calculator`` skill enabled. For each question the model loads the
skill
+ and follows its instructions to compute the answer with the ``bc``
calculator,
+ then the result is printed to stdout. This serves as a minimal, end-to-end
+ example of integrating skill-powered agents with Flink streaming jobs.
+ """
+ # Set up the Flink streaming environment and the Agents execution
environment.
+ env = StreamExecutionEnvironment.get_execution_environment()
+ agents_env = AgentsExecutionEnvironment.get_execution_environment(env)
+
+ # limit async request to avoid overwhelming ollama server
+ agents_env.get_config().set(AgentExecutionOptions.NUM_ASYNC_THREADS, 2)
+
+ # Add Ollama chat model connection to be used by the MathAgent.
+ agents_env.add_resource(
+ "ollama_server",
+ ResourceType.CHAT_MODEL_CONNECTION,
+ ollama_server_descriptor,
+ )
+
+ # A small stream of arithmetic questions to answer.
+ question_stream = env.from_collection(
+ [
+ "What is (2 + 3) * 4?",
+ "Compute 2 ^ 10.",
+ "What is 144 divided by 12?",
+ ],
+ )
+
+ # Use the MathAgent to answer each question with the math-calculator skill.
+ answer_stream = (
+ agents_env.from_datastream(input=question_stream, key_selector=lambda
x: x)
+ .apply(MathAgent())
+ .to_datastream()
+ )
+
+ # Print the answers to stdout.
+ answer_stream.print()
+
+ # Execute the Flink pipeline.
+ agents_env.execute()
+
+
+if __name__ == "__main__":
+ main()
diff --git a/python/pyproject.toml b/python/pyproject.toml
index 0c8f74e5..f5d9814c 100644
--- a/python/pyproject.toml
+++ b/python/pyproject.toml
@@ -71,7 +71,7 @@ exclude = ["_build_backend*"]
[tool.setuptools.package-data]
"flink_agents.lib" = ["**/*.jar"]
"flink_agents.examples.quickstart" = ["**/*.yaml"]
-"flink_agents.examples.quickstart.resources" = ["**/*.txt"]
+"flink_agents.examples.quickstart.resources" = ["**/*.txt", "**/*.md"]
# Optional dependencies (dependency groups)
[project.optional-dependencies]