WisePick API

June 9, 2026 · View on GitHub

Docs: Overview | Integration & SDK | Agent Protocol

WisePick Decision API (WPDA) is a stateless capability routing / decision layer:

POST /v1/decide → ECU → your runtime executes → POST /v1/feedback

It does not run tools or own orchestration.

Agent protocol & runtime behavior: AGENTS.md only.
Product overview: README.md.


Hosted Endpoint

Default hosted deployment: https://api.wishweaver.top

For most evaluations and pilot integrations, use the hosted endpoint before deploying your own infrastructure.

Self-hosted deployment: see Deployment & Production Requirements.


Deployment & Production Requirements | 部署与生产环境要求

Production architecture contract: Deploy WPDA as your product’s routing decision service (not a task orchestrator). In production, run WisePick in your private containers or cloud environment and ensure the agent runtime can reach it reliably (stable base URL, health checks, persistence).

生产环境架构契约:WPDA 作为产品的路由决策服务部署(非任务编排引擎)。在生产环境,您应将 WisePick 服务部署在私有容器/云环境中,并确保 Agent 运行时可稳定访问该 API。

Data sovereignty and evolution: Execution feedback and the learning loop depend on persisted decision records on your WPDA host (DATABASE_URL). That store is the on-ramp to collective decision memory; the long-term Execution Experience Network vision is evolutionary—not a separate product you deploy today.

关于数据主权与演进: 执行反馈与学习闭环依赖您 WPDA 生产环境中的持久化决策记录。该存储是集体决策记忆的起点;长期执行经验网络愿景为演进方向,而非当前需单独部署的网络产品。

Local bootstrap (logic validation only): Configure DATABASE_URL in .env (see .env.example), then run uvicorn app.main:app --reload. Smoke check: curl -s http://localhost:8000/health.

本地启动(仅逻辑验证): 配置 .env 中的 DATABASE_URL 后运行 uvicorn;通过 /health 确认服务可用。


15-Minute Integration Checklist

Wire wisepick/client.py into your Agent completion path and tool-execution hook. No extra client dependencies (stdlib urllib only).

Step 1 — Client bootstrap

from wisepick import WisePickClient

wp = WisePickClient(api_url="https://api.wishweaver.top")  # hosted default; or your self-hosted base URL

Keep one instance per process (or per session factory). Point api_url at your self-hosted WisePick base (no trailing slash required).

Step 2 — Decide before the first completion

Call inject_openai_choice immediately before the first provider chat.completions (or compatible) request for the turn. It POSTs /v1/decide and, when ECU is valid, sets OpenAI-shaped tool_choice from capability_id.

user_task = "Transcribe today's meeting audio"
api_kwargs = {
    "model": "gpt-4o-mini",
    "messages": messages,
    "tools": openai_tools,  # required: inject is a no-op without tools
}

api_call_count = 0  # per user turn / replan cycle

def build_completion_kwargs() -> dict:
    global api_call_count
    api_call_count += 1
    kwargs = dict(api_kwargs)
    if api_call_count == 1:
        wp.inject_openai_choice(kwargs, user_task)
    return kwargs

# first completion only — tool_choice may be present
response = openai_client.chat.completions.create(**build_completion_kwargs())

Persist ECU once per turn if you need decision_id for feedback without a second decide (optional):

ecu = wp.decide(user_task)
# then Step 2 inject still works; or set tool_choice manually from ecu["capability_id"]

inject_openai_choice skips injection when callable is false, capability_id is empty, or tools is missing.

Step 3 — Name alignment (function.namecapability_id)

tool_choice.function.name must equal an entry in tools[].function.name. WisePick ranks by capability_id (semantic capability), not marketing product names.

OpenAI / MCP function.nameWisePick capability_idLocal handler
audio_transcriptionaudio_transcriptiontranscribe_audio(provider, …)
search_filessearch_filesMCP tool search_files

WisePick decouples routing from discovery. Your runtime can load tools statically or query an MCP server dynamically, but it must map discovered tool payloads to stable capability_id strings before hitting the decision layer.

Rule: register tools under the capability_id strings WisePick emits. Use provider + execution_type inside your executor to pick credentials, endpoint, or MCP server—not as the forced function name.

