# Kodosumi Documentation - Complete Version This file contains the complete Kodosumi documentation for LLM consumption. Generated on: 2026-07-10T23:48:55.707Z Website: https://docs.kodosumi.io ## About Kodosumi Kodosumi is a runtime for managing and executing agentic services at scale. ## How to Access Individual Pages as Markdown **This documentation is fully LLM-enabled!** Each page is available in markdown format. ### URL Pattern: ``` https://docs.kodosumi.io/.md ``` ### Examples: - https://docs.kodosumi.io/guides.md - https://docs.kodosumi.io/guides/develop.md - https://docs.kodosumi.io/api-reference.md ### Markdown Index: For a complete list of all available markdown pages, visit: - https://docs.kodosumi.io/md-index - https://docs.kodosumi.io/md-index.md ### Benefits: - Clean markdown without HTML/JSX - CORS-enabled for API access - No authentication required - Bot-friendly --- ## Complete Documentation Below # kodosumi panel API URL: /api-reference *** ## title: kodosumi panel API The purpose of this document is to demonstrate the interaction with kodosumi panel API. To run the example requests below ensure you have an agentic service `Hymn Creator` up and running on your *localhost*. See [README](../README.md) on installation, setup and deployment of Ray, kodosumi and the *Hymn Creator* agentic service. ## Authentication Use the `/login` or `/api/login` endpoint to authenticate, retrieve an API key and a set of cookies for further API interaction. The default username and password is *admin* and *admin*. Endpoint `/login` authenticates with `GET` plus URL parameters. `POST` authenticates with URL-encoded form data (`application/x-www-form-urlencoded`). The API endpoint `/api/login` authenticates with `POST` and a JSON body (`application/json`). ```python import httpx resp = httpx.post( "http://localhost:3370/login", data={ "name": "admin", "password": "admin" } ) api_key = resp.json().get("KODOSUMI_API_KEY") cookies = resp.cookies ``` The corresponding request to `/api/login` is ```python httpx.post( "http://localhost:3370/api/login", json={ "name": "admin", "password": "admin" } ) ``` ## Flow Control Use the `api_key` or `cookies` with further requests. The following example retrieves the list of flows using `api_key`. ```python resp = httpx.get( "http://localhost:3370/flow", headers={"KODOSUMI_API_KEY": api_key}) resp.json() ``` The response is the first page of an offset paginated list of flows. ```python {'items': [{'author': 'm.rau@house-of-communication.com', 'deprecated': None, 'description': 'This agent creates a short hymn about a given ' 'topic of your choice using openai and crewai.', 'method': 'GET', 'organization': None, 'source': 'http://localhost:8001/hymn/openapi.json', 'summary': 'Hymn Creator', 'tags': ['CrewAi', 'Test'], 'uid': '48ff6c11855aceed7f16ab190328c53c', 'url': '/-/localhost/8001/hymn/-/'}], 'offset': None} ``` You can also simply use the `cookies`. This demo uses this approach. ```python resp = httpx.get("http://localhost:3370/flow", cookies=cookies) resp.json() ``` ## Retrieve Inputs Scheme Retrieve *Hymn Creator* input schema at [`GET /-/localhost/8001/hymn/-`](http://localhost:3370/-/localhost/8001/hymn/-) and launch flow execution with `POST /-/localhost/8001/hymn/-` and appropriate *inputs* data. ```python resp = httpx.get( "http://localhost:3370/-/localhost/8001/hymn/-", cookies=cookies) resp.json() ``` The response contains the *openapi.json* fields for *summary* (title), *description* and *tags*. Some extra fields are in `openapi_extra`. Key `elements` delivers the list of *inputs* element. ```python { 'summary': 'Hymn Creator', 'description': 'This agent creates a short hymn about a given topic...', 'tags': ['Test', 'CrewAi'], 'openapi_extra': { 'x-kodosumi': True, 'x-author': 'm.rau@house-of-communication.com', 'x-version': '1.0.1' }, 'elements': [ { 'type': 'markdown', 'text': '# Hymn Creator\nThis agent creates a short hymn... '}, { 'type': 'html', 'text': '
' }, { 'type': 'text', 'name': 'topic', 'label': 'Topic', 'value': 'A Du Du Du and A Da Da Da.', 'required': False, 'placeholder': None, 'size': None, 'pattern': None }, { 'type': 'submit', 'text': 'Submit' }, { 'type': 'cancel', 'text': 'Cancel' } ] } ``` ## Launch kodosumi rendering engine translates all inputs `elements` into a form to post and trigger flow execution at [http://localhost:3370/inputs/-/localhost/8001/hymn/-/](http://localhost:3370/inputs/-/localhost/8001/hymn/-/). [![Hymn](/assets/panel/thumb/form.png)](/assets/panel/form.png) To directly `POST` follow the *inputs* scheme as in example: ```python resp = httpx.post( "http://localhost:3370/-/localhost/8001/hymn/-/", cookies=cookies, json={ "topic": "Ich wollte ich wäre ein Huhn." } ) ``` In case of success the result contains the `fid` (flow identifier). Use this `fid` for further requests. ```python fid = resp.json().get("result") ``` ## Error Handling In case of failure the result is empty. The response has `errors` as a key/value pair with error information. ```python resp = httpx.post( "http://localhost:3370/-/localhost/8001/hymn/-/", cookies=cookies, json={"topic": ""}) # not accepted ! assert resp.status_code == 200 assert resp.json().get("result") is None ``` Example error output on *empty* `topic`: ```python { 'errors': { 'topic': ['Please give me a topic.'], '_global_': [] }, elements: ... } ``` ## Execution Control Request and poll for status updates at `/outputs/status`. ```python resp = httpx.get( f"http://localhost:3370/outputs/status/{fid}", cookies=cookies) resp.json() ``` The result after *starting* but some time before *finish* looks similar to: ```python { 'status': 'running', 'timestamp': 1747813976.091786, 'final': None, 'fid': '682d86536dd659324a5c8901', 'summary': 'Hymn Creator', 'description': 'This agent creates a short hymn about a given topic...', 'tags': ['Test', 'CrewAi'], 'deprecated': None, 'author': 'm.rau@house-of-communication.com', 'organization': None, 'version': '1.0.1', 'kodosumi_version': None, 'base_url': '/-/localhost/8001/hymn/-/', 'entry_point': 'hymn.app:crew', 'username': '35a04fc4-4442-4b24-b109-614b45d52de1' } ``` After completion the status request contains the final result in the `final` field. **Note:** The `final` field is populated for both successful completions (`status == "finished"`) and errors (`status == "error"`). For errors, it contains the serialized error message as `Text`. ```python { 'status': 'finished', 'timestamp': 1747813996.8025322, 'final': '{"CrewOutput":{"raw":"**Hymn Title: \\"Ich wollte ich wäre ein...', 'fid': '682d86536dd659324a5c8901', 'summary': 'Hymn Creator', 'description': 'This agent creates a short hymn about a given topic...', 'tags': ['Test', 'CrewAi'], 'deprecated': None, 'author': 'm.rau@house-of-communication.com', 'organization': None, 'version': '1.0.1', 'kodosumi_version': None, 'base_url': '/-/localhost/8001/hymn/-/', 'entry_point': 'hymn.app:crew', 'username': '35a04fc4-4442-4b24-b109-614b45d52de1' ``` ### Output formats The `final` field is a JSON-encoded string. Its content depends on the return type of the agentic service. The outer key identifies the output type. Below are examples showing what the decoded `final` value looks like for each supported output type. **Link output** — when the service returns a `Link` pointing to an external URL: ```python # Decoded value of final field { "Link": { "url": "https://example.com/result", "title": "Result Page" } } ``` **Image output** — when the service returns an `Image` resource: ```python # Decoded value of final field { "Image": { "url": "https://example.com/output.png", "alt": "Generated image" } } ``` **Video output** — when the service returns a `Video` resource: ```python # Decoded value of final field { "Video": { "url": "https://example.com/output.mp4" } } ``` As a reminder, the `Text` type appears for plain-text results and for serialized error messages (when `status == "error"`): ```python # Decoded value of final field (Text or error) { "Text": { "body": "Processing completed successfully." } } ``` Since the plain `/status` request might fail due to Ray latencies you should harden the intial request past flow launch with `?extended=true` as in the following example: ```python resp = httpx.get( f"http://localhost:3370/outputs/status/{fid}?extended=true", cookies=cookies) resp.json() ``` The complete event stream is available at `/outputs/stream`. ```python with httpx.stream("GET", f"http://localhost:3370/outputs/stream/{fid}", cookies=cookies) as r: for text in r.iter_text(): print(text) ``` ## Runtime Error Handling During flow execution, errors may occur that raise `kodosumi.error.KodosumiError`. **Important:** Only `kodosumi.error.KodosumiError` exceptions are handled and exposed through the API. All other exceptions are silently ignored and will not appear in the status or results endpoints. When a `kodosumi.error.KodosumiError` is raised, the flow status changes to `"error"` and the error message is made available through the status endpoint's `final` field (as serialized `Text`) as well as the results endpoints. ### Error Status When a flow execution encounters a `kodosumi.error.KodosumiError`, the status endpoint returns `status == "error"`: ```python resp = httpx.get( f"http://localhost:3370/outputs/status/{fid}", cookies=cookies) result = resp.json() assert result["status"] == "error" ``` Example response when an error occurs: ```python { 'status': 'error', 'timestamp': 1747813996.8025322, 'final': '{"Text":{"body":"Error message here"}}', 'fid': '682d86536dd659324a5c8901', 'summary': 'Hymn Creator', 'description': 'This agent creates a short hymn about a given topic...', 'tags': ['Test', 'CrewAi'], 'deprecated': None, 'author': 'm.rau@house-of-communication.com', 'organization': None, 'version': '1.0.1', 'kodosumi_version': None, 'base_url': '/-/localhost/8001/hymn/-/', 'entry_point': 'hymn.app:crew', 'username': '35a04fc4-4442-4b24-b109-614b45d52de1' } ``` ### Error Message Retrieval The error message is available through the results endpoints `GET /outputs/raw/{fid}` and `GET /outputs/html/{fid}`. The error message is returned as `Text` (not Markdown): ```python # Get raw error message resp = httpx.get( f"http://localhost:3370/outputs/raw/{fid}", cookies=cookies) error_message = resp.text # Text formatted error message # Get HTML rendered error message resp = httpx.get( f"http://localhost:3370/outputs/html/{fid}", cookies=cookies) error_html = resp.text # HTML rendered error message ``` The error message contains the exception details from `kodosumi.error.KodosumiError` and is formatted as plain text (`dtypes.Text`), making it suitable for display in user interfaces. # koco CLI URL: /cli *** title: koco CLI icon: Terminal -------------- The Kodosumi CLI (`koco`) provides various commands for managing and running Kodosumi services. ## Overview ```bash koco --version # Shows version koco --help # Shows help for all commands ``` ## Commands ### spool Manages the Spooler service, which is responsible for processing flows. ```bash koco spool [OPTIONS] ``` **Options:** * `--ray-server TEXT` - Ray Server URL * `--log-file TEXT` - Spooler log file path * `--log-file-level [DEBUG|INFO|WARNING|ERROR|CRITICAL]` - Spooler log file level * `--level [DEBUG|INFO|WARNING|ERROR|CRITICAL]` - Screen log level * `--exec-dir TEXT` - Execution directory * `--interval FLOAT` - Spooler polling interval (min: 0) * `--batch-size INTEGER` - Batch size for spooling (min: 1) * `--timeout FLOAT` - Batch retrieval timeout (min: 0) * `--start/--stop` - Start/stop spooler (default: start) * `--block` - Run spooler in foreground (blocking mode) * `--status` - Check if spooler is connected and running **Examples:** ```bash # Start spooler koco spool # Check spooler status koco spool --status # Run spooler in foreground with custom configuration koco spool --block --interval 5.0 --batch-size 10 ``` ### serve Starts the Kodosumi panel API and application. ```bash koco serve [OPTIONS] ``` **Options:** * `--address TEXT` - App server URL * `--log-file TEXT` - App server log file path * `--log-file-level [DEBUG|INFO|WARNING|ERROR|CRITICAL]` - App server log file level * `--level [DEBUG|INFO|WARNING|ERROR|CRITICAL]` - Screen log level * `--uvicorn-level [DEBUG|INFO|WARNING|ERROR|CRITICAL]` - Uvicorn log level * `--exec-dir TEXT` - Execution directory * `--reload` - Reload app server on file changes * `--register TEXT` - Register endpoints (can be used multiple times) **Examples:** ```bash # Start server koco serve # Start server with reload and custom address koco serve --reload --address 0.0.0.0:8000 # Start server with flow registration koco serve --register flow1 --register flow2 ``` ### start Starts both the spooler and app server in a single execution. ```bash koco start [OPTIONS] ``` **Options:** * `--exec-dir TEXT` - Execution directory * `--register TEXT` - Register endpoints (can be used multiple times) **Examples:** ```bash # Start complete service koco start # With custom execution directory koco start --exec-dir /path/to/flows ``` ### deploy Manages deployments of Kodosumi configurations. ```bash koco deploy [OPTIONS] ``` **Options:** * `-f, --file TEXT` - Configuration YAML file * `-r, --run` - Execute deployment * `-d, --dry-run` - Test deployment configuration * `-x, --shutdown, --stop` - Shutdown service * `-s, --status` - Show service status * `-j, --json` - Format output as JSON **Examples:** ```bash # Check deployment status koco deploy --status # Test deployment configuration koco deploy --dry-run -f config.yaml # Execute deployment koco deploy --run -f config.yaml # Shutdown service koco deploy --shutdown # Output status as JSON koco deploy --status --json ``` ## Configuration The CLI commands use the same configuration options as the Kodosumi application. These can be set via environment variables or command line parameters. ### Important environment variables: * `RAY_SERVER` - Ray Server URL * `EXEC_DIR` - Execution directory * `SPOOLER_INTERVAL` - Spooler polling interval * `SPOOLER_BATCH_SIZE` - Batch size * `APP_SERVER` - App server address * `APP_RELOAD` - Enable reload mode ## Logging All commands support configurable logging levels: * `DEBUG` - Detailed debug information * `INFO` - General information * `WARNING` - Warnings * `ERROR` - Errors * `CRITICAL` - Critical errors Logs can be output both to screen and to files. # Concepts URL: /guides/concepts *** title: Concepts icon: Layers ------------ This document provides an overview of the key concepts that constitute the Kodosumi framework. ### Flow A *Flow* represents an automated process, workflow, or system of interconnected tasks working towards a common objective. While similar concepts exist in different contexts (such as *[agents](#agents)*, *workflows*, *objects*, *methods*, *functions*, *models*, or *[agentic services](#agentic-service)*), we use the term *Flow* to emphasize its process-oriented nature. ### Agentic Service An Agentic Service is a self-contained, deployable unit within the Kodosumi framework. It integrates one or more [Flows](#flows) with required resources and configurations to deliver complete functionality. An agentic service comprises [endpoints](#endpoint) and [entrypoints](#entrypoint) into [flows](#flows). Service [endpoints](#endpoint) implement request/response patterns, while [flows](#flows) implement business logic and return intermediate and final results among other events. The [entrypoint](#entrypoint) is a Python object or callable to enter flow execution. ### Agent An Agent is an autonomous object within the Kodosumi framework that can perform specific tasks or services. Agents can interact with other agents, process data, and execute complex workflows. Since the term *Agent* is overloaded, so we prefer to use *Flow* to represent automation and workflows. ### Panel The Panel serves as Kodosumi's administrative interface. It enables [flow execution](#flow-execution) management in the Ray cluster, including launching, monitoring, and reviewing flow events and results. ## Ray Concepts ### Ray Head The Ray Head is the central coordinator of the Ray cluster. It manages cluster resources, handles task scheduling, and maintains the cluster's state. The Ray Head is responsible for coordinating communication between Ray workers and managing the overall cluster health. **See also:** * [Get Started with Ray](https://docs.ray.io/en/latest/ray-overview/getting-started.html) * [Ray Key Concepts](https://docs.ray.io/en/latest/ray-core/key-concepts.html) * [Ray Serve](https://docs.ray.io/en/latest/serve/getting_started.html) ### Ray Worker Ray Workers are the execution nodes in the Ray cluster. They perform the actual computation of tasks assigned by the Ray Head. Workers can be dynamically scaled up or down based on workload demands. **See also:** * [Ray Actors, Workers and Resources](https://docs.ray.io/en/latest/ray-core/actors.html#faq-actors-workers-and-resources) ### Ray Cluster The Ray Cluster is the distributed computing backbone of Kodosumi. It provides the infrastructure for parallel and distributed execution of tasks, enabling scalable and fault-tolerant service deployment. ### Ray Driver The Ray Driver is the process that runs the main program in a Ray cluster. It is the entry point for Ray applications and is responsible for submitting tasks to the cluster. The Driver connects to the Ray cluster and coordinates the execution of distributed computations. **See also:** * [Ray Driver](https://docs.ray.io/en/latest/ray-core/key-concepts.html#ray-driver) ## Flow Execution ### Flow Register Flow Registers are the source locations where Flow [endpoints](#endpoint) are registered in the system. They maintain a catalog of available Flow sources, their endpoints, and their [entrypoints](#entrypoint), enabling the discovery and routing of Flow operations. ### Endpoint Endpoints are the defined interfaces of an Agentic Service that expose its functionality externally. They implement the Request/Response pattern and enable service interaction through standardized protocols. ### Entrypoint Entrypoints are the internal entry locations within an Agentic Service where Flows can be initiated or connected. They serve as the bridge between [Endpoints](#endpoint) and [Flow implementations](#flows), defining how external requests are transformed into Flow executions. ### Input Models Input Models define the structure and validation rules for user inputs. They support various input types including text fields, checkboxes, file uploads, and custom components. Inputs Models are used both for initial service entry points and for lock interfaces. ### Serve API Serve API is Kodosumi's specialized wrapper around FastAPI, providing enhanced functionality for building HTTP [endpoints](#endpoint) for [agentic services](#agentic-service). It integrates seamlessly with Ray Serve and offers standardized patterns for API development. ### Runner The Runner is a detached Ray actor created by the Panel to manage service execution. Once launched, it operates autonomously within the [Ray cluster](#ray-cluster), handling the complete service lifecycle. The Runner interacts with the [Spooler](#spooler) to manage and persist the [Event Stream](#event-stream). ### Spooler The Spooler is a specialized component in Kodosumi that handles [event stream](#event-stream) persistence. It processes events from [Flows](#flows) and ensures their reliable storage in the [event stream](#event-stream). The Spooler maintains the event history and makes it accessible throughout the Kodosumi API. ### Event Stream The Event Stream is a real-time communication channel that enables asynchronous event-driven interactions between different components of the Kodosumi system. The Event Stream captures various types of events during flow execution, see [Events](lifecycle#events) ## Human-in-the-Loop (HTIL) System ### Locks Locks are mechanisms that pause flow execution and request human intervention. When a flow encounters a lock, it enters the `awaiting` status and waits for human input before continuing. Locks are essential for implementing approval workflows, content reviews, and other scenarios requiring human decision-making. ### Leases Leases are the handlers that process human input and release locks. When a human submits input through a lock interface, the lease handler validates the input and returns data that allows the flow to continue execution. Leases can include validation logic and error handling. ### Lock Timeouts Locks have configurable timeouts to prevent indefinite waiting. If a lock expires without human intervention, the flow execution fails with a timeout error. The default timeout is 3 hours, but this can be customized per lock. # Configuration URL: /guides/config *** title: Configuration icon: Settings -------------- This document describes all available configuration settings for Kodosumi. Settings can be configured through environment variables, a `.env` file, or command-line arguments. ## Environment Variables All configuration settings can be set using environment variables with the prefix `KODO_`. For example, to set the execution directory, use `KODO_EXEC_DIR`. These environment variables can be overridden using the `koco` command-line tool. ## Configuration Settings ### Execution Settings * `EXEC_DIR` (default: `"./data/execution"`) * Directory where execution files are stored * Will be created automatically if it doesn't exist * `koco` option `--exec-dir` (available in all commands) * Can override `KODO_EXEC_DIR` in all commands ### Spooler Settings * `SPOOLER_LOG_FILE` (default: `"./data/spooler.log"`) * Path to the spooler log file * Parent directory will be created automatically * `koco` option `--log-file` (spool command) * Can override `KODO_SPOOLER_LOG_FILE` in spool command * `SPOOLER_LOG_FILE_LEVEL` (default: `"DEBUG"`) * Log level for the spooler log file * Available levels: `DEBUG`, `INFO`, `WARNING`, `ERROR`, `CRITICAL` * `koco` option `--log-file-level` (spool command) * Can override `KODO_SPOOLER_LOG_FILE_LEVEL` in spool command * `SPOOLER_STD_LEVEL` (default: `"INFO"`) * Log level for spooler console output * Available levels: `DEBUG`, `INFO`, `WARNING`, `ERROR`, `CRITICAL` * `koco` option `--level` (spool command) * Can override `KODO_SPOOLER_STD_LEVEL` in spool command * `SPOOLER_INTERVAL` (default: `0.25`) * Polling interval for the spooler in seconds * Must be greater than 0 * `koco` option `--interval` (spool command) * Can override `KODO_SPOOLER_INTERVAL` in spool command * `SPOOLER_BATCH_SIZE` (default: `10`) * Number of items to process in each batch * Must be at least 1 * `koco` option `--batch-size` (spool command) * Can override `KODO_SPOOLER_BATCH_SIZE` in spool command * `SPOOLER_BATCH_TIMEOUT` (default: `0.1`) * Timeout for batch retrieval in seconds * Must be greater than 0 * `koco` option `--timeout` (spool command) * Can override `KODO_SPOOLER_BATCH_TIMEOUT` in spool command ### Ray Settings * `RAY_SERVER` (default: `"localhost:6379"`) * Ray server URL * `koco` option `--ray-server` (spool command) * Can override `KODO_RAY_SERVER` in spool command * `RAY_DASHBOARD` (default: `"http://localhost:8265"`) * Ray dashboard URL ### Application Server Settings * `APP_SERVER` (default: `"http://localhost:3370"`) * Application server URL * `koco` option `--address` (serve command) * Can override `KODO_APP_SERVER` in serve command * `APP_LOG_FILE` (default: `"./data/app.log"`) * Path to the application log file * `koco` option `--log-file` (serve command) * Can override `KODO_APP_LOG_FILE` in serve command * `APP_LOG_FILE_LEVEL` (default: `"DEBUG"`) * Log level for the application log file * Available levels: `DEBUG`, `INFO`, `WARNING`, `ERROR`, `CRITICAL` * `koco` option `--log-file-level` (serve command) * Can override `KODO_APP_LOG_FILE_LEVEL` in serve command * `APP_STD_LEVEL` (default: `"INFO"`) * Log level for application console output * Available levels: `DEBUG`, `INFO`, `WARNING`, `ERROR`, `CRITICAL` * `koco` option `--level` (serve command) * Can override `KODO_APP_STD_LEVEL` in serve command * `APP_RELOAD` (default: `False`) * Enable auto-reload on file changes * `koco` option `--reload` (serve command) * Can override `KODO_APP_RELOAD` in serve command * `UVICORN_LEVEL` (default: `"WARNING"`) * Log level for Uvicorn server * Available levels: `DEBUG`, `INFO`, `WARNING`, `ERROR`, `CRITICAL` * `koco` option `--uvicorn-level` (serve command) * Can override `KODO_UVICORN_LEVEL` in serve command ### Security Settings * `SECRET_KEY` * Secret key for the application * Should be changed in production * `CORS_ORIGINS` (default: `["*"]`) * List of allowed CORS origins * Can be set as comma-separated string ### Database Settings * `ADMIN_DATABASE` (default: `"sqlite+aiosqlite:///./data/admin.db"`) * Database URL for admin database * `ADMIN_EMAIL` * Admin user email * Should be changed in production * `ADMIN_PASSWORD` (default: `"admin"`) * Admin user password * Should be changed in production ### Other Settings * `WAIT_FOR_JOB` (default: `600`) * Maximum wait time for jobs in seconds * `PROXY_TIMEOUT` (default: `30`) * Proxy timeout in seconds # Analytics Dashboard URL: /guides/dashboard *** title: Analytics Dashboard icon: BarChart3 --------------- The Analytics Dashboard provides comprehensive insights into agent executions, errors, and system performance across your Kodosumi deployment. ## Accessing the Dashboard After starting Kodosumi with `koco start`, navigate to the admin panel at [http://localhost:3370](http://localhost:3370) and click **Analytics** in the sidebar navigation. The dashboard is accessible at: **[http://localhost:3370/admin/dashboard](http://localhost:3370/admin/dashboard)** ## Dashboard Tabs ### Running Agents The Running Agents tab displays all active and historical agent executions in a comprehensive table: **Columns:** * **Agent Name** - Name of the executing agent * **Execution ID** - Unique identifier (fid) * **User** - User who initiated the execution * **Status** - Current state with color-coded badges: * 🟢 **finished** - Completed successfully * 🔵 **running** - Currently executing * 🟡 **awaiting** - Waiting for input/approval * 🔴 **error** - Failed with error * ⚪ **starting** - Initializing * **Started** - When execution began * **Runtime** - Duration of execution * **Files (Up/Down)** - Upload/download file counts (clickable) * **Actions** - Link to detailed execution view **Filtering Options:** The dashboard provides powerful filtering capabilities: * **Global Search** - Free-text search across all fields * **Agent Name** - Filter by specific agent * **User** - Filter by user * **Status** - Filter by execution status * **Time Range** - Select time window: * Last 1 hour * Last 6 hours * Last 24 hours (default) * Last 7 days * Last 30 days * All time The statistics boxes at the top show **filtered / total** counts, making it easy to see how many executions match your current filters. ### File Access Click on the upload/download counts in the Files column to open a modal showing: * List of uploaded files (in `in/` directory) * List of downloaded files (in `out/` directory) * File names, sizes, and download buttons * Full filename shown on hover for truncated names ### Errors The Errors tab aggregates all execution errors: * Error messages and stack traces * Execution context (fid, user, timestamp) * Time-based filtering * Visual error cards for easy scanning ### Timeline Interactive D3.js visualization showing execution history: * Hourly aggregation of executions * Visual distinction between running and finished states * Error highlighting * Hover for detailed counts ### Statistics Detailed analytics with pie charts: * **Status Distribution** - Executions by status * **User Activity** - Executions per user **Summary Statistics:** * Total executions count * Currently running agents * Error rate percentage * Average runtime ## API Endpoints The dashboard exposes REST API endpoints for programmatic access: ```bash # Get running agents with filters GET /api/dashboard/running-agents?hours=24&status=finished&search=keyword # Get recent errors GET /api/dashboard/errors?hours=24 # Get timeline data GET /api/dashboard/timeline?hours=168 # Get aggregate statistics GET /api/dashboard/agent-stats # Get execution details GET /api/dashboard/execution/{fid}/details # Get execution files GET /api/dashboard/execution/{fid}/files ``` ## Data Sources The dashboard aggregates data from multiple sources: ### Execution Databases Location: `./data/execution/{user_id}/{execution_id}/sqlite3.db` Each execution has its own SQLite database with a `monitor` table: | Column | Type | Description | | --------- | ----- | -------------------------- | | id | INT | Auto-increment primary key | | timestamp | FLOAT | Unix timestamp | | kind | TEXT | Event type | | message | TEXT | Event data | **Event Types (kind):** * `status` - Execution state (plain string: "running", "finished", "error", etc.) * `meta` - Agent metadata (JSON object) * `error` - Error details (plain text or JSON) * `inputs` - Input parameters * `result` - Execution results * `action` - Agent actions * `final` - Final output * `upload` - File upload events ### Admin Database Location: `./data/admin.db` Contains user information in the `roles` table, used to resolve user IDs to names. ### File System * Upload files: `./data/execution/{user_id}/{execution_id}/in/` * Download files: `./data/execution/{user_id}/{execution_id}/out/` ## Performance The dashboard is optimized for production use: * Parallel database scanning * Time-range filtering to limit queries * Error lists capped at 100 entries * Efficient user name resolution For large deployments with thousands of executions: * Use shorter time ranges (1-24 hours) * Apply specific filters before viewing all data * Consider implementing caching for statistics ## Technical Details **Backend:** * Framework: Litestar Controller * Database: aiosqlite (async SQLite) * Location: `kodosumi/service/dashboard.py` **Frontend:** * Vanilla JavaScript (ES6) * D3.js v7 for visualizations * Beer CSS for Material Design styling * Jinja2 templating * Location: `kodosumi/service/admin/templates/dashboard/` ## Troubleshooting ### Dashboard shows no data 1. Verify executions exist: ```bash ls -la ./data/execution/ ``` 2. Check database files: ```bash find ./data/execution -name "sqlite3.db" ``` 3. Verify API responses: ```bash curl http://localhost:3370/api/dashboard/running-agents ``` ### Performance issues * Reduce time range filter * Apply specific filters (agent, user, status) * Check database file sizes * Monitor server logs for slow queries ### Authentication errors Verify credentials in config or environment: * Default username: `admin` * Default password: `admin` # Deployment URL: /guides/deploy *** title: Deployment icon: Rocket ------------ import { Callout } from 'fumadocs-ui/components/callout'; ## Runtime environment setup You can reproduce this deployment guide on localhost. Nevertheless this guide is a blueprint for production. All environment variables, package sources, dependencies and other settings are declared with configuration *YAML* files. This is the preferred approach to production settings with Ray. See [Ray Production Guide](https://docs.ray.io/en/latest/serve/production-guide/index.html). As a consequence, this guide is a lot about managing YAML files. Kodosumi simplifies the management of these configuration files by splitting the configuration into a *base* and multiple *app* configurations. This deployment is based on the `company_news` service built in [development workflow](/develop). Let's start with creating a root directory to host our runtime environment, i.e. ```bash mkdir ~/kodosumi cd kodosumi ``` Create a Python Virtual Environment with ```bash python -m venv .venv # depends on OS setup source .venv/bin/activate ``` The location of your system's Python executable python might vary. Next, install Kodosumi from the [Python package index](https://pypi.org/) ```bash pip install kodosumi ``` if instead you prefer to install the latest development trunk run pip install "kodosumi @ git+[https://github.com/masumi-network/kodosumi.git@dev](https://github.com/masumi-network/kodosumi.git@dev)" Start your Ray cluster with ```bash ray start --head ``` In the previous examples you did run `koco start` which launches the Kodosumi *spooler* daemon ***PLUS*** the Kodosumi admin *panel* web app and API. In the current example we start the spooler and the panel seperately. We start with the spooler ```bash koco spool ``` This starts the spooler in the background and as a daemon. You can review daemon status with ```bash koco spool --status ``` Stop the spooler later with `koco spool --stop`. The spooler automatically creates directory `./data/config` to host Ray *serve* configuration files as specified with configuration parameter `YAML_BASE`. The *yaml* base defaults to `./data/config/config.yaml` and locates the base *relative* to the directory where you start `koco spool` and `koco serve`. Create file `./data/config/config.yaml` with Ray *serve* base configuration. The following yaml configuration is a good starting point. For further details read Ray's documentation about [Serve Config Files](https://docs.ray.io/en/latest/serve/production-guide/config.html#serve-in-production-config-file). ```yaml # ./data/config/config.yaml proxy_location: EveryNode http_options: host: 0.0.0.0 port: 8001 grpc_options: port: 9001 grpc_servicer_functions: [] logging_config: encoding: TEXT log_level: DEBUG logs_dir: null enable_access_log: true ``` We will deploy the *agentic-workflow-example* service as a package **`company_news`**. Create the first app configuration named `company_news.yaml` with the following content: ```yaml # ./data/config/company_news.yaml name: company_news route_prefix: /company_news import_path: company_news.query:fast_app runtime_env: py_modules: - https://github.com/plan-net/agentic-workflow-example/archive/45aabddf234cf8beb7118b400e7cb567776e458a.zip pip: - openai env_vars: OTEL_SDK_DISABLED: "true" OPENAI_API_KEY: <-- your-api-key --> ``` Test and deploy your configuration set with ```bash koco deploy --dry-run --file ./data/config/config.yaml koco deploy --run --file ./data/config/config.yaml ``` This will apply your *base* configuration from `./data/config/config.yaml` and adds a key `application` with records from `./data/config/company_news.yaml`. With running Ray, spooler and app we now start the Kodosumi panel and register Ray deployments ```bash koco serve --register http://localhost:8001/-/routes ``` See [Configure Ray Serve Deployments](https://docs.ray.io/en/latest/serve/configure-serve-deployment.html) for additional options on your deployment. Be advised to gather some experience with Ray core components before you rollout your services. Understand [remote resource requirements](https://docs.ray.io/en/latest/ray-core/scheduling/resources.html#resource-requirements) and how to [limit concurrency to avoid OOM issues](https://docs.ray.io/en/latest/ray-core/patterns/limit-running-tasks.html#pattern-using-resources-to-limit-the-number-of-concurrently-running-tasks) ## Deployment API The deployment API at /deploy and /serve is experimental. Use *kodosumi panel API* to change your Ray *serve* deployments at runtime. The panel API ships with a simple CRUD interfacce to create, read, update and delete deployment configurations including the *base configuration* with `config.yaml`. The following Python snippets demonstrates API usage with example service `kodosumi_examples.prime`. ```python import httpx from pprint import pprint # login resp = httpx.get("http://localhost:3370/login?name=admin&password=admin") cookies = resp.cookies # retrieve Ray serve deployments status resp = httpx.get("http://localhost:3370/deploy", cookies=cookies) pprint(resp.json()) ``` Let us first stop *Ray serve* and remove all existing deployments except the base configuration `config.yaml` before we deploy the `prime` service. ```python # retrieve active deployments scope = httpx.get("http://localhost:3370/deploy", cookies=cookies) for name in scope.json(): # remove deployment print(name) resp = httpx.delete(f"http://localhost:3370/deploy/{name}", cookies=cookies) assert resp.status_code == 204 # stop Ray serve resp = httpx.delete("http://localhost:3370/serve", cookies=cookies) assert resp.status_code == 204 ``` Verify *no deployments* with `GET /deploy` and an existing base configuration with `GET /deploy/config`. ```python # verify no deployments resp = httpx.get("http://localhost:3370/deploy", cookies=cookies) assert resp.json() == {} # verify base configuration resp = httpx.get("http://localhost:3370/deploy/config", cookies=cookies) print(resp.content.decode()) ``` This yields the content of the base configuration `./data/config/config.yaml`, for example ```yaml proxy_location: EveryNode http_options: host: 127.0.0.1 port: 8001 grpc_options: port: 9001 grpc_servicer_functions: [] logging_config: encoding: TEXT log_level: DEBUG logs_dir: null enable_access_log: true ``` If the base configuration does not exist and `GET /deploy/config` throws a `404 Not found` exception, then create it with for example ```python base = """ proxy_location: EveryNode http_options: host: 127.0.0.1 port: 8001 grpc_options: port: 9001 grpc_servicer_functions: [] logging_config: encoding: TEXT log_level: DEBUG logs_dir: null enable_access_log: true """ resp = httpx.post("http://localhost:3370/deploy/config", cookies=cookies, content=base) assert resp.status_code == 201 ``` Deploy the *prime* service with the corresponding *Ray serve* configuration. ```python prime = """ name: prime route_prefix: /prime import_path: kodosumi_examples.prime.app:fast_app runtime_env: py_modules: - https://github.com/masumi-network/kodosumi-examples/archive/2db907d955de65bed5dde6513f6359aeb18ebff1.zip deployments: - name: PrimeDistribution num_replicas: auto ray_actor_options: num_cpus: 0.1 """ resp = httpx.post("http://localhost:3370/deploy/prime", cookies=cookies, content=prime) assert resp.status_code == 201 ``` Verify the *to-be* deployment state of the prime service. ```python resp = httpx.get("http://localhost:3370/deploy", cookies=cookies) assert resp.status_code == 200 assert resp.json() == {'prime': 'to-deploy'} ``` To request Ray *serve* to enter this state `POST /serve` with ```python resp = httpx.post("http://localhost:3370/serve", cookies=cookies, timeout=30) assert resp.status_code == 201 ``` Watch the timeout because the response of *Ray serve* might take a while. # Development Workflow URL: /guides/develop *** title: Development Workflow icon: Code ---------- import { Callout } from 'fumadocs-ui/components/callout'; This guide provides a step-by-step guide to implement, publish, deploy and run your custom agentic service using Ray and Kodosumi. ## Background information We will implement an agent utilising [OpenAI](https://openai.com) to find news for a single or multiple companies operationalising the following LLM prompt: > Identify news from `{{start}}` to `{{end}}` about company **"`{{name}}`"**. Format > the output as a bullet point list in the following format: > > `* YYYY-mm-dd - [**Headline**](Link): Brief Summary of the news.` > > Only output the bullet point list about news in the specified date range. Do > not include any other text or additional information. If you cannot find any > news for the given date range then output the text *"no news found"*. ## Development workflow overview The development process with Kodosumi consists of two main work streams: 1. **Implementing the Entrypoint** The entrypoint serves as the foundation of your service, housing the core business logic. It acts as the central hub for distributed computing, where complex calculations or third party system requests are broken down and distributed across multiple processing units using Ray. This component is responsible for orchestrating parallel tasks and ensuring efficient resource utilization. 2. **Implementing the Endpoint** The endpoint establishes the HTTP interface for user interaction, providing a structured way to receive and process user input. It implements comprehensive input validation and manages the entire service lifecycle. This component is crucial for launching and monitoring the execution flow of your service. Together, these components form a robust architecture that enables the creation of scalable and efficient agentic services. The entrypoint handles the computational complexity, while the endpoint ensures reliable user interaction and service management. ## Step-by-step implementation guide We start implementing the service with the folder package structure and the build of the *query* function. ### 1. Create git remote Create a public repository to host your agentic service. Ensure you have write access. For this example we use the following repository URL: * [https://github.com/plan-net/agentic-workflow-example.git](https://github.com/plan-net/agentic-workflow-example.git) ### 2. Create Python Virtual Environment Create and source a Python Virtual Environment with your system Python executable. ```bash python3 -m venv .venv source .venv/bin/activate ``` You need to locate the Python system executable. Depending on your operating system and setup this location differs. ### 3. Clone the repository Clone the repository to your localhost: ```bash git clone https://github.com/plan-net/agentic-workflow-example.git cd agentic-workflow-example/ ``` ### 4. Setup project structure Create a new directory `./company_news` inside your local working directory `./agentic-workflow-example` to host your package. ```bash mkdir ./company_news ``` Use the flat package layout. The flat layout simplifies the deployment process as we will see later in deployment. In contrast to the src layout the the flat layout does not need any additional installation step to be importable by Python. Add the basic package structure and create the usual suspects along with `query.py` to deliver the agentic service. ```bash touch ./company_news/__init__.py touch ./.gitignore touch ./.env ``` Open `./.gitignore` and paste the following listing: ```text __pycache__/ data/* .env .venv ``` Open `./.env` and add your OpenAI api key: ```text OPENAI_API_KEY= ``` You need to have an [OpenAI API key](https://platform.openai.com/api-keys) to run this example. Push our initial commit: ```bash git add . git commit -m "initial version" git push ``` ### 5. Install Kodosumi Install Kodosumi from [PyPi](https://pypi.org/) ```bash pip install kodosumi ``` Or clone the latest `dev` trunk from [Kodosumi at GitHub](https://github.com/masumi-network/kodosumi) ```bash git clone https://github.com/masumi-network/kodosumi cd kodosumi git checkout dev pip install . cd .. rm -Rf kodosumi ``` ### 6. Start ray Start Ray on your localhost. Load `.env` into the environment variables before ```bash dotenv run -- ray start --head ``` ### 7. Implement and test `query` Implement the query function in `./company_news/query.py`. ```python import datetime from jinja2 import Template from openai import OpenAI def query(text: str, start: datetime.datetime, end: datetime.datetime) -> dict: template = Template(""" Identify news from {{start}} to {{end}} about company **"{{name}}"**. Format the output as a bullet point list in the following format: * YYYY-mm-dd - [**Headline**](Link): Brief Summary of the news. Only output the bullet point list about news in the specified date range. Do not include any other text or additional information. If you cannot find any news for the given date range then output the text "no news found". """) try: resp = chat( template.render( start=start.strftime("%Y-%m-%d"), end=end.strftime("%Y-%m-%d"), name=text) ) return {**resp, **{ "query": text, "start": start, "end": end, "error": None, }} except Exception as e: return { "query": text, "start": start, "end": end, "error": e } ``` This method uses `jinja2` templating system to build a prompt with parameters `text`, `start`, and `end` and forwards the request to `chat` function. Add the `chat` function at the end of `query.py`. ```python def chat(query: str, model="gpt-4o-mini"): t0 = datetime.datetime.now() client = OpenAI() response = client.responses.create( model=model, tools=[{"type": "web_search_preview"}], input=query ) runtime = datetime.datetime.now() - t0 return { "response": response.model_dump(), "output": response.output_text, "model": model, "runtime": runtime.total_seconds() } ``` At this stage and in `query.py` we use `jinja2` which has been installed with kodosumi and `openai` which needs to be installed first: ```bash pip install openai ``` Test the `query` function with a Python interactive interpreter ```python import datetime from company_news.query import query from dotenv import load_dotenv load_dotenv() result = query(text="Serviceplan", start=datetime.datetime(2024, 1, 1), end=datetime.datetime(2024, 12, 31)) print(result["output"]) ``` ### 8. Distribute `query` In this next step you decorate `query` as a `@ray.remote` function and implement a driver function `batch` to process multiple concurrent queries with Ray. ```python import ray @ray.remote def query(text: str, start: datetime.datetime, end: datetime.datetime) -> dict: ... ``` The driver function `batch`consumes a `List` of `str` and triggers a `chat` request with OpenAI for each refined query string. ```python from typing import List def batch(texts: List[str], start: datetime.datetime, end: datetime.datetime): refined = [t.strip() for t in texts if t.strip()] futures = [query.remote(t, start, end) for t in refined] remaining_futures = futures.copy() completed_jobs = 0 results = [] while remaining_futures: done_futures, remaining_futures = ray.wait( remaining_futures, num_returns=1) result = ray.get(done_futures[0]) results.append(result) completed_jobs += 1 p = completed_jobs / len(texts) * 100. print(f"{result['query']}\n{completed_jobs}/{len(texts)} = {p:.0f}%") if result["error"]: print(f"**Error:** {result['error']}") else: print(result["output"]) return results ``` The `.remote()` statement forwards `batch` execution to Ray and creates futures to wait for. ```python futures = [query.remote(t, start, end) for t in refined] ``` Test *batch processing* with ```python import datetime from dotenv import load_dotenv import ray from company_news.query import batch load_dotenv() ray.init() result = batch(texts=["Serviceplan", "Plan.Net", "Mediaplus"], start=datetime.datetime(2018, 1, 1), end=datetime.datetime(2018, 1, 31)) print(result) ``` ### 9. Setup app We now proceed to setup the app with an endpoint to interact with your service. For the simplicity of this example we add the endpoint implementation directly into `query.py`. At the end of `query.py`, set up the basic application structure: ```python from kodosumi.core import ServeAPI app = ServeAPI() ``` The `ServeAPI()` initialization creates a FastAPI application with Kodosumi-specific extensions. It provides automatic OpenAPI documentation, error handling, authentication and access control, input validation, and some configuration management. The `app` instance will be used to define the service *endpoint* with `@app.enter` and to define service meta data following [OpenAPI specification](https://swagger.io/specification/#operation-object). We will do this in step **11** of this guide. Before we specify the inputs model. ### 10. Define inputs model Define the user interface of your service with the help of the *forms* module. Import *forms* elements from `kodosumi.core`. See [forms overview](/forms) on the supported form input elements. ```python from kodosumi.core import forms as F news_model = F.Model( F.Markdown(""" # Search News Specify the _query_ - for example the name of your client, the start and end date. You can specify multiple query. Type one query per line. """), F.Break(), F.InputArea(label="Query", name="texts"), F.InputDate(label="Start Date", name="start", required=True), F.InputDate(label="End Date", name="end", required=True), F.Submit("Submit"), F.Cancel("Cancel") ) ``` A simple form is rendered that displays a headline with some introductionary text, followed by a text area for the queries and a start and end date input field. ### 11. Implement endpoint Implement the HTTP endpoint using the `@enter` decorator of the `ServeAPI` instance `app`. We will attach the input model defined in the previous step and declare key OpenAPI and extra properties (*summary*, *description*, and *tags*, *version*, *author* for example). On top of `ServeAPI` and `forms` we import `Launch` to start execution within the endpoint and `InputsError` for form validation and error handling. Kodosumi `Tracer` will be used to log results and debug message. We import `asyncio` and some `typing` which we will need later. ```python import fastapi from kodosumi.core import InputsError from kodosumi.core import Launch from kodosumi.core import Tracer import asyncio from typing import Optional, List ``` Specify the endpoint function `enter` with ```python @app.enter( path="/", model=news_model, summary="News Search", description="Search for news.", tags=["OpenAI"], version="1.0.0", author="m.rau@house-of-communication.com") async def enter(request: fastapi.Request, inputs: dict): # parse and cleanse inputs query = inputs.get("texts", "").strip() start = datetime.datetime.strptime(inputs.get("start"), "%Y-%m-%d") end = datetime.datetime.strptime(inputs.get("end"), "%Y-%m-%d") texts = [s.strip() for s in query.split("\n") if s.strip()] # validate inputs error = InputsError() if not texts: error.add(texts="Please specify a query to search for news.") if start > end: error.add(start="Must be before or equal to end date.") if error.has_errors(): raise error # launch execution return Launch( request, "company_news.query:run_batch", inputs={"texts": texts, "start": start, "end": end} ) ``` The method consists of three parts: 1. the `inputs` are parsed and cleansed 2. the `inputs` are validated 3. the execution is launched The `Launch` object adresses function `run_batch` in `company_news.query` which we implement later. ### 12. Create ingress deployment Finish Ray *serve* setup and apply the Ray `@serve.deployment` and `@serve.ingress` decorators to create an *ingress deployment*. The `@serve.deployment` decorator is used to convert a Python class into a Deployment in Ray Serve. A deployment in Ray Serve is a group of actors that can handle traffic. It is defined as a single class with a number of options, including the number of “replicas” of the deployment, each of which will map to a Ray actor at runtime. Requests to a deployment are load balanced across its replicas. The `@serve.ingress` decorator is used to wrap a deployment class with an application derived from `FastAPI` for HTTP request parsing. It defines the HTTP handling logic for the application and can route to other deployments or call into them using the `DeploymentHandle` API. ```python from ray import serve @serve.deployment @serve.ingress(app) class NewsSearch: pass fast_app = NewsSearch.bind() ``` The `fast_app` object is passed to Ray *serve* for deployment. We will use the module/object *factory* string `company_news.query:fast_app` to configure and run deployments. ### 13. Refactor `batch` But before, we *wrap* the entrypoint to `batch` into a function `run_batch` to convert `inputs` and pass the research request. ```python async def run_batch(inputs: dict, tracer: Tracer): texts = inputs.get("texts", []) start = inputs.get("start", datetime.datetime.now()) end = inputs.get("end", datetime.datetime.now()) return await batch(texts, start, end, tracer) ``` Last but not least we refactor the `batch` function to be **async**. Add an updated version of `batch` at the end of `query.py` and remove the "old" function `batch` further up in file `query.py`. ```python async def batch(texts: List[str], start: datetime.datetime, end: datetime.datetime, tracer: Optional[Tracer]=None): refined = [t.strip() for t in texts if t.strip()] futures = [query.remote(t, start, end) for t in refined] unready = futures.copy() completed_jobs = 0 results = [] while unready: ready, unready = ray.wait(unready, num_returns=1, timeout=1) if ready: result = ray.get(ready[0]) results.append(result) completed_jobs += 1 p = completed_jobs / len(texts) * 100. await tracer.markdown(f"#### {result['query']}") await tracer.markdown(f"{completed_jobs}/{len(texts)} = {p:.0f}%") if result["error"]: await tracer.markdown(f"**Error:** {result['error']}") else: await tracer.markdown(result["output"]) await tracer.html("
") print(f"Job completed ({completed_jobs}/{len(texts)})") await asyncio.sleep(1) return results ``` If you carefully watch the function signature you recognise `inputs` and `tracer`. Both arguments are injected by the Kodosumi `Launch` mechanic and carry the `inputs` arguments from the user and a `kodosumi.core.Tracer` object. Use this object to add markdown, text and other results to flow execution. The `tracer` will be passed to `batch`. A slightly modified `batch` function uses the `tracer` to create result markdown notes and *stdio* output. ### 14. Test with uvicorn We test the `app` in `query.py` with uvicorn ```bash uvicorn company_news.query:app --port 8013 ``` Access the exposed endpoint at [http://localhost:8013/](http://localhost:8013/) and you will retrieve the `inputs` scheme defined above with named form elements *texts*, *start*, and *end*. This time we will skip registering the OpenAPI endpoint [http://localhost:8013/openapi.json](http://localhost:8013/openapi.json) with `koco start`. Instead we immediately turn to Ray *serve* deployments. ### 15. Test with Ray *serve* Run the deployment, this time with the Ray `fast_app` object. Ensure you Ray cluster is up and running, i.e. with `ray status`. ```bash serve run company_news.query:fast_app ``` Ray reports available routes at [http://localhost:8000/-/routes](http://localhost:8000/-/routes). Verify the routes are properly published at [http://localhost:8000/-/routes](http://localhost:8000/-/routes) and retrieve the schema this time at [http://localhost:8000/](http://localhost:8000/). Again we will skip `koco serve` until we have a proper deployment. ### 16. Deploy with Ray *serve* Deploy with Ray *serve* and run `koco start` to register your service with Kodosumi. ```bash serve deploy company_news.query:fast_app koco start --register http://localhost:8000/-/routes ``` Now launch the admin panel at [http://localhost:3370](http://localhost:3370) and run a test from [http://localhost:3370/inputs/-/localhost/8000/-/](http://localhost:3370/inputs/-/localhost/8000/-/). Retrieve the inputs scheme from [/-/localhost/8000/-/](http://localhost:3370/-/localhost/8000/-/), and test the service at [/inputs/-/localhost/8000/-/](http://localhost:3370/inputs/-/localhost/8000/-/). Revisit the raw event stream with a given *Flow Identifier* (`fid`) at [http://localhost:3370/outputs/stream/\{fid}](http://localhost:3370/outputs/stream/\{fid}). Overall you will find the following events in the event stream of the *news search agent*: * **`status`** - execution status transitions from *starting* through *running* to *finished* * **`meta`** - service meta data with OpenAPI declarations among others * **`inputs`** - inputs parameters * **`result`** - intermediate results of the service as a data model, `dict` or `list` dump * **`stdout`** - captured *prints* and *writes* to `stdout` * **`final`** - the final response of the service as a data model, `dict` or `list` dump * **`eof`** - end-of-stream message See [Lifecycle and Events](/lifecycle#events) for further details. Use for example `curl` to POST a service requests with the panel API after successful authentication: ```bash curl -b cookie -c cookie -X POST -d '{"name": "admin", "password": "admin"}' http://localhost:3370/api/login curl -b cookie -c cookie -X POST -d '{"texts": "audi\nbmw", "start": "2025-01-01", "end": "2025-01-31"}' http://localhost:3370/-/localhost/8000/-/ ``` The response returns the *Flow Identifier* (`fid`). ```json { "result": "...", "elements": ["..."] } ``` **Where to get from here?** * continue with [Kodosumi deployment workflow](/deploy) * continue with [Kodosumi upload example](/upload-example) # Files Management ReST API URL: /guides/files-api *** title: Files Management ReST API icon: FileCode -------------- import { Callout } from 'fumadocs-ui/components/callout'; ## API Endpoints The Files Management API provides the following main endpoints: ### Batch Upload Initialization ```python # POST /files/init_batch response = await client.post("/files/init_batch") batch_id = response.json()["batch_id"] ``` **Response Example:** ```json { "batch_id": "550e8400-e29b-41d4-a716-446655440000" } ``` ### Single Upload Initialization ```python # POST /files/init payload = { "filename": "document.txt", "total_chunks": 5, "batch_id": "optional-batch-id" } response = await client.post("/files/init", json=payload) upload_id = response.json()["upload_id"] ``` **Response Example:** ```json { "upload_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890", "batch_id": "550e8400-e29b-41d4-a716-446655440000" } ``` ### Chunk Upload ```python # POST /files/chunk form_data = { "upload_id": upload_id, "chunk_number": "0" } files = { "chunk": ("chunk_0", chunk_data, "application/octet-stream") } response = await client.post("/files/chunk", data=form_data, files=files) ``` **Response Example:** ```json { "status": "chunk received", "chunk_number": 0, "received_chunks": 1 } ``` **Error Response Example:** ```json { "error": "Invalid upload ID - upload not initialized" } ``` Note that the maximum size of a single chunk is 1mB (1024 \* 1024 bytes). ### Upload Completion ```python # POST /files/complete/{dir_type} payload = { "upload_id": upload_id, "filename": "document.txt", "total_chunks": 5, "batch_id": batch_id } response = await client.post("/files/complete/in", json=payload) ``` **Request Payload:** ```json { "upload_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890", "filename": "document.txt", "total_chunks": 5, "batch_id": "550e8400-e29b-41d4-a716-446655440000" } ``` **Response Example:** ```json { "status": "upload complete", "completion_id": "550e8400-e29b-41d4-a716-446655440000", "batch_id": "550e8400-e29b-41d4-a716-446655440000", "final_file": "document.txt", "final_path": "550e8400-e29b-41d4-a716-446655440000/document.txt" } ``` **Error Response Examples:** ```json { "error": "Invalid upload ID" } ``` ```json { "error": "Not all chunks uploaded" } ``` ```json { "error": "Missing chunk files: [2, 3]" } ``` ## Automatic Upload Completion on Flow Start **Important:** Upload completion is automatically triggered when a flow is started. This happens in the following process: 1. **Form Submission:** When a user submits a form with file uploads 2. **Flow Start:** The flow is started with a unique flow ID (`fid`) 3. **Automatic Completion:** All incomplete uploads are automatically completed with the flow ID as `completion_id` ### File Listing ```python # GET /files/{fid}/{dir_type} response = await client.get("/files/flow-id/in") ``` **Response Example:** ```json [ { "path": "in/docs/readme.txt", "size": 1024, "is_directory": false }, { "path": "in/src/main.py", "size": 2048, "is_directory": false }, { "path": "in/src/utils/", "size": 0, "is_directory": true } ] ``` ### File Download ```python # GET /files/{fid}/{dir_type}/{filename} response = await client.get("/files/flow-id/in/document.txt") ``` **Response Headers:** ``` Content-Type: application/octet-stream Content-Disposition: attachment; filename="document.txt" Content-Length: 1024 ``` **Response Body:** Binary file content ### Upload Cancellation ```python # POST /files/cancel payload = { "upload_id": upload_id } response = await client.post("/files/cancel", json=payload) ``` **Request Payload:** ```json { "upload_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890" } ``` **Response Example:** ```json { "status": "upload cancelled" } ``` ## Constraints and Limitations ### Directory Structure * Allowed directories * `in/` # Input files * `out/` # Output files * Not allowed directories * `temp/` # Not supported * `cache/` # Not supported ### Path Validation * Path traversal and absolute paths are not allowed ### File Sizes and Chunking * Chunk Size must not exceed 1024 \* 1024 bytes (1mB) ## Best Practices Summary ### Recommended Practices 1. **Use Context Managers** for automatic resource cleanup 2. **Implement error handling** for robust applications 3. **Use streaming for large files** instead of loading everything into memory 4. **Implement retry logic** for network operations 5. **Use SyncFileSystem in Ray Remote Functions** 6. **Validate paths** before upload operations 7. **Leverage automatic completion** when starting flows 8. **Use batch uploads** for multiple files ### Practices to Avoid 1. **Manual resource management** without context managers 2. **Loading large files completely into memory** 3. **Using AsyncFileSystem in Ray Remote Functions** 4. **Allowing path traversal** 5. **Ignoring errors** without proper handling 6. **Hardcoding JWT tokens** in production code 7. **Manual completion** when automatic completion is available 8. **Uploading files individually** instead of using batches ## Example Below is a step-by-step guide using the synchronous `httpx.Client` to upload a real file in chunks. Each step includes a short explanation and the corresponding code. The upload will be automatically completed with the launch of flow execution. ### Step 0: Setup imports and constants Define the API base URL, the path to a real file in your working directory, and the chunk size (1 MiB as constrained above). ```python import os import math import httpx BASE_URL = "http://localhost:3370" FILE_PATH = "./assets/architecture.png" CHUNK_SIZE = 1024 * 1024 # 1 MiB ``` ### Step 1: Create a synchronous HTTP client Manage connections and authenticate. ```python resp = httpx.post( f"{BASE_URL}/api/login", json={"name": "admin", "password": "admin"}) api_key = resp.json().get("KODOSUMI_API_KEY") client = httpx.Client( base_url=BASE_URL, headers={"KODOSUMI_API_KEY": api_key}) ``` ### Step 2: Initialize a batch Group this upload under a single batch identifier. ```python resp = client.post("/files/init_batch") resp.raise_for_status() batch_id = resp.json()["batch_id"] ``` ### Step 3: Calculate file size and total chunks Compute how many chunks are required for the chosen file. ```python file_size = os.path.getsize(FILE_PATH) total_chunks = math.ceil(file_size / CHUNK_SIZE) filename = os.path.basename(FILE_PATH) ``` ### Step 4: Initialize the upload Inform the server about the filename, expected chunk count, and the batch to associate with. ```python init_payload = { "filename": filename, "total_chunks": total_chunks, "batch_id": batch_id, } resp = client.post("/files/init", json=init_payload) resp.raise_for_status() upload_id = resp.json()["upload_id"] ``` ### Step 5: Upload chunks (0-based) Purpose: Read the file in `CHUNK_SIZE` slices and upload each as a separate chunk. ```python with open(FILE_PATH, "rb") as fh: for i in range(total_chunks): chunk_bytes = fh.read(CHUNK_SIZE) assert chunk_bytes, "Unexpected end of file" form_data = {"upload_id": upload_id, "chunk_number": str(i)} files = { "chunk": (f"chunk_{i}", chunk_bytes, "application/octet-stream") } resp = client.post("/files/chunk", data=form_data, files=files) resp.raise_for_status() ``` ### Step 6: Launch Flow Execution The upload will be automatically completed and assembled to the flow's execution ID on `POST`. Assuming the flow\`s input scheme elements are: ```json "elements": [ { "type": "file", "name": "my_upload", ... }, ] ``` The `POST` to launch this flow with ```python import json complete_payload = { "name": "no", "my_upload": json.dumps({ "batchId": batch_id, "items": { upload_id: { "filename": filename, "totalChunks": total_chunks } } }) } resp = client.post("/-/127.0.0.1/8125/-/", json=complete_payload) resp.raise_for_status() ``` THe form file upload form element (my\_upload) requires a json stringified value with keys batchId and items. The items represent key/value pairs with the individual files' upload\_id as the key and filename and totalChunks as values. ### Close the client when done ``` client.close() ``` # Files Management URL: /guides/files *** title: Files Management icon: FolderOpen ---------------- The Kodosumi Files Management API provides two main classes for working with files: `AsyncFileSystem` and `SyncFileSystem`. Both classes enable you to **download** files uploaded by the user and to **upload** files created during flow execution. Files and folders uploaded by the user are located in a root folder `/in` (*input* files). Files and folders created during flow execution which will be delivered back to the user are located in a root folder `/out` (*output* files). ## Overview ### AsyncFileSystem vs SyncFileSystem | Feature | AsyncFileSystem | SyncFileSystem | | ------------------------ | ------------------------------------ | ----------------------- | | **Usage** | Asynchronous functions (`async def`) | Synchronous functions | | **Performance** | Non-blocking, better I/O performance | Blocking, easier to use | | **Ray Remote Functions** | Not compatible | Compatible | | **Context Manager** | `async with` | `with` | ## AsyncFileSystem and SyncFileSystem Classes ### Basic Structure Both classes share the same interface but differ in implementation. As a developer of agentic services you access both classes through the `Tracer` object: ```python # agentic service entrypoint async def run(inputs: dict, tracer: Tracer): fs = await tracer.fs() files = fs.ls("in") # ... additional operations await fs.close() ``` Access `SyncFileSystem` if you pass the `tracer` object to a Ray remote function. This constraint is due to the fact that Ray remote functions do not support async execution. ```python import ray # agentic service entrypoint spawning remote functions async def run(inputs: dict, tracer: Tracer): fs = await tracer.fs() files = fs.ls("in") await fs.close() futures = [process.remote(f['path'], tracer) for f in files] @ray.remote def process(file: str, tracer: Tracer): fs = tracer.fs_sync() files = fs.ls("in") # ... additional operations fs.close() ``` ## Working with Context Managers Both `AsyncFileSystem` and `SyncFileSystem` support context managers for automatic resource cleanup: ### AsyncFileSystem Context Manager ```python async def run(inputs: dict, tracer: Tracer): async with await tracer.fs() as fs: # File system operations here files = await fs.ls("in") # Automatic cleanup when exiting the context ``` ### SyncFileSystem Context Manager ```python def process(file: str, tracer: Tracer): with tracer.fs_sync() as fs: # File system operations here files = fs.ls("in") # Automatic cleanup when exiting the context ``` ## Listing Files and Folders Use the `ls()` method to list files and subfolders in the input or output directories: ### AsyncFileSystem ```python async def run(inputs: dict, tracer: Tracer): async with await tracer.fs() as fs: # List all files in the input directory input_files = await fs.ls("in") # List files in a specific subfolder subfolder_files = await fs.ls("in/subfolder") # List files in the output directory output_files = await fs.ls("out") for file_info in input_files: print(f"File: {file_info['path']}, Size: {file_info['size']}") ``` ### SyncFileSystem ```python def process(file: str, tracer: Tracer): with tracer.fs_sync() as fs: # List all files in the input directory input_files = fs.ls("in") # List files in a specific subfolder subfolder_files = fs.ls("in/subfolder") # List files in the output directory output_files = fs.ls("out") for file_info in input_files: print(f"File: {file_info['path']}, Size: {file_info['size']}") ``` ## Downloading Files and Folders The `download()` method allows you to download files uploaded by the user to a local temporary directory: ### AsyncFileSystem ```python async def run(inputs: dict, tracer: Tracer): async with await tracer.fs() as fs: # Download a single file async for local_path in fs.download("in/document.pdf"): print(f"Downloaded to: {local_path}") # Download an entire folder async for local_path in fs.download("in/documents"): print(f"Downloaded: {local_path}") # Download a specific subfolder async for local_path in fs.download("in/documents/reports"): print(f"Downloaded: {local_path}") ``` ### SyncFileSystem ```python def process(file: str, tracer: Tracer): with tracer.fs_sync() as fs: # Download a single file for local_path in fs.download("in/document.pdf"): print(f"Downloaded to: {local_path}") # Download an entire folder for local_path in fs.download("in/documents"): print(f"Downloaded: {local_path}") # Download a specific subfolder for local_path in fs.download("in/documents/reports"): print(f"Downloaded: {local_path}") ``` ## Uploading Files and Folders The `upload()` method allows you to upload files created during service execution to the output directory: ### AsyncFileSystem ```python async def run(inputs: dict, tracer: Tracer): async with await tracer.fs() as fs: # Create some output files with open("/tmp/result.txt", "w") as f: f.write("Processing completed") # Upload a single file batch_id = await fs.upload("/tmp/result.txt") print(f"Uploaded with batch ID: {batch_id}") # Upload an entire folder batch_id = await fs.upload("/tmp/output_folder") print(f"Uploaded folder with batch ID: {batch_id}") ``` ### SyncFileSystem ```python def process(file: str, tracer: Tracer): with tracer.fs_sync() as fs: # Create some output files with open("/tmp/result.txt", "w") as f: f.write("Processing completed") # Upload a single file batch_id = fs.upload("/tmp/result.txt") print(f"Uploaded with batch ID: {batch_id}") # Upload an entire folder batch_id = fs.upload("/tmp/output_folder") print(f"Uploaded folder with batch ID: {batch_id}") ``` ## Opening and Reading Files Use the `open()` method to create file streams for reading files. The streams support both `read_all()` and `read()` methods: ### AsyncFileSystem ```python async def run(inputs: dict, tracer: Tracer): async with await tracer.fs() as fs: # Open and read entire file content async with fs.open("in/document.txt") as file_stream: content = await file_stream.read_all() print(f"File content: {content.decode('utf-8')}") # Open and read file in chunks async with fs.open("in/large_file.bin") as file_stream: async for chunk in file_stream.read(): # Process each chunk process_chunk(chunk) ``` ### SyncFileSystem ```python def process(file: str, tracer: Tracer): with tracer.fs_sync() as fs: # Open and read entire file content with fs.open("in/document.txt") as file_stream: content = file_stream.read_all() print(f"File content: {content.decode('utf-8')}") # Open and read file in chunks with fs.open("in/large_file.bin") as file_stream: for chunk in file_stream.read(): # Process each chunk process_chunk(chunk) ``` ## Removing Files and Folders Use the `remove()` method to delete files and folders: ### AsyncFileSystem ```python async def run(inputs: dict, tracer: Tracer): async with await tracer.fs() as fs: # Remove a single file success = await fs.remove("in/temp_file.txt") if success: print("File removed successfully") # Remove a folder (and all its contents) success = await fs.remove("in/temp_folder") if success: print("Folder removed successfully") ``` ### SyncFileSystem ```python def process(file: str, tracer: Tracer): with tracer.fs_sync() as fs: # Remove a single file success = fs.remove("in/temp_file.txt") if success: print("File removed successfully") # Remove a folder (and all its contents) success = fs.remove("in/temp_folder") if success: print("Folder removed successfully") ``` ## Complete Example Here's a complete example showing how to use the file management API in a typical workflow: ```python from pathlib import Path async def run(inputs: dict, tracer: Tracer): async with await tracer.fs() as fs: # List all input files input_files = await fs.ls("in") print(f"Found {len(input_files)} input files") # Process each input file for file_info in input_files: file_path = file_info['path'] # Read the file content async with fs.open(file_path) as file_stream: content = await file_stream.read_all() # Process the content processed_content = process_content(content) # Create output file output_filename = f"processed_{Path(file_info['path']).name}" with open(f"/tmp/{output_filename}", "wb") as f: f.write(processed_content) # Upload the processed file batch_id = await fs.upload(f"/tmp/{output_filename}") print(f"Uploaded {output_filename} with batch ID: {batch_id}") # Clean up temporary files for file_info in input_files: if Path(file_info['path']).name.startswith('temp_'): await fs.remove(file_info['path']) ``` ## Error Handling The file management API raises appropriate exceptions for common error conditions: * `FileNotFoundError`: When trying to access a file or folder that doesn't exist * `RuntimeError`: When trying to use a closed stream or re-enter a stream context * HTTP exceptions: For network-related errors during file operations ```python async def run(inputs: dict, tracer: Tracer): async with await tracer.fs() as fs: try: # Try to access a non-existent file async with fs.open("in/nonexistent.txt") as file_stream: content = await file_stream.read_all() except FileNotFoundError: print("File not found") except Exception as e: print(f"Error accessing file: {e}") ``` **Where to get from here?** * Continue with [complete upload/download example](/upload-example) # Forms URL: /guides/forms *** title: Forms icon: FormInput --------------- This document provides an overview of all user interface elements supported by Kodosumi. The elements are organized into three main categories: Input Elements, Action Elements, and Content Elements. ## 1. Input Elements Input elements are used to collect user data through various input methods. ### Text Input (`InputText`) * Type: `text` * Purpose: Single-line text input * Properties: * `name`: Field identifier (required) * `label`: Display label * `value`: Initial value * `required`: Whether the field is mandatory * `placeholder`: Placeholder text * `size`: Input field size * `pattern`: Regex pattern for validation ### Password Input (`InputPassword`) * Type: `password` * Purpose: Secure password input with masked characters * Properties: * `name`: Field identifier (required) * `label`: Display label * `value`: Initial value * `required`: Whether the field is mandatory * `placeholder`: Placeholder text * `size`: Input field size * `min_length`: Minimum password length * `max_length`: Maximum password length * `pattern`: Regex pattern for password validation * `show_toggle`: Option to show/hide password (boolean) ### Number Input (`InputNumber`) * Type: `number` * Purpose: Numeric input with validation * Properties: * All properties from `InputText` * `min_value`: Minimum allowed value * `max_value`: Maximum allowed value * `step`: Step increment for number input ### Text Area (`InputArea`) * Type: `textarea` * Purpose: Multi-line text input * Properties: * `name`: Field identifier (required) * `label`: Display label * `value`: Initial value * `required`: Whether the field is mandatory * `placeholder`: Placeholder text * `rows`: Number of visible text lines * `cols`: Width of the text area * `max_length`: Maximum number of characters allowed ### Date Input (`InputDate`) * Type: `date` * Purpose: Date selection input * Properties: * `name`: Field identifier (required) * `label`: Display label * `value`: Initial date value * `required`: Whether the field is mandatory * `placeholder`: Placeholder text * `min_date`: Minimum allowed date (YYYY-MM-DD) * `max_date`: Maximum allowed date (YYYY-MM-DD) ### Time Input (`InputTime`) * Type: `time` * Purpose: Time selection input * Properties: * `name`: Field identifier (required) * `label`: Display label * `value`: Initial time value * `required`: Whether the field is mandatory * `placeholder`: Placeholder text * `min_time`: Minimum allowed time (HH:MM) * `max_time`: Maximum allowed time (HH:MM) * `step`: Time increment in seconds ### Date-Time Input (`InputDateTime`) * Type: `datetime-local` * Purpose: Combined date and time selection * Properties: * `name`: Field identifier (required) * `label`: Display label * `value`: Initial date-time value * `required`: Whether the field is mandatory * `placeholder`: Placeholder text * `min_datetime`: Minimum allowed date-time (YYYY-MM-DDTHH:MM) * `max_datetime`: Maximum allowed date-time (YYYY-MM-DDTHH:MM) * `step`: Time increment in seconds ### Checkbox (`Checkbox`) * Type: `boolean` * Purpose: Boolean toggle input * Properties: * `name`: Field identifier (required) * `option`: Display text for the checkbox * `label`: Optional label * `value`: Initial state (true/false) ### Select (`Select`) * Type: `select` * Purpose: Dropdown selection * Properties: * `name`: Field identifier (required) * `option`: List of `InputOption` objects * `label`: Display label * `value`: Selected option value ### Input Option (`InputOption`) * Type: `option` * Purpose: Individual option for Select elements * Properties: * `name`: Option value (required) * `label`: Display text for the option * `value`: Whether this option is selected * Usage: Used within `Select` elements to define available choices ### File Input (`InputFiles`) * Type: `file` * Purpose: File upload input with support for multiple files and directory uploads * Properties: * `name`: Field identifier (required) * `label`: Display label * `required`: Whether the upload is mandatory * `multiple`: Whether multiple files can be selected (boolean) * `directory`: Whether to allow directory uploads (boolean, uses webkitdirectory) ## 2. Action Elements Action elements are used to trigger form submission or navigation. ### Submit (`Submit`) * Type: `submit` * Purpose: Form submission button * Properties: * `text`: Button label text ### Cancel (`Cancel`) * Type: `cancel` * Purpose: Cancel form and return to home * Properties: * `text`: Button label text ### Action (`Action`) * Type: `action` * Purpose: Custom action button * Properties: * `name`: Action identifier * `value`: Action value * `text`: Button label text ## 3. Content Elements Content elements are used to structure and display information within the form. ### HTML (`HTML`) * Type: `html` * Purpose: Raw HTML content * Properties: * `text`: HTML content to render ### Markdown (`Markdown`) * Type: `markdown` * Purpose: Markdown-formatted content * Properties: * `text`: Markdown content to render * Features: * Supports extra markdown features * Code highlighting * Table of contents * Fenced code blocks ### Break (`Break`) * Type: `html` (specialized) * Purpose: Visual spacing element * Properties: None ### Horizontal Rule (`HR`) * Type: `html` (specialized) * Purpose: Visual separator line * Properties: None ### Errors (`Errors`) * Type: `errors` * Purpose: Display `_global_` form validation errors * Properties: * `error`: List of error messages * Features: * Global error display * Styled error container * Bold error text ## Usage Examples ### Basic Form with File Upload ```python from kodosumi.core import forms as F model = F.Model( F.Markdown("# File Processing Form"), F.InputText( name="title", label="Document Title", placeholder="Enter document title", required=True ), F.InputFiles( name="files", label="Upload Documents", multiple=True, required=True ), F.Checkbox( name="process_images", option="Include image processing", value=False ), F.Submit("Process Files"), F.Cancel("Cancel") ) ``` ### Advanced Form with Validation ```python model = F.Model( F.Markdown("## Data Import Configuration"), F.InputText( name="email", label="Email Address", placeholder="user@example.com", pattern=r"^[^\s@]+@[^\s@]+\.[^\s@]+$", required=True ), F.InputNumber( name="batch_size", label="Batch Size", min_value=1, max_value=1000, value=100 ), F.InputDate( name="start_date", label="Start Date", required=True ), F.Select( name="format", label="Output Format", option=[ F.InputOption("json", "JSON"), F.InputOption("csv", "CSV"), F.InputOption("xml", "XML") ], value="json" ), F.InputFiles( name="data_files", label="Data Files", multiple=True, directory=False, required=True ), F.HR(), F.Submit("Import Data"), F.Cancel("Cancel") ) ``` ### Form with Error Handling ```python model = F.Model( F.Markdown("# User Registration"), F.Errors(), # Global error display F.InputText( name="username", label="Username", required=True ), F.InputPassword( name="password", label="Password", min_length=8, required=True ), F.InputPassword( name="confirm_password", label="Confirm Password", required=True ), F.Submit("Register"), F.Cancel("Cancel") ) ``` # Welcome to Kodosumi URL: /guides *** title: Welcome to Kodosumi icon: Home ---------- import { Cards, Card } from 'fumadocs-ui/components/card'; Welcome to **Kodosumi** - the runtime environment that makes managing and executing agentic services at scale simple and powerful. Whether you're building your first AI agent or orchestrating complex multi-agent systems, this documentation will guide you every step of the way. ## Start Your Journey New to Kodosumi? These resources will get you up and running quickly. Learn about Kodosumi's core concepts and capabilitiesDiscover what makes Kodosumi the right choice for your project ## Build and Deploy Ready to start building? Follow our step-by-step guides. Set up your development environment and create your first serviceLearn how to configure and customize your agentic servicesGet your services running in production with our deployment guideManage and monitor your deployed services ## Core Concepts Understand the fundamentals that power Kodosumi. Dive deep into agents, services, lifecycles, and more # Installation URL: /guides/installation *** title: Installation icon: Download -------------- The following quick guide 1. installs kodosumi and all prerequisites along the kodosumi example flows 2. starts Ray and kodosumi on your localhost 3. deploys example flows This installation has been tested with versions `ray==2.48.0` and `python==3.12.6`. If you want to skip the examples then continue with the [kodosumi development workflow](/develop) and start implementing your custom agentic service with the kodosumi framework. ## Install and run examples ### STEP 1 - Clone and install `kodosumi-examples` Clone and install the `kodosumi-examples` into your Python Virtual Environment. The kodosumi and Ray packages are automatically installed as a dependency. ```bash git clone https://github.com/masumi-network/kodosumi-examples.git cd ./kodosumi-examples pip install . ``` Since some of the examples utilize additional frameworks like `CrewAI` and `langchain` the installation of the kodosumi examples takes a while. All dependencies of the examples are installed. Please note that these dependencies are managed by Ray in production. See [deployment](/deploy). ### STEP 2 - Prepare runtime environment You need an OpenAI API key to run some of the examples. Specify the API key in `.env`. ```bash # ./env OPENAI_API_KEY=your-api-key-here ``` ### STEP 3 - Start ray Start Ray *head* node on your localhost. Load environment variables with `dotenv` before starting ray: ```bash dotenv run -- ray start --head ``` Check `ray status` and visit ray dashboard at [http://localhost:8265](http://localhost:8265). For more information about ray visit [ray's documentation](https://docs.ray.io/en/latest). ### STEP 4 - Deploy You have various options to deploy and run the example services. *kodosumi-examples* repository ships with the following examples in `kodosumi_examples`: * **hymn** - creates a hymn based on a given topic. The example demonstrates the use of [CrewAI](https://www.crewai.com/) and [OpenAI](https://openai.com/) * **prime** - calculates prime number gaps. Distributes the tasks across the Ray cluster and demonstrates performance benefits. * **throughput** - real-time experience of different event stream pressures with parameterized BPMs (beats per minute). * **form** - demonstrates form elements supported by kodosumi. You can run any of these examples. The next steps focus on `kodosumi_examples.hymn`. #### Alternative 1: run with uvicorn You can launch each example service as a python module. ```bash uvicorn kodosumi_examples.hymn.app:app --port 8011 ``` This starts a uvicorn (Asynchronous Server Gateway Interface) server at [http://localhost:8011](http://localhost:8011). All HTTP endpoints of `app` are available at URL [http://localhost:8011/openapi.json](http://localhost:8011/openapi.json). Launch another terminal session, source the Python Virtual Environment and register this URL with kodosumi panel: ```bash koco start --register http://localhost:8011/openapi.json ``` Visit kodosumi **[admin panel](http://localhost:3370)** at [http://localhost:3370](http://localhost:3370). The default user is defined in `config.py` and reads `name=admin` and `password=admin`. Launch the **[Hymn Creator](http://localhost:3370/inputs/-/localhost/8011/-/)** from the **[service screen](http://localhost:3370/admin/flow)** and revisit results at the **[timeline screen](http://localhost:3370/timeline/view)**. You can start another service `prime` in a new terminal with ```bash uvicorn kodosumi_examples.prime.app:app --port 8012 ``` Register this service with [kodosumi panel config](http://localhost:3370/admin/routes) with both service endpoints ``` http://localhost:8011/openapi.json http://localhost:8012/openapi.json ``` You can specify multiple *registers* at `koco start` ```bash koco start --register http://localhost:8011/openapi.json --register http://localhost:8012/openapi.json ``` Running your services as standalone uvicorn applications is best practice to facilitate debugging. #### Alternative 2: deploy and run with Ray serve Run your services as Ray serve deployments. This is the preferred approach to deploy services in production. The downside of this approach is that you have to use remote debugging tools and attach to session breakpoints for debugging (see [Using the Ray Debugger](https://docs.ray.io/en/latest/ray-observability/user-guides/debug-apps/ray-debugging.html)). Ray Serve is built on top of Ray, so it easily scales to many machines and offers flexible scheduling support such as fractional GPUs so you can share resources and serve many applications at low cost. With Ray *serve* you either run or deploy your services. Instead of the mechanics with uvicorn which refers the `app` application object, Ray serve demands the bound `fast_app` object. To test and improve your service run it with ```bash serve run kodosumi_examples.hymn.app:fast_app ``` In contrast to the previous command a `serve deploy` command is used to deploy your Serve application to the Ray cluster. It sends a deploy request to the cluster and the application is deployed asynchronously. This command is typically used for deploying applications in a production environment. ```bash serve deploy kodosumi_examples.hymn.app:fast_app ``` Using Ray *serve run* or *deploy* the `--register` must connect to Ray's proxy URL `/-/routes`. With `serve run` or `deploy` the port defaults to `8000` and you start `koco start` with the Ray serve endpoint [http://localhost:8000/-/routes](http://localhost:8000/-/routes). ```bash koco start --register http://localhost:8000/-/routes ``` #### Multi-service setup with Serve config files `serve run` and `serve deploy` feature single services. Running multiple uvicorn services is possible but soon gets dirty and quirky. For multi-service deployments use Ray serve *config files*. In directory `./data/config` create a file `config.yaml` with *serve's* overarching configuration, for example ```yaml proxy_location: EveryNode http_options: host: 127.0.0.1 port: 8001 grpc_options: port: 9001 grpc_servicer_functions: [] logging_config: encoding: TEXT log_level: DEBUG logs_dir: null enable_access_log: true ``` Alongside this file `config.yaml` create service configuration files. For each service deployment create a dedicated configuration file: ##### `hymn.yaml` ```yaml name: hymn route_prefix: /hymn import_path: kodosumi_examples.hymn.app:fast_app runtime_env: pip: - crewai - crewai_tools env_vars: OTEL_SDK_DISABLED: "true" OPENAI_API_KEY: "your-api-key-here" ``` ##### `prime.yaml` ```yaml name: prime route_prefix: /prime import_path: kodosumi_examples.prime.app:fast_app ``` ##### `throughput.yaml` ```yaml name: throughput route_prefix: /throughput import_path: kodosumi_examples.throughput.app:fast_app runtime_env: pip: - lorem-text ``` Test this deployment set with ```bash koco deploy --dry-run --file ./data/config/config.yaml ``` With success, stop `ray serve` and perform a Ray serve deployment ```bash serve shutdown --yes # this is equivalent to koco deploy --stop koco deploy --run --file ./data/config/config.yaml ``` Restart `koco start` with the Ray serve endpoint [http://localhost:8001/-/routes](http://localhost:8001/-/routes) as configured in `config.yaml`. ```bash koco start --register http://localhost:8001/-/routes ``` If one or more Ray serve applications are not yet available when kodosumi starts, you need to refresh the list of registered flows. Visit **[control screen](http://localhost:3370/admin/routes)** in the **[admin panel](http://localhost:3370/)** and click **RECONNECT**. Adding and removing deployments is operationalized with config files in `./data/config`. All files alongside `config.yaml` are deployed. You can test your deployment setup with `koco deploy --dry-run --file ./data/config/config.yaml`. **Where to get from here?** * Continue with [kodosumi development workflow](/develop) * See the admin panel [screenshots](/panel) * Read about [basic concepts and terminology](/concepts) # Lifecycle URL: /guides/lifecycle *** title: Lifecycle icon: RefreshCw --------------- A flow launched with `Launch` response runs through the following states: ```mermaid stateDiagram direction LR [*] --> starting starting --> running running --> finished running --> awaiting awaiting --> running awaiting --> error running --> error finished --> [*] error --> [*] ``` The `Launch` requires the following arguments: * request (`fastAPI.Request`) * entry\_point (`str` or `callable`) * inputs (`Any`) **Example:** The following example launches a CrewAI `Crew` in `hymn.app` with `core.Launch`. ```python @app.enter( path="/", model=hymn_model, summary="Hymn Creator", description="This agent creates a short hymn about a given topic of your choice using openai and crewai.", version="1.0.1", author="m.rau@house-of-communication.com", tags=["Test", "CrewAi"] ) async def enter(request: fastapi.Request, inputs: dict): topic = inputs.get("topic", "").strip() if not topic: raise core.InputsError(topic="Please give me a topic.") return core.Launch(request, "hymn.app:crew", inputs=inputs) ``` # Events In the lifetime of a flow execution the following events are tracked. | event | description | method | screen | | ------ | ----------------------------------------- | ----------------- | -------------------- | | meta | flow metadata and entry point information | (internal) | n/a | | inputs | user input data | (internal) | Input | | agent | agent information | agent meta data | n/a | | debug | debug message | debug | Log | | stdout | stdout information | print, sys.stdout | Log | | stderr | stderr information | sys.stderr | Log | | status | flow status change | (internal) | n/a | | error | error information | (internal) | Output/Reasoning/Log | | action | action information | tool result | Reasoning | | result | task result information | result | Reasoning | | final | final result information | final result | Ouput | | upload | user file upload | (internal) | Reasoning | | lock | human intervention is required | lock | Output | | lease | human input when locks are leased | lease | Log | Kodosumi provides a couple of helper methods to simplify emitting events: * `tracer.debug(message: str)` - emit debug plain text message * `tracer.result(message: Any)` - emit result object * `tracer.action(message: Any)` - emit action result object * `tracer.markdown(message: str)` - emit markdown result string * `tracer.html(message: str)` - emit HTML result string * `tracer.text(message: str)` - emit plain text result string The corresponding synchronous methods are * `tracer.debug_sync(message: str)` * `tracer.result_sync(message: Any)` * `tracer.action_sync(message: Any)` * `tracer.markdown_sync(message: str)` * `tracer.html_sync(message: str)` * `tracer.text_sync(message: str)` # Human-in-the-Loop with Locks URL: /guides/locks *** title: Human-in-the-Loop with Locks icon: Lock ---------- import { Callout } from 'fumadocs-ui/components/callout'; Kodosumi provides a powerful Human-in-the-Loop (HTIL) mechanism through its lock system, allowing automated workflows to pause and request human intervention when needed. This document explains how to implement HTIL services using locks and leases. See also the kodosumi-examples repository on HITL. ## Overview The HTIL system consists of three main components: 1. **Entry Points** - Initial form inputs that launch workflow executions. 2. **Locks** - Points where execution pauses and waits for human input 3. **Leases** - Handlers that process human input and continue execution ## Basic Implementation ### 1. Entry Point with Form Input First, create an entry point that collects initial user input: ```python from kodosumi.core import ServeAPI, Launch from kodosumi.service.inputs.forms import Model, InputText, Checkbox, Submit, Cancel from fastapi import Request app = ServeAPI() # Define the form model for the entry point form_model = Model( InputText(label="Name", name="name", placeholder="Enter your name"), Checkbox(label="Active", name="active", option="ACTIVE", value=False), Submit("Submit"), Cancel("Cancel"), ) @app.enter( "/", model=form_model, summary="HTIL Example", description="Example workflow with human intervention", ) async def post(inputs: dict, request: Request) -> Launch: """Launch the workflow with user inputs.""" return Launch(request, "my_module:runner", inputs=inputs) ``` ### 2. Runner Function with Lock The runner function executes the workflow and can request human intervention: ```python from kodosumi.core import Tracer async def runner(inputs: dict, tracer: Tracer): # Process initial inputs await tracer.debug("Processing user inputs") # Request human intervention result = await tracer.lock("approval-lock", data={ "hello": "from runner", "inputs": inputs }) # Continue processing after human approval return {"final_result": result} ``` ### 3. Lock Handler Define what the human sees when the lock is triggered: ```python from kodosumi.service.inputs.forms import Markdown @app.lock("approval-lock") async def approval_lock(data: dict): return Model( Markdown(f"# Approval Required\n\nReceived data: {data['hello']}"), Checkbox(label="Approve", name="approve", option="APPROVE", value=False), InputText(label="Comments", name="comments", placeholder="Optional comments"), Submit("Approve"), Cancel("Reject"), ) ``` ### 4. Lease Handler Process the human input and continue execution: ```python @app.lease("approval-lock") async def approval_lease(inputs: dict): if not inputs.get("approve"): raise InputsError(approve="Approval is required to continue") return { "approved": True, "comments": inputs.get("comments", ""), "timestamp": "2024-01-01T12:00:00Z" } ``` ## Lock vs Lease Decorators ### `@app.lock(lock_id)` * **Purpose**: Defines the user interface shown when execution pauses * **Parameters**: Receives the data passed from `tracer.lock()` * **Return**: A `Model` object defining the form elements * **When called**: When the lock is first triggered ### `@app.lease(lock_id)` * **Purpose**: Processes human input and continues execution * **Parameters**: Receives the form data submitted by the human * **Return**: Data that will be passed back to the runner function * **When called**: When the human submits the lock form ## Missing Lease Handler ## Complete Example Here's a complete HTIL service implementation: ```python from kodosumi.core import ServeAPI, Launch, Tracer from kodosumi.service.inputs.forms import Model, InputText, Checkbox, Submit, Cancel, Markdown from kodosumi.service.inputs.errors import InputsError from fastapi import Request app = ServeAPI() # Entry point form entry_form = Model( InputText(label="Document Name", name="doc_name", placeholder="Enter document name"), InputText(label="Author", name="author", placeholder="Enter author name"), Submit("Start Review"), Cancel("Cancel"), ) @app.enter( "/review", model=entry_form, summary="Document Review Workflow", description="Review workflow with human approval steps", ) async def start_review(inputs: dict, request: Request) -> Launch: return Launch(request, "document_service:review_runner", inputs=inputs) # Runner function async def review_runner(inputs: dict, tracer: Tracer): await tracer.debug(f"Starting review for document: {inputs['doc_name']}") # First approval - content review content_approval = await tracer.lock("content-review", data={ "document": inputs['doc_name'], "author": inputs['author'], "stage": "content" }) # Second approval - final review final_approval = await tracer.lock("final-review", data={ "document": inputs['doc_name'], "content_approved": content_approval, "stage": "final" }) return { "document": inputs['doc_name'], "content_approval": content_approval, "final_approval": final_approval, "status": "completed" } # Content review lock @app.lock("content-review") async def content_review_lock(data: dict): return Model( Markdown(f"# Content Review Required\n\n**Document**: {data['document']}\n**Author**: {data['author']}"), Checkbox(label="Content is accurate", name="accurate", option="ACCURATE", value=False), Checkbox(label="Grammar is correct", name="grammar", option="GRAMMAR", value=False), InputText(label="Review Comments", name="comments", placeholder="Enter review comments"), Submit("Approve Content"), Cancel("Request Changes"), ) # Content review lease @app.lease("content-review") async def content_review_lease(inputs: dict): if not inputs.get("accurate") or not inputs.get("grammar"): raise InputsError( accurate="Content accuracy must be confirmed", grammar="Grammar must be verified" ) return { "approved": True, "comments": inputs.get("comments", ""), "reviewer": "human_reviewer" } # Final review lock @app.lock("final-review") async def final_review_lock(data: dict): return Model( Markdown(f"# Final Review\n\n**Document**: {data['document']}\n**Content Approved**: {data['content_approved']['approved']}"), Checkbox(label="Ready for publication", name="publish", option="PUBLISH", value=False), InputText(label="Final Comments", name="final_comments", placeholder="Final approval comments"), Submit("Publish"), Cancel("Hold"), ) # Final review lease @app.lease("final-review") async def final_review_lease(inputs: dict): if not inputs.get("publish"): raise InputsError(publish="Publication approval is required") return { "published": True, "comments": inputs.get("final_comments", ""), "publisher": "human_publisher" } ``` ## API Workflow ### Step 1: Get Input Schema **Request:** ```http GET /inputs/-/localhost/8125/-/review ``` **Response:** ```json { "elements": [ { "type": "text", "name": "doc_name", "label": "Document Name", "value": null, "required": false, "placeholder": "Enter document name", "size": null, "pattern": null }, { "type": "text", "name": "author", "label": "Author", "value": null, "required": false, "placeholder": "Enter author name", "size": null, "pattern": null }, { "type": "submit", "text": "Start Review" }, { "type": "cancel", "text": "Cancel" } ] } ``` ### Step 2: Launch Workflow Execution **Request:** ```http POST /-/localhost/8125/-/review Content-Type: application/x-www-form-urlencoded doc_name=My%20Document&author=John%20Doe ``` **Response:** ```json { "result": "abc123-def456-ghi789" } ``` ### Step 3: Check Execution Status **Request:** ```http GET /outputs/status/abc123-def456-ghi789 ``` **Response:** ```json { "status": "awaiting", "locks": ["content-review-xyz789"] } ``` ### Step 4: Get Lock Input Schema **Request:** ```http GET /lock/abc123-def456-ghi789/content-review-xyz789 ``` **Response:** ```json [ { "type": "markdown", "text": "# Content Review Required\n\n**Document**: My Document\n**Author**: John Doe" }, { "type": "boolean", "name": "accurate", "label": "Content is accurate", "value": false, "option": "ACCURATE" }, { "type": "boolean", "name": "grammar", "label": "Grammar is correct", "value": false, "option": "GRAMMAR" }, { "type": "text", "name": "comments", "label": "Review Comments", "value": null, "required": false, "placeholder": "Enter review comments", "size": null, "pattern": null }, { "type": "submit", "text": "Approve Content" }, { "type": "cancel", "text": "Request Changes" } ] ``` ### Step 5: Submit Lock Data **Request:** ```http POST /lock/abc123-def456-ghi789/content-review-xyz789 Content-Type: application/json { "accurate": true, "grammar": true, "comments": "Content looks good, ready for final review" } ``` **Response:** ```json { "result": { "approved": true, "comments": "Content looks good, ready for final review", "reviewer": "human_reviewer" } } ``` ### Step 6: Check Final Status **Request:** ```http GET /outputs/status/abc123-def456-ghi789 ``` **Response:** ```json { "status": "awaiting", "locks": ["final-review-abc456"] } ``` Continue the process for subsequent locks until the workflow completes. ## Error Handling ### Validation Errors If the lease handler raises an `InputsError`, the lock remains active: ```python @app.lease("approval-lock") async def approval_lease(inputs: dict): error = InputsError() if not inputs.get("approve"): error.add(approve="Approval is required") if not inputs.get("comments"): error.add(comments="Comments are required") if error.has_errors(): raise error return {"approved": True} ``` **Response for validation errors:** ```json { "errors": { "approve": ["Approval is required"], "comments": ["Comments are required"], "_global_": [] } } ``` ### Timeout Handling Locks can have timeouts: ```python # In runner function result = await tracer.lock("timeout-lock", data={...}, timeout=300) # 5 minutes ``` If the lock times out, the execution fails with a timeout error. The default timeout is 3 hours. # Documentation Build URL: /guides/make *** title: 'Documentation Build' icon: Hammer ------------ This guide explains how to build and deploy the Kodosumi documentation using Fumadocs. ## Prerequisites Before building the documentation, ensure you have the following installed: * [Node.js](https://nodejs.org/) (version 18 or higher) * [npm](https://www.npmjs.com/) or [pnpm](https://pnpm.io/) ## Installation Install dependencies from the project root: ```bash npm install # or pnpm install ``` ## Local Development To run the documentation locally for development: ```bash npm run dev # or pnpm dev ``` The documentation will be available at `http://localhost:3000`. ## Building for Production To build the documentation for production deployment: ```bash npm run build # or pnpm build ``` This will run `fumadocs-mdx`, generate OpenAPI docs, and create a production build in the `.next` directory. ## Getting Help * Check the [Fumadocs documentation](https://fumadocs.dev) * Review the [Fumadocs MDX guide](https://fumadocs.dev/docs/mdx) * Report issues to the [Kodosumi GitHub repository](https://github.com/masumi-network/kodosumi) # Masumi Skills URL: /guides/masumi-skills AI coding assistant skill with a dedicated Kodosumi Runtime reference guide — context-aware help as you build and deploy agentic services *** title: Masumi Skills description: AI coding assistant skill with a dedicated Kodosumi Runtime reference guide — context-aware help as you build and deploy agentic services icon: Wand ---------- import { Callout } from 'fumadocs-ui/components/callout'; import { Card, Cards } from 'fumadocs-ui/components/card'; import { Tabs, Tab } from 'fumadocs-ui/components/tabs'; The **Masumi Skill** is a context-aware guide for AI coding assistants (Claude Code, Cursor, Windsurf, and others) that provides token-efficient documentation for the entire Masumi ecosystem — including a dedicated **Kodosumi Runtime** reference. Once installed, your AI assistant automatically loads the Kodosumi reference material as you work on deployments, Ray configuration, spooler setup, or agent lifecycle management — without manual doc pasting. ## Installation npx skills add https\://github.com/masumi-network/masumi-skills --skill masumigit clone https\://github.com/masumi-network/masumi-skills cd masumi-skills ./install.sh **Compatible with:** Claude Code, Cursor, Windsurf, Cline, Aider, Codex, and other AI coding assistants that support skills/rules files. The skill entry point is also available directly at [masumi.network/skill.md](https://www.masumi.network/skill.md). ## Kodosumi Runtime Reference The skill's **Kodosumi Runtime** guide covers what you need when deploying agents at scale: * Ray Serve deployment configuration (base YAML + app YAML patterns) * Spooler lifecycle, event transport, and execution modes * Panel API for managing deployments at runtime * Agent registration, input schema, and lifecycle hooks * Files API, forms, locks, and timeline internals * Environment variable reference (`KODO_*` settings) When your AI assistant detects you're working on Kodosumi deployment code, it pulls this guide automatically — keeping the rest of the context window free for your code. ## Full Skill Reference Set Version 2.0 ships with **8 reference guides** covering the complete ecosystem: The skill uses progressive disclosure: only the guide relevant to your current task is loaded, keeping context window usage low across \~10,000+ lines of technical documentation. ## How It Works The skill ships with an entry point (`skill.md`) and a routing guide (`SKILL.md`). As you work, your AI assistant selects the relevant deep-dive: | What you're building | Reference loaded | | ----------------------------- | ------------------------- | | Ray Serve deployment | `kodosumi-runtime.md` | | MIP-003 API compliance | `agentic-services.md` | | Payment integration | `masumi-payments.md` | | Agent registration / identity | `registry-identity.md` | | Sokosumi marketplace listing | `sokosumi-marketplace.md` | | Smart contract interaction | `smart-contracts.md` | ## Common Workflows **Deploy a new agent service:** ``` Build Agent → Deploy on Kodosumi → Register → List on Sokosumi → Payments via Masumi ``` **Scale an existing deployment:** ``` Configure Ray Serve YAML → Set KODO_* env vars → koco deploy → Monitor via dashboard ``` ## Resources * **GitHub:** [masumi-network/masumi-skills](https://github.com/masumi-network/masumi-skills) * **Entry point:** [masumi.network/skill.md](https://www.masumi.network/skill.md) * **Masumi Docs:** [docs.masumi.network](https://docs.masumi.network) * **Sokosumi Docs:** [docs.sokosumi.com](https://docs.sokosumi.com) # Meta URL: /guides/meta *** title: Meta icon: FileJson -------------- ## Agentic Service Annotations The following attributes are defined with an extended openapi specification. | attribute | type | comment | | ------------ | ------- | -------------------------------------------------- | | tags | `[str]` | rendered as chips | | summary | `str` | rendered as service name | | description | `str` | rendered as descriptive text | | deprecated | `bool` | declares this operation to be deprecated. | | entry | `bool` | `False` hides the end point | | author | `str` | | | organization | `str` | | | version | `str` | use semantic versioning with *major, minor, patch* | **Example:** ```python @app.enter("/", tags=["Test"], summary="Hello World Example", description="Say hello world.", author="m.rau@house-of-communication.com", version="0.1.0") ... ``` The following additional meta data is associated with flow execution: | attribute | type | comment | | ------------ | ----- | ------------------------------------- | | fid | `str` | flow execution identifier | | username | `str` | user identifier owning flow execution | | base\_url | `str` | endpoint URL | | entry\_point | `str` | entry point | # Admin Panel URL: /guides/panel *** title: Admin Panel icon: LayoutDashboard --------------------- The Kodosumi admin panel provides a simple and pragmatic web interface for managing and monitoring agentic services. Below is a detailed overview of its key features and components. ## Login Screen The admin panel requires authentication to access. By default, the username is `admin` with password `admin`. These credentials can be configured using environment variables for enhanced security. See [configuration](/config). ## Service Screen The services page displays a comprehensive list of all agentic services available on the current node. This interface allows users to browse, search and start agentic services. ## Inputs Form Kodosumi uses standardized inputs schemes to manage user input. ## Status Screen The status screen provides detailed runtime information of flow execution. The *Results* tab renders intermediate and final results. The *I-O* tab provides the flow's `stdio` streams (`stdout`, `stderr` stream and flow *debug* events). The full event stream is available in tab `Events`. A printable version can be accessed with the download icon. ### Input/Output Streams ### Timeline Screen ## Agentic Control The control panel serves as the operator interface to reconnect and update agentic services. The Swagger UI to the kodosumi API is linked here, too. # Timeline API URL: /guides/timeline *** title: Timeline API icon: Clock ----------- The Timeline API provides endpoints to retrieve and manage execution timeline data. It supports pagination, real-time updates, and change tracking for execution records. ## Overview The timeline system tracks executions with the following key features: * **Pagination**: Load results in configurable page sizes * **Real-time updates**: Track changes since a specific timestamp * **Search filtering**: Filter results by various criteria * **Change detection**: Identify new, modified, and deleted executions ## Endpoints ### GET `/timeline/list` Retrieves paginated timeline results sorted from the specified offset. This endpoint is optimized for traditional pagination workflows. **Query Parameters:** * `pp` (optional): Page size, defaults to 10 * `q` (optional): Search query string for filtering results * `offset` (optional): Execution ID to start pagination from **Response:** ```json { "items": [ { "fid": "20241201T120000Z", "tags": ["production", "batch"], "summary": "Data processing job", "inputs": {...}, "status": "completed", "startup": "2024-12-01T12:00:00", "finish": "2024-12-01T12:15:30", "runtime": 930.5, "locks": [] } ], "query": "search term", "offset": "20241130T150000Z" } ``` ### GET `/timeline/changes` Retrieves changes to timeline items since a given timestamp. This endpoint is designed for real-time updates and change tracking. **Query Parameters:** * `since` (optional): Unix timestamp to check for changes since * `q` (optional): Search query string for filtering results **Response:** ```json { "update": [ { "fid": "20241201T120000Z", "tags": ["production", "batch"], "summary": "Data processing job", "inputs": {...}, "status": "running", "startup": "2024-12-01T12:00:00", "finish": null, "runtime": null, "locks": ["lock_123"] } ], "delete": ["20241130T150000Z"], "timestamp": 1701432000.0, "query": "search term" } ``` **Special Behavior:** * If no `since` parameter is provided, returns only the current maximum modification timestamp * This allows clients to establish a baseline timestamp for future change requests ## Data Model ### Execution Record Fields Each execution record contains the following fields: | Field | Type | Description | | --------- | -------- | ---------------------------------- | | `fid` | string | Execution ID (timestamp-based) | | `tags` | array | Array of execution tags | | `summary` | string | Brief description of the execution | | `inputs` | object | Input data and configuration | | `status` | string | Current execution status | | `startup` | datetime | Execution start time | | `finish` | datetime | Execution completion time | | `runtime` | float | Total execution time in seconds | | `locks` | array | Active lock identifiers | ### Search Fields The search functionality supports filtering by: * `author`: Execution author * `organization`: Organization name * `summary`: Execution summary text * `description`: Detailed description * `fid`: Execution ID * `status`: Execution status * `tags`: Individual tag values * `startup`: Start time * `inputs`: Input data * `final`: Final results ## Usage Examples ### Basic Pagination ```javascript // Get first page of executions const response = await fetch('/timeline/list?pp=20'); const data = await response.json(); // Get next page using offset const nextPage = await fetch(`/timeline/list?pp=20&offset=${data.offset}`); ``` ### Real-time Updates ```javascript // Get initial timestamp const baseline = await fetch('/timeline/changes'); const timestamp = baseline.timestamp; // Poll for changes setInterval(async () => { const changes = await fetch(`/timeline/changes?since=${timestamp}`); const data = await changes.json(); if (data.update.length > 0 || data.delete.length > 0) { // Handle updates console.log('New changes:', data); } }, 5000); ``` ### Search and Filter ```javascript // Search for production jobs const results = await fetch('/timeline/list?q=production&pp=50'); // Search for specific author const authorResults = await fetch('/timeline/list?q=author:john.doe'); ``` ## Error Handling The API returns appropriate HTTP status codes: * `200`: Successful response * `400`: Invalid parameters * `401`: Unauthorized (user not authenticated) * `500`: Internal server error # Files Upload/Download Example URL: /guides/upload-example *** title: Files Upload/Download Example icon: Upload ------------ This documentation describes the implementation of an Upload App, which demonstrates the management of `AsyncFileSystem` and `SyncFileSystem` for synchronous and asynchronous file operations. The app shows how Ray Remote Execution can be used for distributed file processing across multiple nodes. ## Overview The Upload App is an example of: * **Forms with File Upload**: Using `InputFiles` for multiple file uploads * **Ray Remote Execution**: Parallel processing of files across distributed nodes * **AsyncFileSystem vs SyncFileSystem**: Demonstration of the differences between synchronous and asynchronous file system operations * **File Download/Upload across Nodes**: Ray Remote Functions enable file operations across different nodes ## Step-by-Step Implementation ### 1. Imports and Setup ```python import asyncio, os, re, time from pathlib import Path from tempfile import mkdtemp from traceback import format_exc import fastapi import uvicorn from pptx import Presentation from ray import remote, serve from kodosumi.core import ServeAPI, forms as F, Tracer, Launch from kodosumi.response import Markdown app = ServeAPI() ``` **Explanation:** * `ray.remote`: Decorator for Remote Functions * `ray.serve`: For service deployment * `kodosumi.core`: Core components for Kodosumi apps * `pptx.Presentation`: For PowerPoint file processing (example only) ### 2. Ray Remote Function for File Processing ```python @remote def process_file(file: str, tracer: Tracer, ignore_errors: bool = True): fs = tracer.fs_sync() # SyncFileSystem for Remote Functions tempfile = next(fs.download(file)) tracer.debug_sync(f"start processing `{file}`") start_time = time.time() try: # File processing (here: PowerPoint to text) prs = Presentation(tempfile) text_runs = [ run.text for slide in prs.slides for shape in slide.shapes if shape.has_text_frame for paragraph in shape.text_frame.paragraphs for run in paragraph.runs ] # Create local markdown result file tempdir = mkdtemp() md_file = Path(tempdir) / Path(file).with_suffix(".md").name content = re.sub(r"\s+", " ", " ".join(text_runs)) with md_file.open("w") as f: f.write(content) # Upload file to the flow execution fs.upload(str(md_file)) # Remove the local result file os.remove(md_file) return { "source": file, "target": md_file.name, "length": len(text_runs), "words": len(content.split()), "runtime": time.time() - start_time, "error": None } except Exception as e: if not ignore_errors: raise RuntimeError(f"error processing `{file}`: {format_exc()}") return { "source": file, "target": None, "length": 0, "words": 0, "runtime": time.time() - start_time, "error": str(e) } finally: # Remove the downloaded file on the node os.remove(tempfile) fs.close() ``` **Key Points:** * **`@remote`**: Makes the function a Ray Remote Function * **`tracer.fs_sync()`**: Uses SyncFileSystem since Ray Remote Functions are always synchronous * **`fs.download(file)`**: Downloads file from Kodosumi File System * **`fs.upload(str(md_file))`**: Uploads processed file * **Error Handling**: Robust handling of processing errors ### 3. Main Function with AsyncFileSystem ```python async def run(inputs: dict, tracer: Tracer): afs = await tracer.fs() # AsyncFileSystem for main function files = await afs.ls("in") # List all uploaded files in `/in` folder await tracer.markdown("### File Processing Started") # Display file links file_links = [ f"* [{f['path']}](/files/{tracer.fid}/{f['path']})" for f in files] await tracer.markdown("\n".join(file_links)) # Parallel processing with Ray futures = [ process_file.remote( f['path'], tracer, ignore_errors=inputs['ignore_errors']) for f in files ] await afs.close() results = await asyncio.gather(*futures) # Summarize results output = ["### File Processing Completed"] for r in results: err = r['error'] link = f"**ERROR:** {err}" if err else f"[markdown](/files/{tracer.fid}/out/{r['target']})" output.append( f"* {r['source']} with {r['length']} text runs in {r['runtime']:.2f}s - {link}" ) return Markdown("\n".join(output)) ``` **Key Points:** * **`await tracer.fs()`**: Uses AsyncFileSystem for asynchronous operations * **`await afs.ls("in")`**: Asynchronous file listing * **`process_file.remote()`**: Starts Remote Functions * **`asyncio.gather(*futures)`**: Waits for all Remote Functions * **`await afs.close()`**: Properly closes AsyncFileSystem ### 4. Form Definition ```python model = F.Model( F.Markdown(""" # Parallel Text Processing This application demonstrates the use of: * Forms with text input and file upload * `InputsFile` for file processing * Ray for parallel processing """), F.Break(), F.HR(), F.InputFiles( label="Upload Files", name="files", multiple=True, directory=False, required=False ), F.HR(), F.Submit("Start"), F.Cancel("Cancel"), F.Checkbox( name="ignore_errors", option="ignore errors", value=True ), ) ``` **Form Elements:** * **`F.InputFiles`**: Multiple file upload * **`F.Checkbox`**: Option to ignore errors * **`F.Submit/F.Cancel`**: Action buttons ### 5. Endpoint Registration ```python @app.enter( path="/", model=model, summary="Text Processing with Ray", description="Demonstrates forms, InputsFile and Ray for parallel file processing.", version="0.1.0", author="example@kodosumi.com", organization="Kodosumi Examples", tags=["Test"] ) async def enter(request: fastapi.Request, inputs: dict): return Launch(request, "kodosumi_examples.upload.app:run", inputs=inputs) ``` ### 6. Ray Serve Deployment ```python @serve.deployment @serve.ingress(app) class TextProcessor: pass fast_app = TextProcessor.bind() # type: ignore if __name__ == "__main__": uvicorn.run( "kodosumi_examples.upload.app:app", host="0.0.0.0", port=8013, reload=True ) ``` ## AsyncFileSystem vs SyncFileSystem ### AsyncFileSystem * **Usage**: In asynchronous functions (`async def`) * **Methods**: `await fs.ls()`, `await fs.upload()`, `await fs.close()` * **Advantages**: Non-blocking, better performance for I/O operations * **Example**: Main function `run()` ### SyncFileSystem * **Usage**: In synchronous functions (Ray Remote Functions) * **Methods**: `fs.ls()`, `fs.upload()`, `fs.close()` * **Advantages**: Easier to use, compatible with Ray Remote Functions * **Example**: `process_file()` Remote Function ## Ray Remote Execution ### Why Ray Remote Functions? 1. **Distributed Processing**: Files can be processed on different nodes 2. **Scalability**: Automatic load balancing 3. **Fault Tolerance**: Error handling at node level 4. **Resource Management**: Efficient use of CPU and memory ### Remote Function Lifecycle 1. File Upload → Kodosumi File System 2. AsyncFileSystem.ls() → Get file list 3. process\_file.remote() → Start Remote Function 4. SyncFileSystem.download() → Download file to remote node 5. File Processing → PowerPoint to text 6. SyncFileSystem.upload() → Upload result 7. Result Return → To main function ## Running the Upload Example This section provides step-by-step instructions for running the Upload App example from the `kodosumi-examples` repository. ### Prerequisites 1. **Python Environment**: Python 3.12 or higher 2. **Ray Cluster**: Running Ray cluster (local or distributed) 3. **Kodosumi**: Installed and configured 4. **Dependencies**: Required packages for the example ### Setup Instructions #### 1. Clone the Examples Repository ```bash git clone https://github.com/masumi-network/kodosumi-examples.git cd kodosumi-examples ``` #### 2. Install Dependencies ```bash # Install the example package pip install -e . # Install additional dependencies for the upload example pip install python-pptx # For PowerPoint file processing ``` #### 3. Start Ray Cluster ```bash # Start Ray head node ray start --head # Verify Ray is running ray status ``` #### 4. Deploy the Upload Example Create a deployment configuration file `data/config/upload_example.yaml` and a Ray serve deployment file `data/config/config.yaml`: ```yaml # upload_example.yaml name: upload_example route_prefix: /upload import_path: kodosumi_examples.upload.app:fast_app ``` ```yaml # config.yaml proxy_location: EveryNode http_options: host: 127.0.0.1 port: 8001 grpc_options: port: 9001 grpc_servicer_functions: [] logging_config: encoding: TEXT log_level: DEBUG logs_dir: null enable_access_log: true ``` Deploy both the Ray and the service configuration: ```bash # Deploy using Kodosumi CLI koco deploy --run --file ./data/config/config.yaml ``` #### 5. Start Kodosumi Services ```bash # Start the spooler daemon koco spool # Start the panel web interface koco serve --register http://localhost:8001/-/routes ``` #### 6. Access the Application Open your web browser and navigate to [http://localhost:3370/](http://localhost:3370/). # What is Kodosumi? URL: /guides/what-is-kodosumi *** title: What is Kodosumi? icon: Info ---------- Kodosumi is the runtime environment to manage and execute agentic services at scale. The system is based on [Ray](https://ray.io) - a distributed computing framework - and a combination of [litestar](https://litestar.dev/) and [fastapi](https://fastapi.tiangolo.com/) to deliver agentic services to users or other agents. Similar to Ray, Kodosumi follows a *Python first* agenda. Kodosumi is one component of a larger eco system with [masumi and sokosumi](https://www.masumi.network/). Kodosumi consists of three main building blocks. First, a *Ray cluster* to execute agentic services at scale. Kodosumi builds on top of Ray and actively manages the lifecycle and events of service executions from *starting* to *finished* or *error*. No matter how you name your agentic component: application, flow, service or script, it's the third building block of Kodosumi. ### The following architecture shows the relation between the three building blocks: 1. Your service on top of 2) Kodosumi which operates 3) a distributed compute cluster with Ray secure and at scale. You build and deploy your [Flow](/concepts#flow) by providing an [endpoint](/concepts#endpoint) (HTTP route) and an [entrypoint](/concepts#entrypoint) (Python callable) to Kodosumi (left bottom box in the diagram). Kodosumi delivers features for [access control](/api#authentication), [flow control](/api#flow-control) and manages [flow execution](/api#execution-control) with Ray [head node](/concepts#ray-head) and [worker nodes](/concepts#ray-worker). [Kodosumi spooler](/concepts#spooler) gathers flow execution results and outputs into the [event stream](/concepts#event-stream). Deep-dive into [endpoints](/concepts#endpoint) and how these translate into [entrypoints](/concepts#entrypoint) of [flows](#flows) which operationalize the business logic of [agentic services](#agentic-service) or [agents](#agents) in the broader sense. If you need further background information read [why Kodosumi](/why) # Why Kodosumi? URL: /guides/why *** title: Why Kodosumi? icon: Lightbulb --------------- Kodosumi was built to address the challenges in developing and operating agentic services. Agentic services are autonomous systems that solve real-world problems. Agents run in distributed environments and require effective coordination, monitoring, and interaction. Kodosumi provides a comprehensive solution to these challenges through: 1. **Real-time Monitoring and Interaction** * Live monitoring of running services * Direct interaction with active services * Real-time logging of `stdio` (`stdout`, `stderr`), and debug messages 2. **Event-driven Architecture** * Coordination of multiple services through a unified event system * Tracking of service states (starting, running, finished, error) * Structured capture of metadata and results 3. **Unified API** * Easy discovery of available services * Standardized service execution * Access to execution results and event streams These capabilities allow developers to focus on their agentic service logic while Kodosumi provides the infrastructure for scaling, monitoring, and interaction. *Note: Kodosumi is built on [ray](https://ray.io) for distributed computing and [fastapi](https://fastapi.tiangolo.com/) for user interfaces. For setup details, see [kodosumi + ray startup](https://github.com/masumi-network/kodosumi).* # /api-key URL: /api-reference/payment-service/delete-api-key *** title: "/api-key" full: true \_openapi: method: DELETE toc: \[] structuredData: headings: \[] contents: * content: Removes a API key *** {/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} Removes a API key # /payment-source-extended URL: /api-reference/payment-service/delete-payment-source-extended *** title: "/payment-source-extended" full: true \_openapi: method: DELETE toc: \[] structuredData: headings: \[] contents: * content: >- Deletes a payment source. WARNING will also delete all associated wallets and transactions. *** {/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} Deletes a payment source. WARNING will also delete all associated wallets and transactions. # /registry URL: /api-reference/payment-service/delete-registry *** title: "/registry" full: true \_openapi: method: DELETE toc: \[] structuredData: headings: \[] contents: * content: >- Deregisters a agent from the specified registry (Please note that while the command is put on-chain, the transaction is not yet finalized by the blockchain, as designed finality is only eventually reached. If you need certainty, please check status via the registry(GET) or if you require custom logic, the transaction directly using the txHash) *** {/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} Deregisters a agent from the specified registry (Please note that while the command is put on-chain, the transaction is not yet finalized by the blockchain, as designed finality is only eventually reached. If you need certainty, please check status via the registry(GET) or if you require custom logic, the transaction directly using the txHash) # /api-key-status URL: /api-reference/payment-service/get-api-key-status *** title: "/api-key-status" full: true \_openapi: method: GET toc: \[] structuredData: headings: \[] contents: * content: Gets api key status *** {/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} Gets api key status # /api-key URL: /api-reference/payment-service/get-api-key *** title: "/api-key" full: true \_openapi: method: GET toc: \[] structuredData: headings: \[] contents: * content: Gets api key status *** {/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} Gets api key status # /health URL: /api-reference/payment-service/get-health *** title: "/health" full: true \_openapi: method: GET toc: \[] structuredData: headings: \[] contents: \[] ------------- {/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} # /payment-source-extended URL: /api-reference/payment-service/get-payment-source-extended *** title: "/payment-source-extended" full: true \_openapi: method: GET toc: \[] structuredData: headings: \[] contents: * content: Gets the payment contracts including the status. *** {/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} Gets the payment contracts including the status. # /payment-source URL: /api-reference/payment-service/get-payment-source *** title: "/payment-source" full: true \_openapi: method: GET toc: \[] structuredData: headings: \[] contents: * content: Gets the payment source. *** {/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} Gets the payment source. # /payment URL: /api-reference/payment-service/get-payment *** title: "/payment" full: true \_openapi: method: GET toc: \[] structuredData: headings: \[] contents: * content: >- Gets the payment status. It needs to be created first with a POST request. *** {/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} Gets the payment status. It needs to be created first with a POST request. # /purchase URL: /api-reference/payment-service/get-purchase *** title: "/purchase" full: true \_openapi: method: GET toc: \[] structuredData: headings: \[] contents: * content: >- Gets the purchase status. It needs to be created first with a POST request. *** {/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} Gets the purchase status. It needs to be created first with a POST request. # /registry-wallet URL: /api-reference/payment-service/get-registry-wallet *** title: "/registry-wallet" full: true \_openapi: method: GET toc: \[] structuredData: headings: \[] contents: * content: Gets the agent metadata. *** {/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} Gets the agent metadata. # /registry URL: /api-reference/payment-service/get-registry *** title: "/registry" full: true \_openapi: method: GET toc: \[] structuredData: headings: \[] contents: * content: Gets the agent metadata. *** {/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} Gets the agent metadata. # /rpc-api-keys URL: /api-reference/payment-service/get-rpc-api-keys *** title: "/rpc-api-keys" full: true \_openapi: method: GET toc: \[] structuredData: headings: \[] contents: * content: Gets rpc api keys, currently only blockfrost is supported (internal) *** {/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} Gets rpc api keys, currently only blockfrost is supported (internal) # /utxos URL: /api-reference/payment-service/get-utxos *** title: "/utxos" full: true \_openapi: method: GET toc: \[] structuredData: headings: \[] contents: * content: Gets UTXOs (internal) *** {/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} Gets UTXOs (internal) # /wallet URL: /api-reference/payment-service/get-wallet *** title: "/wallet" full: true \_openapi: method: GET toc: \[] structuredData: headings: \[] contents: * content: Gets wallet status *** {/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} Gets wallet status # /api-key URL: /api-reference/payment-service/patch-api-key *** title: "/api-key" full: true \_openapi: method: PATCH toc: \[] structuredData: headings: \[] contents: * content: Creates a API key *** {/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} Creates a API key # /payment-source-extended URL: /api-reference/payment-service/patch-payment-source-extended *** title: "/payment-source-extended" full: true \_openapi: method: PATCH toc: \[] structuredData: headings: \[] contents: * content: Updates a payment source. *** {/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} Updates a payment source. # /api-key URL: /api-reference/payment-service/post-api-key *** title: "/api-key" full: true \_openapi: method: POST toc: \[] structuredData: headings: \[] contents: * content: Creates a API key *** {/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} Creates a API key # /payment-authorize-refund URL: /api-reference/payment-service/post-payment-authorize-refund *** title: "/payment-authorize-refund" full: true \_openapi: method: POST toc: \[] structuredData: headings: \[] contents: * content: >- Authorizes a refund for a payment request. This will stop the right to receive a payment and initiate a refund for the other party. *** {/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} Authorizes a refund for a payment request. This will stop the right to receive a payment and initiate a refund for the other party. # /payment-resolve-blockchain-identifier URL: /api-reference/payment-service/post-payment-resolve-blockchain-identifier *** title: "/payment-resolve-blockchain-identifier" full: true \_openapi: method: POST toc: \[] structuredData: headings: \[] contents: * content: Resolves a payment request by its blockchain identifier. *** {/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} Resolves a payment request by its blockchain identifier. # /payment-source-extended URL: /api-reference/payment-service/post-payment-source-extended *** title: "/payment-source-extended" full: true \_openapi: method: POST toc: \[] structuredData: headings: \[] contents: * content: Creates a payment source. *** {/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} Creates a payment source. # /payment-submit-result URL: /api-reference/payment-service/post-payment-submit-result *** title: "/payment-submit-result" full: true \_openapi: method: POST toc: \[] structuredData: headings: \[] contents: * content: >- Submit the hash of their completed job for a payment request, which triggers the fund unlock process so the seller can collect payment after the unlock time expires. (admin access required +PAY) *** {/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} Submit the hash of their completed job for a payment request, which triggers the fund unlock process so the seller can collect payment after the unlock time expires. (admin access required +PAY) # /payment URL: /api-reference/payment-service/post-payment *** title: "/payment" full: true \_openapi: method: POST toc: \[] structuredData: headings: \[] contents: * content: >- Creates a payment request and identifier. This will check incoming payments in the background. *** {/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} Creates a payment request and identifier. This will check incoming payments in the background. # /purchase-cancel-refund-request URL: /api-reference/payment-service/post-purchase-cancel-refund-request *** title: "/purchase-cancel-refund-request" full: true \_openapi: method: POST toc: \[] structuredData: headings: \[] contents: * content: >- Requests a refund for a completed purchase. This will collect the refund after the refund time. *** {/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} Requests a refund for a completed purchase. This will collect the refund after the refund time. # /purchase-request-refund URL: /api-reference/payment-service/post-purchase-request-refund *** title: "/purchase-request-refund" full: true \_openapi: method: POST toc: \[] structuredData: headings: \[] contents: * content: >- Requests a refund for a completed purchase. This will collect the refund after the refund time. *** {/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} Requests a refund for a completed purchase. This will collect the refund after the refund time. # /purchase-resolve-blockchain-identifier URL: /api-reference/payment-service/post-purchase-resolve-blockchain-identifier *** title: "/purchase-resolve-blockchain-identifier" full: true \_openapi: method: POST toc: \[] structuredData: headings: \[] contents: * content: Resolves a purchase request by its blockchain identifier. *** {/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} Resolves a purchase request by its blockchain identifier. # /purchase URL: /api-reference/payment-service/post-purchase *** title: "/purchase" full: true \_openapi: method: POST toc: \[] structuredData: headings: \[] contents: * content: >- Creates a purchase and pays the seller. This requires funds to be available. *** {/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} Creates a purchase and pays the seller. This requires funds to be available. # /registry URL: /api-reference/payment-service/post-registry *** title: "/registry" full: true \_openapi: method: POST toc: \[] structuredData: headings: \[] contents: * content: >- Registers an agent to the registry (Please note that while it it is put on-chain, the transaction is not yet finalized by the blockchain, as designed finality is only eventually reached. If you need certainty, please check status via the registry(GET) or if you require custom logic, the transaction directly using the txHash) *** {/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} Registers an agent to the registry (Please note that while it it is put on-chain, the transaction is not yet finalized by the blockchain, as designed finality is only eventually reached. If you need certainty, please check status via the registry(GET) or if you require custom logic, the transaction directly using the txHash) # /wallet URL: /api-reference/payment-service/post-wallet *** title: "/wallet" full: true \_openapi: method: POST toc: \[] structuredData: headings: \[] contents: * content: >- Creates a wallet, it will not be saved in the database, please ensure to remember the mnemonic *** {/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} Creates a wallet, it will not be saved in the database, please ensure to remember the mnemonic # /api-key URL: /api-reference/registry-service/delete-api-key *** title: "/api-key" full: true \_openapi: method: DELETE toc: \[] structuredData: headings: \[] contents: * content: Removes a API key *** {/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} Removes a API key # /registry-source URL: /api-reference/registry-service/delete-registry-source *** title: "/registry-source" full: true \_openapi: method: DELETE toc: \[] structuredData: headings: \[] contents: * content: Updates a registry source *** {/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} Updates a registry source # /api-key-status URL: /api-reference/registry-service/get-api-key-status *** title: "/api-key-status" full: true \_openapi: method: GET toc: \[] structuredData: headings: \[] contents: * content: Gets the status of an API key *** {/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} Gets the status of an API key # /api-key URL: /api-reference/registry-service/get-api-key *** title: "/api-key" full: true \_openapi: method: GET toc: \[] structuredData: headings: \[] contents: * content: Gets registry sources, can be paginated *** {/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} Gets registry sources, can be paginated # /capability URL: /api-reference/registry-service/get-capability *** title: "/capability" full: true \_openapi: method: GET toc: \[] structuredData: headings: \[] contents: * content: Gets all capabilities that are currently online *** {/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} Gets all capabilities that are currently online # /health URL: /api-reference/registry-service/get-health *** title: "/health" full: true \_openapi: method: GET toc: \[] structuredData: headings: \[] contents: \[] ------------- {/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} # /payment-information URL: /api-reference/registry-service/get-payment-information *** title: "/payment-information" full: true \_openapi: method: GET toc: \[] structuredData: headings: \[] contents: * content: Get payment information for a registry entry *** {/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} Get payment information for a registry entry # /registry-source URL: /api-reference/registry-service/get-registry-source *** title: "/registry-source" full: true \_openapi: method: GET toc: \[] structuredData: headings: \[] contents: * content: Gets all registry sources *** {/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} Gets all registry sources # /api-key URL: /api-reference/registry-service/patch-api-key *** title: "/api-key" full: true \_openapi: method: PATCH toc: \[] structuredData: headings: \[] contents: * content: Updates a API key *** {/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} Updates a API key # /registry-source URL: /api-reference/registry-service/patch-registry-source *** title: "/registry-source" full: true \_openapi: method: PATCH toc: \[] structuredData: headings: \[] contents: * content: Updates a registry source *** {/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} Updates a registry source # /api-key URL: /api-reference/registry-service/post-api-key *** title: "/api-key" full: true \_openapi: method: POST toc: \[] structuredData: headings: \[] contents: * content: Create a new API key *** {/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} Create a new API key # /registry-diff URL: /api-reference/registry-service/post-registry-diff *** title: "/registry-diff" full: true \_openapi: method: POST toc: \[] structuredData: headings: \[] contents: * content: >- Query registry entries whose status was updated after the provided timestamp. Supports pagination. Always use statusUpdatedAt of the last item + its cursorId to paginate forward. This guarantees to include all items at least once, when paginating. Note: if the cursorId is not valid it will include all items with an id greater than the cursorId (in string comparison order). If no cursorId is provided, all items, including those with the same statusUpdatedAt, will be included. In case the statusUpdatedAt is before the provided statusUpdatedAfter, all items after the statusUpdatedAfter will be included, regardless of the cursorId. *** {/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} Query registry entries whose status was updated after the provided timestamp. Supports pagination. Always use statusUpdatedAt of the last item + its cursorId to paginate forward. This guarantees to include all items at least once, when paginating. Note: if the cursorId is not valid it will include all items with an id greater than the cursorId (in string comparison order). If no cursorId is provided, all items, including those with the same statusUpdatedAt, will be included. In case the statusUpdatedAt is before the provided statusUpdatedAfter, all items after the statusUpdatedAfter will be included, regardless of the cursorId. # /registry-entry URL: /api-reference/registry-service/post-registry-entry *** title: "/registry-entry" full: true \_openapi: method: POST toc: \[] structuredData: headings: \[] contents: * content: >- Query the registry for available and online (health-checked) entries. Registry filter, allows pagination, filtering by payment type and capability and optional date filters (to force update any entries checked before the specified date. Warning: this might take a bit of time as response is not cached). If no filter is set, only online entries are returned. *** {/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} Query the registry for available and online (health-checked) entries. Registry filter, allows pagination, filtering by payment type and capability and optional date filters (to force update any entries checked before the specified date. Warning: this might take a bit of time as response is not cached). If no filter is set, only online entries are returned. # /registry-source URL: /api-reference/registry-service/post-registry-source *** title: "/registry-source" full: true \_openapi: method: POST toc: \[] structuredData: headings: \[] contents: * content: Creates a new registry source *** {/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} Creates a new registry source