Skip to main content

fred-sdk

fred-sdk is the authoring library for what you add to Fred: agents (ReAct, Graph, Team, Deep), their tools, and Knowledge Bases that keep a team's library in sync with an outside source. It depends on no running platform service and stays importable on a bare laptop.


Where fred-sdk fits

fred-pod                    configuration, identity, naming
└── fred-sdk                authoring contracts            ← this package
    ├── [knowledge-base]    + the workflow engine          → a Knowledge Base pod
    └── [agents]            + fred-core, langchain, langgraph
        └── fred-runtime    execution, MCP, model routing  → an agent pod

Agent and Knowledge Base logic belongs here. Infrastructure wiring (database, MCP connections, Keycloak, object storage) belongs in fred-runtime. What a pod is, and which package each kind installs: fred-pod.


Installation

pip install fred-sdk[agents]          # agent authoring
pip install fred-sdk[knowledge-base]  # a Knowledge Base pod

The base install carries only configuration, identity and the plain contract models, so a Knowledge Base pod never pulls in the agents platform; [agents] adds fred-core, langchain and langgraph on top.

Requires Python 3.12.


Agent types

ReAct agent

Tool-calling assistant backed by a ReAct loop. The most common agent type.

from fred_sdk import ReActAgent, ToolContext, ToolOutput, tool

@tool("acme.weather.current", description="Get the current weather for a city.")
async def get_weather(context: ToolContext, city: str) -> ToolOutput:
    # call an external API here
    return context.text(f"It is sunny in {city}.")

class WeatherAgent(ReActAgent):
    agent_id: str = "acme.weather.assistant"
    role: str = "Weather assistant"
    description: str = "Answers weather questions with the get_weather tool."
    system_prompt_template: str = "You are a helpful weather assistant."
    tools = (get_weather,)

WEATHER_AGENT = WeatherAgent()  # what a pod's registry holds

agent_id is a contributed name: a dotted name under a prefix you own.


Graph agent

Deterministic workflow with typed state. Nodes are Python functions; edges and conditional routes are declared in GraphWorkflow.

from pydantic import BaseModel
from fred_sdk import GraphAgent, GraphNodeContext, GraphWorkflow, StepResult, typed_node

class EchoInput(BaseModel):
    message: str

class EchoState(BaseModel):
    latest_user_text: str = ""
    final_text: str | None = None      # the answer the user sees

@typed_node(EchoState)
async def echo(state: EchoState, context: GraphNodeContext) -> StepResult:
    return StepResult(state_update={"final_text": f"You said: {state.latest_user_text}"})

class EchoAgent(GraphAgent):
    agent_id: str = "acme.samples.echo"
    role: str = "Echo"
    description: str = "Repeats the message back, in one graph step."
    input_schema = EchoInput
    state_schema = EchoState
    input_to_state = {"message": "latest_user_text"}
    workflow = GraphWorkflow(entry="echo", nodes={"echo": echo})

A node returns a StepResult; route_key picks the next node among the routes declared in GraphWorkflow, and edges chain nodes unconditionally.

Graph workflow primitives available from fred_sdk:

Primitive What it does
typed_node Decorator — turns a function into a typed graph node
GraphWorkflow Declares nodes, edges, and conditional routes
StepResult What a node returns: a state update and an optional route_key

The *_step functions are helpers you call inside a node, with its context:

Helper What it does
model_text_step Calls the model and returns its text
structured_model_step Calls the model and returns a parsed Pydantic object
intent_router_step Classifies the request and returns a StepResult routed on it
choice_step Pauses for a human choice and returns the chosen id (HITL)
finalize_step Builds the node result that ends the graph with its final text

Team agent

Multi-agent composition. A coordinator routes or sequences work across members.

from fred_sdk import TeamAgent, AgentSpec

class SupportRouter(TeamAgent):
    agent_id: str = "acme.support.router"
    role: str = "Support request router"
    description: str = "Routes support requests to the right specialist."
    mode = "route"
    coordinator_instructions = "Pick the right specialist based on user intent."
    members = (
        AgentSpec(name="Billing", role="Billing questions", agent_ref="acme.billing.agent"),
        AgentSpec(name="Technical", role="Technical issues", agent_ref="acme.technical.agent"),
    )