EXECUTORS = {
    "audio_transcription": lambda provider, task: run_transcribe(provider, task),
    "search_files": lambda provider, task: mcp_call("search_files", task),
}

def run_tool(ecu: dict, task: str):
    fn = EXECUTORS.get(ecu["capability_id"])
    if not fn:
        raise ValueError(ecu["capability_id"])
    return fn(ecu["provider"], task)

Mismatch → the model cannot call the forced function → fall back to Step 4 or fix the registry.

Step 4 — Release after the first completion

WisePick routing applies to turn entry (first completion). Later completions in the same turn should not keep a forced tool.

def build_completion_kwargs() -> dict:
    global api_call_count
    api_call_count += 1
    kwargs = dict(api_kwargs)
    if api_call_count == 1:
        wp.inject_openai_choice(kwargs, user_task)
    else:
        kwargs.pop("tool_choice", None)  # api_call_count > 1: model picks freely
    return kwargs

Reset api_call_count = 0 (and drop cached ecu) on new user intent or explicit replan.

Step 5 — Feedback on the execution hook

After your tool/MCP handler finishes (success or failure), call feedback with the decision_id from the turn’s ECU. If the runtime executed a different tool than WisePick recommended, pass actual_tool_used so ROI accrues to the correct handler.

def on_tool_finished(ecu: dict, *, ok: bool, latency_ms: int, actual_tool: str | None = None, err: str | None = None):
    did = ecu.get("decision_id")
    if not did:
        return
    wp.feedback(
        did,
        success=ok,
        latency_ms=latency_ms,
        error_message=err,
        token_cost={"input": 1200, "output": 450},
        result_quality=0.92 if ok else None,
        actual_tool_used=actual_tool,
    )

# success path (after local execute)
on_tool_finished(ecu, ok=True, latency_ms=1200)

# failure path
on_tool_finished(ecu, ok=False, latency_ms=300000, err="timeout after 300s")

Skipping feedback disables learning for that decision.

Closed loop: decide → execute mapped handler → feedbacktool_stats updates next decide.


🔄 Decision Feedback Loop

WisePick learns from actual runtime execution, not only from routing decisions.

A complete integration should follow:

Decision ↓ Execute ↓ Feedback

Runtime requirements:

  • Call /v1/decide before execution.

  • Persist the returned decision_id.

  • After execution, call /v1/feedback.

  • Include:

    • decision_id
    • success
    • latency_ms
  • Recommended:

    • runtime_name
    • actual_tool_used
    • token_cost
    • result_quality

When callable=false, the runtime may execute its own fallback tool.

Feedback should still be submitted using the original decision_id.

WisePick will associate:

  • Recommended tool → decision record
  • Actual executed tool → runtime observation record

This enables real-world ROI learning.


HTTP Surface (reference)

MethodPathRole
GET/healthLiveness
GET/v1/analytics/summaryOperator usage snapshot (totals, closure rate, pooled ROI)
GET/v1/analytics/dashboardOperator entry point — summary + 7-day volume + top runtime
GET/v1/analytics/providersPer-provider stats from tool_stats
GET/v1/analytics/runtimesPer-runtime stats from self-reported runtime_name
GET/v1/analytics/timelineDaily decide/feedback counts (UTC)
GET/v1/analytics/attributionRecommended vs actual_tool_used feedback breakdown
POST/v1/decideTask → ECU
POST/v1/feedbackOutcome → stats

Operator usage (read-only): curl -s http://localhost:8000/v1/analytics/dashboard — single operator entry point for adoption, closure rate, provider/runtime leaders, and recent volume.

GET /v1/analytics/dashboard

{
  "decisions_total": 156,
  "feedback_total": 121,
  "closure_rate": 0.7756,
  "active_providers": 7,
  "active_runtimes": 3,
  "avg_success_rate": 0.89,
  "avg_latency_ms": 1532.0,
  "avg_token_cost": 1100.25,
  "avg_result_quality": 0.91,
  "top_provider": "chatgpt",
  "top_provider_decisions": 42,
  "top_provider_feedback_count": 38,
  "top_runtime": "yantrikdb-hermes",
  "top_runtime_feedback_count": 84,
  "decisions_last_7d": 23,
  "feedback_last_7d": 18
}

