WayAI Skill
WayAI is a SaaS platform for AI-powered communication hubs. Each hub combines AI agents and a human team across channels (WhatsApp, Email, Instagram, Telegram, native App). This workspace stores hubs as code — one folder per hub (hub.yaml + agents/*.{yaml,md} + evals/, journeys/, resources/) synced bidirectionally to the platform via the wayai CLI.
Platform is the source of truth. Workspace files are the edit surface — changes flow through files → wayai push → platform. Always wayai pull before editing to catch out-of-band changes.
How to use this skill: this file is the complete concept map — every WayAI primitive is defined here with enough depth to decide what to build and which files to touch. Field-level schemas, per-provider specifics, and mechanics live in references/; each domain section below ends with a pointer to its deep-dive file — open it when you're about to author or debug that domain. Before generating a full hub from scratch, read references/canonical-example/README.md once — it shows how the pieces wire together. The full routing table is at the end (Reference Documentation).
Agent Guidelines
- At the start of every session, before anything else, get current — every time, even for a quick task: update the CLI, then check whether this skill is stale and refresh it if so. A stale CLI or skill is the most common cause of a step below not working. Run the procedure as written in Workflow → Existing hub steps 1–2 (cold start: State machine rows 1–1c), and take the skill-install command from row 1b rather than retyping it — its
mkdir -p .claude prefix is load-bearing for Claude Code, and row 1c carries what to do when the install fails
- Talk like a person, not a manual: the user may be new to WayAI — plain language, no jargon. Keep answers short and to the point; don't explain what they didn't ask about. If they seem stuck or ask what something means, give a one-line answer and move on — don't turn a reply into a tutorial
- Interface: setup runs on the
wayai CLI and workspace files — the only automation surface, and it needs filesystem/shell access (code-harness agents — Claude Code, Codex, Cursor, OpenCode). Drive everything below through it. If you do not have shell access (app-harness agents — Claude Desktop, ChatGPT, etc.), you cannot configure WayAI yourself — hand the work to the person, the same shape as the OAuth connection handoff below (one URL, one action, one return signal): "Open https://app.wayai.pro. Set it up there, or run this with a shell-having agent — the one-prompt install is on https://wayai.pro. Tell me when done." Then keep answering from this skill, and never report config you didn't make
- Only provide information from this skill, tool descriptions, or reference documentation
- Do not invent URLs, paths, or steps
- Hub config flows through files + the
wayai CLI; one-time setup (orgs, OAuth) goes through the platform UI. Publishing preview → production is now CLI-capable (wayai publish) or UI
- Always
wayai pull -y before editing — catches out-of-band changes
- Always
wayai push -y immediately after editing — editing and pushing are a single action
- Never auto-commit — show
git diff, wait for user approval
Quick Decision: What Can I Do?
| Entity |
How |
| Hub settings, agents, agent instructions, tools, kanban, states, resources, evals, journeys, outbound, custom tools |
CLI (wayai push) |
Eval journeys (hub-as-code) — journeys/<slug>.yaml, flat folder |
CLI (wayai push / wayai pull; pull after first create to sync step ids) |
| Connections — non-OAuth (Agent providers, STT/TTS, Tool API key, MCP Bearer Token) |
CLI (auto-created from org credentials) |
| Connections — OAuth (WhatsApp, Instagram, MCP OAuth) |
Platform UI |
| Set/rotate a connection's credential directly (incl. production) |
CLI (wayai set-connection-credential) or UI |
| Org credentials — create / rotate / edit |
CLI (wayai create-credential / wayai update-credential) or UI |
| Org-level shared resources (org-as-code) |
CLI (wayai org pull / push / diff) |
| Bases — schemas, records, relationships, files, toolsets (the Data surface) |
CLI (wayai bases, wayai records, wayai record-types, …; config-as-code via wayai pull/push bases/<base>) — open references/bases/README.md first |
| Skills sync to providers |
CLI (wayai sync-skills) |
| Conversation testing |
CLI (wayai send-message, wayai conversations, wayai delete-history) |
| Diagnose why a hub misbehaves (audio/TTS not delivered, agent silent, a tool failing) — check connection/credential health FIRST |
CLI (wayai alerts) — surfaces active Status & Notices alerts (e.g. an invalid provider key shows as connection_auth 401). Run this before reading code or filing a report |
| Diagnose an inexplicable agent reply (wrong date, ignored rule, hallucinated value) — don't reason from the transcript; read what the agent actually received (resolved prompt, rendered context, injected timestamps, tool calls) |
CLI (wayai conversations <id> observability [--message-id <id>]) — run this before editing instructions |
| Record a post-hoc business outcome on an ended conversation (e.g. customer purchased) as an analytics dimension |
CLI (wayai conversations <id> annotate --set key=value [--type ...]) |
| Analytics |
CLI (wayai analytics, wayai analytics query) |
| Cost / token spend analysis (per message, model, agent, role, or credential) |
CLI (wayai analytics sql over the message table) — see references/analytics.md — data.* paths need an explicit cast to run at all, plus the rules that make sums correct |
| Eval runs and results |
CLI (wayai run-eval, wayai eval-results) |
| List eval scenarios / raw SQL over eval results |
CLI (wayai evals, wayai evals sql) |
| Capture production conversation as eval |
CLI (wayai eval capture <conversation_id>) |
| Capture production conversation as a journey (full multi-turn transcript) |
CLI (wayai eval journey capture <conversation_id>) |
| Stop a running eval session |
CLI (wayai eval session stop <session_id>) |
| Delete eval session(s) / run history |
CLI (wayai eval session delete <session_id>, or --all for every session on the hub) |
| Bug reporting |
CLI (wayai report create) |
| Workspace discovery |
CLI (wayai list) |
| Organization — create |
CLI (wayai org create) or UI |
| Organization — update, delete |
UI |
| Publish/sync a preview to production |
CLI (wayai publish, alias wayai sync) or UI |
| Delete hubs |
UI |
| Replicate a preview, set/clear a preview's label |
CLI (wayai replicate / wayai relabel) or UI |
| Teams, team users, hub users, admins, contact approval |
UI (Hub → Users tab) |
| Org tags (create; edit display name/color — the slug is permanent) |
UI (referenced from hub.yaml tags: by slug) |
Entity Hierarchy
Organization ← CLI (`wayai org create`) or UI
├── Org Credentials ← CLI (`wayai create-credential`/`update-credential`) or UI — API keys stored once, reused across hubs
├── Org Tags ← UI — gate which credentials each hub can resolve
├── Org Resources ← CLI (`wayai org pull/push`) — shared knowledge/skills, fan out to linked hubs
├── Bases ← CLI (`wayai bases`) — the Data surface. Org-level, NOT inside a hub (see Bases)
└── Hub ← CLI (`wayai create`, or auto-creates on push) or UI; publish/sync via CLI (`wayai publish`) or UI
├── Connections ← auto-created from org credentials on push (non-OAuth); OAuth via UI
├── Channels ← auto-provisioned, never authored (see Channels)
├── Agents ← CLI — `agents/<slug>.yaml` + `<slug>.md`
│ ├── Tools ← CLI — native, custom HTTP, MCP, delegation
│ └── Resource links ← CLI — `resources:` block in agent YAML
├── Kanban statuses ← CLI — `hub.yaml` (workflow stages for conversations)
├── States ← CLI — `hub.yaml` (JSON-schema data agents read/write)
├── Resources ← CLI — `hub.yaml` + `resources/` folder (knowledge + skills)
├── Evals + Journeys ← CLI — `evals/`, `journeys/`
├── Outbound ← CLI — `hub.yaml` (contacts, lists, schedules)
└── Teams + Users ← UI (Hub → Users) — teams, admins, team users, hub users
Setup order: Organization (CLI wayai org create or UI) → Org Credentials (CLI or UI) → Hub (CLI wayai create, or push auto-creates, or UI) → configure agents, tools, connections via CLI.
The wayai connection (native tools) is auto-created when a hub is created — no setup needed.
Hub Types
| Type |
Conversations |
Channels |
Use Case |
chat |
ONE per end user |
WhatsApp, Instagram, Email, Telegram, App |
Person-centered: support, sales, helpdesk |
task |
MULTIPLE per user |
App only |
Task-centered: invoices, inventory, approvals |
Decision: external channels (WhatsApp/Instagram/Email/Telegram) needed → chat. Object/task processing → task.
AI Modes & the Conversation Model
Hub-level ai_mode sets what the AI does:
| Mode |
Behavior |
pilot |
AI handles end users autonomously |
copilot |
AI suggests responses to the support team (no channel delivery) |
pilot+copilot |
Switches dynamically based on who currently responds |
turned_off |
AI disabled; humans only |
A conversation is the runtime session between an end user and the hub (config entities define behavior; conversations and messages are what they act on):
conversation_status: agent (AI handles it) | team (human team handles it) | ended (closed + archived)
- Status selects the active agent track: status
agent → Pilot track replies to the end user through the channel; status team (with copilot/pilot+copilot mode) → Copilot track drafts suggestions the team sees in /support
- Track switches: the
transfer_to_team tool (agent → team) or a team handback in the support UI (team → agent). transfer_to_agent/consult_agent move between agents within a track
- Close paths: the agent's
close_conversation tool, transitioning into an isTerminalStatus kanban status (any surface — agent or harness tool, team drag-drop, REST), the team UI, or the hub's auto_close_inactive_days. Ended conversations are archived and listed in the Ended tab; within conversation_retention_days they still accept post-hoc wayai conversations <id> annotate
- An agent's reply text is delivered automatically — there is no send-message tool; tools exist for actions beyond replying
Kanban status is orthogonal to all of this: it tracks workflow stage (custom slugs like qualified), not who is responding.
Agent Roles
| Role |
Track |
Per Hub |
Description |
pilot |
Pilot |
1 |
Responds to end users autonomously |
copilot |
Copilot |
1 |
Suggests responses to the support team |
pilot_specialist / copilot_specialist |
Both |
Multiple |
Delegation target — full transfer via transfer_to_agent |
pilot_advisor / copilot_advisor |
Both |
1 each |
Advisory input via consult_agent; returns control |
monitor |
Background |
1 |
Observes silently |
conversation_evaluator / message_evaluator |
Background |
1 each |
Async quality assessment; excluded from normal routing. Their evaluation_variables feed Analytics; the message_evaluator also scores eval runs |
summarizer |
Background |
1 |
Auto-provisioned with the first pilot/copilot. Rolling JSON summary of older messages, stored as conversation state with reserved slug conversation_summary. Fires async post-turn when effective input tokens cross the summarizer agent's summarization_threshold_tokens (default 120000; see below). Non-background agents see the summary as a <conversation_summary> block and can call expand_summary(section_id) to fetch original messages. Schema is user-editable but must satisfy the anchor invariant (sections[].id, message_id_start, message_id_end) |
consultant |
Track-independent (on-demand) |
Multiple |
Consulted by people (and agents) in visible consult threads. Never a pilot/copilot responder, never auto-fired, and never a transfer/advisor target. An advisor advises an AI mid-turn and is invisible; a consultant is consulted by people (and agents) in visible threads. Consult turns bill as normal foreground operations. Configurable today; consult dispatch (tagging a consultant from the support composer) ships in a follow-up |
transfer_to_agent targets any same-track agent — a *_specialist or the entry pilot/copilot, so the pilot can act as a hub-and-spoke router (specialists transfer cross-domain requests back to it for re-dispatch). Cross-track, advisor, consultant, and background roles are never transfer targets.
For role flow, delegation, and settings depth, see references/agents/roles-and-settings.md.
Handoff context engineering
When a conversation changes hands — transfer_to_agent or transfer_to_team — whoever resumes rebuilds history from scratch, where every prior agent's turns appear as undifferentiated assistant messages and the human team's turns appear unattributed (the model can't tell which turns it authored vs. inherited). The runtime closes that gap automatically — author your agents to cooperate with it:
- The runtime persists a durable custody marker (
This conversation was handed off from X to Y.) on each transfer_to_agent and transfer_to_team, and delivers a one-time continuation note to a receiving agent's first turn (agent→agent only — a team handoff has no AI receiver to brief). You don't write these — so don't put "you were just transferred this conversation" framing in an agent's instructions; it's handled and would double up.
- Always open each agent's instructions with its identity —
You are <Agent Name>, the <role/purpose>…. The runtime reinforces identity at the handoff moment, but the system prompt is the strongest signal and the only one present on every steady-state turn; the custody marker ("…to Y") only lands if the agent knows it is Y.
- A specialist must do work, not bounce. The runtime blocks delegating back to any agent that already held the conversation earlier in the same turn (A→B→A and longer revisits) — the transfer is refused with an error telling the agent to complete the task, transfer to a different agent, or return control to the user. So don't write a
*_specialist whose instructions reflexively hand the conversation back to its delegator; it'll just hit the guard. (The chain resets each user turn, so re-routing to an earlier agent on a later turn is fine.)
Summarizer agent config
The summarizer agent exposes summarization_threshold_tokens (default 120000, min 1000, max 1000000) as a top-level key in agents/summarizer.yaml. Lower it for testing; raise it for very long conversations. The summarizer's connection defaults to the pilot's; edit agents/summarizer.yaml to change its model or system prompt. The conversation_summary state's schema is round-trippable like any other state — extra fields beyond the anchors are allowed but the anchors are load-bearing. (Previously a hub-level hub.yaml setting — relocated to the summarizer agent.)
Connections & Credentials
A connection is a configured instance of a connector (a catalog entry: LLM provider, channel API, tool API, speech service) with its credential, scoped to one hub. An org credential stores the secret once at the organization level; connections reference it by name — raw secrets never enter YAML.
| Category |
Examples |
| Agent |
OpenAI, Anthropic, Google AI Studio, OpenRouter, xAI (required for AI) |
| Channel |
WhatsApp, Instagram (OAuth — UI only); Resend (email), Telegram (API Key — auto-created) |
| Tool — Native |
Wayai (auto-created), External Resources (API Key) |
| Tool — Custom |
User-defined HTTP endpoints (API Key, Bearer Token, Basic Auth) |
| Tool — MCP |
External MCP servers (Streamable HTTP) — Bearer Token via CLI; OAuth via UI |
| Speech |
STT transcribes inbound voice notes (Groq, OpenAI, ElevenLabs); TTS synthesizes spoken replies (OpenAI, Groq, ElevenLabs) |
Auto-creation rule: Non-OAuth connections (Agent, STT, TTS, Tool — Custom, Tool — MCP via Bearer Token) are auto-created from matching organization credentials when hub.yaml is pushed. Matching respects org tags (an untagged credential is global — every hub can use it; a tagged credential is visible only to hubs sharing ≥1 of its tags) and credential environment. OAuth connections must be set up in the UI first.
OAuth connection handoff (any time — not just onboarding): OAuth connections (WhatsApp, Instagram, MCP OAuth) can't be created from the CLI — they need a one-time UI flow. Whenever one is needed — first-time setup or later (a new channel, an OAuth MCP server) — hand the user the full-path connections-tab deeplink https://app.wayai.pro/settings/organizations/<orgId>/hubs/<hubId>/connections?connector=<slug> (<orgId>/<hubId> from wayai status --json; <slug> ∈ whatsapp, instagram, mcp-server), then wayai pull -y once they're done. The deeplink opens the Connections tab (and highlights the connector if a connection already exists — e.g. re-auth); to create one the user clicks Add Connection, picks the <Connector> card, chooses OAuth, and finishes the provider flow. Use this tab form — not /connections/new?connector=…, which takes a connector_id UUID and defaults to the first auth type (MCP → Bearer Token), so it can't reach MCP OAuth (see navigation.md).
For per-provider setup, credential binding (credential:, no_auth:), tags, and production-credential decoupling, see references/connections.md.
Channels
Communication endpoints on a hub — where messages arrive and replies get delivered. Channels are never authored in YAML:
app (in-app chat) and system (internal) channels are created automatically with the hub
- WhatsApp / Instagram / Email (Resend) / Telegram channels are provisioned automatically when their Channel connection is created
Channel uniqueness (phone / page / inbound address) is enforced across production hubs only — a preview can share endpoints with its production, and external channels are testable on previews via #test CODE tester registration (see Hub Environments).
Tools
Capabilities assigned per agent in agents/<slug>.yaml. Remember: replying with text needs no tool — tools are for everything else.
| Type |
Source |
How |
| Native |
Platform built-ins (e.g., update_kanban_status, get_state, send_files, close_conversation, read_file) |
Listed by name under tools.native |
| Custom |
HTTP endpoints you define |
Defined under tools.custom with connection, method, path, config |
| MCP |
Tools from connected MCP servers |
Dual-origin — declared per-agent under tools.mcp and/or assigned in the Platform UI. wayai push discovers + assigns in one run; a present mcp key (even []) is authoritative, an omitted one preserves UI-assigned tools. See native-tools.md |
| Delegation |
Agent-to-agent (transfer_to_agent, consult_agent, start_consult_thread), agent-to-team (transfer_to_team), or agent-to-hub (delegate_to_hub, start_consult_thread) |
Declared under tools.delegation with target (agent display name, team name, or hub name) |
Meta tools (get_tool_schema, execute_tool) let agents call tools whose schemas are excluded from the inline list. Full native catalog + params: references/agents/native-tools.md; custom tool schema: references/agents/custom-tools.md; designing which tools/params to expose: references/agents/tool-principles.md.
Hub delegation (delegate_to_hub)
Treats another hub as a consultant. The tool spawns a task conversation in the target hub, and that hub's answer comes back asynchronously as a message in the consult thread the request came from — so the tagging agent's turn ends immediately and must not wait for a result.
- type: hub
tool: delegate_to_hub
target: Billing Hub # a PRODUCTION (published) hub in the same organization
context_boundary: summary # instruction | summary | transcript
target must be a published (production) hub_type: task hub in the same organization. Production because it matches branching semantics; task because a task hub gives each request its own conversation — a chat hub keeps one conversation per user, so every delegation would pile into a single thread and mix customers. A cross-org, unpublished, or chat-type target fails the push (and, at runtime, the tool call).
- The target hub must CLOSE the spawned conversation — its close is the completion signal. The request text instructs it to, but a target whose agents can never close (no
close_conversation tool, no terminal kanban status) will leave the asker waiting. (An agent-started consult carries an expiry — see start_consult_thread; a team-started hub delegation does not, so give the target hub a way to finish.)
context_boundary is a data-sensitivity decision, not a tuning knob. Hubs can differ in team membership and what they may see, so choose deliberately — there is no safe default that fits every pair of hubs:
| Value |
What crosses into the target hub |
instruction |
Only the agent's own request. Nothing from the conversation. |
summary (default) |
The request + the rolling conversation summary. |
transcript |
The request + the full customer transcript. |
Internal consult traffic never crosses at any setting, and the model can neither choose the target nor widen the boundary — both are admin config.
- Only available inside a consult thread, which is where the result is delivered — so assign it to a
consultant-role agent. A result arriving after the conversation is closed is dropped (the thread is recorded as cancelled_at_close).
- Configured via CI/YAML only in v1 — the Platform UI's tool "Add" grid omits it until the hub picker and boundary control ship, because attaching it needs both choices above.
Agent-initiated consults (start_consult_thread)
Lets a non-background agent put a question to a configured consultant (or partner hub) in a consult thread the support team can see — the third initiator of the one consult substrate, alongside a human tagging a consultant and an agent delegating to a hub.
- type: agent
tool: start_consult_thread
target: Billing Expert # a `consultant`-role agent on this hub
- The mode follows the target. A same-hub, non-harness consultant answers inside the tool call; a partner hub or a harness-backed consultant answers asynchronously — the asking agent ends its turn ("I'll check and get back to you") and is brought back automatically when the answer lands. A slow sync consult converts to async by itself, so agent instructions must handle "the answer will follow" for any target.
- Consultant→consultant chains are off by default, and sync-only when enabled — set
allow_consultant_chain: true on the tool to permit them, against a same-hub, non-harness consultant. monitor, the evaluators, and summarizer can never initiate.
- Budgets are enforced: consult chain depth, plus cycle refusal in both directions (agent A → B → A inside a hub, hub A → B → A across hubs), a cap per LLM call, and a durable cap per conversation. Every consult turn bills a foreground operation, so these caps are what stop an agent multiplying cost unattended.
- Nothing stays pending forever: an unanswered consult expires and brings the asker back with a timeout notice; a consult outstanding when the conversation closes is recorded
cancelled_at_close.
- A team member posting in an agent-started thread takes it over permanently — the AI is no longer brought back for that thread, and further consults into it return a fixed "taken over by the team" notice.
- Configured via CI/YAML only in v1, like
delegate_to_hub.
Full parameters and YAML shapes: references/agents/native-tools.md.
Kanban & States
Kanban statuses are workflow stages for conversations (visible in support/task views), defined per hub in hub.yaml:
- Identity: immutable lowercase
slug (stored in conversations, analytics, tool params; never renameable) + freely editable display name. Tools accept only slugs (display names ride along as labels) — instructions must reference statuses by slug
- Behavioral flags:
isInitialStatus (exactly one per hub), triggersAgentResponse (transition fires an agent turn), allowsAgentUpdate, isTerminalStatus (entering it closes the conversation; at most one per hub), isSchedulingStatus (+ eventName). Several combinations are mutually exclusive — validated server-side on every write
allowed_next_statuses — optional transition allowlist, enforced at runtime on every surface, with two exemptions: a non-agent caller reaching the terminal status (the REST surface — board and programmatic alike; agents stay gated), and a re-close of a still-open conversation already sitting in the terminal status — any non-agent REST caller, programmatic ones included, but an agent only when reusing a stored outcome, when the status declares no outcomes, or when the outcome it selects is unrestricted. Omit = unrestricted; [] rejected (use isTerminalStatus)
- Outcomes — the terminal status only may declare
outcomes: [{slug, name, color?, from_statuses?}] (closing dispositions, e.g. resolved/canceled). from_statuses is a non-empty source-slug allowlist; omit it to accept the outcome from any source. A genuine terminal Kanban transition requires an eligible outcome and stores its slug for analytics as data.meta.outcome. Agent closes (close_conversation, harness end_conversation) are routed through that transition and gated identically. The team Close button (web and mobile) is routed through it too and asks for an outcome first, but as a non-agent caller it keeps the terminal exemption from allowed_next_statuses described above; when no outcome is eligible from the conversation's current status it falls back to the bare close rather than stranding it. Inactivity auto-close and POST /:id/close stay outcome-free
- Followups — per-status timed messages:
inactivity (after silence), before_event (counting back to the event), inactivity_after_event (the post-visit chase — its first step counts from the event, each later step from the previous nudge) or inactivity_after_before_event (the same chase, but starting when one specific before_event fires, named by after_followup_id). The last three require isSchedulingStatus, and none arms unless the conversation carries a scheduled_event_date, supplied per transition and never in status config. With threshold/timeUnit, quiet hours, holiday exclusion
- Additional context on transition — a
triggersAgentResponse status may declare additional_context_schema (JSON-Schema form the team fills on transition) + additional_instructions (prose template with {{path.to.field}} / {{additional_data}} placeholders injected into the triggered turn)
- Lanes — optional presentational board grouping; no behavioral effect
Full field specs, constraint matrix, warnings, and a complete example: references/kanban.md.
States are JSON-schema data agents read/write during conversations — via native tools (get_state, update_state, set_state_path, reset_state, all addressing a state by its slug) and the {{state(scope, slug)}} instruction placeholder. Each state has conversation or user scope, a json_schema, and an optional initial_value (pre-populated virtual record rendered until the first real write; omit to keep state silent until written).
Kanban vs State: kanban tracks workflow progression; state tracks structured data. Both coexist. Schemas and patterns: references/states.md.
Resources
Knowledge and skills attached to agents. Content lives as real files under resources/<slugified-name>/ (the filesystem is the source of truth for resource content); hub.yaml resources: declares only name/type/description.
| Type |
What |
Runtime behavior |
knowledge (default) |
Document collections — FAQ, catalogs, policies |
The linked resources are injected as resources/<slug> mounts into the list_files native-tool schema at turn time; the agent explores content via list_files + read_file |
skill |
Versioned capability package — SKILL.md (frontmatter name + description) + optional references/ |
Injected as a callable tool (default, works on all providers), or run natively in a provider container (use_native_integration: true, Anthropic/OpenAI only; auto-syncs to the provider on wayai push, wayai sync-skills re-syncs after failures or late-added connections) |
Agents link resources in agents/<slug>.yaml under a resources: block (by name, with priority). Org-level resources shared across hubs live in wayai-ws/org/ via wayai org pull/push (push fans out to linked hubs).
File handling (text vs binary, 10 MB cap), skill authoring, execution modes: references/resources.md.
Bases (the Data surface)
A base is an org-level data container — the system of record behind your hubs. Hubs hold conversations; bases hold the structured data those conversations act on. Same CLI, same login, same workspace; a separate entity with its own subtree and its own promote verb.
- Primitives: record type → record, relationship type → relationship, file type → file, plus Actions and toolsets (curated MCP tools an agent calls), triggers and inbound webhooks (change in/out), external sources (back a record type with an external API), and seed fixtures (hermetic eval data)
- The one rule: config writes (record types, relationship types, file types, triggers, inbound webhooks, Actions, toolsets, seeds) only land on a preview base; data writes work anywhere. Promotion is human-run — surface
wayai bases promote <prod> --from <preview> --dry-run and wait
- Ids are immutable. A base, record type, relationship type, Action or toolset
id is its identity — there is no rename, only create-new + migrate. Choose stable lowercase slugs up front
- Workspace:
wayai-ws/bases/<base>/ (base.yaml + one file per entity), reached by wayai pull bases/<base> / wayai push bases/<base>. A pull/push that names targets in both hubs/ and bases/ is refused, never merged
- Commands:
wayai bases plus the top-level records, record-types, relationships, relationship-types, query-relationships, files, file-types, attachments, toolsets, actions, triggers, inbound-webhooks, seed (each takes --base), and wayai bases tokens|secrets|sql|import|batch|providers|report
- Connecting a hub: today via an ordinary MCP Server connection pointed at a toolset, or as an eval
target_base:. Hub-local resources/ files are a different surface and stay hub-local
Open references/bases/README.md before doing any base work — it carries the full object model and routes to the per-domain files below. Nothing in this section is enough to author a schema from.
Evals
Test scenarios that run the real agent with its real tools and score the result. The primitives:
- Scenario (
evals/<name>.yaml or evals/<set>/<name>.yaml) — optional multi-turn history, one input, an expected response (text and/or tool_calls), optional evaluator_instructions. Scored by the hub's message_evaluator agent; a required-but-skipped tool call fails the eval even when the reply text reads fine
- Scenario set — first-level subfolder (one level only).
wayai run-eval runs exactly one set per session, whole or narrowed to chosen scenarios with repeatable --eval (and --runs for chosen repetitions) — the cheap loop when a change touches a few scenarios of a large set
- Journey (
journeys/<slug>.yaml, flat folder) — a stored happy-path transcript that materializes one derived eval per agent turn. The default way to build broad regression coverage: wayai eval journey capture <conversation_id>, then wayai pull (syncs server-minted step ids)
- Per-run
variables + runs: N — reliability is a distribution, not a 1/1 sample; each run resolves {{var(name)}} against its own disjoint row
- Seed
fixture: — for any eval that writes: names a base fixture the platform LEASES for the session — resetting it on acquire, clearing it on release — so runs start from a known baseline instead of the last run's residue. One preview base admits one eval session at a time: a second launch is refused with fixture_target_in_use (409) rather than allowed to corrupt the first, and run-eval waits it out by default — as it does fixture_seed_unavailable (409), the base briefly refusing the lease under its own write backpressure
- Seed
initial_state: — pre-populate user-scope WayAI state (a recurring-customer record, a saved profile) before input runs, so behavior that depends on memory of prior conversations is testable; isolated + torn down per session like fixture:
- Capture —
wayai eval capture <conversation_id> freezes a production conversation's last exchange into a scenario YAML
Good practice for tool-dependent evals: compose journey + fixture: + variables for repeatable, parallel runs, and phrase evaluator_instructions as functional outcomes, not raw call counts ("one successful booking", not "exactly one book_appointment call") — tools fail transiently, and a correct agent retries. Full YAML shapes, seed-connection setup, run pacing, and authoring/interpreting principles: references/evals.md.
Outbound
Proactive messaging — the hub contacts people before they write. Three hub.yaml blocks:
outbound_contacts — named contacts with ≥1 channel identifier (phone E.164 / email / instagram_sid) + free-form tags
outbound_lists — named static collections of contacts (referenced by contact name)
outbound_schedules — cron expression + timezone + list + channel + execution mode: direct_message (template / free text sent as-is) or agent_trigger (a system message triggers the agent, which opens the conversation naturally using its tools and instructions)
WhatsApp/Instagram delivery is constrained by the 24-hour messaging window (WhatsApp falls back to an approved template; Instagram skips). Inline contacts are practical to ~500 — beyond that, import via UI/API. Shapes, channel rules, limits: references/outbound.md.
Analytics
Every conversation lands in the analytics store with variables from five origins:
| Origin |
Path |
Set by |
| System metrics |
data.system.* |
Platform — message counts, response times, durations, tokens (~25 metrics) |
| Agent-defined variables |
data.variables.* |
evaluation_variables declared on conversation_evaluator / message_evaluator agents |
| Metadata |
data.meta.* |
Platform — subject, kanban_status, hub_type |
| Post-hoc annotations |
data.annotations.* |
wayai conversations <id> annotate --set key=value — real business outcomes (purchased, churned) recorded after the conversation ends; correlate predictions vs reality |
| Eval scores |
data.eval_scores.* |
Eval runs only (is_eval = true rows — excluded from production analytics) |
Conversation rows are one grain; a second table, message, decomposes each conversation's spend per message (tokens, USD cost, operations) for per-model, per-agent, and per-credential cost analysis.
Query with wayai analytics (summary + per-variable aggregates; --metric, --filter, --period), wayai analytics query (structured: multi-variable, group_by, correlations), wayai analytics sql (raw SQL over conversation and message — the surface for cost analysis), or wayai evals sql (same SQL over eval rows). Defining good variables happens on the evaluator agents (roles-and-settings.md → Evaluation Variables); filters, aggregations, cost queries, and workflows: references/analytics.md.
Teams, Users & Access
People entities are UI-managed (Hub → Users tab: /settings/organizations/<orgId>/hubs/<hubId>/users), never in YAML:
- Hub User — the end user the AI talks to (customer/lead/employee). Uses
/chat or /task
- Hub Team User — support team member handling conversations in
/support; grouped into Teams (e.g. "Tier 2 Support") that transfer_to_team targets by name — an unknown target fails at runtime
- Hub Admin — full hub config access. Org Owner/Admin — org level (billing, credentials, hubs). Access is per-level, not inherited (an org admin isn't automatically a hub admin)
- Contact access control — with
non_app_permission: require_permission, unknown channel contacts are held pending (localized auto-reply, overridable via access_request_message) until approved/blocked by the role in access_approval_role
Hub Environments
| Environment |
Description |
preview |
Default. Editable workspace for configuring and testing |
production |
Read-only. Serves live traffic. Changes flow from preview via publish/sync |
Lifecycle:
- New hubs start as
preview — edit freely. wayai create --label <l> (or wayai push --label <l> on auto-create) names the first preview at creation
- Publish (CLI
wayai publish, or UI) — first promotion creates a production hub cloned from preview
- Sync (CLI
wayai publish / alias wayai sync, or UI) — pushes subsequent preview changes to the linked production. The one command auto-detects first-publish vs sync; it confirms by default (shows the preview→production diff) and -y skips the prompt. Promotes the pushed preview state, so wayai push first
- Replicate Preview (CLI
wayai replicate [hub] --label <l> or UI) — creates a new sibling preview (from a preview or production) for experimentation
- Relabel (CLI
wayai relabel <label> / --clear, or UI) — set/clear a preview's preview_label (the sibling disambiguator). NOT editable via hub.yaml + push — it's server-owned
Production is read-only — all config mutations flow through preview. Multiple previews can link to the same production (many-to-1). Channel uniqueness is enforced on production only — previews can share phone/email/SID with their production.
WhatsApp/Instagram/Telegram channels can be exercised on a preview before publishing — register a tester via a #test CODE claim code (see references/connections.md → Channel → "Te
…(truncated)
1---2name: wayai3description: Configure WayAI hubs, agents, tools, channels, resources, states, evals, outbound, and analytics, plus the Data surface (bases, record types, records, relationships, files, toolsets). Use when: creating or editing a hub or hub config; adding/configuring agents, tools, channels, connections, teams, kanban, states, resources, eval scenarios or journeys, outbound campaigns; running analytics or evals; annotating conversation outcomes; reviewing or editing workspace YAML (hub.yaml, agents/*.yaml, base.yaml, record-types/*.yaml) or agent instruction Markdown; designing a base schema, upserting or querying records, linking records with relationships, storing versioned files or attachments, wiring inbound webhooks, triggers or external sources, building MCP toolsets and Actions, scoping base API tokens, or seeding eval fixtures; using the wayai CLI (push, pull, publish, send-message, conversations, sync-skills, create-credential, update-credential, analytics, analytics sql, run-eval, eval capture, evals sql, org, in4---56# WayAI Skill78WayAI is a SaaS platform for AI-powered communication hubs. Each hub combines AI agents and a human team across channels (WhatsApp, Email, Instagram, Telegram, native App). This workspace stores hubs as code — one folder per hub (`hub.yaml` + `agents/*.{yaml,md}` + `evals/`, `journeys/`, `resources/`) synced bidirectionally to the platform via the `wayai` CLI.910**Platform is the source of truth.** Workspace files are the edit surface — changes flow through files → `wayai push` → platform. Always `wayai pull` before editing to catch out-of-band changes.1112**How to use this skill:** this file is the complete concept map — every WayAI primitive is defined here with enough depth to decide what to build and which files to touch. Field-level schemas, per-provider specifics, and mechanics live in `references/`; each domain section below ends with a pointer to its deep-dive file — open it when you're about to author or debug that domain. Before generating a full hub from scratch, read [`references/canonical-example/README.md`](references/canonical-example/README.md) once — it shows how the pieces wire together. The full routing table is at the end ([Reference Documentation](#reference-documentation)).1314## Agent Guidelines1516- **At the start of every session, before anything else, get current** — every time, even for a quick task: update the CLI, then check whether this skill is stale and refresh it if so. A stale CLI or skill is the most common cause of a step below not working. Run the procedure as written in [Workflow → Existing hub](#existing-hub) steps 1–2 (cold start: [State machine](#state-machine) rows 1–1c), and take the skill-install command from row 1b rather than retyping it — its `mkdir -p .claude` prefix is load-bearing for Claude Code, and row 1c carries what to do when the install fails17- **Talk like a person, not a manual:** the user may be new to WayAI — plain language, no jargon. Keep answers short and to the point; don't explain what they didn't ask about. If they seem stuck or ask what something means, give a one-line answer and move on — don't turn a reply into a tutorial18- **Interface:** setup runs on the **`wayai` CLI and workspace files** — the only automation surface, and it needs filesystem/shell access (code-harness agents — Claude Code, Codex, Cursor, OpenCode). Drive everything below through it. If you do **not** have shell access (app-harness agents — Claude Desktop, ChatGPT, etc.), you cannot configure WayAI yourself — hand the work to the person, the same shape as the **OAuth connection handoff** below (one URL, one action, one return signal): "Open `https://app.wayai.pro`. Set it up there, or run this with a shell-having agent — the one-prompt install is on `https://wayai.pro`. Tell me when done." Then keep answering from this skill, and never report config you didn't make19- Only provide information from this skill, tool descriptions, or reference documentation20- Do not invent URLs, paths, or steps21- Hub config flows through files + the `wayai` CLI; one-time setup (orgs, OAuth) goes through the platform UI. Publishing preview → production is now CLI-capable (`wayai publish`) or UI22- Always `wayai pull -y` before editing — catches out-of-band changes23- Always `wayai push -y` immediately after editing — editing and pushing are a single action24- Never auto-commit — show `git diff`, wait for user approval2526## Quick Decision: What Can I Do?2728| Entity | How |29|--------|-----|30| Hub settings, agents, agent instructions, tools, kanban, states, resources, evals, journeys, outbound, custom tools | CLI (`wayai push`) |31| Eval journeys (hub-as-code) — `journeys/<slug>.yaml`, flat folder | CLI (`wayai push` / `wayai pull`; pull after first create to sync step ids) |32| Connections — non-OAuth (Agent providers, STT/TTS, Tool API key, MCP Bearer Token) | CLI (auto-created from org credentials) |33| Connections — OAuth (WhatsApp, Instagram, MCP OAuth) | Platform UI |34| Set/rotate a connection's credential directly (incl. production) | CLI (`wayai set-connection-credential`) or UI |35| Org credentials — create / rotate / edit | CLI (`wayai create-credential` / `wayai update-credential`) or UI |36| Org-level shared resources (org-as-code) | CLI (`wayai org pull` / `push` / `diff`) |37| Bases — schemas, records, relationships, files, toolsets (the Data surface) | CLI (`wayai bases`, `wayai records`, `wayai record-types`, …; config-as-code via `wayai pull`/`push bases/<base>`) — open [`references/bases/README.md`](references/bases/README.md) first |38| Skills sync to providers | CLI (`wayai sync-skills`) |39| Conversation testing | CLI (`wayai send-message`, `wayai conversations`, `wayai delete-history`) |40| Diagnose why a hub misbehaves (audio/TTS not delivered, agent silent, a tool failing) — check connection/credential health FIRST | CLI (`wayai alerts`) — surfaces active Status & Notices alerts (e.g. an invalid provider key shows as `connection_auth` 401). Run this before reading code or filing a report |41| Diagnose an inexplicable agent reply (wrong date, ignored rule, hallucinated value) — don't reason from the transcript; read what the agent actually received (resolved prompt, rendered context, injected timestamps, tool calls) | CLI (`wayai conversations <id> observability [--message-id <id>]`) — run this before editing instructions |42| Record a post-hoc business outcome on an ended conversation (e.g. customer purchased) as an analytics dimension | CLI (`wayai conversations <id> annotate --set key=value [--type ...]`) |43| Analytics | CLI (`wayai analytics`, `wayai analytics query`) |44| Cost / token spend analysis (per message, model, agent, role, or credential) | CLI (`wayai analytics sql` over the `message` table) — see [`references/analytics.md`](references/analytics.md#raw-sql--cost-analysis) — `data.*` paths need an explicit cast to run at all, plus the rules that make sums correct |45| Eval runs and results | CLI (`wayai run-eval`, `wayai eval-results`) |46| List eval scenarios / raw SQL over eval results | CLI (`wayai evals`, `wayai evals sql`) |47| Capture production conversation as eval | CLI (`wayai eval capture <conversation_id>`) |48| Capture production conversation as a journey (full multi-turn transcript) | CLI (`wayai eval journey capture <conversation_id>`) |49| Stop a running eval session | CLI (`wayai eval session stop <session_id>`) |50| Delete eval session(s) / run history | CLI (`wayai eval session delete <session_id>`, or `--all` for every session on the hub) |51| Bug reporting | CLI (`wayai report create`) |52| Workspace discovery | CLI (`wayai list`) |53| Organization — create | CLI (`wayai org create`) or UI |54| Organization — update, delete | UI |55| Publish/sync a preview to production | CLI (`wayai publish`, alias `wayai sync`) or UI |56| Delete hubs | UI |57| Replicate a preview, set/clear a preview's label | CLI (`wayai replicate` / `wayai relabel`) or UI |58| Teams, team users, hub users, admins, contact approval | UI (Hub → Users tab) |59| Org tags (create; edit display name/color — the slug is permanent) | UI (referenced from `hub.yaml` `tags:` by slug) |6061## Entity Hierarchy6263```64Organization ← CLI (`wayai org create`) or UI65├── Org Credentials ← CLI (`wayai create-credential`/`update-credential`) or UI — API keys stored once, reused across hubs66├── Org Tags ← UI — gate which credentials each hub can resolve67├── Org Resources ← CLI (`wayai org pull/push`) — shared knowledge/skills, fan out to linked hubs68├── Bases ← CLI (`wayai bases`) — the Data surface. Org-level, NOT inside a hub (see Bases)69└── Hub ← CLI (`wayai create`, or auto-creates on push) or UI; publish/sync via CLI (`wayai publish`) or UI70 ├── Connections ← auto-created from org credentials on push (non-OAuth); OAuth via UI71 ├── Channels ← auto-provisioned, never authored (see Channels)72 ├── Agents ← CLI — `agents/<slug>.yaml` + `<slug>.md`73 │ ├── Tools ← CLI — native, custom HTTP, MCP, delegation74 │ └── Resource links ← CLI — `resources:` block in agent YAML75 ├── Kanban statuses ← CLI — `hub.yaml` (workflow stages for conversations)76 ├── States ← CLI — `hub.yaml` (JSON-schema data agents read/write)77 ├── Resources ← CLI — `hub.yaml` + `resources/` folder (knowledge + skills)78 ├── Evals + Journeys ← CLI — `evals/`, `journeys/`79 ├── Outbound ← CLI — `hub.yaml` (contacts, lists, schedules)80 └── Teams + Users ← UI (Hub → Users) — teams, admins, team users, hub users81```8283Setup order: Organization (CLI `wayai org create` or UI) → Org Credentials (CLI or UI) → Hub (CLI `wayai create`, or push auto-creates, or UI) → configure agents, tools, connections via CLI.8485The `wayai` connection (native tools) is auto-created when a hub is created — no setup needed.8687## Hub Types8889| Type | Conversations | Channels | Use Case |90|------|--------------|----------|----------|91| `chat` | ONE per end user | WhatsApp, Instagram, Email, Telegram, App | Person-centered: support, sales, helpdesk |92| `task` | MULTIPLE per user | App only | Task-centered: invoices, inventory, approvals |9394Decision: external channels (WhatsApp/Instagram/Email/Telegram) needed → `chat`. Object/task processing → `task`.9596## AI Modes & the Conversation Model9798Hub-level `ai_mode` sets what the AI does:99100| Mode | Behavior |101|------|----------|102| `pilot` | AI handles end users autonomously |103| `copilot` | AI suggests responses to the support team (no channel delivery) |104| `pilot+copilot` | Switches dynamically based on who currently responds |105| `turned_off` | AI disabled; humans only |106107A **conversation** is the runtime session between an end user and the hub (config entities define behavior; conversations and messages are what they act on):108109- `conversation_status`: `agent` (AI handles it) | `team` (human team handles it) | `ended` (closed + archived)110- Status selects the active agent **track**: status `agent` → **Pilot** track replies to the end user through the channel; status `team` (with `copilot`/`pilot+copilot` mode) → **Copilot** track drafts suggestions the team sees in `/support`111- Track switches: the `transfer_to_team` tool (agent → team) or a team handback in the support UI (team → agent). `transfer_to_agent`/`consult_agent` move between agents *within* a track112- Close paths: the agent's `close_conversation` tool, transitioning into an `isTerminalStatus` kanban status (any surface — agent or harness tool, team drag-drop, REST), the team UI, or the hub's `auto_close_inactive_days`. Ended conversations are archived and listed in the Ended tab; within `conversation_retention_days` they still accept post-hoc `wayai conversations <id> annotate`113- An agent's reply text is delivered automatically — **there is no send-message tool**; tools exist for actions beyond replying114115Kanban status is orthogonal to all of this: it tracks *workflow stage* (custom slugs like `qualified`), not who is responding.116117## Agent Roles118119| Role | Track | Per Hub | Description |120|------|-------|---------|-------------|121| `pilot` | Pilot | 1 | Responds to end users autonomously |122| `copilot` | Copilot | 1 | Suggests responses to the support team |123| `pilot_specialist` / `copilot_specialist` | Both | Multiple | Delegation target — full transfer via `transfer_to_agent` |124| `pilot_advisor` / `copilot_advisor` | Both | 1 each | Advisory input via `consult_agent`; returns control |125| `monitor` | Background | 1 | Observes silently |126| `conversation_evaluator` / `message_evaluator` | Background | 1 each | Async quality assessment; excluded from normal routing. Their `evaluation_variables` feed Analytics; the `message_evaluator` also scores eval runs |127| `summarizer` | Background | 1 | Auto-provisioned with the first pilot/copilot. Rolling JSON summary of older messages, stored as conversation state with reserved slug `conversation_summary`. Fires async post-turn when effective input tokens cross the summarizer agent's `summarization_threshold_tokens` (default 120000; see below). Non-background agents see the summary as a `<conversation_summary>` block and can call `expand_summary(section_id)` to fetch original messages. Schema is user-editable but must satisfy the anchor invariant (`sections[].id`, `message_id_start`, `message_id_end`) |128| `consultant` | Track-independent (on-demand) | Multiple | Consulted by people (and agents) in visible consult threads. Never a pilot/copilot responder, never auto-fired, and never a transfer/advisor target. *An advisor advises an AI mid-turn and is invisible; a consultant is consulted by people (and agents) in visible threads.* Consult turns bill as normal foreground operations. Configurable today; consult dispatch (tagging a consultant from the support composer) ships in a follow-up |129130`transfer_to_agent` targets **any same-track agent** — a `*_specialist` *or* the entry `pilot`/`copilot`, so the pilot can act as a **hub-and-spoke router** (specialists transfer cross-domain requests back to it for re-dispatch). Cross-track, advisor, consultant, and background roles are never transfer targets.131132For role flow, delegation, and settings depth, see [`references/agents/roles-and-settings.md`](references/agents/roles-and-settings.md).133134### Handoff context engineering135136When a conversation changes hands — `transfer_to_agent` or `transfer_to_team` — whoever resumes rebuilds history from scratch, where **every prior agent's turns appear as undifferentiated `assistant` messages** and the human team's turns appear unattributed (the model can't tell which turns it authored vs. inherited). The runtime closes that gap automatically — author your agents to cooperate with it:137138- **The runtime persists a durable custody marker** (`This conversation was handed off from X to Y.`) on each `transfer_to_agent` and `transfer_to_team`, and delivers a **one-time continuation note** to a receiving *agent's* first turn (agent→agent only — a team handoff has no AI receiver to brief). You don't write these — so **don't** put "you were just transferred this conversation" framing in an agent's instructions; it's handled and would double up.139- **Always open each agent's instructions with its identity** — `You are <Agent Name>, the <role/purpose>…`. The runtime reinforces identity at the handoff moment, but the system prompt is the strongest signal and the only one present on every steady-state turn; the custody marker ("…to Y") only lands if the agent knows it *is* Y.140- **A specialist must do work, not bounce.** The runtime **blocks** delegating back to any agent that already held the conversation earlier in the same turn (A→B→A and longer revisits) — the transfer is refused with an error telling the agent to complete the task, transfer to a *different* agent, or return control to the user. So don't write a `*_specialist` whose instructions reflexively hand the conversation back to its delegator; it'll just hit the guard. (The chain resets each user turn, so re-routing to an earlier agent on a *later* turn is fine.)141142### Summarizer agent config143144The summarizer agent exposes `summarization_threshold_tokens` (default 120000, min 1000, max 1000000) as a top-level key in `agents/summarizer.yaml`. Lower it for testing; raise it for very long conversations. The summarizer's `connection` defaults to the pilot's; edit `agents/summarizer.yaml` to change its model or system prompt. The `conversation_summary` state's schema is round-trippable like any other state — extra fields beyond the anchors are allowed but the anchors are load-bearing. (Previously a hub-level `hub.yaml` setting — relocated to the summarizer agent.)145146## Connections & Credentials147148A **connection** is a configured instance of a connector (a catalog entry: LLM provider, channel API, tool API, speech service) with its credential, scoped to one hub. An **org credential** stores the secret once at the organization level; connections reference it by name — raw secrets never enter YAML.149150| Category | Examples |151|----------|----------|152| **Agent** | OpenAI, Anthropic, Google AI Studio, OpenRouter, xAI (required for AI) |153| **Channel** | WhatsApp, Instagram (OAuth — UI only); Resend (email), Telegram (API Key — auto-created) |154| **Tool — Native** | Wayai (auto-created), External Resources (API Key) |155| **Tool — Custom** | User-defined HTTP endpoints (API Key, Bearer Token, Basic Auth) |156| **Tool — MCP** | External MCP servers (Streamable HTTP) — Bearer Token via CLI; OAuth via UI |157| **Speech** | STT transcribes inbound voice notes (Groq, OpenAI, ElevenLabs); TTS synthesizes spoken replies (OpenAI, Groq, ElevenLabs) |158159**Auto-creation rule:** Non-OAuth connections (Agent, STT, TTS, Tool — Custom, Tool — MCP via Bearer Token) are auto-created from matching organization credentials when `hub.yaml` is pushed. Matching respects **org tags** (an untagged credential is global — every hub can use it; a tagged credential is visible only to hubs sharing ≥1 of its tags) and credential `environment`. OAuth connections must be set up in the UI first.160161**OAuth connection handoff (any time — not just onboarding):** OAuth connections (WhatsApp, Instagram, **MCP OAuth**) can't be created from the CLI — they need a one-time UI flow. **Whenever** one is needed — first-time setup *or* later (a new channel, an OAuth MCP server) — hand the user the full-path connections-tab deeplink `https://app.wayai.pro/settings/organizations/<orgId>/hubs/<hubId>/connections?connector=<slug>` (`<orgId>`/`<hubId>` from `wayai status --json`; `<slug>` ∈ `whatsapp`, `instagram`, `mcp-server`), then `wayai pull -y` once they're done. The deeplink opens the **Connections** tab (and highlights the connector if a connection already exists — e.g. re-auth); to create one the user clicks **Add Connection**, picks the **\<Connector\>** card, chooses **OAuth**, and finishes the provider flow. Use this tab form — **not** `/connections/new?connector=…`, which takes a `connector_id` UUID and defaults to the first auth type (MCP → Bearer Token), so it can't reach MCP OAuth (see [navigation.md](references/navigation.md)).162163For per-provider setup, credential binding (`credential:`, `no_auth:`), tags, and production-credential decoupling, see [`references/connections.md`](references/connections.md).164165## Channels166167Communication endpoints on a hub — where messages arrive and replies get delivered. **Channels are never authored in YAML:**168169- `app` (in-app chat) and `system` (internal) channels are created automatically with the hub170- WhatsApp / Instagram / Email (Resend) / Telegram channels are provisioned automatically when their **Channel connection** is created171172Channel uniqueness (phone / page / inbound address) is enforced across **production** hubs only — a preview can share endpoints with its production, and external channels are testable on previews via `#test CODE` tester registration (see Hub Environments).173174## Tools175176Capabilities assigned per agent in `agents/<slug>.yaml`. Remember: replying with text needs no tool — tools are for everything else.177178| Type | Source | How |179|------|--------|-----|180| Native | Platform built-ins (e.g., `update_kanban_status`, `get_state`, `send_files`, `close_conversation`, `read_file`) | Listed by name under `tools.native` |181| Custom | HTTP endpoints you define | Defined under `tools.custom` with `connection`, `method`, `path`, `config` |182| MCP | Tools from connected MCP servers | Dual-origin — declared per-agent under `tools.mcp` **and/or** assigned in the Platform UI. `wayai push` discovers + assigns in one run; a present `mcp` key (even `[]`) is authoritative, an omitted one preserves UI-assigned tools. See [native-tools.md](references/agents/native-tools.md#mcp-tools) |183| Delegation | Agent-to-agent (`transfer_to_agent`, `consult_agent`, `start_consult_thread`), agent-to-team (`transfer_to_team`), or agent-to-**hub** (`delegate_to_hub`, `start_consult_thread`) | Declared under `tools.delegation` with `target` (agent display name, team name, or hub name) |184185Meta tools (`get_tool_schema`, `execute_tool`) let agents call tools whose schemas are excluded from the inline list. Full native catalog + params: [`references/agents/native-tools.md`](references/agents/native-tools.md); custom tool schema: [`references/agents/custom-tools.md`](references/agents/custom-tools.md); designing *which* tools/params to expose: [`references/agents/tool-principles.md`](references/agents/tool-principles.md).186187### Hub delegation (`delegate_to_hub`)188189Treats **another hub as a consultant**. The tool spawns a task conversation in the target hub, and that hub's answer comes back **asynchronously** as a message in the consult thread the request came from — so the tagging agent's turn ends immediately and must not wait for a result.190191```yaml192- type: hub193 tool: delegate_to_hub194 target: Billing Hub # a PRODUCTION (published) hub in the same organization195 context_boundary: summary # instruction | summary | transcript196```197198- **`target` must be a published (production) `hub_type: task` hub in the same organization.** Production because it matches branching semantics; **task** because a task hub gives each request its own conversation — a `chat` hub keeps one conversation per user, so every delegation would pile into a single thread and mix customers. A cross-org, unpublished, or chat-type target fails the push (and, at runtime, the tool call).199- **The target hub must CLOSE the spawned conversation** — its close is the completion signal. The request text instructs it to, but a target whose agents can never close (no `close_conversation` tool, no terminal kanban status) will leave the asker waiting. (An **agent-started** consult carries an expiry — see `start_consult_thread`; a team-started hub delegation does not, so give the target hub a way to finish.)200- **`context_boundary` is a data-sensitivity decision, not a tuning knob.** Hubs can differ in team membership and what they may see, so choose deliberately — there is no safe default that fits every pair of hubs:201202| Value | What crosses into the target hub |203|---|---|204| `instruction` | Only the agent's own request. Nothing from the conversation. |205| `summary` (default) | The request + the rolling conversation summary. |206| `transcript` | The request + the full customer transcript. |207208 Internal consult traffic never crosses at any setting, and the model can neither choose the target nor widen the boundary — both are admin config.209- **Only available inside a consult thread**, which is where the result is delivered — so assign it to a `consultant`-role agent. A result arriving after the conversation is closed is dropped (the thread is recorded as `cancelled_at_close`).210- **Configured via CI/YAML only in v1** — the Platform UI's tool "Add" grid omits it until the hub picker and boundary control ship, because attaching it needs both choices above.211212### Agent-initiated consults (`start_consult_thread`)213214Lets a **non-background agent** put a question to a configured consultant (or partner hub) in a consult thread the support team can see — the third initiator of the one consult substrate, alongside a human tagging a consultant and an agent delegating to a hub.215216```yaml217- type: agent218 tool: start_consult_thread219 target: Billing Expert # a `consultant`-role agent on this hub220```221222- **The mode follows the target.** A same-hub, non-harness consultant answers inside the tool call; a partner hub or a harness-backed consultant answers **asynchronously** — the asking agent ends its turn ("I'll check and get back to you") and is brought back automatically when the answer lands. A slow sync consult converts to async by itself, so agent instructions must handle "the answer will follow" for any target.223- **Consultant→consultant chains are off by default, and sync-only when enabled** — set `allow_consultant_chain: true` on the tool to permit them, against a same-hub, non-harness consultant. `monitor`, the evaluators, and `summarizer` can never initiate.224- **Budgets are enforced**: consult chain depth, plus cycle refusal in both directions (agent A → B → A inside a hub, hub A → B → A across hubs), a cap per LLM call, and a durable cap per conversation. Every consult turn bills a foreground operation, so these caps are what stop an agent multiplying cost unattended.225- **Nothing stays pending forever**: an unanswered consult expires and brings the asker back with a timeout notice; a consult outstanding when the conversation closes is recorded `cancelled_at_close`.226- **A team member posting in an agent-started thread takes it over permanently** — the AI is no longer brought back for that thread, and further consults into it return a fixed "taken over by the team" notice.227- **Configured via CI/YAML only in v1**, like `delegate_to_hub`.228229Full parameters and YAML shapes: [`references/agents/native-tools.md`](references/agents/native-tools.md#start_consult_thread).230231## Kanban & States232233**Kanban statuses** are workflow stages for conversations (visible in support/task views), defined per hub in `hub.yaml`:234235- Identity: immutable lowercase `slug` (stored in conversations, analytics, tool params; **never renameable**) + freely editable display `name`. Tools accept only slugs (display names ride along as labels) — instructions must reference statuses by slug236- Behavioral flags: `isInitialStatus` (exactly one per hub), `triggersAgentResponse` (transition fires an agent turn), `allowsAgentUpdate`, `isTerminalStatus` (**entering it closes the conversation**; **at most one per hub**), `isSchedulingStatus` (+ `eventName`). Several combinations are mutually exclusive — validated server-side on every write237- `allowed_next_statuses` — optional transition allowlist, enforced at runtime on every surface, with two exemptions: a **non-agent caller** reaching the terminal status (the REST surface — board and programmatic alike; agents stay gated), and a **re-close** of a still-open conversation already sitting in the terminal status — any non-agent REST caller, programmatic ones included, but an agent only when reusing a stored outcome, when the status declares no outcomes, or when the outcome it selects is unrestricted. Omit = unrestricted; `[]` rejected (use `isTerminalStatus`)238- **Outcomes** — the terminal status only may declare `outcomes: [{slug, name, color?, from_statuses?}]` (closing dispositions, e.g. resolved/canceled). `from_statuses` is a non-empty source-slug allowlist; omit it to accept the outcome from any source. A genuine terminal Kanban transition requires an eligible outcome and stores its slug for analytics as `data.meta.outcome`. Agent closes (`close_conversation`, harness `end_conversation`) are routed through that transition and gated identically. The **team Close button** (web and mobile) is routed through it too and asks for an outcome first, but as a non-agent caller it keeps the terminal exemption from `allowed_next_statuses` described above; when no outcome is eligible from the conversation's current status it falls back to the bare close rather than stranding it. Inactivity auto-close and `POST /:id/close` stay outcome-free239- **Followups** — per-status timed messages: `inactivity` (after silence), `before_event` (counting back to the event), `inactivity_after_event` (the post-visit chase — its first step counts from the event, each later step from the previous nudge) or `inactivity_after_before_event` (the same chase, but starting when one specific `before_event` fires, named by `after_followup_id`). The last three require `isSchedulingStatus`, and none arms unless the conversation carries a `scheduled_event_date`, supplied per transition and never in status config. With threshold/timeUnit, quiet hours, holiday exclusion240- **Additional context on transition** — a `triggersAgentResponse` status may declare `additional_context_schema` (JSON-Schema form the team fills on transition) + `additional_instructions` (prose template with `{{path.to.field}}` / `{{additional_data}}` placeholders injected into the triggered turn)241- **Lanes** — optional presentational board grouping; no behavioral effect242243Full field specs, constraint matrix, warnings, and a complete example: [`references/kanban.md`](references/kanban.md).244245**States** are JSON-schema data agents read/write during conversations — via native tools (`get_state`, `update_state`, `set_state_path`, `reset_state`, all addressing a state by its `slug`) and the `{{state(scope, slug)}}` instruction placeholder. Each state has `conversation` or `user` scope, a `json_schema`, and an optional `initial_value` (pre-populated virtual record rendered until the first real write; omit to keep state silent until written).246247**Kanban vs State:** kanban tracks workflow progression; state tracks structured data. Both coexist. Schemas and patterns: [`references/states.md`](references/states.md).248249## Resources250251Knowledge and skills attached to agents. Content lives as real files under `resources/<slugified-name>/` (the filesystem is the source of truth **for resource content**); `hub.yaml` `resources:` declares only name/type/description.252253| Type | What | Runtime behavior |254|------|------|------------------|255| `knowledge` (default) | Document collections — FAQ, catalogs, policies | The linked resources are injected as `resources/<slug>` mounts into the `list_files` native-tool schema at turn time; the agent explores content via `list_files` + `read_file` |256| `skill` | Versioned capability package — `SKILL.md` (frontmatter `name` + `description`) + optional `references/` | Injected as a callable tool (default, works on all providers), or run natively in a provider container (`use_native_integration: true`, Anthropic/OpenAI only; auto-syncs to the provider on `wayai push`, `wayai sync-skills` re-syncs after failures or late-added connections) |257258Agents link resources in `agents/<slug>.yaml` under a `resources:` block (by name, with `priority`). Org-level resources shared across hubs live in `wayai-ws/org/` via `wayai org pull/push` (push fans out to linked hubs).259260File handling (text vs binary, 10 MB cap), skill authoring, execution modes: [`references/resources.md`](references/resources.md).261262## Bases (the Data surface)263264A **base** is an org-level data container — the system of record behind your hubs. Hubs hold conversations; bases hold the structured data those conversations act on. Same CLI, same login, same workspace; a separate entity with its own subtree and its own promote verb.265266- **Primitives:** record type → record, relationship type → relationship, file type → file, plus Actions and toolsets (curated MCP tools an agent calls), triggers and inbound webhooks (change in/out), external sources (back a record type with an external API), and seed fixtures (hermetic eval data)267- **The one rule:** config writes (record types, relationship types, file types, triggers, inbound webhooks, Actions, toolsets, seeds) only land on a **preview** base; data writes work anywhere. Promotion is human-run — surface `wayai bases promote <prod> --from <preview> --dry-run` and wait268- **Ids are immutable.** A base, record type, relationship type, Action or toolset `id` *is* its identity — there is no rename, only create-new + migrate. Choose stable lowercase slugs up front269- **Workspace:** `wayai-ws/bases/<base>/` (`base.yaml` + one file per entity), reached by `wayai pull bases/<base>` / `wayai push bases/<base>`. A `pull`/`push` that names targets in both `hubs/` and `bases/` is **refused, never merged**270- **Commands:** `wayai bases` plus the top-level `records`, `record-types`, `relationships`, `relationship-types`, `query-relationships`, `files`, `file-types`, `attachments`, `toolsets`, `actions`, `triggers`, `inbound-webhooks`, `seed` (each takes `--base`), and `wayai bases tokens|secrets|sql|import|batch|providers|report`271- **Connecting a hub:** today via an ordinary MCP Server connection pointed at a toolset, or as an eval `target_base:`. Hub-local `resources/` files are a *different* surface and stay hub-local272273**Open [`references/bases/README.md`](references/bases/README.md) before doing any base work** — it carries the full object model and routes to the per-domain files below. Nothing in this section is enough to author a schema from.274275## Evals276277Test scenarios that run the **real** agent with its **real** tools and score the result. The primitives:278279- **Scenario** (`evals/<name>.yaml` or `evals/<set>/<name>.yaml`) — optional multi-turn `history`, one `input`, an `expected` response (text and/or `tool_calls`), optional `evaluator_instructions`. Scored by the hub's `message_evaluator` agent; a required-but-skipped tool call fails the eval even when the reply text reads fine280- **Scenario set** — first-level subfolder (one level only). `wayai run-eval` runs exactly one set per session, whole or narrowed to chosen scenarios with repeatable `--eval` (and `--runs` for chosen repetitions) — the cheap loop when a change touches a few scenarios of a large set281- **Journey** (`journeys/<slug>.yaml`, flat folder) — a stored happy-path transcript that materializes one derived eval per agent turn. The default way to build broad regression coverage: `wayai eval journey capture <conversation_id>`, then `wayai pull` (syncs server-minted step ids)282- **Per-run `variables`** + `runs: N` — reliability is a distribution, not a 1/1 sample; each run resolves `{{var(name)}}` against its own disjoint row283- **Seed `fixture:`** — for any eval that *writes*: names a [base](references/bases/README.md) fixture the platform LEASES for the session — resetting it on acquire, clearing it on release — so runs start from a known baseline instead of the last run's residue. One preview base admits **one eval session at a time**: a second launch is refused with `fixture_target_in_use` (409) rather than allowed to corrupt the first, and `run-eval` waits it out by default — as it does `fixture_seed_unavailable` (409), the base briefly refusing the lease under its own write backpressure284- **Seed `initial_state:`** — pre-populate user-scope WayAI [state](references/states.md) (a recurring-customer record, a saved profile) before `input` runs, so behavior that depends on memory of prior conversations is testable; isolated + torn down per session like `fixture:`285- **Capture** — `wayai eval capture <conversation_id>` freezes a production conversation's last exchange into a scenario YAML286287Good practice for tool-dependent evals: compose **journey + `fixture:` + `variables`** for repeatable, parallel runs, and phrase `evaluator_instructions` as **functional outcomes, not raw call counts** ("one successful booking", not "exactly one `book_appointment` call") — tools fail transiently, and a correct agent retries. Full YAML shapes, seed-connection setup, run pacing, and authoring/interpreting principles: [`references/evals.md`](references/evals.md).288289## Outbound290291Proactive messaging — the hub contacts people before they write. Three `hub.yaml` blocks:292293- `outbound_contacts` — named contacts with ≥1 channel identifier (`phone` E.164 / `email` / `instagram_sid`) + free-form tags294- `outbound_lists` — named static collections of contacts (referenced by contact name)295- `outbound_schedules` — cron expression + timezone + list + channel + execution mode: `direct_message` (template / free text sent as-is) or `agent_trigger` (a system message triggers the agent, which opens the conversation naturally using its tools and instructions)296297WhatsApp/Instagram delivery is constrained by the 24-hour messaging window (WhatsApp falls back to an approved template; Instagram skips). Inline contacts are practical to ~500 — beyond that, import via UI/API. Shapes, channel rules, limits: [`references/outbound.md`](references/outbound.md).298299## Analytics300301Every conversation lands in the analytics store with variables from five origins:302303| Origin | Path | Set by |304|--------|------|--------|305| System metrics | `data.system.*` | Platform — message counts, response times, durations, tokens (~25 metrics) |306| Agent-defined variables | `data.variables.*` | `evaluation_variables` declared on `conversation_evaluator` / `message_evaluator` agents |307| Metadata | `data.meta.*` | Platform — subject, kanban_status, hub_type |308| Post-hoc annotations | `data.annotations.*` | `wayai conversations <id> annotate --set key=value` — real business outcomes (purchased, churned) recorded after the conversation ends; correlate predictions vs reality |309| Eval scores | `data.eval_scores.*` | Eval runs only (`is_eval = true` rows — excluded from production analytics) |310311Conversation rows are one grain; a second table, `message`, decomposes each conversation's spend per message (tokens, USD cost, operations) for per-model, per-agent, and per-credential cost analysis.312313Query with `wayai analytics` (summary + per-variable aggregates; `--metric`, `--filter`, `--period`), `wayai analytics query` (structured: multi-variable, group_by, correlations), `wayai analytics sql` (raw SQL over `conversation` and `message` — the surface for cost analysis), or `wayai evals sql` (same SQL over eval rows). Defining *good* variables happens on the evaluator agents ([roles-and-settings.md → Evaluation Variables](references/agents/roles-and-settings.md#evaluation-variables)); filters, aggregations, cost queries, and workflows: [`references/analytics.md`](references/analytics.md).314315## Teams, Users & Access316317People entities are **UI-managed** (Hub → Users tab: `/settings/organizations/<orgId>/hubs/<hubId>/users`), never in YAML:318319- **Hub User** — the end user the AI talks to (customer/lead/employee). Uses `/chat` or `/task`320- **Hub Team User** — support team member handling conversations in `/support`; grouped into **Teams** (e.g. "Tier 2 Support") that `transfer_to_team` targets by name — an unknown `target` fails at runtime321- **Hub Admin** — full hub config access. **Org Owner/Admin** — org level (billing, credentials, hubs). Access is per-level, not inherited (an org admin isn't automatically a hub admin)322- **Contact access control** — with `non_app_permission: require_permission`, unknown channel contacts are held `pending` (localized auto-reply, overridable via `access_request_message`) until approved/blocked by the role in `access_approval_role`323324## Hub Environments325326| Environment | Description |327|-------------|-------------|328| `preview` | Default. Editable workspace for configuring and testing |329| `production` | Read-only. Serves live traffic. Changes flow from preview via publish/sync |330331**Lifecycle:**3321. New hubs start as `preview` — edit freely. `wayai create --label <l>` (or `wayai push --label <l>` on auto-create) names the first preview at creation3332. **Publish** (CLI `wayai publish`, or UI) — first promotion creates a `production` hub cloned from preview3343. **Sync** (CLI `wayai publish` / alias `wayai sync`, or UI) — pushes subsequent preview changes to the linked production. The one command auto-detects first-publish vs sync; it confirms by default (shows the preview→production diff) and `-y` skips the prompt. Promotes the pushed **preview** state, so `wayai push` first3354. **Replicate Preview** (CLI `wayai replicate [hub] --label <l>` or UI) — creates a new sibling preview (from a preview or production) for experimentation3365. **Relabel** (CLI `wayai relabel <label>` / `--clear`, or UI) — set/clear a preview's `preview_label` (the sibling disambiguator). NOT editable via `hub.yaml` + push — it's server-owned337338Production is read-only — all config mutations flow through preview. Multiple previews can link to the same production (many-to-1). Channel uniqueness is enforced on production only — previews can share phone/email/SID with their production.339340WhatsApp/Instagram/Telegram channels can be exercised on a preview before publishing — register a tester via a `#test CODE` claim code (see `references/connections.md` → Channel → "Te341342…(truncated)