Three modes:

Mode Behaviour
sequential Members run in order; each is an inline LLM call
dynamic A coordinator LLM decides who runs next after each member
route A coordinator LLM picks exactly one registered agent and delegates the full request

Child agents used as agent_ref targets should set public = False so they are not exposed as top-level models in Open WebUI or other OpenAI-compatible frontends.


Deep agent

Extended ReAct variant with a built-in planning step. Inherits the full ReAct authoring surface; the planning engine is wired by the runtime.

from fred_sdk import DeepAgentDefinition, ReActPolicy

class ResearchAgent(DeepAgentDefinition):
    agent_id: str = "acme.research.deep"
    role: str = "Research assistant"
    description: str = "Plans multi-step research before answering."
    system_prompt_template: str = "Break the request into steps, then answer."

    def policy(self) -> ReActPolicy:
        return ReActPolicy(system_prompt_template=self.system_prompt_template)

Tool authoring

A tool is a module-level async function decorated with @tool and listed in the agent's tools = (...). Its first parameter is always the ToolContext; the others are its inputs, and their type annotations become the schema the model sees.

from fred_sdk import ToolContext, ToolOutput, tool

@tool("acme.docs.search", description="Search the team's documents for a query.")
async def search_docs(context: ToolContext, query: str) -> ToolOutput:
    result = await context.invoke_tool("knowledge.search", query=query, top_k=5)
    if not result.sources:
        return context.error("No documents found.")
    return context.text(f"Found {len(result.sources)} passages.")

ToolContext is how a tool uses Fred without touching the runtime: call other tools (invoke_tool), read the agent's settings (config), read and write files (read, write, ls), and answer (text, json, error, link). The user's identity and token stay inside the runtime.


Human-in-the-loop (HITL)

A graph node pauses for the user with choice_step. The run is checkpointed, and the node resumes with the chosen id once the user answers; route on it with route_key.

from fred_sdk import GraphNodeContext, HumanChoiceOption, StepResult, choice_step, typed_node

@typed_node(TransferState)
async def confirm_transfer(state: TransferState, context: GraphNodeContext) -> StepResult:
    choice = await choice_step(
        context,
        stage="confirm_transfer",
        title="Confirm transfer",
        question=f"Transfer {state.amount} EUR?",
        choices=[
            HumanChoiceOption(id="confirm", label="Yes, confirm"),
            HumanChoiceOption(id="cancel", label="No, cancel"),
        ],
    )
    return StepResult(route_key="confirmed" if choice == "confirm" else "cancelled")

The bank transfer sample in fred-samples has two such gates.


MCP server references

Declare which MCP servers an agent needs. The runtime wires the actual connection.

from fred_sdk import MCP_SERVER_KNOWLEDGE_FLOW_CORPUS, MCPServerRef, ReActAgent

class DocumentAgent(ReActAgent):
    agent_id: str = "acme.docs.assistant"
    role: str = "Document assistant"
    description: str = "Answers from the team's documents."
    system_prompt_template: str = "Answer from the documents you find."
    default_mcp_servers: tuple[MCPServerRef, ...] = (MCP_SERVER_KNOWLEDGE_FLOW_CORPUS,)

Built-in MCP server constants:

Constant Connects to
MCP_SERVER_KNOWLEDGE_FLOW_CORPUS Document search and retrieval
MCP_SERVER_KNOWLEDGE_FLOW_FS Workspace file system
MCP_SERVER_KNOWLEDGE_FLOW_TABULAR Tabular data / CSV
MCP_SERVER_KNOWLEDGE_FLOW_OPENSEARCH_OPS OpenSearch operations

Built-in tool references

Pre-built platform tools declared by reference (no implementation needed in the agent):

from fred_sdk import TOOL_REF_KNOWLEDGE_SEARCH, ReActAgent, ToolRefRequirement