If feedback.runtime_name has not been migrated yet, active_runtimes is 0, top_runtime is null, and top_runtime_feedback_count is 0.

POST /v1/decide

{
  "task": "Summarize this technical document",
  "context": { "language": "Chinese" },
  "constraints": { "max_cost": 10.0, "timeout_seconds": 300 }
}

ECU fields (integrate against these): decision_id, capability_id, provider, execution_type (api | mcp | function_call), callable, confidence, reason, explain, trace. Legacy tool_key mirrors provider.

When callable is false, WisePick found no matching capability in the registry. The API still returns a persistent decision_id backed by a fallback_unknown anchor so that feedback and runtime execution observations can still be linked later. provider, tool_key, and capability_id are empty strings in the response for compatibility; reason is "No matching capability found"; explain.no_match is true. Do not force tool_choice — let your runtime fallback.

POST /v1/feedback

{
  "decision_id": "dec_abc123def4567890",
  "success": true,
  "latency_ms": 1200,
  "token_cost": { "input": 1200, "output": 450 },
  "result_quality": 0.92,
  "actual_tool_used": "browser_navigate",
  "runtime_name": "hermes",
  "user_note": "optional free-text error or context"
}

latency_ms is required. Use token_cost and result_quality for ROI aggregates (avg_token_cost, avg_result_quality); do not embed them in user_note.

  • tool_key (optional audit field): if sent, it must match the decision’s recommended selected_tool_key.
  • actual_tool_used (optional): the tool/MCP your runtime actually executed. When set, ROI attribution should follow the actual executed tool rather than the recommended tool.
  • runtime_name (optional): self-label for usage analytics only.

Runtime Attribution

Runtimes may self-label feedback for usage analytics only:

{
  "decision_id": "dec_abc123def4567890",
  "success": true,
  "latency_ms": 1200,
  "runtime_name": "yantrikdb-hermes"
}

This field is optional. Existing feedback requests without runtime_name continue to work unchanged.

  • Used only for usage analytics (GET /v1/analytics/runtimes, active_runtimes in summary).
  • No authentication required.
  • No impact on routing, billing, or quotas.

Apply scripts/migrate_runtime_name.sql on Supabase/PostgreSQL before persisting or querying runtime attribution.

Apply scripts/migrate_actual_tool_used.sql before persisting or querying actual_tool_used and the updated tool_stats view.

Apply scripts/migrate_fallback_unknown.sql when upgrading an existing database so the fallback_unknown anchor exists in api_tool_specs.

Errors

{ "error": "error_type", "message": "Human-readable description" }
errorHTTPWhen
invalid_request422Validation / no matching tools (ValueError from router)
persistence_failed500Decision log could not be persisted
{ "error": "persistence_failed", "message": "Failed to persist decision log" }

Environment variables

See .env.example for the full list.

Agent / integrator runtime (consumer of WisePick)

VariableRequiredDefaultRole
WISEPICK_API_URLrecommendedhttps://api.wishweaver.topBase URL for WisePickClient / HTTP adapter
WISEPICK_DECIDE_URLoptional{WISEPICK_API_URL}/v1/decideFull decide endpoint override (Hermes-style routers)
HERMES_WISEPICK_ROUTINGoptional11/true → enable decide + first-completion injection (Hermes integrations)
WISEPICK_FORCE_TOOLoptionalTest/dry-run: skip HTTP; force tool name (must exist in tool_capability_map)
HERMES_WISEPICK_FORCE_TOOLoptionalHermes alias of WISEPICK_FORCE_TOOL

Set WISEPICK_API_URL in deployment manifests; map tool_capability_map from the agent’s live tool list before enabling routing (see AGENTS.md wisepick.agent.v1).

WisePick API host (server process)

