Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 25 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,30 @@ Requires: nothing — just a model API key

---

### Hello Graph — minimal graph agent

The smallest possible v2 graph agent: one classify step, one answer step, one
finalize step. No MCP server, no HITL gate. Read this before Bank Transfer or
Postal Tracking to see the shape of a graph agent with nothing else in the way.

```
Agent ID: fred.samples.hello_graph
Requires: nothing — just a model API key
```

**Workflow:**
1. `classify` reads your message and decides "greeting" or "question".
2. `greet` or `answer` produces one model reply on the matching branch.
3. `finalize` returns it.

**Try it:**
```
Hi!
What's the capital of France?
```

---

### Bank Transfer — HITL demo

A workflow agent that executes a fund transfer through two mandatory human
Expand Down Expand Up @@ -166,6 +190,7 @@ Current agent: assistant
Switch to a sample agent:

```
/agent fred.samples.hello_graph
/agent fred.samples.bank_transfer.graph
/agent fred.samples.postal_tracking.graph
/agent fred.samples.team_of_3.router
Expand Down
10 changes: 7 additions & 3 deletions agents/config/configuration.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,6 @@ app:
base_url: "/samples/agents/v1"
port: 8010
log_level: "debug"
metrics_address: "127.0.0.1"
metrics_port: 9115

security:
m2m:
Expand All @@ -25,7 +23,13 @@ ai:

observability:
tracer: logging # null | logging | langfuse
metrics: prometheus # null | logging | prometheus
kpi:
log:
enabled: true
prometheus:
enabled: false
opensearch:
enabled: false

storage:
postgres:
Expand Down
66 changes: 66 additions & 0 deletions agents/config/configuration_prod.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
app:
name: "Fred Sample Agents"
base_url: "/samples/agents/v1"
host: "0.0.0.0"
port: 8010
log_level: "info"

security:
m2m:
enabled: true
client_id: "agentic"
realm_url: "http://app-keycloak:8080/realms/app"
secret_env_var: KEYCLOAK_AGENTIC_CLIENT_SECRET # pragma: allowlist secret (env var name only)
user:
enabled: true
client_id: "app"
# Must be browser-reachable: tokens validated here are issued client-side
# by keycloak-js against this same URL. "app-keycloak" only resolves
# inside the docker-compose network. security.m2m.realm_url above stays
# docker-internal (backend-to-Keycloak only). Mirrors the split applied
# in fred/apps/control-plane-backend and fred/apps/knowledge-flow-backend.
realm_url: "http://localhost:8080/realms/app"
authorized_origins:
- "http://localhost:5173"
rebac:
enabled: true
type: openfga
api_url: "http://localhost:9080"

ai:
knowledge_flow_url: "http://localhost:8111/knowledge-flow/v1"
timeout:
connect: 5
read: 30

observability:
tracer: logging # null | logging | langfuse — no Langfuse creds in this pod's .env yet
kpi:
log:
enabled: true
prometheus:
enabled: true
port: 9010 # app.port + 1000, same convention as the other pods; 9000/9111/9222 already taken
opensearch:
enabled: true

storage:
postgres:
host: localhost
port: 5432
database: fred
username: fred
opensearch:
host: "https://localhost:9200"
username: "admin"
secure: true
verify_certs: false
log_store:
type: opensearch
index: app-logs-index

scheduler:
enabled: false

platform:
control_plane_url: "http://localhost:8222/control-plane/v1"
13 changes: 13 additions & 0 deletions agents/fred_samples_agents/hello_graph/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
# Copyright Thales 2026
#
# Licensed 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.
94 changes: 94 additions & 0 deletions agents/fred_samples_agents/hello_graph/graph_agent.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
# Copyright Thales 2026
#
# Licensed 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.

"""
Graph definition for the Hello Graph sample agent.

Purpose:
- the minimal v2 graph agent: no MCP server, no human-in-the-loop gate, no
external dependency of any kind beyond a configured chat model
- read this first, before bank_transfer or postal_tracking, to see the shape
of a graph agent with nothing else in the way

Workflow overview:
classify
├─ greeting ──► greet ──► finalize
└─ question ──► answer ──► finalize

No MCP servers required — just a configured model.
"""

from __future__ import annotations

from fred_sdk import GraphAgent, GraphWorkflow

from .graph_state import HelloGraphInput, HelloGraphState
from .graph_steps import (
answer_step,
classify_step,
finalize_step,
greet_step,
)


class HelloGraphAgent(GraphAgent):
"""
Sample v2 graph agent with the smallest possible shape: classify, then
answer on one of two branches.

Use this agent as a reference when building any workflow agent, before
adding MCP tools or HITL gates. Once this shape is clear, look at
bank_transfer (MCP + two HITL gates) or postal_tracking (MCP + map +
one HITL gate) for a fuller example.

Change graph_agent.py when the workflow shape changes.
Change graph_state.py when the data model changes.
Change graph_steps.py when step behaviour changes.
"""

agent_id: str = "fred.samples.hello_graph"
role: str = "Hello Graph"
description: str = (
"Minimal sample graph agent: classifies a message as a greeting or a "
"question, then answers on the matching branch. No MCP server, no HITL."
)
tags: tuple[str, ...] = ("sample", "graph", "hello_world", "minimal", "v2")

input_schema = HelloGraphInput
state_schema = HelloGraphState
input_to_state = {"message": "latest_user_text"}
output_state_field = "final_text"

workflow = GraphWorkflow(
entry="classify",
nodes={
"classify": classify_step,
"greet": greet_step,
"answer": answer_step,
"finalize": finalize_step,
},
edges={
"greet": "finalize",
"answer": "finalize",
},
routes={
"classify": {
"greeting": "greet",
"question": "answer",
},
},
)


HELLO_GRAPH_AGENT = HelloGraphAgent()
66 changes: 66 additions & 0 deletions agents/fred_samples_agents/hello_graph/graph_state.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
# Copyright Thales 2026
#
# Licensed 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.

"""
Input and state models for the Hello Graph sample agent.

This is the smallest state shape a v2 graph agent needs: one field for what the
user said, one field for the routing decision, one field for the answer.
"""

from __future__ import annotations

from typing import Literal

from pydantic import BaseModel, Field


class HelloGraphInput(BaseModel):
"""
User message that starts one hello-graph turn.

Example:
```python
request = HelloGraphInput(message="Hi there!")
```
"""

message: str = Field(..., min_length=1)


class HelloGraphState(BaseModel):
"""
Workflow state for the hello-graph sample.

Example:
```python
state = HelloGraphState(
latest_user_text="Hi there!",
kind="greeting",
final_text="Hello! Ask me anything.",
)
```
"""

latest_user_text: str

# Set by classify_step; drives the routes= branch in the workflow.
kind: Literal["greeting", "question"] | None = None

# Terminal output written by greet_step or answer_step.
final_text: str | None = None
done_reason: str | None = None

# Set by the runtime when a node raises and on_error routing fires.
node_error: str = ""
Loading