nasiko

Nasiko-Labs活跃维护

用 Rust 写的 AI Agent 开放运行时,统一托管多智能体、MCP 网关和权限管控,省去自己搭调度与安全层的麻烦。

The Open Runtime for AI Agents

v1.0.0发布于 2026-02-12 11:29 UTC11,968star +31 / 7 天3,274fork同步于 2026-11-10 23:29 UTC

怎么上手

由 README 提炼,执行前请确认
git clone https://github.com/Nasiko-Labs/nasiko.git && cd nasiko
cp .env.example .env
docker compose up -d
硬件要求按 README 说明

README 未说明 GPU 或显存要求;服务端以 Docker Compose 运行,包含 Postgres、Redis、RustFS、OTel Collector、Tempo、Loki 与 nasiko-server。

有 Docker 镜像
下载:源码 zip·GitHub Releases(安装包)本站不镜像安装包

项目说明

由 README 自动翻译提炼

统一纳管本机编码 Agent 的运行时,把各家花费归一到一套口径并按你选的模型路由

用 Rust 写的 AI Agent 开放运行时,统一托管多智能体、MCP 网关和权限管控,省去自己搭调度与安全层的麻烦。

核心能力

  • 用 nasiko agents discover 只读探测已装编码 Agent
  • 按 harness 与模型汇总各编码 Agent 的花费
  • 把编码 Agent 的 LLM 调用路由到自选模型
  • 通过事件钩子采集会话上报,可关闭内容采集
  • Docker Compose 一键拉起服务端与可观测栈

容易踩的坑

启动前必须改 .env:SECRETS_ENCRYPTION_KEY 需为 32 字节、AGENT_JWT_SECRET 要单独生成,并填真实 OPENAI_API_KEY;改完 .env 要用 docker compose up -d 重建,restart 不会重载环境变量;CLI 需 Rust 编译安装,旧构建版本号同为 0.1.0,需 --force 重装才有 agents discover;OpenCode 装插件后需重启,Codex 需手动信任新钩子。

内容由模型根据 README 生成,可能不完整或不准确,以官方文档为准。看原文 README

最近发版

1 个版本v1.0.0
  1. v1.0.0Nasiko v1.0.0 - Desktop App for macOS最新正式版2026-02-12 · 2026-02-12 11:29 UTC

    Nasiko v1.0.0 - Initial Release

    🖥️ Desktop App (macOS)

    This release includes the native macOS desktop application for Nasiko.

    Download & Install

    1. Download: Click on nasiko.zip below to download
    2. Extract: Unzip the downloaded file
    3. Install: Move Nasiko.app to your Applications folder
    4. Permissions: Run xattr -dr com.apple.quarantine /Applications/Nasiko.app if needed

    Features

    • 🎯 Native macOS interface built with SwiftUI
    • 🔄 Real-time agent monitoring and status
    • 📊 Visual analytics dashboard
    • 🚀 One-click agent deployment
    • 🔍 Integrated agent browser
    • 💬 Built-in chat interface for testing agents
    • 🔔 System notifications for agent events

    System Requirements

    • macOS 12.0 (Monterey) or later
    • Apple Silicon (M1/M2) or Intel processor
    • 4GB RAM minimum, 8GB recommended

    For complete setup instructions, see the README.md.

用户点评