VariableRequired on hostRole
DATABASE_URLyesPostgreSQL / Supabase for decisions, feedback, and tool_stats
YANTRIK_DB_URLnoCluster health plugin for /v1/decide score penalty
YANTRIK_DB_API_KEYnoBearer token for YantrikDB health
WISEPICK_LANGFUSE_PUBLIC_KEYnoTelemetry (mcp.route_decision.v1)
WISEPICK_LANGFUSE_SECRET_KEYnoTelemetry
WISEPICK_LANGFUSE_HOSTnoLangfuse base URL
WISEPICK_LANGFUSE_OTELnotrue → OTLP ingestion
WISEPICK_LANGFUSE_ROUTER_NAMEnoContract router_name (default wisepick)

How it works (implementation)

Capability matching | 能力匹配

Task text → capability labels derived from bootstrap rules (api_tool_specs).

task → capabilities

Capability scoring | 能力评分

base_score =
  capability_match * 0.70
  + effective_success_rate * 0.20
  + effective_bootstrap_weight * 0.10

final_score = base_score * efficacy

efficacy = result_quality / (log(max(avg_latency_ms, 100)) * log(max(avg_token_cost, 10)))

ROI metrics are read from tool_stats. If capability_match is 0, effective_success_rate is penalized × 0.1. effective_bootstrap_weight decays with feedback_count (half-life 20).

Feedback loop | 反馈闭环

decision → execution → feedback → tool_stats → next decision

Components | 核心组件

Routing Core (decision_engine)       — task → ECU scoring and selection
Capability Registry (api_tool_specs) — providers, labels, bootstrap weights
Execution Memory (tool_stats)        — success rate, latency, token ROI from feedback
Runtime Observation (observed_tools)  — actual runtime tool usage history

Optional plugins

YantrikDB (YANTRIK_DB_URL, optional YANTRIK_DB_API_KEY): On /v1/decide, reads YantrikDB /v1/health. If replication_lag_log_entries > 500, all candidate scores × 0.5 for that request. Empty URL → skipped. Inspect explain.yantrik_cluster, trace.yantrik_cluster. No primary schema change.

Langfuse (WISEPICK_LANGFUSE_PUBLIC_KEY + WISEPICK_LANGFUSE_SECRET_KEY): Background export of mcp.route_decision.v1 (and execution feedback spans when configured). Pass trace_id / session_id in decide context to correlate. Does not add request latency on the hot path.

Telemetry payload shape for observability tools: AGENTS.md.


Runtime adapters

Thin bridges: WisePick decide / feedback; your runtime executes. Pattern: docs/ADAPTER_PATTERN.md.

ChainWeaver

Maps ECU → FlowExecutor.execute_flow; explicit capability_id(flow_id, flow_version) table only.

  • Module: adapters/chainweaver_adapter.py
  • Types: ChainWeaverAdapter, RoutingDecision (flow_id, flow_version, confidence, reasoning)
  • Entry: select_and_execute(user_request) or route() for decide-only

SafeAgent

Maps ECU → idempotent request_id + SafeAgent runtime.execute; closes feedback with ROI user_note.

  • Module: adapters/safeagent_adapter.py
  • Types: SafeAgentAdapter, SafeAgentRoutingDecision
  • Requires session_id, turn_id (and orchestrator start_time_ms for distributed idempotency)

Aetheris (experimental)

Maps DecideResponseAetherisRouteEvidence for durable evidence stores (no HTTP in adapter).

THYMOS (OpenThymos)

Maps ECU → RoutingEvidence; attaches routing_evidence on outer Proposal per OpenThymos Proposal Contract v1 Option 2 (outside ProposalBody; no HTTP in adapter).

  • Module: adapters/thymos_adapter.py
  • Types: FallbackHint, RoutingEvidence, ThymosRoutingAdvisor
  • Entry: ThymosRoutingAdvisor(...).to_evidence() then attach_routing_evidence_to_proposal(proposal_envelope, evidence)

Examples

examples/wisepick_router.py · examples/omnicore_adapter.py


Deploy (Quick Start)

pip install -r requirements.txt
# configure DATABASE_URL in .env
# Existing Supabase/PostgreSQL DB: run scripts/migrate_v0_2_2.sql first if you still use legacy schema
# Then apply scripts/migrate_fallback_unknown.sql, scripts/migrate_runtime_name.sql, and scripts/migrate_actual_tool_used.sql
uvicorn app.main:app --reload --host 0.0.0.0 --port 8000