diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 40fad89..f7c62d4 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -25,7 +25,7 @@ repos: exclude: ^(tests|docs|examples)/ - repo: https://github.com/asottile/pyupgrade - rev: v3.17.0 + rev: v3.21.2 hooks: - id: pyupgrade args: [--py310-plus] diff --git a/MANIFEST.in b/MANIFEST.in index 01d81f9..5c97e05 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -6,6 +6,7 @@ include example_weather_agent.py recursive-include agentflow_cli *.json recursive-include agentflow_cli *.yaml recursive-include agentflow_cli *.yml +recursive-include agentflow_cli *.md recursive-include agentflow_cli *.py recursive-include src *.json recursive-include src *.yaml diff --git a/agentflow_cli/cli/commands/skills.py b/agentflow_cli/cli/commands/skills.py new file mode 100644 index 0000000..4d22452 --- /dev/null +++ b/agentflow_cli/cli/commands/skills.py @@ -0,0 +1,248 @@ +"""Skills command implementation.""" + +from __future__ import annotations + +import json +import shutil +import sys +from dataclasses import dataclass +from datetime import UTC, datetime +from pathlib import Path +from typing import Any, Literal + +import typer + +from agentflow_cli.cli.commands import BaseCommand +from agentflow_cli.cli.constants import CLI_VERSION +from agentflow_cli.cli.exceptions import FileOperationError, ValidationError + + +_MANIFEST_FILENAME = ".agentflow-skill.json" + + +@dataclass(frozen=True) +class _AgentTarget: + """Describes how the bundled skill is materialised for one agent.""" + + name: str + kind: Literal["folder", "file"] + install_relpath: str + source_relpath: str + + +_TARGETS: tuple[_AgentTarget, ...] = ( + _AgentTarget( + name="Codex", + kind="folder", + install_relpath=".agents/skills/agentflow", + source_relpath="agent-skills", + ), + _AgentTarget( + name="Claude", + kind="folder", + install_relpath=".claude/skills/agentflow", + source_relpath="agent-skills", + ), + _AgentTarget( + name="GitHub", + kind="file", + install_relpath=".github/instructions/agentflow.instructions.md", + source_relpath="copilot/agentflow.instructions.md", + ), +) + +_AGENT_LOOKUP: dict[str, _AgentTarget] = { + **{t.name.lower(): t for t in _TARGETS}, + "1": _TARGETS[0], + "2": _TARGETS[1], + "3": _TARGETS[2], +} + + +class SkillsCommand(BaseCommand): + """Command to install bundled Agentflow skills for supported agents.""" + + def execute( + self, + agent: str | None = None, + path: str = ".", + force: bool = False, + all_agents: bool = False, + list_agents: bool = False, + **kwargs: Any, + ) -> int: + """Execute the skills command. + + Args: + agent: Target agent name or menu number. + path: Project directory where the agent skill should be installed. + force: Overwrite an existing installation. + all_agents: Install for every supported agent. + list_agents: Print supported agents and exit. + **kwargs: Additional arguments. + + Returns: + Exit code. + """ + try: + self.output.print_banner( + "Skills", + "Install bundled Agentflow skills for Codex, Claude, or GitHub Copilot.", + color="magenta", + ) + + if list_agents: + self._print_agents() + return 0 + + if all_agents and agent: + raise ValidationError("--all cannot be combined with --agent.", field="agent") + + templates_root = self._templates_root() + project_root = self._safe_project_root(path) + + if all_agents: + return self._install_all(templates_root, project_root, force=force) + + target = self._select_agent(agent) + self._install_one(templates_root, project_root, target, force=force) + return 0 + + except (FileOperationError, ValidationError) as e: + return self.handle_error(e) + except OSError as e: + file_error = FileOperationError(f"Failed to install Agentflow skills: {e}") + file_error.__cause__ = e + return self.handle_error(file_error) + + def _install_one( + self, + templates_root: Path, + project_root: Path, + target: _AgentTarget, + *, + force: bool, + ) -> None: + source = templates_root / target.source_relpath + if not source.exists(): + raise FileOperationError( + f"Bundled skills template not found: {source}", file_path=str(source) + ) + + dest = project_root / target.install_relpath + + if dest.exists(): + if not force: + raise FileOperationError( + f"Skill already installed at {dest}. Use --force to overwrite.", + file_path=str(dest), + ) + if dest.is_dir(): + shutil.rmtree(dest) + else: + dest.unlink() + + dest.parent.mkdir(parents=True, exist_ok=True) + + if target.kind == "folder": + shutil.copytree( + source, + dest, + ignore=shutil.ignore_patterns("__pycache__", "*.pyc", ".DS_Store"), + ) + self._write_manifest(dest, target.name) + else: + shutil.copyfile(source, dest) + + self.output.success(f"Installed Agentflow skills for {target.name} at {dest}") + + def _install_all(self, templates_root: Path, project_root: Path, *, force: bool) -> int: + installed = 0 + skipped: list[str] = [] + failed: list[str] = [] + for target in _TARGETS: + dest = project_root / target.install_relpath + if dest.exists() and not force: + skipped.append(f"{target.name} ({dest})") + continue + try: + self._install_one(templates_root, project_root, target, force=force) + installed += 1 + except (FileOperationError, OSError, UnicodeError) as e: + self.logger.error("Install failed for %s: %s", target.name, e) + failed.append(f"{target.name}: {e}") + + if skipped: + self.output.warning( + "Skipped existing installs (use --force to overwrite): " + ", ".join(skipped) + ) + if failed: + self.output.error("Failed installs: " + "; ".join(failed)) + + if failed and installed == 0: + return 1 + return 0 + + def _write_manifest(self, target_dir: Path, agent_name: str) -> None: + manifest = { + "agent": agent_name, + "cli_version": CLI_VERSION, + "installed_at": datetime.now(UTC).isoformat(timespec="seconds"), + } + (target_dir / _MANIFEST_FILENAME).write_text( + json.dumps(manifest, indent=2) + "\n", encoding="utf-8" + ) + + def _print_agents(self) -> None: + rows = [[t.name, t.kind, t.install_relpath] for t in _TARGETS] + self.output.print_table( + ["Agent", "Kind", "Install path (relative to --path)"], + rows, + title="Supported agents", + ) + + def _safe_project_root(self, path: str) -> Path: + project_root = Path(path).resolve() + if project_root.parent == project_root: + raise ValidationError( + f"Refusing to install skills at filesystem root: {project_root}", + field="path", + ) + if project_root == Path.home().resolve(): + raise ValidationError( + f"Refusing to install skills directly into the home directory: {project_root}. " + "Pass --path pointing at a project directory.", + field="path", + ) + return project_root + + def _select_agent(self, agent: str | None) -> _AgentTarget: + if agent: + return self._normalize_agent(agent) + + if not sys.stdin.isatty(): + raise ValidationError( + "No --agent provided and stdin is not interactive. " + "Pass --agent codex|claude|github or --all.", + field="agent", + ) + + self.output.print_list( + [f"{i}. {t.name}" for i, t in enumerate(_TARGETS, 1)], + title="Which agent?", + bullet="-", + ) + selected = typer.prompt("Select an agent", default="1") + return self._normalize_agent(selected) + + def _normalize_agent(self, value: str) -> _AgentTarget: + key = value.strip().lower() + if key in _AGENT_LOOKUP: + return _AGENT_LOOKUP[key] + + valid = "Codex, Claude, GitHub, or 1, 2, 3" + raise ValidationError(f"Invalid agent '{value}'. Choose {valid}.", field="agent") + + def _templates_root(self) -> Path: + cli_dir = Path(__file__).resolve().parents[1] + return cli_dir / "templates" / "skills" diff --git a/agentflow_cli/cli/main.py b/agentflow_cli/cli/main.py index acc5b79..08add67 100644 --- a/agentflow_cli/cli/main.py +++ b/agentflow_cli/cli/main.py @@ -8,6 +8,7 @@ from agentflow_cli.cli.commands.api import APICommand from agentflow_cli.cli.commands.build import BuildCommand from agentflow_cli.cli.commands.init import InitCommand +from agentflow_cli.cli.commands.skills import SkillsCommand from agentflow_cli.cli.commands.version import VersionCommand from agentflow_cli.cli.constants import ( DEFAULT_CONFIG_FILE, @@ -308,6 +309,67 @@ def build( sys.exit(handle_exception(e)) +@app.command() +def skills( + agent: str | None = typer.Option( + None, + "--agent", + "-a", + help="Target agent: codex, claude, github, or menu number 1, 2, 3", + ), + path: str = typer.Option( + ".", + "--path", + "-p", + help="Project directory where the skills should be installed", + ), + force: bool = typer.Option( + False, + "--force", + "-f", + help="Overwrite the existing installed Agentflow skill directory", + ), + all_agents: bool = typer.Option( + False, + "--all", + help="Install skills for every supported agent", + ), + list_agents: bool = typer.Option( + False, + "--list", + "-l", + help="List supported agents and exit", + ), + verbose: bool = typer.Option( + False, + "--verbose", + "-v", + help="Enable verbose logging", + ), + quiet: bool = typer.Option( + False, + "--quiet", + "-q", + help="Suppress all output except errors", + ), +) -> None: + """Install bundled Agentflow skills for Codex, Claude, or GitHub.""" + setup_cli_logging(verbose=verbose, quiet=quiet) + + try: + command = SkillsCommand(output) + exit_code = command.execute( + agent=agent, + path=path, + force=force, + all_agents=all_agents, + list_agents=list_agents, + ) + sys.exit(exit_code) + except Exception as e: + sys.exit(handle_exception(e)) + + def main() -> None: """Main CLI entry point.""" try: diff --git a/agentflow_cli/cli/templates/skills/agent-skills/SKILL.md b/agentflow_cli/cli/templates/skills/agent-skills/SKILL.md new file mode 100644 index 0000000..0d73205 --- /dev/null +++ b/agentflow_cli/cli/templates/skills/agent-skills/SKILL.md @@ -0,0 +1,100 @@ +--- +name: agentflow +description: Use for building, debugging, documenting, or extending Agentflow agents, tools, graphs, API/CLI services, TypeScript clients, memory, checkpointing, streaming, media, dependency injection, production runtime, and multi-agent workflows in this repository. +metadata: + resources: + - references/architecture.md + - references/agents-and-tools.md + - references/state-graph.md + - references/state-and-messages.md + - references/checkpointing-and-threads.md + - references/dependency-injection.md + - references/media-and-files.md + - references/memory-and-store.md + - references/streaming.md + - references/production-runtime.md + - references/api-client.md + - references/remote-tools.md + - references/callbacks-and-command.md + - references/prebuilt-agents-and-tools.md + - references/testing-and-evaluation.md + - references/publishers-and-runtime-protocols.md + - references/context-id-background.md + - references/providers-and-adapters.md + - references/security-and-validators.md + - references/cli-commands.md + - references/api-configuration.md + - references/auth-and-authorization.md + - references/api-settings-and-middleware.md + - references/rest-api-and-errors.md + - references/id-and-thread-name-generators.md + - references/client-auth-and-errors.md + - references/client-messages-invoke-stream.md + - references/client-threads-memory-files.md + tags: + - agentflow + - agents + - multi-agent + - framework + priority: 10 +--- + +# Agentflow Project Skill + +Use this skill when working in this Agentflow monorepo. Agentflow is a multi-agent framework that wraps official OpenAI and Google SDK capabilities behind a unified graph, agent, tool, state, storage, API, CLI, and TypeScript client interface. + +Treat `agentflow-docs/docs` as the first source of truth for public package names, install commands, and user-facing behavior. Use implementation source after the docs establish the intended API. + +## Workflow + +1. Identify the published package or docs surface involved: + - PyPI core Python SDK: `10xscale-agentflow` (`pip install 10xscale-agentflow`), source in `agentflow/agentflow` + - PyPI API/CLI SDK: `10xscale-agentflow-cli` (`pip install 10xscale-agentflow-cli`), source in `agentflow-api/agentflow_cli` + - npm TypeScript SDK: `@10xscale/agentflow-client` (`npm install @10xscale/agentflow-client`), source in `agentflow-client/src` + - Main docs: `agentflow-docs/docs` + - Playground/UI: `agentflow play` command after installed cli + +2. Read the matching reference file before changing behavior: + - Architecture and package flow: `references/architecture.md` + - Agent and tool behavior: `references/agents-and-tools.md` + - Graph construction and execution: `references/state-graph.md` + - State, messages, and content blocks: `references/state-and-messages.md` + - Threads and persistence: `references/checkpointing-and-threads.md` + - Dependency injection: `references/dependency-injection.md` + - Multimodal files and media stores: `references/media-and-files.md` + - Long-term memory stores: `references/memory-and-store.md` + - Streaming, chunks, and SSE: `references/streaming.md` + - API server and deployment runtime: `references/production-runtime.md` + - REST and TypeScript client surface: `references/api-client.md` + - Browser/client-side tool execution: `references/remote-tools.md` + - Observability hooks, validators, and runtime jumps: `references/callbacks-and-command.md` + - Prebuilt agents/tools and handoff helpers: `references/prebuilt-agents-and-tools.md` + - Testing helpers and evaluation framework: `references/testing-and-evaluation.md` + - Event publishers and A2A/ACP runtime protocols: `references/publishers-and-runtime-protocols.md` + - Context management, ID generation, and background tasks: `references/context-id-background.md` + - Provider internals and adapters: `references/providers-and-adapters.md` + - Prompt-injection and validation safety: `references/security-and-validators.md` + - CLI commands and generated project files: `references/cli-commands.md` + - `agentflow.json` and dependency loading: `references/api-configuration.md` + - API auth and authorization: `references/auth-and-authorization.md` + - API environment, settings, and middleware: `references/api-settings-and-middleware.md` + - REST routes and error behavior: `references/rest-api-and-errors.md` + - API Snowflake IDs and thread naming: `references/id-and-thread-name-generators.md` + - TypeScript auth helpers and structured errors: `references/client-auth-and-errors.md` + - TypeScript messages, invoke, and stream details: `references/client-messages-invoke-stream.md` + - TypeScript thread, memory, and file APIs: `references/client-threads-memory-files.md` +3. Prefer existing Agentflow abstractions over new custom wiring: + - Build workflows with `StateGraph`, `Agent`, `ToolNode`, `AgentState`, and `Message`. + - Persist conversation state with checkpointers; use stores only for cross-thread memory. + - Put business services in `InjectQ` instead of global variables. + - Keep API/CLI graph modules storage-agnostic and wire dependencies through `agentflow.json`. +4. Verify against source when implementation details matter. Public names and expected behavior should match `agentflow-docs/docs`; source under `agentflow/`, `agentflow-api/`, and `agentflow-client/src/` explains how that behavior is implemented. + +## Local Conventions + +- A compiled graph is normally loaded once by the API server and reused per request. +- Public package naming matters: use `10xscale-agentflow`, `10xscale-agentflow-cli`, and `@10xscale/agentflow-client` in user-facing docs and examples, not repository folder names. +- Every persisted interaction should include `config.thread_id`. +- Tools need docstrings and type annotations so model-facing schemas are useful. +- Injectable tool and node parameters such as `state`, `config`, and `tool_call_id` are hidden from the model schema. +- For production, avoid process-local storage for shared state; use durable checkpointer/store backends. diff --git a/agentflow_cli/cli/templates/skills/agent-skills/references/agents-and-tools.md b/agentflow_cli/cli/templates/skills/agent-skills/references/agents-and-tools.md new file mode 100644 index 0000000..530b735 --- /dev/null +++ b/agentflow_cli/cli/templates/skills/agent-skills/references/agents-and-tools.md @@ -0,0 +1,65 @@ +# Agents and Tools + +Use this when adding model behavior, tools, tool routing, provider options, retries, fallbacks, memory tools, or skills to an agent. + +## Agent + +`Agent` is a graph node that calls an LLM provider and appends the response to state. It supports OpenAI and Google provider flows behind a unified interface. Provider can be explicit (`"openai"` or `"google"`) or inferred from the model/config in existing code paths. + +Common constructor concerns: + +- `model`: model name. +- `provider`: provider name when auto-detection is not enough. +- `output_type`: `"text"`, `"image"`, `"video"`, or `"audio"`. +- `system_prompt`: string/list message instructions; supports `{state_field}` interpolation. +- `tool_node`: `ToolNode` instance or a graph node name string. +- `trim_context`: trims messages before model calls and writes summaries to `context_summary`. +- `reasoning_config`: effort/budget/provider reasoning options. +- `retry_config` and `fallback_models`: retry and cross-provider fallback behavior. +- `multimodal_config`: image/document/audio/video handling. +- `memory`: `MemoryConfig` that wires memory tools onto the tool node. +- `skills`: `SkillConfig` that discovers and exposes skills through the tool node. +- Extra kwargs such as `temperature`, `max_tokens`, and `base_url`. + +## ToolNode + +`ToolNode` registers and executes callable tools requested by the model. It supports local Python callables, MCP clients, Composio/LangChain style tools where supported by the implementation, and dynamically added tools. + +For client-executed tools registered through `@10xscale/agentflow-client`, read `remote-tools.md`. + +Tool authoring rules: + +- Provide useful docstrings; they become model-facing descriptions. +- Provide type annotations; they become the parameter schema. +- Return plain values for normal tool results. +- Return `ToolResult` when a tool also needs to update state fields. +- Keep model-visible parameters separate from injected parameters. + +Injected parameters are hidden from the model schema: + +- `state`: current `AgentState` or subclass. +- `config`: execution config including `thread_id`, `user_id`, and `run_id`. +- `tool_call_id`: current model tool call ID. + +## ReAct Loop + +The standard tool-using graph loops from agent to tools and back: + +1. `Agent` returns an assistant message. +2. Route to `ToolNode` when the last assistant message has `tools_calls`. +3. `ToolNode` appends a tool result message. +4. Route back to `Agent` so the model can produce a final answer. +5. End when there are no more tool calls. + +## `@tool` + +Use `agentflow.utils.tool` to add metadata such as name, description, tags, provider, capabilities, and arbitrary metadata. The decorator enriches the schema; it does not change injected parameter behavior. + +## Source Map + +- Agent implementation: `agentflow/agentflow/core/graph/agent.py` +- Tool node and graph exports: `agentflow/agentflow/core/graph` +- Tool result: `agentflow/agentflow/core/state/tool_result.py` +- Tool decorator: `agentflow/agentflow/utils/decorators.py` +- Skill integration: `agentflow/agentflow/core/skills` +- Prebuilt tools: `agentflow/agentflow/prebuilt/tools` diff --git a/agentflow_cli/cli/templates/skills/agent-skills/references/api-client.md b/agentflow_cli/cli/templates/skills/agent-skills/references/api-client.md new file mode 100644 index 0000000..6692cca --- /dev/null +++ b/agentflow_cli/cli/templates/skills/agent-skills/references/api-client.md @@ -0,0 +1,67 @@ +# API and TypeScript Client + +Use this when aligning REST routes, schemas, generated docs, or the TypeScript npm SDK. The public npm package is `@10xscale/agentflow-client`; install it with `npm install @10xscale/agentflow-client`. + +Check current docs first: + +- `agentflow-docs/docs/get-started/connect-client.md` +- `agentflow-docs/docs/beginner/call-from-typescript.md` +- `agentflow-docs/docs/concepts/architecture.md` + +## API Routers + +Active routers are included from `agentflow-api/agentflow_cli/src/app/routers/setup_router.py`: + +- Graph router +- Checkpointer/thread router +- Store router +- Ping router +- Media/files router + +Graph routes include: + +- `POST /v1/graph/invoke` +- `POST /v1/graph/stream` +- `GET /v1/graph` +- `GET /v1/graph:StateSchema` +- `POST /v1/graph/stop` +- `POST /v1/graph/setup` +- `POST /v1/graph/fix` + +Memory/file routes are summarized in their topic references. + +## TypeScript SDK Facade + +`AgentFlowClient` is exported by `@10xscale/agentflow-client`. Its source facade is `agentflow-client/src/client.ts`, and it wraps: + +- Connectivity and metadata: `ping`, `graph`, `graphStateSchema` +- Execution: `invoke`, `stream`, `stopGraph`, `fixGraph`, `setup` +- Threads/messages: `threadState`, `updateThreadState`, `clearThreadState`, `threadDetails`, `threads`, `threadMessages`, `addThreadMessages`, `singleMessage`, `deleteMessage`, `deleteThread` +- Memory: `storeMemory`, `searchMemory`, `getMemory`, `updateMemory`, `deleteMemory`, `listMemories`, `forgetMemories` +- Files: `uploadFile`, `getFile`, `getFileInfo`, `getFileAccessUrl`, `getMultimodalConfig` +- Remote tools: `registerTool`, then `setup` + +For remote tools, read `remote-tools.md`; that flow has a client-managed execution loop around `remote_tool_call` blocks. + +## Request Conventions + +- The client serializes `Message` instances to plain API payloads. +- Server expects message content as an array of content blocks. +- `message_id` is sent as a string; `"0"` lets the server generate/normalize where applicable. +- `recursion_limit` defaults to `25`. +- Streaming defaults to low response granularity in the client. + +## Rules + +- Use `@10xscale/agentflow-client` in user-facing examples and docs; use `agentflow-client/src` only when referring to repository source. +- The TypeScript SDK calls a running Agentflow API server. It does not import or execute Python graph code. +- Keep endpoint schema changes mirrored in `agentflow-client/src/endpoints`. +- Keep exports in `agentflow-client/src/index.ts` aligned when adding public types. +- Test both endpoint helpers and the `AgentFlowClient` facade for client-visible changes. + +## Source Map + +- Client facade: `agentflow-client/src/client.ts` +- Client endpoints: `agentflow-client/src/endpoints` +- Client message model: `agentflow-client/src/message.ts` +- API schemas/services: `agentflow-api/agentflow_cli/src/app/routers` diff --git a/agentflow_cli/cli/templates/skills/agent-skills/references/api-configuration.md b/agentflow_cli/cli/templates/skills/agent-skills/references/api-configuration.md new file mode 100644 index 0000000..072f061 --- /dev/null +++ b/agentflow_cli/cli/templates/skills/agent-skills/references/api-configuration.md @@ -0,0 +1,64 @@ +# API Configuration + +Use this when changing `agentflow.json`, dependency loading, app startup, or graph import behavior. + +## `agentflow.json` + +Minimal: + +```json +{ + "agent": "graph.react:app" +} +``` + +Common full shape: + +```json +{ + "agent": "graph.react:app", + "checkpointer": "graph.dependencies:my_checkpointer", + "store": "graph.dependencies:my_store", + "injectq": "graph.dependencies:container", + "thread_name_generator": "graph.thread_name_generator:MyNameGenerator", + "authorization": "graph.auth:my_authorization_backend", + "env": ".env", + "auth": "jwt" +} +``` + +## Fields + +- `agent`: required import path to a compiled graph variable, in `module.path:attribute` format. +- `checkpointer`: optional import path to a `BaseCheckpointer` instance. +- `store`: optional import path to a `BaseStore` instance; required for store endpoints. +- `injectq`: optional import path to an `InjectQ` container. +- `thread_name_generator`: optional import path to a thread-name generator class/instance. +- `authorization`: optional import path to an authorization backend. +- `env`: optional `.env` path loaded before graph import. +- `auth`: `null`, `"jwt"`, or `{"method": "custom", "path": "module:backend"}`. + +## Loading Order + +1. Read `agentflow.json`. +2. Load `.env` when configured. +3. Import the compiled graph from `agent`. +4. Import and bind `checkpointer`, `store`, `injectq`, `thread_name_generator`, and `authorization` when configured. +5. Configure auth. +6. Start FastAPI routes and services. + +## Rules + +- Keep graph modules importable from the project root. +- Keep `agent` pointing to a compiled graph object, not an uncompiled `StateGraph`. +- Keep dependency modules side-effect light. +- Load secrets through `.env` or process environment, not committed config. +- Validate import paths early and surface clear CLI/API errors. + +## Source Map + +- Graph config: `agentflow-api/agentflow_cli/src/app/core/config/graph_config.py` +- Loader: `agentflow-api/agentflow_cli/src/app/loader.py` +- App startup: `agentflow-api/agentflow_cli/src/app/main.py` +- Main docs: `agentflow-docs/docs/reference/api-cli/configuration.md` +- How-to: `agentflow-docs/docs/how-to/api-cli/configure-agentflow-json.md` diff --git a/agentflow_cli/cli/templates/skills/agent-skills/references/api-settings-and-middleware.md b/agentflow_cli/cli/templates/skills/agent-skills/references/api-settings-and-middleware.md new file mode 100644 index 0000000..b151a70 --- /dev/null +++ b/agentflow_cli/cli/templates/skills/agent-skills/references/api-settings-and-middleware.md @@ -0,0 +1,82 @@ +# API Settings and Middleware + +Use this when changing environment variables, app settings, middleware, CORS, request limits, security headers, docs paths, or Sentry. + +## Settings + +`Settings` reads environment variables through Pydantic settings. + +Important variables: + +- `APP_NAME` +- `APP_VERSION` +- `MODE` +- `LOG_LEVEL` +- `IS_DEBUG` +- `MAX_REQUEST_SIZE` +- `ORIGINS` +- `ALLOWED_HOST` +- `ROOT_PATH` +- `DOCS_PATH` +- `REDOCS_PATH` +- `REDIS_URL` +- `SENTRY_DSN` +- `JWT_SECRET_KEY` +- `JWT_ALGORITHM` + +Security header variables: + +- `SECURITY_HEADERS_ENABLED` +- `HSTS_ENABLED` +- `HSTS_MAX_AGE` +- `HSTS_INCLUDE_SUBDOMAINS` +- `HSTS_PRELOAD` +- `FRAME_OPTIONS` +- `CONTENT_TYPE_OPTIONS` +- `XSS_PROTECTION` +- `REFERRER_POLICY` +- `PERMISSIONS_POLICY` +- `CSP_POLICY` + +## Middleware + +Active middleware areas: + +- CORS and host handling. +- Request size limits. +- Security headers. +- Request ID assignment. +- Selective gzip behavior; streaming endpoints should avoid gzip buffering when configured. +- Worker middleware where used by deployment. + +## Production Warnings + +Production mode warns about unsafe defaults such as: + +- `ORIGINS=*` +- debug enabled +- docs endpoints enabled +- `ALLOWED_HOST=*` + +## Sentry + +`SENTRY_DSN` enables Sentry setup through the API config module. Keep error reporting optional and safe when unset. + +## Rules + +- Keep environment variable docs in sync with `Settings`. +- Use `ROOT_PATH` when serving behind a reverse proxy subpath. +- Disable or protect docs paths in production when needed. +- Do not gzip SSE streams. +- Sanitize logs for tokens, secrets, and large payloads. + +## Source Map + +- Settings: `agentflow-api/agentflow_cli/src/app/core/config/settings.py` +- Middleware setup: `agentflow-api/agentflow_cli/src/app/core/config/setup_middleware.py` +- Request limits: `agentflow-api/agentflow_cli/src/app/core/middleware/request_limits.py` +- Security headers: `agentflow-api/agentflow_cli/src/app/core/middleware/security_headers.py` +- Sentry: `agentflow-api/agentflow_cli/src/app/core/config/sentry_config.py` +- Log sanitizer: `agentflow-api/agentflow_cli/src/app/core/utils/log_sanitizer.py` +- Main docs: `agentflow-docs/docs/reference/api-cli/environment.md` +- Production docs: `agentflow-docs/docs/how-to/production/environment-variables.md` diff --git a/agentflow_cli/cli/templates/skills/agent-skills/references/architecture.md b/agentflow_cli/cli/templates/skills/agent-skills/references/architecture.md new file mode 100644 index 0000000..1323f25 --- /dev/null +++ b/agentflow_cli/cli/templates/skills/agent-skills/references/architecture.md @@ -0,0 +1,55 @@ +# Architecture + +Use this when deciding where a change belongs or explaining how Agentflow packages interact. Check `agentflow-docs/docs/concepts/architecture.md` and `agentflow-docs/docs/get-started/installation.md` first for public package names. + +## Published Packages + +| Public package | Registry | Install | Source | +| --- | --- | --- | --- | +| `10xscale-agentflow` | PyPI | `pip install 10xscale-agentflow` | `agentflow/agentflow` | +| `10xscale-agentflow-cli` | PyPI | `pip install 10xscale-agentflow-cli` | `agentflow-api/agentflow_cli` | +| `@10xscale/agentflow-client` | npm | `npm install @10xscale/agentflow-client` | `agentflow-client/src` | + +## Layer Responsibilities + +- `10xscale-agentflow`: Core Python SDK. Owns `StateGraph`, `Agent`, `ToolNode`, `AgentState`, `Message`, checkpointers, stores, media, prebuilt agents/tools, runtime publishers, QA helpers, and skills support. +- `10xscale-agentflow-cli`: Python API and CLI SDK. Owns `agentflow api`, `agentflow play`, `agentflow init`, `agentflow build`, routers, auth/middleware, config loading, and graph service execution. +- `@10xscale/agentflow-client`: TypeScript npm SDK. Owns typed methods for invoke, stream, threads, memory store, files, graph metadata, remote tools, and auth/request helpers. It calls a running Agentflow API server and does not run Python graphs. +- `agentflow-docs/docs`: Main user-facing docs, concepts, how-to guides, and reference material. Prefer these docs over legacy docs for public behavior. +- `agentflow-playground` / UI packages: Interactive graph and agent testing surfaces. + +## Request Flow + +1. `@10xscale/agentflow-client` or another HTTP caller sends messages plus `config.thread_id`. +2. FastAPI receives the request through auth and routers. +3. `GraphService` invokes or streams against the compiled graph loaded at startup. +4. The compiled graph loads state through the checkpointer when a `thread_id` exists. +5. Graph nodes run and update `AgentState`. +6. Checkpointer saves state/messages/thread metadata. +7. API returns JSON or SSE chunks to the client. + +## Design Rules + +- Compile graphs once at startup for API serving. +- Keep graph code storage-agnostic; wire checkpointer/store/media/dependencies through compile arguments, `InjectQ`, or `agentflow.json`. +- Treat `thread_id` as the continuity key for conversation state. +- Treat long-term memory store records as cross-thread knowledge, not thread history. +- Keep auth and request permissions in API middleware/routers, not inside graph nodes. + +## Source Map + +- Core graph: `agentflow/agentflow/core/graph` +- State/message models: `agentflow/agentflow/core/state` +- Checkpointers: `agentflow/agentflow/storage/checkpointer` +- Memory stores: `agentflow/agentflow/storage/store` +- Media stores/resolvers: `agentflow/agentflow/storage/media` +- API routers: `agentflow-api/agentflow_cli/src/app/routers` +- API loader/config: `agentflow-api/agentflow_cli/src/app/loader.py`, `agentflow-api/agentflow_cli/src/app/core/config` +- TS client facade: `agentflow-client/src/client.ts` + +## Public Docs Map + +- Package overview: `agentflow-docs/docs/concepts/architecture.md` +- Install commands and package roles: `agentflow-docs/docs/get-started/installation.md` +- API serving: `agentflow-docs/docs/get-started/expose-with-api.md` +- TypeScript client usage: `agentflow-docs/docs/get-started/connect-client.md` and `agentflow-docs/docs/beginner/call-from-typescript.md` diff --git a/agentflow_cli/cli/templates/skills/agent-skills/references/auth-and-authorization.md b/agentflow_cli/cli/templates/skills/agent-skills/references/auth-and-authorization.md new file mode 100644 index 0000000..af8b501 --- /dev/null +++ b/agentflow_cli/cli/templates/skills/agent-skills/references/auth-and-authorization.md @@ -0,0 +1,88 @@ +# Auth and Authorization + +Use this when changing HTTP authentication, authorization, permission checks, or client auth examples. + +## Authentication Modes + +Default: + +- `auth: null` +- No authentication. +- Suitable only for local development or trusted gateways. + +JWT: + +- `auth: "jwt"` +- Requires `JWT_SECRET_KEY` and `JWT_ALGORITHM`. +- Requests use `Authorization: Bearer `. +- Decoded JWT payload becomes the endpoint `user` context. + +Custom: + +```json +{ + "auth": { + "method": "custom", + "path": "graph.auth:MyAuthBackend" + } +} +``` + +Custom auth backends implement `BaseAuth.authenticate(request) -> dict | None`. + +## Authorization + +Authorization decides whether an authenticated user can perform a resource/action pair. + +Configure: + +```json +{ + "authorization": "graph.auth:my_authorization_backend" +} +``` + +Backends implement: + +```python +async def authorize(self, user: dict, resource: str, action: str) -> bool: + ... +``` + +Common resources/actions: + +- `graph`: `invoke`, `stream`, `read`, `stop`, `setup`, `fix` +- `checkpointer`: `read`, `write`, `delete` +- `store`: `read`, `write`, `delete` +- `files`: `upload`, `read` + +Routes use `RequirePermission(resource, action)`. + +## TypeScript Client + +Pass auth headers through the client config: + +```typescript +const client = new AgentFlowClient({ + baseUrl: "http://127.0.0.1:8000", + headers: { Authorization: `Bearer ${token}` }, +}); +``` + +## Rules + +- Do not use unauthenticated API mode in production unless a trusted gateway handles auth. +- Keep authorization separate from graph business logic. +- Return 401 for authentication failure and 403 for authorization failure. +- Use sanitized logging for tokens and user payloads. +- Update permission tables when adding routes. + +## Source Map + +- Base auth: `agentflow-api/agentflow_cli/src/app/core/auth/base_auth.py` +- JWT auth: `agentflow-api/agentflow_cli/src/app/core/auth/jwt_auth.py` +- Auth backend loader: `agentflow-api/agentflow_cli/src/app/core/auth/auth_backend.py` +- Authorization: `agentflow-api/agentflow_cli/src/app/core/auth/authorization.py` +- Permission dependency: `agentflow-api/agentflow_cli/src/app/core/auth/permissions.py` +- Main docs: `agentflow-docs/docs/reference/api-cli/auth.md` +- Production docs: `agentflow-docs/docs/how-to/production/auth-and-authorization.md` diff --git a/agentflow_cli/cli/templates/skills/agent-skills/references/callbacks-and-command.md b/agentflow_cli/cli/templates/skills/agent-skills/references/callbacks-and-command.md new file mode 100644 index 0000000..94ad1ee --- /dev/null +++ b/agentflow_cli/cli/templates/skills/agent-skills/references/callbacks-and-command.md @@ -0,0 +1,102 @@ +# Callbacks and Command + +Use this when adding validation, tracing, invocation hooks, error recovery, or runtime graph jumps. + +## CallbackManager + +`CallbackManager` registers hooks around AI, tool, MCP, input validation, and skill invocations. + +Invocation types: + +- `InvocationType.AI` +- `InvocationType.TOOL` +- `InvocationType.MCP` +- `InvocationType.INPUT_VALIDATION` +- `InvocationType.SKILL` + +Hook families: + +- `register_before_invoke(invocation_type, callback)`: validate or transform input before invocation. +- `register_after_invoke(invocation_type, callback)`: inspect or transform output after invocation. +- `register_on_error(invocation_type, callback)`: log, recover, or return a recovery `Message`. +- `register_input_validator(validator)`: add a `BaseValidator` that validates incoming messages. + +Callbacks can be callable objects or functions and may be sync or async. Error callbacks should return a `Message` recovery value or `None`. + +## Validators + +Use `BaseValidator` for input validation. Implement: + +```python +async def validate(self, messages: list[Message]) -> bool: + ... +``` + +Register validators on a `CallbackManager`, then pass it to graph compilation: + +```python +callback_manager = CallbackManager() +callback_manager.register_input_validator(MyValidator()) + +app = graph.compile(callback_manager=callback_manager) +``` + +Use validators for safety checks, business rules, input policy, and prompt-injection detection. + +## Command + +`Command` lets a node combine state/message updates with control flow. Use it when the next node depends on runtime logic inside the node and is awkward to express as a static conditional edge. + +Fields: + +- `update`: state update, `Message`, string, converter, or `None`. +- `goto`: next node name or `END`. +- `graph`: optional graph target; `Command.PARENT` is reserved for parent graph navigation patterns. +- `state`: optional attached state. + +Prefer conditional edges for normal routing because they are easier to visualize and test. Use `Command(goto=...)` for dynamic jumps, recovery branches, handoffs, or side-effect-dependent routing. + +## Patterns + +Callback for tracing: + +```python +from agentflow.utils.callbacks import CallbackContext, CallbackManager, InvocationType + +async def trace_after(context: CallbackContext, input_data, output_data): + print(context.invocation_type, context.node_name) + return output_data + +callback_manager = CallbackManager() +callback_manager.register_after_invoke(InvocationType.TOOL, trace_after) +app = graph.compile(callback_manager=callback_manager) +``` + +Command for dynamic routing: + +```python +from agentflow.utils import Command, END + +def router_node(state, config): + if state.context[-1].text() == "stop": + return Command(goto=END) + return Command(update={"route": "repair"}, goto="REPAIR") +``` + +## Rules + +- Keep callback side effects bounded; they run inside graph execution paths. +- Avoid storing per-request mutable state globally inside callbacks. +- Use `CallbackContext` metadata to distinguish node/function/invocation details. +- Return transformed data from callbacks only when the downstream invocation expects that shape. +- Use `Command` sparingly; static and conditional edges remain the default graph structure. +- Always test command routes for recursion limits and missing destination nodes. + +## Source Map + +- Callback system: `agentflow/agentflow/utils/callbacks.py` +- Default validators: `agentflow/agentflow/utils/validators.py` +- Graph compile callback argument: `agentflow/agentflow/core/graph/state_graph.py` +- Command API: `agentflow/agentflow/utils/command.py` +- Command execution paths: `agentflow/agentflow/core/graph/compiled_graph.py` +- Legacy docs: `agentflow-docs/docs-mkdocs-legacy/reference/library/Command.md` diff --git a/agentflow_cli/cli/templates/skills/agent-skills/references/checkpointing-and-threads.md b/agentflow_cli/cli/templates/skills/agent-skills/references/checkpointing-and-threads.md new file mode 100644 index 0000000..cb245ee --- /dev/null +++ b/agentflow_cli/cli/templates/skills/agent-skills/references/checkpointing-and-threads.md @@ -0,0 +1,63 @@ +# Checkpointing and Threads + +Use this when adding persistence, thread APIs, message history, interrupts, or multi-worker behavior. + +## Concept + +A checkpointer persists per-thread `AgentState`, messages, and thread metadata. Without a checkpointer, each `invoke` starts from a fresh state. With a checkpointer, calls with the same `config.thread_id` resume the saved state. + +## Config + +At minimum, checkpointed calls need: + +```python +config = {"thread_id": "conv-1"} +``` + +Use `user_id` for multi-tenant scoping where the backend supports it. + +## Backends + +- `InMemoryCheckpointer`: development/tests/single-process only. State is lost on restart and not shared across workers. +- `PgCheckpointer`: production backend using PostgreSQL for durable data and Redis for fast cache. Requires setup before use. + +Use production storage for load-balanced API deployments. + +## API Surface + +Checkpointer methods exist in async and sync forms: + +- State: put/get/clear state and state cache. +- Messages: append/list/get/delete thread messages. +- Threads: create/get/list/clean thread metadata and content. +- Generic cache: namespaced key-value cache with optional TTL. + +## REST and Client + +The API exposes thread state/message/thread operations under `/v1/threads...` routes in the checkpointer router. The TypeScript client wraps these with methods such as: + +- `threadState` +- `updateThreadState` +- `clearThreadState` +- `threadDetails` +- `threads` +- `threadMessages` +- `addThreadMessages` +- `singleMessage` +- `deleteMessage` +- `deleteThread` + +## Rules + +- Always preserve `thread_id` across a conversation. +- Use `PgCheckpointer` or equivalent durable storage for multi-worker production. +- Store conversation continuity in the checkpointer, not the memory store. +- Use `aclean_thread`/delete APIs when removing a thread so state, messages, and metadata stay consistent. + +## Source Map + +- Base API: `agentflow/agentflow/storage/checkpointer/base_checkpointer.py` +- In-memory backend: `agentflow/agentflow/storage/checkpointer/in_memory_checkpointer.py` +- Postgres backend: `agentflow/agentflow/storage/checkpointer/pg_checkpointer.py` +- API router: `agentflow-api/agentflow_cli/src/app/routers/checkpointer` +- TS endpoints: `agentflow-client/src/endpoints/thread*.ts` diff --git a/agentflow_cli/cli/templates/skills/agent-skills/references/cli-commands.md b/agentflow_cli/cli/templates/skills/agent-skills/references/cli-commands.md new file mode 100644 index 0000000..c34d06c --- /dev/null +++ b/agentflow_cli/cli/templates/skills/agent-skills/references/cli-commands.md @@ -0,0 +1,73 @@ +# CLI Commands + +Use this when changing `10xscale-agentflow-cli` command behavior, generated files, or deployment scaffolding. + +## Package + +Install: + +```bash +pip install 10xscale-agentflow-cli +``` + +Entry point: + +```text +agentflow = agentflow_cli.cli.main:main +``` + +## Commands + +`agentflow init` + +- Scaffolds `agentflow.json`, `graph/__init__.py`, `graph/react.py`, and `skills/agent-skills`. +- Options include `--path/-p`, `--force/-f`, `--prod`, `--verbose/-v`, and `--quiet/-q`. +- `--prod` also adds production project files such as `pyproject.toml` and `.pre-commit-config.yaml`. + +`agentflow api` + +- Starts the FastAPI server for a compiled graph. +- Options include `--config/-c`, `--host/-H`, `--port/-p`, `--reload/--no-reload`, `--verbose/-v`, and `--quiet/-q`. +- Defaults are config `agentflow.json`, host `0.0.0.0`, port `8000`, reload enabled. + +`agentflow play` + +- Starts the API server and opens/prints the hosted playground URL. +- Accepts the same server options as `api`. +- Uses host/port to build the playground backend URL. + +`agentflow build` + +- Generates Docker deployment files. +- Options include `--output/-o`, `--force/-f`, `--python-version`, `--port/-p`, `--docker-compose/--no-docker-compose`, `--service-name`, `--verbose/-v`, and `--quiet/-q`. + +`agentflow skills` + +- Installs the bundled Agentflow skill into an agent-specific project directory. +- Prompts for the target agent when `--agent` is omitted: + - `1` / `codex`: `.agent/skills/agentflow` + - `2` / `claude`: `.claude/skills/agentflow` + - `3` / `github`: `.github/skills/agentflow` +- Options include `--agent/-a`, `--path/-p`, `--force/-f`, `--verbose/-v`, and `--quiet/-q`. +- Source template: `agentflow-api/agentflow_cli/cli/templates/skills/agent-skills`. + +`agentflow version` + +- Prints CLI and library version information. + +## Rules + +- Use docs package names in user-facing text. +- Keep command options aligned with `agentflow-docs/docs/reference/api-cli/commands.md`. +- If command defaults change, update docs, templates, and tests together. +- Generated skill templates live under `agentflow-api/agentflow_cli/cli/templates/skills`. + +## Source Map + +- CLI main: `agentflow-api/agentflow_cli/cli/main.py` +- Commands: `agentflow-api/agentflow_cli/cli/commands` +- CLI config/output/validation: `agentflow-api/agentflow_cli/cli/core` +- Templates: `agentflow-api/agentflow_cli/cli/templates` +- Main docs: `agentflow-docs/docs/reference/api-cli/commands.md` +- How-to init: `agentflow-docs/docs/how-to/api-cli/initialize-project.md` +- How-to server: `agentflow-docs/docs/how-to/api-cli/run-api-server.md` diff --git a/agentflow_cli/cli/templates/skills/agent-skills/references/client-auth-and-errors.md b/agentflow_cli/cli/templates/skills/agent-skills/references/client-auth-and-errors.md new file mode 100644 index 0000000..1a16994 --- /dev/null +++ b/agentflow_cli/cli/templates/skills/agent-skills/references/client-auth-and-errors.md @@ -0,0 +1,95 @@ +# Client Auth and Errors + +Use this when changing `@10xscale/agentflow-client` authentication, request headers, credentials, timeouts, debug behavior, or structured error handling. + +## Auth Helpers + +Public auth types and helpers live in `agentflow-client/src/request.ts`. + +Auth union: + +- `AgentFlowBearerAuth`: `{ type: "bearer", token }` +- `AgentFlowBasicAuth`: `{ type: "basic", username, password }` +- `AgentFlowHeaderAuth`: `{ type: "header", name, value, prefix? }` +- `AgentFlowAuth`: union of the above + +Helpers: + +- `bearerAuth(token)` +- `basicAuth(username, password)` +- `headerAuth(name, value, prefix?)` +- `buildHeaders(context, defaults?)` +- `getRequestCredentials(context)` + +## AgentFlowClient Config + +`AgentFlowConfig` fields: + +- `baseUrl`: required API base URL. +- `authToken`: bearer-token shorthand. +- `auth`: structured auth helper. +- `headers`: additional headers merged into every request. +- `credentials`: browser fetch credentials mode. +- `timeout`: request timeout in milliseconds; default is 300000. +- `debug`: enables client debug logging. + +Auth precedence: + +1. Defaults are applied first. +2. `headers` are merged next. +3. If `auth` is set, it writes auth headers. +4. Otherwise, `authToken` writes `Authorization: Bearer ...` only if no authorization header already exists. + +Use `credentials` for cookie-based auth or same-origin sessions. + +## Structured Errors + +All endpoint helpers call `createErrorFromResponse` for non-OK HTTP responses where implemented. + +Core error types: + +- `AgentFlowError` +- `BadRequestError` +- `AuthenticationError` +- `PermissionError` +- `NotFoundError` +- `ValidationError` +- `ServerError` +- `GraphError` +- `NodeError` +- `GraphRecursionError` +- `StorageError` +- `TransientStorageError` +- `MetricsError` +- `SchemaVersionError` +- `SerializationError` + +Error instances expose: + +- `statusCode` +- `errorCode` +- `requestId` +- `timestamp` +- `details` +- `context` +- `endpoint` +- `method` +- `recoverySuggestion` +- `getUserMessage()` +- `toJSON()` + +## Rules + +- Prefer structured `auth` helpers for new examples; keep `authToken` as shorthand. +- Do not overwrite an explicit user-provided `Authorization` header unless `auth` is set. +- Keep fetch `credentials` forwarding in every endpoint helper. +- Keep new server error codes mapped in `createErrorFromResponse`. +- In UI code, show `getUserMessage()` to users and log `toJSON()` for debugging. + +## Source Map + +- Auth/request helpers: `agentflow-client/src/request.ts` +- Error classes: `agentflow-client/src/errors.ts` +- Client facade: `agentflow-client/src/client.ts` +- Docs: `agentflow-docs/docs/reference/client/auth.md` +- Docs: `agentflow-docs/docs/troubleshooting/client.md` diff --git a/agentflow_cli/cli/templates/skills/agent-skills/references/client-messages-invoke-stream.md b/agentflow_cli/cli/templates/skills/agent-skills/references/client-messages-invoke-stream.md new file mode 100644 index 0000000..50178b7 --- /dev/null +++ b/agentflow_cli/cli/templates/skills/agent-skills/references/client-messages-invoke-stream.md @@ -0,0 +1,117 @@ +# Client Messages, Invoke, and Stream + +Use this when changing TypeScript message classes, invoke behavior, streaming behavior, response granularity, partial callbacks, or SSE parsing. + +## Message Model + +Message and content block classes live in `agentflow-client/src/message.ts`. + +Core classes: + +- `Message` +- `MediaRef` +- `AnnotationRef` +- `TokenUsages` + +Content blocks: + +- `TextBlock` +- `ImageBlock` +- `AudioBlock` +- `VideoBlock` +- `DocumentBlock` +- `DataBlock` +- `ToolCallBlock` +- `RemoteToolCallBlock` +- `ToolResultBlock` +- `ReasoningBlock` +- `AnnotationBlock` +- `ErrorBlock` + +Message helpers: + +- `Message.text_message(content, role?, message_id?)` +- `Message.tool_message(content, message_id?, meta?)` +- `message.text()` +- `message.attach_media(media, as_type)` +- `Message.withImage(text, imageUrl, altText?)` +- `Message.withFile(text, fileId, mimeType, filename?)` +- `Message.multimodal(text, mediaItems)` + +The client serializes message content as arrays of content blocks. Avoid sending plain strings directly to API endpoints. + +## Invoke + +`AgentFlowClient.invoke(messages, options?)` wraps `/v1/graph/invoke`. + +Options: + +- `initial_state` +- `config` +- `recursion_limit` +- `response_granularity`: `"full"`, `"partial"`, or `"low"` +- `onPartialResult` + +`InvokeResult` includes: + +- `messages` +- `state` +- `context` +- `summary` +- `meta` +- `all_messages` +- `iterations` +- `recursion_limit_reached` + +`onPartialResult` receives `InvokePartialResult` for every iteration, including `has_tool_calls` and `is_final`. Use it to observe remote tool loops or intermediate server results. + +## Remote Tool Loop + +Invoke automatically loops when server responses include `remote_tool_call` blocks and a `ToolExecutor` is available. The client executes registered handlers, sends tool result messages, and repeats until no remote calls remain or `recursion_limit` is reached. + +Read `remote-tools.md` for the full remote tool flow. + +## Stream + +`AgentFlowClient.stream(messages, options?)` wraps `/v1/graph/stream` and returns `AsyncGenerator`. + +`StreamEventType` values: + +- `MESSAGE` +- `STATE` +- `ERROR` +- `UPDATES` + +`StreamChunk` includes: + +- `event` +- `message` +- `state` +- `data` +- `thread_id` +- `run_id` +- `metadata` +- `timestamp` + +The stream endpoint parses server-sent event payloads and newline-delimited JSON variants. It also participates in the remote tool loop when remote tool calls appear. + +Use `client.stopGraph(threadId, config?)` to request cancellation for a running stream. + +## Rules + +- Keep TypeScript `Message` block types aligned with Python `Message` and REST schemas. +- Use `response_granularity: "low"` for UI streaming unless state is needed. +- Surface `recursion_limit_reached` when remote tool loops do not finish. +- In React/browser code, consume streams with `for await`. +- Test both invoke and stream when changing message serialization. + +## Source Map + +- Message classes: `agentflow-client/src/message.ts` +- Client facade: `agentflow-client/src/client.ts` +- Invoke endpoint: `agentflow-client/src/endpoints/invoke.ts` +- Stream endpoint: `agentflow-client/src/endpoints/stream.ts` +- Stop graph endpoint: `agentflow-client/src/endpoints/stopGraph.ts` +- Docs: `agentflow-docs/docs/reference/client/message.md` +- Docs: `agentflow-docs/docs/reference/client/invoke.md` +- Docs: `agentflow-docs/docs/reference/client/stream.md` diff --git a/agentflow_cli/cli/templates/skills/agent-skills/references/client-threads-memory-files.md b/agentflow_cli/cli/templates/skills/agent-skills/references/client-threads-memory-files.md new file mode 100644 index 0000000..a0e479d --- /dev/null +++ b/agentflow_cli/cli/templates/skills/agent-skills/references/client-threads-memory-files.md @@ -0,0 +1,82 @@ +# Client Threads, Memory, and Files + +Use this when changing TypeScript APIs for thread management, checkpoint state/messages, memory store access, or file upload/multimodal helpers. + +## Threads + +Thread methods on `AgentFlowClient`: + +- `threads(request?)` +- `threadDetails(threadId)` +- `deleteThread(threadId, config?)` +- `threadState(threadId)` +- `updateThreadState(threadId, config, state)` +- `clearThreadState(threadId)` +- `threadMessages(threadId, request?)` +- `addThreadMessages(threadId, messages, config?, metadata?)` +- `singleMessage(threadId, messageId)` +- `deleteMessage(threadId, messageId, config?)` + +Request patterns: + +- Thread listing supports search, offset, and limit. +- Thread message listing supports search, offset, and limit. +- Thread IDs may be string or number in most facade methods, but some endpoint signatures are narrower; check source before changing types. + +## Memory + +Memory methods: + +- `storeMemory(request)` +- `searchMemory(request)` +- `getMemory(memoryId, options?)` +- `updateMemory(memoryId, content, options?)` +- `deleteMemory(memoryId, options?)` +- `listMemories(options?)` +- `forgetMemories(options?)` + +Important enums/types: + +- `MemoryType` +- `RetrievalStrategy` +- `DistanceMetric` +- memory result/response interfaces in endpoint files + +The API must have a store configured for memory endpoints to work. + +## Files and Multimodal + +File methods: + +- `uploadFile(file)` +- `getFile(fileId)` +- `getFileInfo(fileId)` +- `getFileAccessUrl(fileId)` +- `getMultimodalConfig()` + +`uploadFile` accepts: + +- Browser `File` +- `Blob` +- `{ data: Blob; filename: string }` + +Use returned `file_id` in multimodal messages through `MediaRef(kind: "file_id", ...)` and content blocks such as `ImageBlock`, `DocumentBlock`, and `AudioBlock`. + +## Rules + +- Use stable `config.thread_id` for continuity; thread APIs inspect and mutate server-side checkpoints. +- Keep pagination options backwards-compatible. +- Keep memory enum values aligned with server store schemas. +- In browser code, ensure CORS and auth headers are configured before file or memory calls. +- Preserve MIME types and filenames when uploading files. +- Use `getFileAccessUrl` for fresh direct/signed URLs instead of caching old URLs indefinitely. + +## Source Map + +- Client facade: `agentflow-client/src/client.ts` +- Thread endpoints: `agentflow-client/src/endpoints/thread*.ts`, `threads.ts`, `deleteThread.ts` +- Memory endpoints: `agentflow-client/src/endpoints/*Memory.ts`, `forgetMemories.ts` +- File endpoint: `agentflow-client/src/endpoints/files.ts` +- Docs: `agentflow-docs/docs/reference/client/threads.md` +- Docs: `agentflow-docs/docs/reference/client/memory.md` +- Docs: `agentflow-docs/docs/reference/client/files.md` diff --git a/agentflow_cli/cli/templates/skills/agent-skills/references/context-id-background.md b/agentflow_cli/cli/templates/skills/agent-skills/references/context-id-background.md new file mode 100644 index 0000000..ac27a82 --- /dev/null +++ b/agentflow_cli/cli/templates/skills/agent-skills/references/context-id-background.md @@ -0,0 +1,82 @@ +# Context, ID Generation, and Background Tasks + +Use this when changing context trimming, generated IDs, background work, or graceful shutdown. + +## Context Managers + +`MessageContextManager` controls what conversation history is sent to the model without deleting stored state. + +Use it for: + +- Token budget control. +- Preserving important system/tool messages. +- Summarizing or trimming old context. +- Custom context policies per graph. + +Pass a context manager to `StateGraph(context_manager=...)`. Custom managers should subclass `BaseContextManager` and implement the required transform behavior. + +## ID Generators + +Import ID generators from `agentflow.utils`. + +Built-ins: + +- `DefaultIDGenerator` +- `UUIDGenerator` +- `BigIntIDGenerator` +- `TimestampIDGenerator` +- `IntIDGenerator` +- `HexIDGenerator` +- `ShortIDGenerator` +- `AsyncIDGenerator` + +Related types: + +- `BaseIDGenerator` +- `IDType` + +Pass an ID generator to `StateGraph(id_generator=...)`. Generated IDs can be accessed through runtime config/container patterns where supported. + +## Background Tasks + +`BackgroundTaskManager` manages async background work such as non-blocking memory writes. + +Important methods: + +- `create_task` +- `get_task_count` +- `get_task_info` +- `wait_for_all` +- `cancel_all` +- `shutdown` + +Use `TaskMetadata` for task tracking. + +## Graceful Shutdown + +Utilities include: + +- `GracefulShutdownManager` +- `DelayedKeyboardInterrupt` +- `delayed_keyboard_interrupt` +- `shutdown_with_timeout` +- `setup_exception_handler` + +Use these when a process needs to stop streams, wait for background tasks, and close publishers or storage clients cleanly. + +## Rules + +- Context managers change model input, not persisted checkpointer history. +- ID generator changes can affect API/client assumptions; verify serialized ID types. +- Always wait for or cancel background tasks during shutdown. +- Keep background task payloads bounded and avoid capturing large graph state unless needed. + +## Source Map + +- Context managers: `agentflow/agentflow/core/state/message_context_manager.py` +- ID generators: `agentflow/agentflow/utils/id_generator.py` +- Background tasks: `agentflow/agentflow/utils/background_task_manager.py` +- Shutdown utilities: `agentflow/agentflow/utils/shutdown.py` +- Main docs: `agentflow-docs/docs/reference/python/context-manager.md` +- Main docs: `agentflow-docs/docs/reference/python/id-generator.md` +- Main docs: `agentflow-docs/docs/reference/python/background-tasks.md` diff --git a/agentflow_cli/cli/templates/skills/agent-skills/references/dependency-injection.md b/agentflow_cli/cli/templates/skills/agent-skills/references/dependency-injection.md new file mode 100644 index 0000000..137e1cc --- /dev/null +++ b/agentflow_cli/cli/templates/skills/agent-skills/references/dependency-injection.md @@ -0,0 +1,58 @@ +# Dependency Injection + +Use this when wiring services into nodes/tools, configuring API dependencies, or removing hidden globals. + +## Automatic Injection + +Agentflow injects these parameters by name/type conventions when they appear in node or tool signatures: + +- `state`: current `AgentState` or subclass. +- `config`: execution config such as `thread_id`, `user_id`, and `run_id`. +- `tool_call_id`: current tool call ID, tools only. + +Injected parameters are hidden from the model-facing tool schema. + +## Service Injection + +Use `injectq` for application services: + +```python +from injectq import Inject, InjectQ + +container = InjectQ.get_instance() +container.bind_instance(MyService, service) + +def tool(query: str, service: MyService = Inject[MyService]) -> str: + """Search with MyService.""" + return service.search(query) +``` + +You can bind singleton instances, string keys, or factories. Pass a container to `StateGraph(container=container)` when using a non-default container. + +## API Configuration + +In `agentflow.json`, use import paths to wire dependencies into the server runtime: + +```json +{ + "agent": "graph.react:app", + "checkpointer": "graph.dependencies:checkpointer", + "store": "graph.dependencies:store", + "injectq": "graph.dependencies:container" +} +``` + +The API loader imports these and binds them for graph execution. + +## Rules + +- Prefer `InjectQ` over module-level globals for databases, stores, callbacks, and custom services. +- Keep user-provided tool arguments model-visible; keep runtime context injected. +- Configure shared services at graph/server boundaries, not deep inside individual tools. + +## Source Map + +- DI docs source: `agentflow-docs/docs/concepts/dependency-injection.md` +- API loader: `agentflow-api/agentflow_cli/src/app/loader.py` +- Graph config: `agentflow-api/agentflow_cli/src/app/core/config/graph_config.py` +- Tool/node call helpers: `agentflow/agentflow/utils/callable_utils.py` diff --git a/agentflow_cli/cli/templates/skills/agent-skills/references/id-and-thread-name-generators.md b/agentflow_cli/cli/templates/skills/agent-skills/references/id-and-thread-name-generators.md new file mode 100644 index 0000000..81c200e --- /dev/null +++ b/agentflow_cli/cli/templates/skills/agent-skills/references/id-and-thread-name-generators.md @@ -0,0 +1,71 @@ +# ID and Thread Name Generators + +Use this when changing API ID generation, Snowflake IDs, graph ID generators, or thread display-name generation. + +## Python Graph ID Generators + +Core graph ID generators live in `agentflow.utils`. + +Built-ins: + +- `DefaultIDGenerator` +- `UUIDGenerator` +- `BigIntIDGenerator` +- `TimestampIDGenerator` +- `IntIDGenerator` +- `HexIDGenerator` +- `ShortIDGenerator` +- `AsyncIDGenerator` + +Pass to `StateGraph(id_generator=...)`. + +## API Snowflake ID Generator + +The API package includes a Snowflake-style ID generator for server-side/generated IDs. + +Environment/config variables: + +- `SNOWFLAKE_EPOCH` +- `SNOWFLAKE_NODE_ID` +- `SNOWFLAKE_WORKER_ID` +- `SNOWFLAKE_TIME_BITS` +- `SNOWFLAKE_NODE_BITS` +- `SNOWFLAKE_WORKER_BITS` + +Use Snowflake IDs when deployment needs sortable, distributed, non-UUID identifiers. + +## Thread Name Generator + +Configure in `agentflow.json`: + +```json +{ + "thread_name_generator": "graph.thread_name_generator:MyNameGenerator" +} +``` + +The API uses the configured generator when creating or naming threads. If no generator is configured, a dummy/default generator may be used. + +Keep thread name generation: + +- Fast enough for request paths. +- Safe for arbitrary user messages. +- Deterministic enough for tests where needed. + +## Rules + +- Keep ID type expectations aligned across Python state, API responses, checkpointers, and TypeScript client types. +- For multi-worker deployments, avoid process-local counters unless partitioned safely. +- Validate Snowflake node/worker bit settings before production use. +- Treat thread names as display labels, not stable identifiers. + +## Source Map + +- Python ID generators: `agentflow/agentflow/utils/id_generator.py` +- API Snowflake generator: `agentflow-api/agentflow_cli/src/app/utils/snowflake_id_generator.py` +- Thread name generator: `agentflow-api/agentflow_cli/src/app/utils/thread_name_generator.py` +- Graph service thread naming: `agentflow-api/agentflow_cli/src/app/routers/graph/services/graph_service.py` +- Settings: `agentflow-api/agentflow_cli/src/app/core/config/settings.py` +- Main docs: `agentflow-docs/docs/reference/python/id-generator.md` +- API docs: `agentflow-docs/docs/reference/api-cli/id-generator.md` +- API docs: `agentflow-docs/docs/reference/api-cli/thread-name-generator.md` diff --git a/agentflow_cli/cli/templates/skills/agent-skills/references/media-and-files.md b/agentflow_cli/cli/templates/skills/agent-skills/references/media-and-files.md new file mode 100644 index 0000000..f03bb82 --- /dev/null +++ b/agentflow_cli/cli/templates/skills/agent-skills/references/media-and-files.md @@ -0,0 +1,66 @@ +# Media and Files + +Use this when implementing multimodal messages, file upload/download, media stores, or provider media handling. + +## Message Model + +Messages can mix text and media blocks. Media bytes are referenced through `MediaRef` rather than stored directly in graph logic. + +`MediaRef.kind` values: + +- `"url"`: external URL. +- `"data"`: inline base64 bytes for small payloads. +- `"file_id"`: key returned by a media store or upload route. + +Content blocks include image, audio, video, document, and generic data blocks. See `state-and-messages.md` for the full list. + +## Media Stores + +Media stores keep binary data outside messages: + +- `InMemoryMediaStore`: tests/dev only. +- `LocalFileMediaStore`: local single-server storage with metadata sidecars. +- `CloudMediaStore`: cloud object storage with signed URL support where configured. + +The base store supports storing, retrieving, deleting, existence checks, and metadata reads. + +## MultimodalConfig + +Use `MultimodalConfig` on `Agent` to control provider transport: + +- Image handling: base64, URL, or provider file ID. +- Document handling: extract text, forward raw, or skip. +- Size/dimension/type constraints for media safety and provider compatibility. + +The resolver should pick a supported transport based on provider/model capabilities. + +## REST and Client + +API routes: + +- `POST /v1/files/upload` +- `GET /v1/files/{file_id}` +- `GET /v1/files/{file_id}/info` +- `GET /v1/files/{file_id}/url` +- `GET /v1/config/multimodal` + +TypeScript client methods: + +- `uploadFile` +- `getFile` +- `getFileInfo` +- `getFileAccessUrl` +- `getMultimodalConfig` + +## Rules + +- Use `file_id` for repeated or large media. +- Inline base64 only for small payloads. +- Preserve MIME types. +- Pass `media_store` at compile/runtime boundaries where the graph must dereference `file_id` media. + +## Source Map + +- Media config/resolver: `agentflow/agentflow/storage/media` +- Media router: `agentflow-api/agentflow_cli/src/app/routers/media` +- TS files endpoint: `agentflow-client/src/endpoints/files.ts` diff --git a/agentflow_cli/cli/templates/skills/agent-skills/references/memory-and-store.md b/agentflow_cli/cli/templates/skills/agent-skills/references/memory-and-store.md new file mode 100644 index 0000000..e17d96e --- /dev/null +++ b/agentflow_cli/cli/templates/skills/agent-skills/references/memory-and-store.md @@ -0,0 +1,70 @@ +# Memory and Store + +Use this when adding long-term memory, store APIs, semantic search, memory tools, or memory retrieval modes. + +## Concept + +The memory store is cross-thread, cross-user long-term knowledge. It is not the same as checkpointing. + +- Checkpointer: exact `thread_id` conversation continuity and full state snapshots. +- Memory store: semantic memory records, user preferences, facts, and knowledge across conversations. + +## Access Pattern + +Agents access memory through tools, especially `memory_tool`. The LLM can decide to: + +- `store`: save or update a memory. +- `search`: recall relevant memories. +- `delete`: remove a memory by ID. + +Writes can run in the background and memory keys deduplicate/update records. + +## Retrieval Modes + +- `"no_retrieval"`: model cannot read memory automatically but can write memories. +- `"preload"`: relevant memories are injected before the model call. +- `"postload"`: model searches memory on demand through the tool. + +All modes can include write instructions. + +## Backends + +- `QdrantStore`: vector database backend, local or cloud. +- `Mem0Store`: managed memory service backend. + +Embedding helpers such as `OpenAIEmbedding` and factories like `create_local_qdrant_store` exist in the store package where available. + +## Wiring Options + +High-level `Agent`: + +- Pass `MemoryConfig(store=..., retrieval_mode=...)`. +- Also pass a `ToolNode`; memory setup registers memory tools there. + +Lower-level graph: + +- Use `MemoryIntegration`. +- Add `memory.tools` to your `ToolNode`. +- Use `memory.system_prompt`. +- Let `memory.wire(graph, entry_to="AGENT")` add preload routing when needed. + +## REST and Client + +Store API routes include: + +- `POST /v1/store/memories` +- `POST /v1/store/search` +- `POST /v1/store/memories/{memory_id}` +- `POST /v1/store/memories/list` +- `PUT /v1/store/memories/{memory_id}` +- `DELETE /v1/store/memories/{memory_id}` +- `POST /v1/store/memories/forget` + +TypeScript client methods include `storeMemory`, `searchMemory`, `getMemory`, `updateMemory`, `deleteMemory`, `listMemories`, and `forgetMemories`. + +## Source Map + +- Store interfaces/backends: `agentflow/agentflow/storage/store` +- Memory tool: `agentflow/agentflow/prebuilt/tools/memory.py` +- API store router: `agentflow-api/agentflow_cli/src/app/routers/store` +- TS memory endpoints: `agentflow-client/src/endpoints/*Memory.ts` diff --git a/agentflow_cli/cli/templates/skills/agent-skills/references/prebuilt-agents-and-tools.md b/agentflow_cli/cli/templates/skills/agent-skills/references/prebuilt-agents-and-tools.md new file mode 100644 index 0000000..c7f4511 --- /dev/null +++ b/agentflow_cli/cli/templates/skills/agent-skills/references/prebuilt-agents-and-tools.md @@ -0,0 +1,60 @@ +# Prebuilt Agents and Tools + +Use this when a task asks for ready-made agent patterns, common tools, or multi-agent handoff. + +## Public Surface + +Import from `agentflow.prebuilt`, `agentflow.prebuilt.agent`, or `agentflow.prebuilt.tools`. + +Prebuilt agents currently exported: + +- `ReactAgent`: builds the standard agent/tool loop around `Agent` and `ToolNode`. +- `RouterAgent`: routes work to specialized agents or branches. +- `RAGAgent`: retrieval augmented graph pattern with retriever and synthesis nodes. + +Prebuilt tools currently exported: + +- `safe_calculator` +- `fetch_url` +- `file_read` +- `file_search` +- `file_write` +- `google_web_search` +- `vertex_ai_search` +- `memory_tool` +- `make_user_memory_tool` +- `make_agent_memory_tool` +- `create_handoff_tool` +- `is_handoff_tool` + +Several experimental prebuilt agent modules exist in source but are not exported from `agentflow.prebuilt.agent.__all__`; do not document them as stable without checking source and main docs. + +## Handoff + +Use `create_handoff_tool(target_agent_name)` when a model should transfer control to another agent. Handoff tools follow the naming convention `transfer_to_` and return a graph navigation command. + +Typical use: + +1. Add handoff tools to the coordinator or specialist `ToolNode`. +2. Mention available transfers in the relevant agent prompt. +3. Route tool calls through the standard ReAct loop. +4. Handoff interception detects transfer tools and jumps to the target agent. + +Use `Command(goto=...)` directly for explicit runtime routing when not using the handoff helper. + +## Rules + +- Prefer prebuilt agents for common patterns before hand-writing graph loops. +- Check constructor signatures in source before using less common options. +- Treat exported `__all__` names as the stable public surface. +- Use handoff for agent-to-agent delegation, not for ordinary function calls. +- Keep handoff target names aligned with graph node names. + +## Source Map + +- Exports: `agentflow/agentflow/prebuilt/__init__.py` +- Agents: `agentflow/agentflow/prebuilt/agent` +- Tools: `agentflow/agentflow/prebuilt/tools` +- Handoff tool: `agentflow/agentflow/prebuilt/tools/handoff.py` +- Main docs: `agentflow-docs/docs/reference/python/command-handoff.md` +- How-to: `agentflow-docs/docs/how-to/python/handoff-between-agents.md` diff --git a/agentflow_cli/cli/templates/skills/agent-skills/references/production-runtime.md b/agentflow_cli/cli/templates/skills/agent-skills/references/production-runtime.md new file mode 100644 index 0000000..ab34ab0 --- /dev/null +++ b/agentflow_cli/cli/templates/skills/agent-skills/references/production-runtime.md @@ -0,0 +1,56 @@ +# Production Runtime + +Use this when changing API serving, deployment, auth/middleware, worker scaling, environment config, or Docker generation. + +## API Server + +`agentflow api` starts a Uvicorn ASGI server with FastAPI. The server loads the configured compiled graph once at startup and reuses it for requests. + +Runtime components: + +- Uvicorn ASGI process. +- FastAPI app. +- Auth and permission middleware. +- Routers for graph, threads/checkpointer, store, ping, and files. +- `GraphService` for invoke/stream/stop/setup/fix operations. +- Configured checkpointer, store, media store, and DI container. + +## Async Execution + +The API runtime handles sync and async graph nodes. Blocking model calls should not block the FastAPI event loop; service/runtime code schedules appropriately. + +## Multi-worker Rules + +- Use durable/shared storage when multiple worker processes serve the same graph. +- `InMemoryCheckpointer` is process-local and unsuitable for load-balanced continuity. +- Use `PgCheckpointer` or equivalent durable backend for shared state. +- Use shared memory/media stores when clients can hit any worker. + +## Environment Configuration + +Important variables include: + +- `MODE`: development or production. +- `LOG_LEVEL` +- `ORIGINS` +- `JWT_SECRET_KEY` +- `JWT_ALGORITHM` +- `REDIS_URL` +- `GRAPH_PATH`: path to `agentflow.json`; defaults to `agentflow.json`. + +Production should set `MODE=production`, configure CORS origins, and use real auth secrets. + +## CLI and Docker + +- `agentflow api`: serve graph over HTTP. +- `agentflow play`: serve graph and open/use playground flow. +- `agentflow init`: scaffold config and graph. +- `agentflow build --docker-compose`: generate Docker deployment files. + +## Source Map + +- App startup: `agentflow-api/agentflow_cli/src/app/main.py` +- Route setup: `agentflow-api/agentflow_cli/src/app/routers/setup_router.py` +- Graph service: `agentflow-api/agentflow_cli/src/app/routers/graph/services` +- Config/middleware/auth: `agentflow-api/agentflow_cli/src/app/core` +- CLI commands: `agentflow-api/agentflow_cli/cli` diff --git a/agentflow_cli/cli/templates/skills/agent-skills/references/providers-and-adapters.md b/agentflow_cli/cli/templates/skills/agent-skills/references/providers-and-adapters.md new file mode 100644 index 0000000..8a463bd --- /dev/null +++ b/agentflow_cli/cli/templates/skills/agent-skills/references/providers-and-adapters.md @@ -0,0 +1,81 @@ +# Providers and Adapters + +Use this when changing OpenAI, Google Gemini, Vertex AI, reasoning, response conversion, or third-party tool adapters. + +## Providers + +`Agent` supports OpenAI and Google provider flows through provider-specific internal modules: + +- OpenAI: `agentflow/agentflow/core/graph/agent_internal/openai.py` +- Google: `agentflow/agentflow/core/graph/agent_internal/google.py` +- Provider inference/helpers: `agentflow/agentflow/core/graph/agent_internal/providers.py` + +Provider docs live in: + +- `agentflow-docs/docs/providers/openai.md` +- `agentflow-docs/docs/providers/google.md` + +## Environment Variables + +OpenAI: + +- `OPENAI_API_KEY` + +Google Gemini API: + +- `GEMINI_API_KEY` +- `GOOGLE_API_KEY` fallback + +Vertex AI: + +- `GOOGLE_GENAI_USE_VERTEXAI=true` +- `GOOGLE_CLOUD_PROJECT` +- `GOOGLE_CLOUD_LOCATION` +- `GOOGLE_APPLICATION_CREDENTIALS` + +## Reasoning + +Reasoning config is provider-specific: + +- OpenAI style: effort and summary options where supported. +- Google style: effort mapped to thinking budget or explicit `thinking_budget`. + +Check provider capability and converter behavior before adding new reasoning fields. + +## LLM Converters + +Runtime converter exports: + +- `BaseConverter` +- `GoogleGenAIConverter` +- `OpenAIConverter` +- `OpenAIResponsesConverter` +- `ModelResponseConverter` +- `reasoning_utils` + +Converters normalize provider-native responses into Agentflow `Message`, content blocks, usage, reasoning, and tool call structures. + +## Tool Adapters + +Third-party adapters: + +- `LangChainAdapter`: registers LangChain tools and exposes LLM-compatible schemas. +- `ComposioAdapter`: integrates Composio tools where dependency is installed. + +Tool execution precedence in `ToolNode` is MCP, Composio, LangChain, then local tools, with remote tool checks before local execution where configured. + +## Rules + +- Use official provider SDKs through existing provider modules. +- Do not leak provider-native response shapes past converter boundaries unless stored in `Message.raw`. +- Keep optional provider/tool dependencies optional and guarded. +- Update provider docs and tests when adding provider-specific behavior. +- Verify multimodal and tool-call behavior for each provider separately. + +## Source Map + +- Agent internals: `agentflow/agentflow/core/graph/agent_internal` +- LLM adapters: `agentflow/agentflow/runtime/adapters/llm` +- Tool adapters: `agentflow/agentflow/runtime/adapters/tools` +- Provider docs: `agentflow-docs/docs/providers` +- Agent docs: `agentflow-docs/docs/reference/python/agent.md` diff --git a/agentflow_cli/cli/templates/skills/agent-skills/references/publishers-and-runtime-protocols.md b/agentflow_cli/cli/templates/skills/agent-skills/references/publishers-and-runtime-protocols.md new file mode 100644 index 0000000..f57ec29 --- /dev/null +++ b/agentflow_cli/cli/templates/skills/agent-skills/references/publishers-and-runtime-protocols.md @@ -0,0 +1,78 @@ +# Publishers and Runtime Protocols + +Use this when adding observability events, external event buses, A2A integration, ACP, or runtime adapters. + +## Publishers + +Publisher exports live under `agentflow.runtime.publisher` and `agentflow.runtime`. + +Core types: + +- `EventModel`: event payload model. +- `Event`: event source enum. +- `EventType`: lifecycle/phase enum. +- `ContentType`: payload content enum. +- `BasePublisher`: abstract publisher interface. +- `publish_event`: helper used by runtime internals. + +Implementations: + +- `ConsolePublisher` +- `RedisPublisher` +- `KafkaPublisher` +- `RabbitMQPublisher` + +Publishers receive structured events from graph/tool execution. Use them for tracing, monitoring, audit logs, and external streaming/event bus integrations. + +## Runtime Adapters + +LLM adapters: + +- `BaseConverter` +- `GoogleGenAIConverter` +- `OpenAIConverter` +- `OpenAIResponsesConverter` +- `ConverterType` + +Tool adapters: + +- `LangChainAdapter` +- `ComposioAdapter` + +Use adapters when translating provider-native or third-party tool formats into Agentflow messages, tool schemas, and execution results. + +## A2A Runtime + +A2A helpers live in `agentflow.runtime.protocols.a2a`. + +Key helpers: + +- `make_agent_card` +- `build_a2a_app` +- `create_a2a_server` +- `delegate_to_a2a_agent` +- `create_a2a_client_node` +- `AgentFlowExecutor` + +The A2A extras require the optional `a2a-sdk` dependency. Keep imports lazy or guarded where the dependency may not be installed. + +## ACP + +ACP support is in `agentflow/agentflow/runtime/protocols/acp.py`. Treat it as a runtime protocol surface and check source before extending because public docs are thinner than core graph docs. + +## Rules + +- Prefer publisher events for observability instead of ad hoc print/log statements in reusable runtime paths. +- Keep publisher config serializable and environment-friendly. +- Close publishers on shutdown when they own network connections. +- Do not require optional protocol dependencies at core import time. +- For A2A, distinguish serving an Agentflow graph as an A2A app from delegating to another A2A agent. + +## Source Map + +- Runtime exports: `agentflow/agentflow/runtime/__init__.py` +- Publishers: `agentflow/agentflow/runtime/publisher` +- A2A protocol: `agentflow/agentflow/runtime/protocols/a2a` +- ACP protocol: `agentflow/agentflow/runtime/protocols/acp.py` +- Main docs: `agentflow-docs/docs/reference/python/publishers.md` +- Examples docs: `agentflow-docs/docs/tutorials/from-examples/graceful-shutdown.md` diff --git a/agentflow_cli/cli/templates/skills/agent-skills/references/remote-tools.md b/agentflow_cli/cli/templates/skills/agent-skills/references/remote-tools.md new file mode 100644 index 0000000..a80a974 --- /dev/null +++ b/agentflow_cli/cli/templates/skills/agent-skills/references/remote-tools.md @@ -0,0 +1,86 @@ +# Remote Tools + +Use this when implementing tools that execute in a TypeScript client or browser process while the Python graph runs on the Agentflow API server. + +## Concept + +Remote tools let a graph advertise a tool schema to the model while deferring execution to the client. The server returns a `RemoteToolCallBlock` instead of executing the tool locally. The TypeScript SDK detects that block, runs the registered handler, sends back a `ToolResultBlock`, and continues the invoke or stream loop. + +Use remote tools for: + +- Browser-only capabilities such as DOM access, local files selected by the user, Web APIs, or UI state. +- Client-owned integrations that should not expose credentials to the server. +- Tools whose implementation belongs with the app using `@10xscale/agentflow-client`. + +Use local Python tools, MCP, Composio, or LangChain tools when execution should happen server-side. + +## Registration Flow + +1. In TypeScript, call `client.registerTool({ node, name, description, parameters, handler })`. +2. Call `await client.setup()` before invoking the graph. +3. The client posts remote tool schemas to `POST /v1/graph/setup`. +4. The API groups tools by `node_name` and calls `CompiledGraph.attach_remote_tools(tools, node_name)`. +5. The relevant Python `ToolNode` marks those names as remote. + +Remote tool schema shape: + +```typescript +{ + node_name: "TOOLS", + name: "read_browser_state", + description: "Read selected browser state.", + parameters: { + type: "object", + properties: {}, + required: [] + } +} +``` + +## Execution Flow + +1. Model requests a tool call. +2. `ToolNode` sees the tool name in `remote_tool_names`. +3. Instead of executing locally, the server returns a `Message` with `RemoteToolCallBlock`. +4. TypeScript `invoke` or `stream` detects content blocks with `type === "remote_tool_call"`. +5. `ToolExecutor.executeToolCalls` runs matching registered handlers. +6. The client creates `Message.tool_message([ToolResultBlock(...)] )`. +7. The client sends tool result messages in the next API iteration. + +The client controls the recursion loop and stops when no remote tool calls remain or when `recursion_limit` is reached. + +## Important Types + +Python: + +- `RemoteToolCallBlock`: `agentflow/agentflow/core/state/message_block.py` +- `ToolResultBlock`: same module +- `ToolNode.remote_tool_names`: checked during tool execution + +TypeScript: + +- `RemoteTool`: `agentflow-client/src/endpoints/setupGraph.ts` +- `ToolRegistration`: `agentflow-client/src/tools.ts` +- `ToolExecutor`: `agentflow-client/src/tools.ts` +- `RemoteToolCallBlock`: `agentflow-client/src/message.ts` + +## Rules + +- `node` / `node_name` must match the graph tool node that should expose the remote tools. +- `name` must match the tool call name the model will emit. +- `parameters` should be JSON-schema-like and precise; this schema is model-facing. +- Register tools and call `client.setup()` before `invoke` or `stream`. +- Return serializable values from TypeScript handlers so they can be placed in `ToolResultBlock.output`. +- Handle missing tools and handler errors as tool result errors, not transport failures. + +## Source Map + +- API setup schema: `agentflow-api/agentflow_cli/src/app/routers/graph/schemas/graph_schemas.py` +- API setup route: `agentflow-api/agentflow_cli/src/app/routers/graph/router.py` +- API setup service: `agentflow-api/agentflow_cli/src/app/routers/graph/services/graph_service.py` +- Python block model: `agentflow/agentflow/core/state/message_block.py` +- Python remote handling: `agentflow/agentflow/core/graph/tool_node/base.py` +- TS setup endpoint: `agentflow-client/src/endpoints/setupGraph.ts` +- TS invoke loop: `agentflow-client/src/endpoints/invoke.ts` +- TS stream loop: `agentflow-client/src/endpoints/stream.ts` +- TS executor: `agentflow-client/src/tools.ts` diff --git a/agentflow_cli/cli/templates/skills/agent-skills/references/rest-api-and-errors.md b/agentflow_cli/cli/templates/skills/agent-skills/references/rest-api-and-errors.md new file mode 100644 index 0000000..968a0c2 --- /dev/null +++ b/agentflow_cli/cli/templates/skills/agent-skills/references/rest-api-and-errors.md @@ -0,0 +1,93 @@ +# REST API and Errors + +Use this when changing API routes, response envelopes, schemas, Swagger docs, or error handling. + +## Active Routers + +Registered in `agentflow-api/agentflow_cli/src/app/routers/setup_router.py`: + +- graph +- checkpointer / threads +- store +- ping +- media / files + +## Graph Routes + +- `POST /v1/graph/invoke` +- `POST /v1/graph/stream` +- `GET /v1/graph` +- `GET /v1/graph:StateSchema` +- `POST /v1/graph/stop` +- `POST /v1/graph/setup` +- `POST /v1/graph/fix` + +## Thread / Checkpointer Routes + +These expose thread state, messages, thread listing/details, message mutation, and deletion. Use the docs and router source for exact method/path names because client helpers map to these endpoints. + +TypeScript client wrappers include: + +- `threadState` +- `updateThreadState` +- `clearThreadState` +- `threadDetails` +- `threads` +- `threadMessages` +- `addThreadMessages` +- `singleMessage` +- `deleteMessage` +- `deleteThread` + +## Store Routes + +- `POST /v1/store/memories` +- `POST /v1/store/search` +- `POST /v1/store/memories/{memory_id}` +- `POST /v1/store/memories/list` +- `PUT /v1/store/memories/{memory_id}` +- `DELETE /v1/store/memories/{memory_id}` +- `POST /v1/store/memories/forget` + +## File Routes + +- `POST /v1/files/upload` +- `GET /v1/files/{file_id}` +- `GET /v1/files/{file_id}/info` +- `GET /v1/files/{file_id}/url` +- `GET /v1/config/multimodal` + +## Ping + +Use the ping route for liveness/connectivity checks. The TypeScript client wraps this as `client.ping()`. + +## Response and Error Handling + +API helpers wrap successful responses with metadata and request context. Error handlers normalize framework exceptions such as graph errors, node errors, recursion errors, storage errors, validation errors, auth errors, and generic exceptions. + +Common status expectations: + +- 400/422 for invalid input. +- 401 for unauthenticated requests. +- 403 for authorization failures. +- 404 for missing resources. +- 500 for unexpected graph/server failures. + +## Rules + +- Keep Pydantic schemas, router docs, TypeScript endpoint types, and docs aligned. +- Use `success_response` helpers for consistent envelopes. +- Preserve SSE format for streaming responses. +- Sanitize error logs and avoid exposing secrets in details. +- Add Swagger response metadata for new routes. + +## Source Map + +- Route setup: `agentflow-api/agentflow_cli/src/app/routers/setup_router.py` +- Graph router/schema/service: `agentflow-api/agentflow_cli/src/app/routers/graph` +- Checkpointer router: `agentflow-api/agentflow_cli/src/app/routers/checkpointer` +- Store router: `agentflow-api/agentflow_cli/src/app/routers/store` +- Media router: `agentflow-api/agentflow_cli/src/app/routers/media` +- Error handlers: `agentflow-api/agentflow_cli/src/app/core/exceptions/handle_errors.py` +- Response helper: `agentflow-api/agentflow_cli/src/app/utils/response_helper.py` +- Main docs: `agentflow-docs/docs/reference/rest-api` diff --git a/agentflow_cli/cli/templates/skills/agent-skills/references/security-and-validators.md b/agentflow_cli/cli/templates/skills/agent-skills/references/security-and-validators.md new file mode 100644 index 0000000..d2030d4 --- /dev/null +++ b/agentflow_cli/cli/templates/skills/agent-skills/references/security-and-validators.md @@ -0,0 +1,69 @@ +# Security and Validators + +Use this when adding prompt-injection protection, content validation, auth-sensitive tool behavior, or safety checks. + +## Validator API + +Validators subclass `BaseValidator` and implement: + +```python +async def validate(self, messages: list[Message]) -> bool: + ... +``` + +Register validators on `CallbackManager`: + +```python +callback_manager = CallbackManager() +callback_manager.register_input_validator(MyValidator()) +app = graph.compile(callback_manager=callback_manager) +``` + +## Built-ins + +Exports from `agentflow.utils.validators`: + +- `PromptInjectionValidator` +- `MessageContentValidator` +- `ValidationError` +- `register_default_validators` + +Use `register_default_validators(callback_manager, strict_mode=True)` to enable standard protections. Strict mode blocks by raising `ValidationError`; lenient behavior may sanitize/log depending on validator settings. + +## Callback-Based Safety + +Use `register_before_invoke` for pre-model or pre-tool checks. Use `register_after_invoke` for output policy checks. Use `register_on_error` for controlled recovery values. + +Common safety points: + +- User input validation before AI invocation. +- Tool argument policy before tool execution. +- Model output validation before returning to API clients. +- Store/memory write validation. + +## API Security Boundary + +This reference covers graph-level validators. For HTTP auth, read `auth-and-authorization.md`. + +For production: + +- Enable API auth unless behind a trusted gateway. +- Restrict CORS origins. +- Keep docs endpoints off or protected if needed. +- Validate remote tool registrations if exposed beyond trusted clients. + +## Rules + +- Keep validators deterministic and fast. +- Avoid calling LLMs inside validators on the hot path unless explicitly intended. +- Include the relevant invocation type when registering callbacks. +- Raise `ValidationError` for expected policy failures. +- Log safely; sanitize user/tool data before logs. + +## Source Map + +- Callback system: `agentflow/agentflow/utils/callbacks.py` +- Validators: `agentflow/agentflow/utils/validators.py` +- API error handlers: `agentflow-api/agentflow_cli/src/app/core/exceptions/handle_errors.py` +- Main docs: `agentflow-docs/docs/reference/python/callback-manager.md` +- How-to: `agentflow-docs/docs/how-to/python/protect-against-prompt-injection.md` diff --git a/agentflow_cli/cli/templates/skills/agent-skills/references/state-and-messages.md b/agentflow_cli/cli/templates/skills/agent-skills/references/state-and-messages.md new file mode 100644 index 0000000..1314e75 --- /dev/null +++ b/agentflow_cli/cli/templates/skills/agent-skills/references/state-and-messages.md @@ -0,0 +1,68 @@ +# State and Messages + +Use this when changing state schemas, message serialization, multimodal blocks, tool call/result blocks, context trimming, or API/client message compatibility. + +## AgentState + +Default state fields: + +- `context`: list of `Message`, using the `add_messages` reducer. +- `context_summary`: optional summary for trimmed context. +- `execution_meta`: runtime metadata such as current node, step count, interrupt, error, and stop state. + +Extend `AgentState` for app fields: + +```python +from agentflow.core.state import AgentState + +class OrderState(AgentState): + order_id: str = "" + total: float = 0.0 +``` + +Only returned dict keys are merged. Returning a `Message` appends to `context`; it does not replace history. + +## Message + +`Message` represents one conversation turn. Important fields: + +- `message_id` +- `role`: `"user"`, `"assistant"`, `"system"`, or `"tool"` +- `content`: list of typed content blocks +- `delta`: true for partial/streaming messages +- `tools_calls`: provider-native tool call list +- `reasoning`, `timestamp`, `metadata`, `usages`, `raw` + +Use `Message.text_message("...", role="user")` for plain text. Use `msg.text()` to concatenate `TextBlock` content. + +## Content Blocks + +Supported block families: + +- `TextBlock`: text and annotations. +- `ImageBlock`: image plus `MediaRef`. +- `AudioBlock`: audio plus transcript/hints. +- `VideoBlock`: video plus thumbnail. +- `DocumentBlock`: document media plus optional extracted text/pages/excerpt. +- `DataBlock`: generic binary data. +- `ToolCallBlock`: model-requested tool call. +- `ToolResultBlock`: tool output paired by call ID. +- `ReasoningBlock`: provider reasoning trace. +- `AnnotationBlock`: citations/references. +- `ErrorBlock`: structured error data. + +## ToolResult + +Use `ToolResult(message=..., state={...})` when a tool must both return text to the model and mutate state. Only the named state fields are updated. + +## Context Trimming + +`Agent(trim_context=True)` trims what is sent to the model and writes a summary to `context_summary`. The checkpointer should still preserve full history. + +## Source Map + +- State models: `agentflow/agentflow/core/state/agent_state.py` +- Message model: `agentflow/agentflow/core/state/message.py` +- Content blocks: `agentflow/agentflow/core/state/message_block.py` +- Reducers: `agentflow/agentflow/core/state/reducers.py` +- TS message class: `agentflow-client/src/message.ts` diff --git a/agentflow_cli/cli/templates/skills/agent-skills/references/state-graph.md b/agentflow_cli/cli/templates/skills/agent-skills/references/state-graph.md new file mode 100644 index 0000000..c7aea7b --- /dev/null +++ b/agentflow_cli/cli/templates/skills/agent-skills/references/state-graph.md @@ -0,0 +1,67 @@ +# StateGraph and Nodes + +Use this when adding workflows, nodes, edges, interrupts, graph compilation, or execution behavior. + +## Core Model + +`StateGraph` is the workflow engine. A graph describes directed node execution over an `AgentState` subclass. The compiled graph handles state loading, node execution, routing, checkpointing, streaming, interrupts, recursion limits, and shutdown. + +## Creating Graphs + +- `StateGraph()` uses `AgentState`. +- `StateGraph(MyState)` or `StateGraph(MyState())` uses a custom Pydantic state subclass. +- Constructor-level dependencies include optional context manager, publisher, ID generator, and `InjectQ` container. + +## Nodes + +A node can be: + +- A callable receiving state and returning a `Message`, `ToolResult`, or state dict. +- An `Agent`. +- A `ToolNode`. +- Any compatible sync or async function. + +Use `graph.add_node("NAME", node)` for explicit naming. Use `graph.add_node(function)` only when the function name is the right node name. Use `graph.override_node("NAME", replacement)` in tests or controlled overrides. + +## Edges + +- `add_edge("A", "B")`: static transition. +- `set_entry_point("A")`: starts graph at a node. +- `add_conditional_edges("A", route, path_map)`: route by state-derived string keys. +- Use `END` for termination. +- Use `Command` for runtime jumps only when static or conditional edges are not enough; see `callbacks-and-command.md`. + +## Compilation + +`graph.compile(...)` accepts: + +- `checkpointer`: state persistence; defaults to in-memory where implementation supplies it. +- `store`: long-term memory store. +- `media_store`: multimodal file store. +- `interrupt_before` / `interrupt_after`: pause points. +- `callback_manager`: observability hooks. +- `shutdown_timeout`: graceful close timeout. + +Compilation should fail fast when the entry point is missing or interrupt nodes do not exist. + +For callbacks, validators, and tracing hooks, read `callbacks-and-command.md`. + +## Execution + +- `app.invoke(input, config, response_granularity)` runs synchronously and returns a dict. +- `app.ainvoke(...)` is the async equivalent. +- `app.stream(...)` yields sync `StreamChunk` values. +- `app.astream(...)` yields async `StreamChunk` values. +- `config.thread_id` enables checkpointed continuity. +- `config.recursion_limit` caps node execution count. + +## Interrupts + +Compile with `interrupt_before=["NODE"]` or `interrupt_after=["NODE"]`. Resume by invoking again with the same `thread_id`. + +## Source Map + +- StateGraph: `agentflow/agentflow/core/graph/state_graph.py` +- Compiled graph: `agentflow/agentflow/core/graph/compiled_graph.py` +- Graph errors: `agentflow/agentflow/core/exceptions` +- Constants: `agentflow/agentflow/utils/constants.py` diff --git a/agentflow_cli/cli/templates/skills/agent-skills/references/streaming.md b/agentflow_cli/cli/templates/skills/agent-skills/references/streaming.md new file mode 100644 index 0000000..b15c492 --- /dev/null +++ b/agentflow_cli/cli/templates/skills/agent-skills/references/streaming.md @@ -0,0 +1,53 @@ +# Streaming + +Use this when working on incremental output, SSE, cancellation, response granularity, or TypeScript async iteration. + +## Modes + +- `invoke`: run to completion and return a final result dict. +- `ainvoke`: async invoke. +- `stream`: sync generator yielding `StreamChunk`. +- `astream`: async generator yielding `StreamChunk`. + +Use streaming for chat UIs and any caller that needs partial output. + +## StreamChunk + +Important fields: + +- `event`: `"message"`, `"state"`, `"error"`, or `"updates"`. +- `message`: populated for message events. +- `state`: populated for state events. +- `data`: populated for errors/updates. +- `thread_id`, `run_id`, `metadata`, `timestamp`. + +## ResponseGranularity + +- `LOW`: latest messages only; good default for UI streaming. +- `PARTIAL`: context, summary, and latest messages. +- `FULL`: complete state and messages. + +Use lower granularity for client performance and privacy unless full state is needed. + +## Stop + +Use `app.stop()` / `app.astop()` or `POST /v1/graph/stop` to request cancellation for a thread. The graph exits cleanly after node boundaries. + +## REST and Client + +REST: + +- `POST /v1/graph/stream` returns server-sent events. +- Each SSE data payload is a serialized `StreamChunk`. + +TypeScript: + +- `AgentFlowClient.stream(messages, options)` returns an async generator. +- Handle `chunk.event === "message"` for incremental text. + +## Source Map + +- Stream chunks: `agentflow/agentflow/core/state/stream_chunks.py` +- Compiled graph streaming: `agentflow/agentflow/core/graph/compiled_graph.py` +- Graph API router/service: `agentflow-api/agentflow_cli/src/app/routers/graph` +- TS stream endpoint: `agentflow-client/src/endpoints/stream.ts` diff --git a/agentflow_cli/cli/templates/skills/agent-skills/references/testing-and-evaluation.md b/agentflow_cli/cli/templates/skills/agent-skills/references/testing-and-evaluation.md new file mode 100644 index 0000000..d2ad1af --- /dev/null +++ b/agentflow_cli/cli/templates/skills/agent-skills/references/testing-and-evaluation.md @@ -0,0 +1,66 @@ +# Testing and Evaluation + +Use this when writing tests, mocks, eval datasets, automated quality checks, or trajectory assertions. + +## Testing Helpers + +Import from `agentflow.qa.testing` or `agentflow.qa`. + +Key helpers: + +- `TestAgent`: model-free agent double that returns predefined responses and can simulate tool calls. +- `QuickTest`: quick single-turn, multi-turn, tool, and custom graph tests. +- `TestResult`: fluent assertions for output text, tool calls, message counts, and errors. +- `TestContext`: context manager for creating graphs, agents, stores, and mocks. +- `MockToolRegistry`: register sync/async mock tools and assert calls. +- `MockMCPClient`: mock MCP tool listing/calls. +- `MockComposioAdapter` and `MockLangChainAdapter`: test third-party tool adapter behavior. +- `InMemoryStore`: deterministic memory store for tests. + +Use these instead of real model/provider calls in unit tests. + +## Evaluation Framework + +Import from `agentflow.qa.evaluation` or `agentflow.qa`. + +Core dataset/results types: + +- `EvalCase`, `EvalSet`, `EvalSetBuilder` +- `ToolCall`, `Invocation`, `TrajectoryStep`, `StepType` +- `EvalConfig`, `CriterionConfig`, `MatchType`, `Rubric` +- `EvalReport`, `EvalCaseResult`, `CriterionResult` + +Runner and shortcuts: + +- `AgentEvaluator` +- `QuickEval` +- `run_eval` +- `create_eval_app` +- `create_simple_eval_set` +- `eval_test` +- `parametrize_eval_cases` + +Criteria/reporters: + +- Tool, trajectory, node-order, response, exact-match, keyword, ROUGE, rubric, safety, hallucination, factual accuracy, LLM judge, and simulation criteria. +- Console, JSON, HTML, JUnit XML, and reporter manager outputs. + +## Trajectory Collection + +Use `TrajectoryCollector` plus `make_trajectory_callback` to record node/tool execution through the callback system. Compile the graph once with the callback manager and reuse it for eval runs. + +## Rules + +- Keep unit tests model-free with `TestAgent` and mocks. +- Use evals for behavior quality, trajectory matching, safety, and regression checks. +- Avoid mixing live providers into fast unit tests; isolate them as integration tests. +- For trajectory evals, compile once with the collector callback to avoid losing callback state. + +## Source Map + +- Testing package: `agentflow/agentflow/qa/testing` +- Evaluation package: `agentflow/agentflow/qa/evaluation` +- Public exports: `agentflow/agentflow/qa/__init__.py` +- Main docs: `agentflow-docs/docs/reference/python/testing.md` +- Main docs: `agentflow-docs/docs/reference/python/evaluation.md` +- Tutorial docs: `agentflow-docs/docs/tutorials/from-examples/testing.md` diff --git a/agentflow_cli/cli/templates/skills/copilot/agentflow.instructions.md b/agentflow_cli/cli/templates/skills/copilot/agentflow.instructions.md new file mode 100644 index 0000000..231c30e --- /dev/null +++ b/agentflow_cli/cli/templates/skills/copilot/agentflow.instructions.md @@ -0,0 +1,47 @@ +--- +applyTo: "**" +--- + +# Agentflow project instructions + +This repo uses **Agentflow** — a multi-agent framework that wraps the official OpenAI and Google SDKs behind a unified graph, agent, tool, state, storage, API, CLI, and TypeScript-client interface. + +When generating, refactoring, or debugging code in this repo, prefer Agentflow's own abstractions over hand-rolled equivalents. + +## Public package names (use these in user-facing examples) + +- Python core SDK: `10xscale-agentflow` — `pip install 10xscale-agentflow` — source under `agentflow/agentflow` +- Python API/CLI SDK: `10xscale-agentflow-cli` — `pip install 10xscale-agentflow-cli` — source under `agentflow-api/agentflow_cli` +- TypeScript SDK: `@10xscale/agentflow-client` — `npm install @10xscale/agentflow-client` — source under `agentflow-client/src` +- Docs: `agentflow-docs/docs` (treat as the source of truth for public API names) +- Playground: `agentflow play` (after the CLI is installed) + +Never use repository folder names (e.g. `agentflow-cli`) in install commands or user-facing docs — use the published package names above. + +## Core abstractions to reach for + +- Build workflows with `StateGraph`, `Agent`, `ToolNode`, `AgentState`, and `Message`. +- Persist conversation state with **checkpointers**. Use **stores** only for cross-thread memory. +- Inject business services through **`InjectQ`**, not module-level globals. +- Keep API/CLI graph modules storage-agnostic; wire dependencies via `agentflow.json`. +- Every persisted interaction must include `config.thread_id`. +- Tools need docstrings and type annotations so model-facing schemas are useful. +- Injectable parameters (`state`, `config`, `tool_call_id`) are hidden from the model schema. +- For production, avoid process-local storage for shared state — use durable checkpointer/store backends. + +## Where to look when you need more detail + +For deeper context on any subsystem, read the matching reference under `.github/skills/agentflow/references/` (if installed) or `agentflow-docs/docs`: + +- Architecture and package flow +- Agent and tool behavior, prebuilt agents +- Graph construction, state, messages, content blocks +- Threads, checkpointers, dependency injection +- Multimodal media, long-term memory stores +- Streaming, SSE, runtime publishers, A2A/ACP protocols +- API server, REST routes, auth, errors, settings, middleware +- TypeScript client: invoke, stream, threads, memory, files, A2UI + +## Verifying behavior + +Public names and behavior should match `agentflow-docs/docs`. Implementation under `agentflow/`, `agentflow-api/`, and `agentflow-client/src/` shows *how* — only consult source after docs establish *what*. diff --git a/pyproject.toml b/pyproject.toml index ea128e6..0e1c834 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -165,7 +165,7 @@ docstring-code-format = true [tool.bandit] exclude_dirs = ["*/tests/*", "*/agentflow-cli/tests/*"] -skips = ["B101", "B611", "B601", "B608"] +skips = ["B101", "B611", "B601"] [tool.ruff.lint.pydocstyle] diff --git a/tests/cli/test_skills.py b/tests/cli/test_skills.py new file mode 100644 index 0000000..70523bf --- /dev/null +++ b/tests/cli/test_skills.py @@ -0,0 +1,231 @@ +"""Tests for the `agentflow skills` command.""" + +from __future__ import annotations + +import json +import sys +from pathlib import Path +from unittest.mock import patch + +import pytest + +from agentflow_cli.cli.commands.skills import SkillsCommand +from agentflow_cli.cli.constants import CLI_VERSION +from agentflow_cli.cli.core.output import OutputFormatter + + +class _CapturingOutput(OutputFormatter): + """Output formatter that records messages instead of printing.""" + + def __init__(self) -> None: + super().__init__() + self.successes: list[str] = [] + self.errors: list[str] = [] + self.warnings: list[str] = [] + self.infos: list[str] = [] + self.tables: list[tuple[list[str], list[list[str]]]] = [] + self.lists: list[tuple[str | None, list[str]]] = [] + + def print_banner(self, *args, **kwargs) -> None: # type: ignore[override] + return + + def success(self, message: str, emoji: bool = True) -> None: # type: ignore[override] + self.successes.append(message) + + def error(self, message: str, emoji: bool = True) -> None: # type: ignore[override] + self.errors.append(message) + + def warning(self, message: str, emoji: bool = True) -> None: # type: ignore[override] + self.warnings.append(message) + + def info(self, message: str, emoji: bool = True) -> None: # type: ignore[override] + self.infos.append(message) + + def print_table(self, headers, rows, title=None) -> None: # type: ignore[override] + self.tables.append((headers, rows)) + + def print_list(self, items, title=None, bullet="-") -> None: # type: ignore[override] + self.lists.append((title, list(items))) + + +@pytest.fixture +def out() -> _CapturingOutput: + return _CapturingOutput() + + +@pytest.fixture +def cmd(out: _CapturingOutput) -> SkillsCommand: + return SkillsCommand(output=out) + + +# --- agent normalisation ------------------------------------------------- + + +def test_list_flag_prints_all_three_agents(cmd: SkillsCommand, out: _CapturingOutput) -> None: + exit_code = cmd.execute(list_agents=True) + assert exit_code == 0 + assert out.tables, "expected --list to print a table" + headers, rows = out.tables[0] + assert "Agent" in headers + names = {row[0] for row in rows} + assert names == {"Codex", "Claude", "GitHub"} + + +def test_invalid_agent_name_is_rejected(cmd: SkillsCommand, out: _CapturingOutput) -> None: + exit_code = cmd.execute(agent="not-a-real-agent", path=".") + assert exit_code != 0 + assert any("Invalid agent" in e for e in out.errors) + + +def test_all_with_explicit_agent_is_rejected( + cmd: SkillsCommand, out: _CapturingOutput, tmp_path: Path +) -> None: + exit_code = cmd.execute(agent="claude", all_agents=True, path=str(tmp_path)) + assert exit_code != 0 + assert any("--all cannot be combined with --agent" in e for e in out.errors) + + +# --- single-agent install ------------------------------------------------- + + +def test_install_claude_creates_folder_and_manifest( + cmd: SkillsCommand, tmp_path: Path +) -> None: + exit_code = cmd.execute(agent="claude", path=str(tmp_path)) + assert exit_code == 0 + + skill_dir = tmp_path / ".claude" / "skills" / "agentflow" + assert (skill_dir / "SKILL.md").is_file() + assert (skill_dir / "references").is_dir() + + manifest = json.loads((skill_dir / ".agentflow-skill.json").read_text(encoding="utf-8")) + assert manifest["agent"] == "Claude" + assert manifest["cli_version"] == CLI_VERSION + assert "installed_at" in manifest + + +def test_install_codex_uses_agents_dotdir(cmd: SkillsCommand, tmp_path: Path) -> None: + exit_code = cmd.execute(agent="codex", path=str(tmp_path)) + assert exit_code == 0 + assert (tmp_path / ".agents" / "skills" / "agentflow" / "SKILL.md").is_file() + # Earlier wrong paths must NOT be created + assert not (tmp_path / ".agent").exists() + assert not (tmp_path / ".codex").exists() + + +def test_install_github_writes_copilot_instructions_file( + cmd: SkillsCommand, tmp_path: Path +) -> None: + exit_code = cmd.execute(agent="github", path=str(tmp_path)) + assert exit_code == 0 + + instructions = tmp_path / ".github" / "instructions" / "agentflow.instructions.md" + assert instructions.is_file() + content = instructions.read_text(encoding="utf-8") + # Copilot frontmatter required for the file to be picked up + assert content.startswith("---\napplyTo:") + # GitHub install does NOT create the old skills folder + assert not (tmp_path / ".github" / "skills").exists() + + +def test_install_existing_dir_without_force_fails( + cmd: SkillsCommand, out: _CapturingOutput, tmp_path: Path +) -> None: + assert cmd.execute(agent="claude", path=str(tmp_path)) == 0 + out.errors.clear() + exit_code = cmd.execute(agent="claude", path=str(tmp_path)) + assert exit_code != 0 + assert any("already installed" in e for e in out.errors) + + +def test_install_force_overwrites_existing(cmd: SkillsCommand, tmp_path: Path) -> None: + skill_dir = tmp_path / ".claude" / "skills" / "agentflow" + cmd.execute(agent="claude", path=str(tmp_path)) + # mutate the install so we can detect overwrite + sentinel = skill_dir / "SENTINEL.txt" + sentinel.write_text("user-local content", encoding="utf-8") + + exit_code = cmd.execute(agent="claude", path=str(tmp_path), force=True) + assert exit_code == 0 + assert not sentinel.exists(), "force install should remove old contents" + assert (skill_dir / "SKILL.md").is_file() + + +def test_force_overwrites_copilot_file(cmd: SkillsCommand, tmp_path: Path) -> None: + instructions = tmp_path / ".github" / "instructions" / "agentflow.instructions.md" + cmd.execute(agent="github", path=str(tmp_path)) + instructions.write_text("user-edited", encoding="utf-8") + + exit_code = cmd.execute(agent="github", path=str(tmp_path), force=True) + assert exit_code == 0 + assert instructions.read_text(encoding="utf-8").startswith("---\napplyTo:") + + +# --- --all flow ----------------------------------------------------------- + + +def test_all_installs_every_agent(cmd: SkillsCommand, tmp_path: Path) -> None: + exit_code = cmd.execute(all_agents=True, path=str(tmp_path)) + assert exit_code == 0 + + assert (tmp_path / ".agents" / "skills" / "agentflow" / "SKILL.md").is_file() + assert (tmp_path / ".claude" / "skills" / "agentflow" / "SKILL.md").is_file() + assert (tmp_path / ".github" / "instructions" / "agentflow.instructions.md").is_file() + + +def test_all_skips_existing_without_force( + cmd: SkillsCommand, out: _CapturingOutput, tmp_path: Path +) -> None: + cmd.execute(agent="claude", path=str(tmp_path)) + out.successes.clear() + + exit_code = cmd.execute(all_agents=True, path=str(tmp_path)) + assert exit_code == 0 + # Codex and GitHub were installed, Claude was skipped + installed = " ".join(out.successes) + assert "Codex" in installed + assert "GitHub" in installed + assert "Claude" not in installed + assert any("Skipped existing" in w and "Claude" in w for w in out.warnings) + + +def test_all_with_force_reinstalls_everything(cmd: SkillsCommand, tmp_path: Path) -> None: + cmd.execute(all_agents=True, path=str(tmp_path)) + sentinel = tmp_path / ".claude" / "skills" / "agentflow" / "SENTINEL.txt" + sentinel.write_text("x", encoding="utf-8") + + exit_code = cmd.execute(all_agents=True, path=str(tmp_path), force=True) + assert exit_code == 0 + assert not sentinel.exists() + + +# --- path safety ---------------------------------------------------------- + + +def test_install_at_filesystem_root_is_refused( + cmd: SkillsCommand, out: _CapturingOutput +) -> None: + root = Path(Path.cwd().anchor) if Path.cwd().anchor else Path("/") + exit_code = cmd.execute(agent="claude", path=str(root)) + assert exit_code != 0 + assert any("filesystem root" in e for e in out.errors) + + +def test_install_at_home_dir_is_refused( + cmd: SkillsCommand, out: _CapturingOutput +) -> None: + exit_code = cmd.execute(agent="claude", path=str(Path.home())) + assert exit_code != 0 + assert any("home directory" in e for e in out.errors) + + +# --- non-interactive guard ------------------------------------------------ + + +def test_no_agent_with_non_tty_stdin_errors( + cmd: SkillsCommand, out: _CapturingOutput, tmp_path: Path +) -> None: + with patch.object(sys.stdin, "isatty", return_value=False): + exit_code = cmd.execute(path=str(tmp_path)) + assert exit_code != 0 + assert any("stdin is not interactive" in e for e in out.errors)