登录用户的真实使用记录,可按版本和硬件筛选
共 0 条

    还没有人点评。第一个说出真实体验的人最有价值。

    README 原文

    未翻译

    Nasiko

    Nasiko is the OpenRuntime for agents, coding harnesses, frameworks and tools. Find the coding agents already running. See what each one costs, by harness and model. Route them to the models you choose while developers keep using their existing harness workflows.

    📚 Documentation  •  💬 Discord  •  🐛 Report a bug

    GitHub stars GitHub forks Latest release License: Apache-2.0

    Built with Rust Open issues Pull requests CI PRs Welcome

    Table of Contents

    What is Nasiko?

    Your developers are already running coding agents. Claude Code, Codex, Cursor, OpenCode, often more than one at a time, each with its own dashboard and its own billing unit. At the end of the month there is a real bill, and nobody can say who spent it, on which model, or what it produced.

    Nasiko finds those agents, puts their spend in one schema, and hands back the model choice the harness made for you. An OpenRuntime belongs to no vendor inside it: your cost history, your policy and your model choice stay with you rather than with whichever harness you happened to install.

    Two properties hold the rest of this together, and they are not the same thing.

    Non-invasive discovery is about your architecture. Nasiko can detect the harnesses you already run without changing their configuration. Reporting and routing are explicit opt-ins that add hooks or update harness settings, but they require no wrapper or SDK in your application code.

    Non-intrusive operation is about your day. After one-time setup, developers keep using the same harness commands and interfaces. OpenCode must be restarted after its plugin is installed, and Codex asks you to trust the new Nasiko hooks.

    Nasiko Dashboard Nasiko Dashboard: deploy agents, route traffic, manage tools, and watch traces.

    Start here: see what is already running

    Install the current CLI from this checkout, then run its read-only discovery command:

    git clone https://github.com/Nasiko-Labs/nasiko.git && cd nasiko
    cargo install --path cli/ --force
    nasiko agents discover
    

    nasiko agents discover reads your machine, prints what it finds, and exits. You get a row per supported harness: whether it is detected, whether it is reporting to Nasiko, its version, and where its config lives. Discovery changes no harness settings, requires no account, and sends nothing off the machine.

    If nasiko agents discover is not listed by nasiko agents --help, reinstall with the --force command above. Older builds use the same 0.1.0 version number, so nasiko --version alone cannot tell you whether the command is present.

    HarnessDiscovery and session reportingLLM routing
    Claude Codeyesyes
    Codexyesyes
    OpenCodeyesyes
    Cursor CLIyesnot yet

    Those four are the supported set today and we will continue to add support for me. If you have a request please create an issue!

    The CLI is a Rust crate, so this path needs Rust. Reporting and routing also need an active control plane and valid login: see Quick Start.

    Coding agents: reporting and routing

    Nasiko manages the coding-agent CLIs already installed on your machine: it can record what they do, route their LLM calls, or both. The two are independent opt-ins. agents uninstall removes local reporting hooks but preserves the registered agent and its history; disconnect restores routing settings, though a running harness may need to be stopped or disconnected with --force.

    Session reporting

    nasiko agents discover           # DETECTED / CONNECTED / VERSION / CONFIG per agent
    nasiko agents install <agent>    # claude, codex, opencode, or cursor
    nasiko agents install <agent> --no-content  # omit prompt and response text
    nasiko agents uninstall <agent>
    nasiko agents sync               # flush queued session-turn events to the control plane
    

    Auto-install also fires on nasiko connect <control-plane-url>, nasiko use, and nasiko auth login, but only when authenticated and only for agents with no existing install record. Routing commands such as nasiko connect claude do not trigger auto-install. Nasiko never silently rebinds an already-installed agent to a new cluster; rebind explicitly with nasiko agents install <agent>.

    Nasiko installs each harness's supported event hooks: Claude uses Stop, OpenCode reports on session.idle, and Codex and Cursor use their richer hook event sets. Completed turns are queued locally under ~/.nasiko/integrations/queue/ and delivered to POST /api/telemetry/coding-agent/events/batch, so a control plane that is briefly unreachable costs you nothing. Permanently-failed deliveries (after a cluster is deleted or renamed, say) land in ~/.nasiko/integrations/rejected/ and are safe to delete.

    Ingested turns show up as chat sessions right away (nasiko sessions, nasiko history <id>). Traces, token counts, and cost additionally require CODING_AGENT_OTLP_ENDPOINT, covered below.

    Routing: give the harness back the model choice

    Some harnesses arrive tied to a provider by default. Claude Code defaults to Anthropic; Codex defaults to OpenAI. That choice arrived with the tool rather than with you.

    Register a provider and key with Nasiko once, then point a harness at Nasiko instead of at its vendor. Inbound wire protocol and outbound provider are decoupled, so an Anthropic-format request from Claude Code is served by your OpenAI key:

    nasiko llm-config create --name my-openai --provider openai --model gpt-4o \
      --api-key-secret OPENAI_API_KEY --secret-value "$OPENAI_API_KEY"
    nasiko connect claude --config my-openai
    

    The full set of bindings:

    nasiko connect claude --config <llm-config-name>
    nasiko connect codex --config <llm-config-name>
    nasiko connect opencode --config <llm-config-name>
    nasiko disconnect <agent>        # reverse the settings/plugin changes
    nasiko status <agent>            # show current binding
    

    connect claude configures Claude Code's apiKeyHelper and ANTHROPIC_BASE_URL. connect codex adds a model_providers.nasiko entry and command-based auth to Codex's config.toml. Their credential helpers obtain a one-hour JWT from POST /api/agents/{id}/llm-token; Codex refreshes its credential on a timer rather than minting one for every request. Claude Code sends the credential in the x-api-key header rather than Authorization; the router accepts both.

    connect opencode instead installs plugins/nasiko-llm-router.js under OpenCode's config directory (respecting OPENCODE_CONFIG_DIR and XDG_CONFIG_HOME), registers a nasiko provider, and makes nasiko/router the default model for new OpenCode sessions only. OpenCode fixes a session's model at creation time in its own database, so resuming an existing session will not route it. Start a new one, or pick "Nasiko Router" explicitly.

    Inbound wire protocol and outbound provider are fully decoupled: nasiko connect claude --config my-openai-config routes Claude Code's Anthropic-format traffic to OpenAI.

    ~/.claude/settings.json and OpenCode's config are per-user, global files. Connecting an agent affects every Claude Code / OpenCode process on the machine, not just the current project.

    LLM config management

    nasiko llm-config create --name <name> --provider <provider> --model <model>
    nasiko llm-config list
    nasiko llm-config update <name> [--provider ...] [--model ...]
    nasiko llm-config set-default <name>
    nasiko llm-config attach <name> --agent <agent>     # attach to a deployed agent
    nasiko llm-config detach --agent <agent>
    nasiko llm-config get <agent>                       # resolved routing config for an agent
    nasiko llm-config providers                         # valid provider/model values + pricing
    

    Set a config's model for predictable routing. If it is unset, the router falls back through tier models and then the platform default; list shows provider/? for the unset value.

    Server configuration

    Everything is env-driven. server/.env.example is the complete annotated reference — every variable the server reads, grouped, with its default; the root .env.example is the shorter docker-compose quick start. Two that are easy to miss:

    VariablePurpose
    AGENT_JWT_SECRETRequired for local coding-agent routing, including custom LLM configs. Signs agent identity tokens separately from user-login JWTs and provider credentials. Missing/empty ⇒ /api/agents/{id}/llm-token returns 503; gateway authentication fails closed.
    CODING_AGENT_OTLP_ENDPOINTServer-side OTLP/HTTP export for coding-agent telemetry. Compose already sets http://otel-collector:4318; for a host-run server with local infra, set http://localhost:4318 in server/.env. Unset leaves receipts pending without an export worker.

    Features

    Three things break first when an agent estate grows: spend nobody can attribute, permissions nobody can enumerate, and a fleet nobody can run as one system. The feature set is grouped accordingly.

    TokenOps

    FeatureWhat it does
    LLM RouterAgents get an OPENAI_BASE_URL and a short-lived identity token instead of a real key. The router resolves provider, model, and key server-side. No agent and no log ever sees a real API key.
    Cost and token attributionUsage and cost are collected from gen_ai.* span attributes and broken out per agent and per model, rather than arriving as a single provider invoice you have to reverse-engineer.
    One trace per interactionEvery dispatch and proxy hop emits a real OTel span, so a request is one end-to-end trace across every agent hop, with cost attached.

    Policy, security, and governance

    FeatureWhat it does
    Single ingress, always proxiedAgents are never publicly reachable. Every agent-to-agent call is proxied through the server, so limits and tracing apply at every hop rather than only the first.
    Access control, for agents as well as peopleUser-to-agent ownership and grants, plus an agent-to-agent allowlist. The two gate every proxy call independently.
    MCP GatewayOne permanent URL gives every agent a merged, permission-filtered view of Composio toolkits and custom MCP servers, without the agent ever holding the credentials. An agent discovers only what it was granted.
    Flow guardsRedis-backed cascade limits (depth, fan-out, token budget, timeout, cycle detection) stop a loop between two agents from quietly consuming your budget.
    Encrypted secretsPer-agent secrets are encrypted at rest with AES-256-GCM and injected only at deploy time, so they stay out of your repo.

    Orchestration

    FeatureWhat it does
    Deploy anything that speaks A2Anasiko deploy builds, pushes to the embedded registry, and runs it. No external registry required, and no SDK to adopt.
    Intelligent routing engineA 3-stage pipeline: shortlist by embedding similarity, rerank on conversation context, then an LLM makes the final pick. Callers do not need to know your fleet.
    Embedded OCI registrySelf-hosted, S3-backed, with layer dedup, so nasiko push and nasiko deploy need nothing external.
    CLI-first, no lock-innasiko new, run, chat, deploy. Bring your own LLM provider, and change it later.

    Why this runs in the call path

    There is no version of this that works from the outside, and saying so plainly is the shortest way to explain the architecture below.

    Every coding-agent vendor meters in its own private unit, and none of them offers an ingest endpoint for another vendor's usage. There is no invoice reconciliation that produces one number, and no procurement policy that produces one either. To attribute spend across vendors you have to be in the path where the calls happen.

    Enforcement has the same shape. A budget cap or a tool allowlist is only real if it sits where the request goes, rather than in a review meeting or in an environment variable a developer can unset.

    That constraint is what the next section is a picture of.

    Architecture

    Nasiko is a single process with no separate gateway. Every inter-agent call is proxied back through the server, the single chokepoint where flow limits, ACLs, and observability are enforced. Durable state lives in Postgres, Redis, and S3 (RustFS), with optional observability via Tempo / Loki / the OTel Collector.

    flowchart LR
        subgraph Clients["Clients"]
            UI["Web Dashboard (embedded)"]
            CLI["nasiko CLI"]
        end
    
        subgraph CP["nasiko-server (single control-plane process)"]
            direction TB
            API["REST API (agents, builds, uploads)"]
            AUTH["Auth (session JWT, TLS, rate-limit, ACLs)"]
            OIDC["OIDC client (SSO)"]
            ROUTE["Routing engine (shortlist, rerank, select)"]
            PROXY["A2A Proxy (generic agent reverse-proxy)"]
            MCP["MCP Gateway (tools/list, tools/call, OAuth)"]
            LLM["LLM Router (OpenAI-compatible egress)"]
            OCI["Embedded OCI registry (/v2/*)"]
            FLOW["Flow guards (depth, fan-out, token budget, cycles)"]
            SECRETS["Secrets engine (AES-256-GCM)"]
            GITHUB["GitHub App integration"]
        end
    
        subgraph Infra["Backing services (Docker)"]
            PG[(Postgres)]
            RD[(Redis)]
            S3[(RustFS S3)]
            OTEL["OTel Collector"]
            TEMPO["Tempo"]
            LOKI["Loki"]
        end
    
        subgraph Agents["Agent containers (Docker runtime)"]
            A1["Agent A"]
            A2["Agent B"]
            A3["Agent C"]
        end
    
        UI --> API
        CLI --> API
        CLI -. "push / pull images" .-> OCI
    
        API --> ROUTE
        API --> GITHUB
        ROUTE --> FLOW
        OIDC --> AUTH
        SECRETS --> API
    
        AUTH -. gates .-> API
        AUTH -. gates .-> MCP
        AUTH -. gates .-> OCI
        AUTH -. gates .-> PROXY
    
        ROUTE -- "selected agent, direct call" --> Agents
        PROXY -. "proxied A2A calls (bypasses routing)" .-> A1
        PROXY -. "proxied A2A calls (bypasses routing)" .-> A2
        PROXY -. "proxied A2A calls (bypasses routing)" .-> A3
        OCI -- "image pull at deploy" --> Agents
        Agents -- "OPENAI_BASE_URL" --> LLM
        Agents -- "tools/list, tools/call" --> MCP
    
        OCI --> S3
        FLOW --> RD
        SECRETS --> PG
        AUTH --> PG
        CP --> OTEL --> TEMPO
        OTEL --> LOKI
    

    Every request to an agent is either dispatched by the routing engine or proxied generically, and both paths originate inside the server. Agents never receive a direct, public request, and both call back out into the LLM Router and MCP Gateway rather than holding real API keys or tool credentials.

    Requirements

    ComponentMinimum versionWhy
    Docker Engine + Compose V2Compose V2 plugin (the docker compose command, not the legacy standalone docker-compose v1 binary)docker-compose.yml uses the extended depends_on: condition: service_healthy syntax; the Docker-only path needs nothing else.
    Rust1.85+ (stable)The workspace targets edition = "2024" (see [Cargo.toml](https://github.com/Nasiko-Labs/nasiko/blob/HEAD/Cargo.toml)), stabilized in Rust 1.85, and is only needed for the CLI / Path B developer setup, not the Docker-only path.

    Quick Start: Docker only (no Rust needed)

    The fastest way to run Nasiko requires only Docker (with Compose). The server builds itself from source inside Docker.

    1. Clone and configure

    git clone https://github.com/Nasiko-Labs/nasiko.git
    cd nasiko
    cp .env.example .env
    

    Edit .env before starting:

    • SECRETS_ENCRYPTION_KEY: generate with openssl rand -base64 32 (exactly 32 decoded bytes).
    • JWT_SECRET: generate with openssl rand -base64 48 for user-login tokens.
    • AGENT_JWT_SECRET: generate a separate value with openssl rand -base64 48. Required for local coding-agent LLM routing (nasiko connect claude|codex|opencode --config <name>), including custom LLM configs. Keep this signing secret on the server; clients obtain short-lived tokens.
    • ADMIN_USERNAME / ADMIN_PASSWORD: first-login credentials; replace the demo password.
    • OPENAI_API_KEY: a real provider key for the default OpenAI-backed routing/chat setup. Local coding agents using a custom LLM config can instead use its stored provider credentials; those credentials do not replace AGENT_JWT_SECRET.

    Keep the supplied S3_* credentials aligned with the bundled RustFS service. They are development credentials, not automatically generated secrets. Do not regenerate SECRETS_ENCRYPTION_KEY on routine restarts: existing encrypted secrets need the same key.

    No manual telemetry URLs are needed for Compose. It sets TEMPO_URL, LOKI_URL, OTEL_EXPORTER_OTLP_ENDPOINT, OTEL_COLLECTOR_ENDPOINT, and CODING_AGENT_OTLP_ENDPOINT, as well as DATABASE_URL, REDIS_URL, S3_ENDPOINT, MCP_GATEWAY_PUBLIC_URL, LLM_GATEWAY_BASE_URL, and DOCKER_AGENT_NETWORK. These Compose environment entries override .env values. Local coding clients connect to http://localhost:8080 (or your reachable server URL), not the Docker-only http://server:8080 address.

    After editing an existing .env, run docker compose up -d to recreate the server with the new values; docker compose restart alone does not reload its environment.

    2. Start the platform

    docker compose up -d
    

    This builds the server image and starts the full stack: Postgres · Redis · RustFS (S3) · OTel Collector · Tempo · Loki · nasiko-server.

    • First build takes a few minutes (compiles Rust inside Docker), and subsequent builds are fast.
    • Open http://localhost:8080 for the dashboard and log in with ADMIN_USERNAME / ADMIN_PASSWORD (default admin / changeme).
    docker compose logs -f server   # follow server logs
    docker compose down             # stop everything
    docker compose up -d --build    # rebuild after pulling new changes
    

    No Docker? Use the Developer / Rust setup below.

    Setup guides by operating system

    You have two supported paths:

    PathRequiresBest for
    A. Docker-onlyDocker onlyAnyone who just wants to run the platform
    B. Source / RustRust + justContributors, developers, hot-reload

    Path A: Docker-only

    Windows

    1. Install Docker Desktop -> https://www.docker.com/products/docker-desktop/
    2. Open Docker Desktop and wait until the engine is running.
    3. In a terminal (PowerShell or Git Bash):
     git clone https://github.com/Nasiko-Labs/nasiko.git
     cd nasiko
     Copy-Item .env.example .env
     # edit .env -> set OPENAI_API_KEY and ADMIN_PASSWORD
     docker compose up -d
    
    1. Open http://localhost:8080 and log in.

    Windows troubleshooting: see the Troubleshooting section (port conflicts, line endings, encryption key, WSL, Docker Desktop).

    macOS

    1. Install Docker Desktop for Mac -> https://www.docker.com/products/docker-desktop/
    2. Open Docker Desktop until the engine is running.
    3. In Terminal:
     git clone https://github.com/Nasiko-Labs/nasiko.git
     cd nasiko
     cp .env.example .env
     # edit .env -> set OPENAI_API_KEY and ADMIN_PASSWORD
     docker compose up -d
    
    1. Open http://localhost:8080 and log in.

    host.docker.internal resolves out of the box on Docker Desktop (macOS + Windows), so agents can reach the MCP gateway without extra setup.

    Linux

    1. Install Docker engine + Compose plugin -> https://docs.docker.com/engine/install/
    2. Add your user to the docker group and re-login:
     sudo usermod -aG docker "$USER"
     newgrp docker
    
    1. In a terminal:
     git clone https://github.com/Nasiko-Labs/nasiko.git
     cd nasiko
     cp .env.example .env
     # edit .env -> set OPENAI_API_KEY and ADMIN_PASSWORD
     docker compose up -d
    
    1. Open http://localhost:8080 and log in.

    Linux note: native Docker does not provide host.docker.internal automatically. If agents report [Errno -2] Name or service not known, run Docker with --add-host host.docker.internal:host-gateway or set MCP_GATEWAY_PUBLIC_URL to the bridge IP (see Troubleshooting).

    Toolchain setup: Rust, just, etc. (for the CLI / Path B)

    Windows

    # 1. Rust (installs rustup + stable toolchain)
    winget install --id Rustlang.Rustup -e
    # (reopen your terminal, then verify)
    rustc --version; cargo --version
    
    # 2. `just` command runner + cargo-watch (after Rust is installed)
    cargo install just cargo-watch
    
    # 3. If you plan to build native Windows binaries, also install the C++ linkers:
    winget install Microsoft.VisualStudio.2022.BuildTools --override "--wait --quiet --add Microsoft.VisualStudio.Workload.VCTools --includeRecommended"
    

    Building the CLI does not require the C++Build Tools, since it uses a pure-Rust toolchain. The C++ linkers are only needed if native crates (e.g. ring) fail to link on the MSVC toolchain.

    macOS

    # 1. Xcode Command Line Tools (provides the C toolchain/linker)
    xcode-select --install
    
    # 2. Rust
    curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
    source "$HOME/.cargo/env"
    
    # 3. `just` + cargo-watch
    cargo install just cargo-watch
    

    Linux (Debian/Ubuntu)

    # 1. Build dependencies (cc, OpenSSL)
    sudo apt update && sudo apt install -y build-essential pkg-config libssl-dev
    
    # 2. Rust
    curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
    source "$HOME/.cargo/env"
    
    # 3. `just` + cargo-watch
    cargo install just cargo-watch
    
    # 4. Docker engine + compose (if not using Docker Desktop)
    #    See https://docs.docker.com/engine/install/ubuntu/
    

    After setup, verify everything:

    rustc --version   && cargo --version
    just --version
    docker --version  && docker compose version
    

    Path B: Developer / Rust setup

    Requires Rust (rustup), **[just](https://github.com/casey/just)** (cargo install just), and Docker.

    # 1. Start infrastructure only (Postgres, Redis, RustFS, OTel stack)
    just infra
    
    # 2. Configure the server env
    cp server/.env.example server/.env
    # edit server/.env -> set OPENAI_API_KEY at minimum
    
    # 3. Run the server natively (hot-reload)
    just dev
    # ...or without hot-reload:
    just run
    

    The server runs on http://localhost:8080. just dev auto-rebuilds on changes (needs [cargo-watch](https://github.com/watchexec/cargo-watch)).

    Useful dev commands:

    just check        # cargo check --workspace
    just clippy       # lint (zero-warnings policy)
    just test-unit    # fast hermetic unit tests (no infra needed)
    just test         # unit + integration tests (needs: just infra)
    

    Building from source:

    cargo build --release -p nasiko          # CLI binary
    cargo build --release -p nasiko-server   # Server binary
    

    Deploying your own agents

    Past the coding agents you already run, Nasiko deploys agents you write yourself. They can be in Python, Rust, Go, or TypeScript. At runtime they must speak A2A v1.0 exactly; source deployments also include an AgentCard.json and Dockerfile for metadata and packaging. Nasiko hardcodes the A2A-Version: 1.0 header on every outbound A2A request, so the target agent must accept the v1.0 wire format. See [docs/A2A_PROTOCOL.md](https://github.com/Nasiko-Labs/nasiko/blob/HEAD/docs/A2A_PROTOCOL.md) for the detail. There is no proprietary agent format and no SDK to adopt.

    This path needs Rust to build the CLI from source (it is a separate cli/ crate). Docker is required for local image builds; nasiko upload instead sends source to the server to build and deploy without local Docker.

    Install the CLI

    # from the repo root
    cargo install --path cli/ --force
    # ...or build a standalone binary
    cargo build --release -p nasiko
    

    Add it to your PATH if it is not already (Cargo's bin dir: ~/.cargo/bin).

    Deploy your first agent

    nasiko connect http://localhost:8080
    nasiko auth login                          # log in with ADMIN_USERNAME / ADMIN_PASSWORD
    nasiko new openai my-agent && cd my-agent  # scaffold from a template
    nasiko deploy .                            # build, push, and deploy
    nasiko chat "Hello there"                  # talk to your agent (message must contain a space,
                                                # or use --agent: nasiko chat --agent my-agent "Hello")
    

    You can also deploy agents directly from the dashboard UI: upload source, import from GitHub, or pull from the artifact registry.

    Handy CLI commands

    CommandDescription
    nasiko connect <url>Register a control plane and switch to it
    nasiko auth loginAuthenticate with the active cluster
    nasiko new [template] [name]Scaffold a new agent project
    nasiko build / nasiko runBuild the agent image / build + run it locally
    nasiko push / nasiko deploy <image>Push image / build-push-deploy to the cluster
    nasiko upload [source]Upload source; the server builds it (no local Docker)
    nasiko psList running agents
    nasiko logs <agent> -fStream (and follow) agent logs
    nasiko stop / start / restart / scale <n>Agent lifecycle
    nasiko rm --name <agent>Terminate + deregister an agent (positional id only accepts a UUID)
    nasiko chat <agent>Interactive or one-shot A2A chat
    nasiko secrets setConfigure encrypted per-agent secrets
    nasiko mcpManage MCP Gateway connectors and tool permissions
    nasiko observeObservability: sessions, traces, spans, stats, FinOps
    nasiko mafMulti-agent flow workflows (create/run/inspect)
    nasiko registryBrowse the artifact registry
    nasiko githubGitHub integration (status/repos/connect/disconnect/clone)

    Run nasiko --help for the full, workflow-ordered command list.

    Give your agent MCP tools

    Every deployed agent can call tools through the MCP Gateway, a single JSON-RPC endpoint that merges Composio toolkits and your own MCP servers into one permission-filtered catalog. The agent never holds a third-party credential: it authenticates to the gateway, and the gateway calls the tool with the user's stored connection.

    A complete working example of everything in this section and the next is in agents/general-assistant-1.1.1.zip. It's a Python a2a-sdk agent that uses whatever MCP tools it is granted, asks the user instead of guessing, and pauses for tool approvals. Deploy it with nasiko upload agents/general-assistant-1.1.1.zip. Then type hitl input test, hitl auth test, hitl options test or hitl multiselect test to check the HITL wiring without an LLM key. Version 1.1.1 pins opentelemetry-util-genai==1.1b0: version 1.2b0 breaks the OpenAI instrumentor's import, leaving request traces visible but LLM token usage missing from TokenOps.

    1. What the platform injects. At deploy time every agent container gets two env vars (when MCP_GATEWAY_PUBLIC_URL is set on the server, which compose does for you):

    Env varMeaning
    MCP_GATEWAY_URLThe gateway's JSON-RPC endpoint (/api/mcp)
    MCP_GATEWAY_TOKENThis agent's own gateway credential, minted at deploy. Send it as a Bearer token.

    2. Make the agent a client. Plain JSON-RPC over HTTP is enough, no MCP SDK required. Two methods: tools/list (discover the catalog at runtime) and tools/call (invoke one).

    import os, uuid, httpx
    
    async def mcp(client, method, params=None, traceparent=None):
        headers = {"Authorization": f"Bearer {os.environ['MCP_GATEWAY_TOKEN']}"}
        if traceparent:
            headers["traceparent"] = traceparent  # required, see below
        body = {"jsonrpc": "2.0", "id": str(uuid.uuid4()), "method": method}
        if params is not None:
            body["params"] = params
        resp = (await client.post(os.environ["MCP_GATEWAY_URL"], json=body, headers=headers)).json()
        if "error" in resp:
            err = resp["error"]
            raise McpError(err["code"], err["message"], err.get("data") or {})
        return resp["result"]
    
    tools = (await mcp(client, "tools/list", traceparent=tp))["tools"]
    result = await mcp(client, "tools/call", {"name": tool_name, "arguments": args}, tp)
    

    Each tools/list entry has name, description and an inputSchema (JSON Schema), so it maps 1:1 onto an OpenAI/Anthropic function-tool definition. Hand the list to your LLM and route any tool call it makes back through tools/call.

    3. Forward the inbound traceparent, untouched. The gateway authorizes tools/call by checking that your agent is a participant in the live flow the trace id names. Without the right traceparent, every call is rejected with 403, even with a valid token. Read the header from the A2A request you are serving and send that exact value. Two gotchas, both seen in practice:

    • OTel httpx auto-instrumentation overwrites traceparent with one from the ambient span, which is not the inbound flow's span, so calls 403 with "does not resolve to a live flow". Suppress instrumentation for the gateway request only:
      from opentelemetry.context import attach, detach, set_value
      from opentelemetry.instrumentation.utils import _SUPPRESS_INSTRUMENTATION_KEY
      
      token = attach(set_value(_SUPPRESS_INSTRUMENTATION_KEY, True))
      try:
          resp = await client.post(url, json=body, headers=headers)
      finally:
          detach(token)
      
    • Read traceparent per request, not from a ContextVar. In a2a-sdk, a resumed task runs on the worker created by the original request, so a ContextVar holds a stale trace id. Read it from context.call_context.state["headers"]["traceparent"] inside execute().

    4. Handle the gateway's error codes. These are what make a tool call pause instead of fail (codes from mcp-gateway/src/types.rs):

    CodeMeaningWhat the agent should do
    -32000TOOL_BLOCKED: the tool's stance for this agent is blockReturn the error to the LLM as a normal tool result
    -32001TOOL_ASK: the stance is ask, and a human has not approved it yetPause with auth_required (see HITL)
    -32002AUTH_REQUIRED: the connector's own credential is missing or brokenPause with auth_required

    For the two pause codes, error.data carries the gateway's own HITL request id: hitl_request_id, or hitl_request_ids (a list) for a batched call such as Composio's COMPOSIO_MULTI_EXECUTE_TOOL. Read both keys.

    5. Grant the agent access. An agent only sees the tools it was granted:

    nasiko mcp catalog                                         # what can be connected
    nasiko mcp connect --toolkit github                        # connect your account (OAuth/API key)
    nasiko mcp agent-tools connectors my-agent                 # what my-agent can see
    nasiko mcp agent-tools enable my-agent <connector-id>      # turn a connector on for my-agent
    nasiko mcp agent-tools set-rule my-agent <connector-id> "GITHUB_DELETE_*" ask   # allow | ask | block
    nasiko mcp agent-tools tools my-agent <connector-id>       # catalog + effective stance per tool
    

    Bring your own MCP server with nasiko mcp connector register (a URL), or upload / upload-github to have the platform build and host it. See docs/MCP_GATEWAY_DESIGN.md for gateway internals.

    Human-in-the-loop (HITL)

    An agent can stop mid-task and wait for a person, then resume the same task with their answer. This is plain A2A with no Nasiko SDK: the agent ends its turn in one of two paused task states, and the platform does the rest. It records a pending request, shows it in the chat UI and nasiko chat, and sends the human's reply back to the agent as the next message on that task.

    A2A stateUse it when
    TASK_STATE_INPUT_REQUIREDThe agent is missing information it shouldn't guess (a name, date, amount, recipient...)
    TASK_STATE_AUTH_REQUIREDA human must grant something first: a tool approval (TOOL_ASK) or a connector re-auth

    1. Pause. With a2a-sdk (Python), emit the state with a message and optional metadata:

    await updater.update_status(
        TaskState.TASK_STATE_INPUT_REQUIRED,
        message=updater.new_agent_message([new_text_part("Which repo should I file this in?")]),
        metadata={"messages": llm_history, "pending_tool_call_id": call.id},  # your resume state
    )
    

    The message text becomes the question shown to the human. metadata is persisted on the task and handed back to you on resume, so store everything you need to continue there (the LLM conversation so far, the pending tool call). Use a persistent task store if the agent can restart while a task is paused, because InMemoryTaskStore loses it.

    Some metadata keys are hoisted onto the question so clients can render them:

    KeyEffect
    auth_url, providerauth_required: the client shows an "Authorize with provider" link
    expected_inputinput_required: hint about the expected answer
    options, header, multi_select, allow_custom_inputinput_required: show selectable choices instead of a free-text box (see below)
    hitl_request_idauth_required that mirrors a gateway TOOL_ASK / AUTH_REQUIRED: links the pause to the gateway's request, so approving it actually grants the tool

    2. Resume. The human's reply arrives as a new message on the same task id. context.current_task is the stored task. Its status.state is still the paused state, and its metadata is what you saved. Branch on that state:

    stored = context.current_task
    if stored and stored.status.state == TaskState.TASK_STATE_INPUT_REQUIRED:
        meta = MessageToDict(stored.metadata)   # metadata comes back as a protobuf Struct
        history = meta["messages"] + [{"role": "tool",
                                       "tool_call_id": meta["pending_tool_call_id"],
                                       "content": user_text}]
        # ...continue the LLM loop with `history`
    elif stored and stored.status.state == TaskState.TASK_STATE_AUTH_REQUIRED:
        # retry the exact tool call the human just approved; pause again if it is still TOOL_ASK
        ...
    

    For an auth_required pause, the reply text is confirmed or denied. Don't treat any other value as approval.

    Selectable options (optional). Add these to input_required metadata to give the human clickable choices:

    metadata = {
        "header": "Format",
        "options": [
            {"label": "Summary", "description": "Brief overview"},
            {"label": "Detailed"},
        ],
        "multi_select": False,        # default false
        "allow_custom_input": True,   # also allow a typed answer; default false
    }
    

    On resume you get back the chosen label verbatim. For multi-select, each picked label (and any custom text) is on its own line. Labels must be non-empty and unique, with at most 20 options. A malformed block is dropped and the pause falls back to a plain text question, with a server-side warning (types/src/a2a.rs).

    Making an LLM agent pause reliably. Give the model a synthetic ask_human(question, options?) tool and a finish_task(message) tool alongside the MCP tools, and call it with tool_choice="required". With "auto", models often ask a clarifying question as plain chat text, which you can't tell apart from a final answer. When every turn must be a tool call, "paused" and "done" are told apart by the tool called, not by guessing at the text: ask_human → input_required, gateway TOOL_ASK/AUTH_REQUIRED → auth_required, finish_task → complete.

    Answering a pause outside the chat UI. nasiko chat prompts for pauses inline. For other clients, pending requests are at GET /api/hitl/pending and GET /api/hitl/{id}, and are answered with POST /api/hitl/{id}/resolve. The body depends on the request kind:

    KindResolve body
    input_required{"answer": "acme/api"}; for multi-select, {"answer": ["Intro", "Security"], "custom_answer": "..."}
    auth_required{"auth_action": "start"}, then {"auth_action": "confirm"} after authorizing
    tool approval (TOOL_ASK){"decision": "approve" | "reject", "scope": "once" | "session"}

    Environment Variables

    Configuration is env-driven; startup-required keys are validated separately from feature-specific requirements such as local coding-agent routing. See .env.example for the Compose quick start and server/.env.example for the detailed reference. docker-compose.yml supplies infrastructure and telemetry URLs and the agent network; do not replace those with host-local addresses in the Compose setup.

    VariablePurposeDefault
    OPENAI_API_KEYLLM provider for the router + agentsoptional (sk-...)
    SECRETS_ENCRYPTION_KEYBase64 32-byte AES-256-GCM keyrequired
    ADMIN_USERNAME / ADMIN_PASSWORDBootstrap admin accountadmin / changeme
    JWT_SECRETJWT signing secretrequired
    S3_BUCKET / S3_ACCESS_KEY / S3_SECRET_KEY / S3_REGIONS3 settings; credentials must match RustFSsupplied by .env.example; S3_SECRET_KEY required
    OCI_STORAGE_BUCKETBucket for embedded OCI registry blobs/manifestsnasiko-artifacts (no override needed)
    AGENT_RUNTIMEContainer runtime (docker in OSS)docker
    DATABASE_URL / REDIS_URL / S3_ENDPOINTInfra connectionsset by compose
    COMPOSIO_API_KEYComposio platform (MCP toolkits)optional
    SEED_TOOLKITSComposio toolkits to auto-register at bootoptional
    MCP_GATEWAY_PUBLIC_URLPublic URL injected into agents for the MCP gatewayset by compose
    SEED_AGENTSSpace-separated images auto-deployed at bootoptional
    AGENT_JWT_SECRETSigns agent LLM-router identity tokens, including custom config routingrequired for local coding-agent routing; generate your own
    CODING_AGENT_OTLP_ENDPOINTServer-side OTLP/HTTP export for coding-agent telemetryset by Compose; host-run server must set it
    ROUTER_MODEL / EMBEDDING_MODELRouting-engine modelssee config/
    NASIKO_FLOW_MAX_DEPTH / NASIKO_FLOW_MAX_FAN_OUT / NASIKO_FLOW_MAX_TOKENSFlow-guard cascade limitssee config/

    Project Structure

    server/         Control plane: Axum routes, auth, agent proxy, build worker, embedded UI
    orchestrator/   Routing engine: semantic agent selection (shortlist, rerank, select)
    mcp-gateway/    MCP Gateway: connectors, tool aggregation, per-agent permissions, OAuth
    llm-router/     Provider-agnostic OpenAI-compatible egress proxy for agent LLM calls
    runtime/        ContainerRuntime trait + DockerRuntime (bollard)
    auth/           AuthService trait + OSS implementation (JWT login, RBAC hooks)
    flow/           FlowGuard: anti-DoS cascade limits + live flow events
    secrets/        AES-256-GCM encryption for agent secrets at rest
    oci/            Embedded OCI Distribution v2 registry (S3-backed, layer dedup)
    observability/  OTel init, Tempo/Loki clients, DB-backed model pricing
    agent-proxy/    Agent ID -> running-container endpoint resolution
    github/         GitHub OAuth + repo import for source-based deploys
    types/          A2A protocol + registry types
    config/         Single env-driven Config struct
    utils/          Shared helpers
    cli/            nasiko binary (agent developer CLI, sync HTTP via ureq)
    agents/         Example and seed agents (each a standalone A2A container)
    migrations/     Postgres migrations (sqlx, run automatically at startup)
    ui/             Frontend (vanilla JS web components, embedded in the server binary)
    docs/           Design docs (architecture, protocol, conventions)
    

    Troubleshooting

    Quick fixes: one command

    ProblemOne command
    CLI won't compile: link.exe not found / cc not found (Windows)Use the Docker-only path, or winget install Microsoft.VisualStudio.2022.BuildTools --override "--wait --quiet --add Microsoft.VisualStudio.Workload.VCTools --includeRecommended" then reopen the terminal
    CLI won't compile: dlltool ... Invalid bfd targetwinget install MSYS2.MSYS2 then add C:\msys64\mingw64\bin to PATH ahead of C:\MinGW, or switch to MSVC
    : command not found when sourcing .envsed -i 's/\r$//' server/.env
    invalid SECRETS_ENCRYPTION_KEY at startupsed -i.bak "s/^SECRETS_ENCRYPTION_KEY=.*/SECRETS_ENCRYPTION_KEY=$(openssl rand -base64 32)/" .env
    address already in use on ports 9000/4317/4318Stop Docker Desktop, then wsl --shutdown (Windows) and rerun docker compose up -d
    permission denied on Docker socketsudo usermod -aG docker "$USER" && newgrp docker (*nix/WSL)
    WSL ext4.vhdx: path not foundwsl --unregister Ubuntu && wsl --install -d Ubuntu
    Agent upload -> 500 agents_owner_id_fkeyLog out and back in, or docker compose down -v && docker compose up -d then log in fresh
    Server can't reach Postgresdocker compose up -d and wait for healthy
    Agent Name or service not known (Linux Docker)Recreate with --add-host host.docker.internal:host-gateway
    SEED_TOOLKITS is set but COMPOSIO_API_KEY is not at startupSEED_TOOLKITS is optional and commented out in the template. If you enable it, also set COMPOSIO_API_KEY, or leave both unset if Composio is not needed.

    Windows

    SymptomCause / fix
    link.exe not found / linker 'cc' not found when building the CLIMSVC C++Build Tools not installed. Use the Docker-only path (no Rust), or install VS Build Tools with the "Desktop development with C++" workload.
    error: dlltool ... Invalid bfd targetA broken 32-bit MinGW (C:\MinGW) cannot build 64-bit. Install a real 64-bit MinGW-w64 (e.g. MSYS2) or switch to the MSVC toolchain.
    : command not found when sourcing .envWindows line endings (CRLF) break bash source. Convert: sed -i 's/\r$//' server/.env
    invalid SECRETS_ENCRYPTION_KEY ... Invalid paddingInvalid key in .env. Generate one: openssl rand -base64 32
    address already in use on ports 9000/4317/4318Two Docker engines fighting (Docker Desktop + WSL native). Keep one; run wsl --shutdown, reopen, docker compose up -d
    permission denied ... Docker daemon socket (inside WSL)Add user to docker group: sudo usermod -aG docker $USER, then re-login
    Wsl ... ext4.vhdx: path not foundCorrupt WSL distro. wsl --unregister Ubuntu then wsl --install -d Ubuntu
    Agent upload -> 500 / agents_owner_id_fkeyStale login token from an old DB. Log out, log back in (or docker compose down -v + fresh login)

    macOS

    SymptomCause / fix
    linker 'cc' not foundxcode-select --install (Command Line Tools) missing
    permission denied ... Docker daemonStart Docker Desktop and wait for the engine
    address already in useAnother process on ports 9000/4317/4318. lsof -i :9000 to find it.

    Linux

    SymptomCause / fix
    permission denied ... Docker socketsudo usermod -aG docker $USER then log out/in (or newgrp docker)
    error: linker 'cc' not found (building CLI)Missing build tools: sudo apt install -y build-essential pkg-config libssl-dev
    Agent [Errno -2] Name or service not knownhost.docker.internal is not provided by native Docker. See the Linux note in Path A, or set MCP_GATEWAY_PUBLIC_URL to the bridge IP
    First cargo build very slowNormal, it compiles the whole workspace. Prefer a native clone over a mounted/9p filesystem.

    All platforms

    SymptomFix
    failed to connect to Postgres at startupInfra is not up yet. Run docker compose up -d (or just infra) and wait for healthy
    docker: command not foundDocker not installed/running. Install [Docker](http