class SearchAgent(ReActAgent):
    agent_id: str = "acme.docs.search"
    role: str = "Search assistant"
    description: str = "Finds passages in the team's documents."
    system_prompt_template: str = "Search before you answer."
    declared_tool_refs: tuple[ToolRefRequirement, ...] = (
        ToolRefRequirement(tool_ref=TOOL_REF_KNOWLEDGE_SEARCH),
    )
Constant What it does
TOOL_REF_KNOWLEDGE_SEARCH Semantic/hybrid search over indexed documents
TOOL_REF_RESOURCES_FETCH_TEXT Fetch document content as text
TOOL_REF_ARTIFACTS_PUBLISH_TEXT Publish a text artifact to the workspace
TOOL_REF_GEO_RENDER_POINTS Render geographic points on a map
TOOL_REF_TRACES_SUMMARIZE_CONVERSATION Summarize conversation traces

Testing your nodes offline

Graph nodes reach the model and sub-agents through two calls: context.invoke_agent(...) and context.invoke_structured_model(...) (used by structured_model_step). fred_sdk.testing ships FakeGraphNodeContext, a double covering exactly those two, so a node's business logic can be tested without a real model or a real sub-agent.

from typing import cast
from fred_sdk import AgentInvocationResult, GraphNodeContext
from fred_sdk.testing import FakeGraphNodeContext

context = FakeGraphNodeContext(
    agent_result=AgentInvocationResult(
        agent_id="my.specialist.agent", structured={"trust": "high"}
    ),
    structured_results={"intent": "question_cloud_general"},
)
result = await my_node(state, cast(GraphNodeContext, context))

assert context.agent_calls[0]["agent_id"] == "my.specialist.agent"

A call you did not configure raises AssertionError immediately, so an under-specified test fails loudly instead of silently returning None into your node's logic.


Running an agent

fred-sdk defines agents; fred-runtime executes them. A pod serves a registry of agent instances:

# main.py
from fred_runtime.app import create_agent_app, load_agent_pod_config

REGISTRY = {WEATHER_AGENT.agent_id: WEATHER_AGENT}

app = create_agent_app(registry=REGISTRY, config=load_agent_pod_config())

The full pod setup is in the fred-runtime README, and fred-samples is a working pod with several agents.


Knowledge Bases — beta

fred_sdk.knowledge_base tells Fred where a team's documents come from and how to keep them current: declare an identity and configuration fields, write one async handler, call knowledge_base_main(kb). Fred supplies the destination library, a workload identity and a cadence; the handler owns discovery, replay-safe writes and explicit retractions — Fred never infers a deletion from absence.

from fred_sdk.contracts.models import FieldSpec
from fred_sdk.knowledge_base import (
    DocumentPublisher, KnowledgeBase, KnowledgeBaseRunContext,
    KnowledgeBaseRunOutcome, KnowledgeBaseSyncResult, knowledge_base_main,
)
from fred_sdk.knowledge_base.configuration import PodConfiguration

kb = KnowledgeBase(
    id="acme.notes.hello",
    version="1.0.0",
    name="Hello notes",
    description="Keeps one note in the team's library.",
    configuration_fields=[
        FieldSpec(key="greeting", type="string", title="Greeting",
                  description="Text of the note.", default="Hello"),
    ],
)

@kb.synchronize
async def synchronize(context: KnowledgeBaseRunContext) -> KnowledgeBaseSyncResult:
    text = str(context.configuration.get("greeting") or "Hello")
    async with DocumentPublisher(
        PodConfiguration.load(), library_id=context.library_id, source_tag="fred"
    ) as publisher:
        handle = await publisher.publish(
            relative_path="hello.md", content=f"# {text}".encode(), version=text
        )
        outcome = await publisher.wait(handle.task_id)
    return KnowledgeBaseSyncResult(
        outcome=KnowledgeBaseRunOutcome.succeeded if outcome.succeeded
        else KnowledgeBaseRunOutcome.failed,
        reconciliation_complete=True,
        discovered=1,
        created=int(handle.created),
        updated=int(not handle.created),
    )

if __name__ == "__main__":
    raise SystemExit(knowledge_base_main(kb))  # the `publish` and `run` commands

DocumentPublisher is the shortcut for writing: a write is accepted at once and ingested by Fred afterwards, wait follows it to its end, and documents() reads back what the library holds so a run can reconcile against it. A source that can say what changed since a version (a Git revision) keeps that version in Fred with record_source_version() and reads it back with source_version(). Install fred-sdk[knowledge-base]. Three working Knowledge Bases live in fred-samples.

A Knowledge Base acts as a workload, so it needs a deployment that authenticates and a confidential client of its own. A pod started without its client secret and realm fails immediately, naming what it lacks. A stack running with authentication off cannot host one — including for local development.

This surface is beta: pin your fred-sdk version, as it may change between beta releases. Known limits today:

  • Configuration fields are fixed once instances exist — there is no schema migration. Deleting a synchronized folder deletes its documents.
  • A run's fate is visible in the workflow engine only. Counters, summaries and issues a handler returns are not stored or displayed, and Fred offers no run history, manual trigger or worker-health check yet.
  • A relative path is the document key, so a rename reads as a delete plus an add.
  • Cadences are hourly, daily or weekly, with no immediate first run.

The label applies to Knowledge Bases only, not to this SDK's agent APIs.


Package PyPI Role
fred-pod pypi What every pod shares — configuration, identity, naming
fred-core pypi The agents platform — stores, model providers, logging, observability
fred-sdk pypi This package
fred-runtime pypi Agent execution, platform adapters and the pod factory

License

Apache License 2.0.

Metadata

Release files for fred-sdk 4.4.1

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for fred-sdk 4.4.1
File Size Uploaded
fred_sdk-4.4.1.tar.gz 212.4 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for fred-sdk 4.4.1
File Interpreter ABI Platform
fred_sdk-4.4.1-py3-none-any.whl Python 3 none any Details

Total release size: 410.3 kB

Release files / fred_sdk-4.4.1.tar.gz

Download URL fred_sdk-4.4.1.tar.gz
Size 212.4 kB
Tags Source
SHA-256 checksum
How to use checksums
7808d17ea8c2c0f477ba292cf562c21e7a09ea5f6997fe0a26b1123ce6fecd87
BLAKE2b-256 checksum
How to use checksums
ad20da0ede8ea15a7eb0af24fdb66769580e64dea0513ec6e86808abe49109ad
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via uv/0.11.21 {"installer":{"name":"uv","version":"0.11.21","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}

Release files / fred_sdk-4.4.1-py3-none-any.whl

Download URL fred_sdk-4.4.1-py3-none-any.whl
Size 197.9 kB
Tags Python 3
SHA-256 checksum
How to use checksums
99f6149171f5203716384f350e41b1e6d2dc588dc8de788609719357702bc439
BLAKE2b-256 checksum
How to use checksums
c3663d3dd4293f73bd3315f40b0d843b4cf3bff340291d5be6480bdc16e1dc57
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via uv/0.11.21 {"installer":{"name":"uv","version":"0.11.21","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}

Release history Release notifications | RSS feed

4.5.1

2 release files

4.5.0

2 release files

4.4.3

2 release files

4.4.2

2 release files

This release

4.4.1 This release

2 release files

4.4.0

2 release files

4.3.0

2 release files

4.1.0

2 release files

4.0.0

2 release files

3.7.0

2 release files

3.5.0

2 release files

3.4.3

2 release files

3.4.2

2 release files

3.4.1

2 release files

3.4.0

2 release files

3.3.5

2 release files

3.3.4

2 release files

3.3.3

2 release files

3.3.2

2 release files

3.3.1

2 release files

3.3.0

2 release files

3.1.1

2 release files

3.1.0

2 release files

2.0.9

2 release files

2.0.8

2 release files

2.0.7

2 release files

2.0.6

2 release files

2.0.5

2 release files

2.0.3

2 release files

2.0.2

2 release files

2.0.1

2 release files

2.0.0

2 release files

0.1.11

2 release files

0.1.10

2 release files

0.1.9

2 release files

0.1.8

2 release files

0.1.6

2 release files

0.1.5

2 release files

0.1.4

2 release files

0.1.3

2 release files

0.1.2

2 release files

0.1.1

2 release files

0.1.0

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page