mcpField
W
wayai

wayai

github.com/wayai-proAgent workflows0162 installsUpdated today
Repository

About wayai

A skill for configuring and operating WayAI hubs through workspace files and the `wayai` CLI. It covers agents, tools, evaluations, journeys, analytics, and platform synchronization for teams building customer-facing or back-office operations.

Key features

  1. 01Manage hubs as workspace files
  2. 02Pull and push hub configuration
  3. 03Configure agents, tools, and skills
  4. 04Run evaluations and inspect results
  5. 05Test conversations and view analytics

Use cases

  1. 01Configuring a customer support or sales hub
  2. 02Maintaining hub configuration as code
  3. 03Testing conversations and agent behavior
  4. 04Reviewing hub analytics and evaluation results

MCP support

WayAI supports MCP connections as a connection type: non-OAuth MCP Bearer Token connections are managed through the CLI, while MCP OAuth connections are configured in the platform UI.

Install

Copy the command and run it in a terminal.

$npx skills add https://github.com/wayai-pro/wayai-skill --skill wayai

After installing, type/wayaiin Claude Code to confirm it loaded.

Bundled files

References and scripts installed along with SKILL.md.

references/
agents-md-template.md
analytics.md
automations.md
calls.md
communities.md
connections.md
references/agents/
custom-tools.md
instructions.md
native-tools.md
prompt-principles.md
roles-and-settings.md
tool-principles.md
references/bases/
config-as-code.md
executors.md
files.md
integrations.md
querying.md
README.md
records.md
toolsets.md
references/canonical-example/
hub.yaml
README.md
references/canonical-example/agents/
support-pilot.md
support-pilot.yaml
references/canonical-example/company-faq/
billing.md
refunds.md
references/canonical-example/evals/
refund-eligible.yaml
references/canonical-example/journeys/
refund-flow.yaml
references/canonical-example/knowledge-base/file-types/
company-faq.yaml
references/canonical-example/knowledge-base/toolsets/
support-faq.yaml

Details

From the project README

WayAI Skill

WayAI is where AI agents and people work together — serving your customers and running your operations. Each hub combines AI agents and a human team, from person-centered support and sales across channels to task-centered back-office work (Hub Types). This workspace stores hubs as code — one folder per hub (hub.yaml + agents/*.{yaml,md} + evals/, journeys/, skills/) 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. Except a community: its push edits the LIVE community (no preview), so show its changes (wayai push communities/<folder> --dry-run) and push only when the user says so
  • Never auto-commit — show git diff, wait for user approval

Quick Decision: What Can I Do?

EntityHow
Hub settings, agents, agent instructions, tools, kanban, states, skills, evals, journeys, automations, custom toolsCLI (wayai push); an automation is paused from the app
Contacts and org lists (the organization's contact book, which automations target)UI (organization settings → Contacts) or API (/api/contact-book) — never hub.yaml. See Automations
A contact's customer record in a base (what a customer-scoped toolset serves when a conversation names the contact)CLI (wayai contacts link-customer / unlink-customer, browser sign-in only) or API (/api/contact-book/contacts/:id/customer) — see Linking a contact to its customer record
Hub lists (a hub's own filters over the contact book, which its automations target)CLI (hub.yaml contact_lists:, wayai push) or UI (hub → Automations → Lists)
Eval journeys (hub-as-code) — journeys/<slug>.yaml, flat folderCLI (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 / editCLI (wayai create-credential / wayai update-credential) or UI
Org-level shared skills (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 providersCLI (wayai sync-skills)
Conversation testingCLI (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 FIRSTCLI (wayai alerts) — surfaces active Status & Notices alerts (e.g. an invalid provider key shows as connection_auth, usually at 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 dimensionCLI (wayai conversations <id> annotate --set key=value [--type ...])
AnalyticsCLI (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 resultsCLI (wayai run-eval, wayai eval-results)
Voice calls — Realtime connection, pilot_voice agent, call_utterance steering monitorCLI (wayai push) or UI — see references/calls.md
Call evals — call-mode text runs, scored machine callsCLI (wayai run-eval --call-mode, wayai eval call) — see references/evals.md
List eval scenarios / raw SQL over eval resultsCLI (wayai evals, wayai evals sql)
Capture production conversation as evalCLI (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 sessionCLI (wayai eval session stop <session_id>)
Delete eval session(s) / run historyCLI (wayai eval session delete <session_id>, or --all for every session on the hub)
Bug reportingCLI (wayai report create)
Workspace discoveryCLI (wayai list)
Communities — list an organization's, read / rotate / turn off a join link, read the stats, the access list of an invite-only communityCLI (wayai communities, browser sign-in only) or UI — see references/communities.md
Communities — the name, settings (who may join: admission), rules, the join link's on/off state, rooms, courses (modules, lessons)CLI (wayai-ws/communities/<folder>/, wayai pull/push communities/<folder>; push edits the live community) or UI — see references/communities.md. Moderation: UI
Organization — createCLI (wayai org create) or UI
Organization — update, deleteUI
Publish/sync a preview to productionCLI (wayai publish, alias wayai sync) or UI
Delete hubsUI
Replicate a preview, set/clear a preview's labelCLI (wayai replicate / wayai relabel) or UI
Teams, team users, hub users, admins, contact approvalUI (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 and which contacts it sees
├── Org Skills              ← CLI (`wayai org pull/push`) — shared skills, fan out to linked hubs
├── Bases                   ← CLI (`wayai bases`) — the Data surface. Org-level, NOT inside a hub (see Bases)
├── Contact book            ← UI (Contacts) or API — contacts + org lists that automations target. Org-level, NOT in `hub.yaml`
└── 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
    │   └── Skill links     ← CLI — `skills:` block in agent YAML
    ├── Kanban statuses     ← CLI — `hub.yaml` (workflow stages for conversations)
    ├── States              ← CLI — `hub.yaml` (JSON-schema data agents read/write)
    ├── Skills              ← CLI — `hub.yaml` `skills:` + `skills/` folder
    ├── Evals + Journeys    ← CLI — `evals/`, `journeys/`
    ├── Hub lists           ← CLI — `hub.yaml` `contact_lists:`; filters over the contact book (labels, scope tags)
    ├── Automations         ← CLI — `hub.yaml`; paused in the app; each targets a hub list or an org list by name
    └── 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

TypeConversationsChannelsUse Case
chatONE per end userWhatsApp, Instagram, Email, Telegram, AppPerson-centered: support, sales, helpdesk
taskMULTIPLE per userApp onlyTask-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:

ModeBehavior
pilotAI handles end users autonomously
copilotAI suggests responses to the support team (no channel delivery)
pilot+copilotSwitches dynamically based on who currently responds
turned_offAI 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

RoleTrackPer HubDescription
pilotPilot1Responds to end users autonomously
copilotCopilot1Suggests responses to the support team
pilot_specialist / copilot_specialistBothMultipleDelegation target — full transfer via transfer_to_agent
pilot_advisor / copilot_advisorBoth1 eachAdvisory input via consult_agent; returns control. Runs only on a model connection, never a hosted agent connection
monitorBackground1 per firing triggerObserves silently, and on user_message its rules may act — briefing the answering agent with a note you wrote, running another monitor for a closer look, or handing the conversation to another agent or to a human team, all taking effect on that same message. On assistant_reply it is a reply gate: it reads the drafted reply before anything is sent and can hold it for the team or have the agent rewrite it once. On call_utterance it watches a live voice call and can steer the voice with a note you wrote. On automation_fire it is an automation's gate: before each run it decides to run it, run it with another agent, or skip it; on automation_item, for each contact of a list's run, to start it, start it with another agent, or skip it. At most one enabled monitor on each trigger except manual (any number — they run only when another monitor calls them) and the two automation gates (any number — each automation names its own)
conversation_evaluator / message_evaluatorBackground1 eachAsync quality assessment; excluded from normal routing. Their evaluation_variables feed Analytics; the message_evaluator also scores eval runs
summarizerBackground1Auto-provisioned with the first pilot/copilot, on its connection unless that is a hosted agent connection (a summarizer runs only on a model connection). 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)
pilot_voicePilot (voice of live calls)Multiple (one answers calls)The voice of the hub's live AI voice calls: talks with the caller and, as its instructions tell it, hands questions to the pilot, which answers. Binds only a Realtime connection; no text turn, eval run or transfer ever runs it — see Voice Calls
consultantTrack-independent (on-demand)MultipleConsulted 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. The support team consults one by tagging it in the support composer (support-inbox.md)

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 (none when the pilot's is a hosted agent connection: add a summarizer on a model connection); 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.

CategoryExamples
AgentOpenAI, Anthropic, Google AI Studio, OpenRouter, xAI (required for AI)
ChannelWhatsApp, Instagram (OAuth — UI only); Resend (email), Telegram (API Key — auto-created)
Tool — NativeWayai (auto-created), External Resources (API Key), Base Toolset (Bearer Token — a token bound to one toolset of a base)
Tool — CustomUser-defined HTTP endpoints (API Key, Bearer Token, Basic Auth)
Tool — MCPExternal MCP servers (Streamable HTTP) — Bearer Token via CLI; OAuth via UI
SpeechSTT transcribes inbound voice notes (Groq, OpenAI, ElevenLabs) — the app's composer offers the voice-message button, to end users and team alike, only on a hub with an enabled STT connection; TTS synthesizes spoken replies (OpenAI, Groq, ElevenLabs, Google), each at the same loudness
RealtimeOpenAI GPT-Live — the voice of live AI voice calls, used only by a pilot_voice agent

Auto-creation rule: Non-OAuth connections (Agent, STT, TTS, Realtime, 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).

Voice Calls

A chat hub can take live AI voice calls from the web app's chat view: the caller presses Start a voice call, hears a notice that they are talking to an AI (WayAI's, or the voice agent's own opening in that language), and talks with a voice. Any hub can be set up for them. WayAI can turn voice calls off platform-wide, and while they are off, wayai push refuses to create a Realtime connection or a pilot_voice agent, or to turn on any of the pieces below, with Voice calls are not enabled for this platform, so … — stop and tell the user when you see it.

  • The voice is a pilot_voice agent on a Realtime connection (OpenAI GPT-Live, on the organization's own OpenAI project key). Its settings are its voice, language, call limits, whether calls are recorded (off by default), whether it says progress cues while an answer is awaited, and the wording of its fixed lines (greeting, cues, repeat, apology, timeout); its instructions are the only rules it follows: how to talk, and handing every question to the pilot (start from the example)
  • Answers come from the hub's pilot when the voice hands it the question, as its instructions should tell it to; the pilot runs a normal turn — tools, monitors, reply gate — for each question the voice hands over. The same rules as text decide whether the AI may answer, and the call ends when they stop allowing it (a transfer to the team, a takeover, a close, …)
  • Actions with side effects wait for the caller's spoken yes; a call_utterance monitor can steer the voice mid-call; wayai run-eval --call-mode and wayai eval call evaluate calls
  • Billing: each question the voice hands over bills 1 operation, plus a per-minute rate for the call's connected minutes

Setup, endings, what the team sees, recording, billing and limits: references/calls.md.

Tools

Capabilities assigned per agent in agents/<slug>.yaml. Remember: replying with text needs no tool — tools are for everything else.

TypeSourceHow
NativePlatform built-ins (e.g., update_kanban_status, get_state, send_files, close_conversation, read_file)Listed by name under tools.native
CustomHTTP endpoints you defineDefined under tools.custom with connection, method, path, config
MCPTools from connected MCP serversDual-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
DelegationAgent-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:
ValueWhat crosses into the target hub
instructionOnly the agent's own request. Nothing from the conversation.
summary (default)The request + the rolling conversation summary.
transcriptThe 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 consultant answers inside the tool call; a partner hub 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. A consultant, like every role but a pilot, a copilot or their specialists, runs only on a model connection, never on a hosted agent connection (Claude Agent (Managed)): saving one there is refused, and one consulted there, or with no connection, is refused before anything is sent.
  • 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 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.

Skills

Skills attached to agents. Content lives as real files under skills/<skill_name>/ (the filesystem is the source of truth for skill content); hub.yaml skills: declares only name, skill_name and description.

TypeWhatRuntime behavior
skillVersioned capability package — SKILL.md (frontmatter name + description) + optional references/The agent reads its files at skills/<skill_name>/… (list_files, read_file, {{file_content(skills/…)}}); a skill cited under resources/ in any text agents act on (instructions, settings, monitor notes, automations, kanban follow-ups, custom tools, skill descriptions) is refused on save and push. 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)

Knowledge resources are retired: documents (FAQ, catalogs, policies) live in the organization's knowledge base, read by agents at data/<file type>/… through a Base Toolset connection (Bases). A skill takes no type.

Agents link skills in agents/<slug>.yaml under a skills: block (by name and skill_id, with priority). Org-level skills shared across hubs live in wayai-ws/org/ (skills.yaml + skills/<skill_name>/) via wayai org pull/push (push fans out to linked hubs). Push is declarative: removing a skill from hub.yaml (the last one too) deletes it.

Folders pulled before skills hold resources: (in hub.yaml and the agent files) and a resources/ folder (org/resources.yaml for an organization). wayai push, diff and publish (and org push/org diff) refuse them before sending anything; wayai pull (wayai org pull) converts them, and stops, naming it, on anything in resources/ that is not the folder of a skill it writes, or already in its skills/ (a knowledge backup: move it out of the workspace). This needs @wayai/cli 0.3.234 or later (wayai update): an older CLI's push of an old-layout folder is refused with a 400 naming it, its push of a converted folder changes no skill, its wayai pull over an old-layout folder gets the same 400 on its comparison and cannot list its changes (update before pulling), and its wayai org pull/push/diff and sync-skills get a 404 (the old paths are gone). Details: references/skills.md.

File handling (text vs binary, 10 MB cap), skill authoring, execution modes: references/skills.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
  • Connecting a hub: a Base Toolset connection gives native agents a toolset's tools, and its documents under data/<file-type>/ in list_files and read_file, written with write_file and edit_file (references/connections.md); a harness-backed agent uses an ordinary MCP Server connection pointed at the toolset (one without get_url or put_url: URL tools and hub agents); an eval names a target_base:. The organization's documents live here, in its knowledge base (references/bases/files.md); skills stay in the hub (Skills)

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:
  • Name the customer customer_contact_id: — the contact a scenario's runs, or a journey's steps, serve, so a customer-scoped toolset's calls in the eval read only that customer's records; honoured on preview hubs only (references/evals.md)
  • Capture — wayai eval capture <conversation_id> freezes a production conversation's last exchange into a scenario YAML
  • Call evals (voice calls) — wayai run-eval --call-mode and wayai eval call: references/evals.md

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.

Automations

An automation starts work in the hub on its own: a trigger (a schedule — 5-field cron + timezone; on a task hub also an event — conversation.flagged or conversation.closed, one task per event with the conversation handed to it as fenced data, never on the automation's own tasks, and refused three automations deep so automations cannot loop: references/automations.md — of this hub, or with source_hub of another production hub of the organization, in production only, its publisher able to read that hub's conversations: references/automations.md — or a webhook — an external system's signed delivery to the automation's own URL: references/automations.md), a target (a contact_list — one of the hub's own lists, named by list, or an org list, named by org_list; on a task hub also none) and an action run for each contact the fire reaches:

  • send_message (chat hubs only) — a WhatsApp message template on whatsapp, a text email on email (from its connection; a marketing email carries an unsubscribe link), or a text on app
  • run_agent — an agent (a pilot or pilot_specialist) and admin-written instructions. On a chat hub on a channel (whatsapp / instagram / email / app; every channel but app names its connection, and whatsapp also the approved template that opens a conversation whose 24-hour window is closed): the named agent takes a turn on the instructions in each contact's own conversation on that channel and replies to them there. On a task hub — the only action it can save — with no channel: each run opens tasks, one per run (target: { type: none }) or one per contact, each with no person behind it, run by the named agent (a pilot or pilot_specialist) on the instructions; a contact's record reaches the task as fenced data, never inside the instructions. references/automations.md
  • instructions are sent as written: {{…}} placeholders are not filled in there (they work in an agent's own instructions)
  • Webhook trigger (task hubs only): trigger: { type: webhook, auth: hmac } — each delivery signed the way a base's inbound webhook is (X-Data-Signature / -Timestamp / -Id, so the published SDK's signRequest signs it) — or auth: static_header with the header that carries the secret. Each accepted delivery opens a task that opens with the delivery's body, fenced as data, outside the instructions. The secret is shown once, when the automation is created in the app or API and at each rotate (a hub admin on production); a push or a publish creates none. references/automations.md

Contacts and org lists are organization data, not hub config: they live in the organization's contact book, managed in the organization's settings (Contacts tab) and its API (/api/contact-book), never in hub.yaml. An automation names an org list — target: { type: contact_list, org_list: <list-name> } — or one of the hub's own lists — target: { type: contact_list, list: <list-name> }, a filter over the contact book's labels and scope tags kept in hub.yaml under contact_lists: — and each fire reaches the members of that list the firing hub can see. Organization admins, or a token holding contacts:write over the whole organization, manage the book; a hub admin can view the contacts their hub sees. A contact's visibility (its scope tags, or all hubs, chosen at create/import, plus an environment: production by default, so a preview hub sees only preview and all contacts), org lists and CSV import: references/automations.md.

  • Absent vs empty: no automations: (or contact_lists:) key changes nothing; [] deletes every automation (or hub list). A hub list an automation targets is never deleted, and renaming one renames the automations' references (unless another list takes the old name). The old outbound_schedules: key, and the hub contact and list blocks the contact book replaced, are no longer read
  • An org list is never looked up at save (a hub list is: it must name one of the hub's lists): deleting or renaming an org list an automation names is allowed; its next fire records list_not_found and raises a hub alert. A run reads its list a page of 100 contacts at a time, so a list of any size the contact book holds is reached, each contact at most once; the run shows its contacts as they are reached, and pausing stops it before its next page
  • Suppressions: the contact book holds who asked not to be sent automations — a phone, email or Instagram id, with scope marketing or all, kept after its contact is deleted. Each automation's purpose (marketing, the default, or operational) decides which stop it: a marketing send skips both scopes, an operational one (a reminder) only all. Managers add and remove them in the Contacts tab or the API; an email's unsubscribe link writes all, and WhatsApp refusing a marketing message because the person stopped marketing messages writes marketing. references/automations.md
  • Who a fire reaches: a contact the hub blocked is never sent to; a contact's entry in the contact book approves them on a hub that asks permission for new contacts, so no access request is raised and their replies reach the agent (one whose request was already waiting is approved and stays with the team, and run_agent skips a conversation the team holds); on the app, only a contact linked to a WayAI account that already has access to the hub — an automation never grants access
  • Runs while enabled, not paused and able to run: enabled: in hub.yaml (or the app's toggle) turns it on — a schedule's next run is queued, an event automation runs on its events, a webhook automation accepts deliveries — on preview and production hubs alike, except an event automation with source_hub, which runs on production only; Pause in the app stops it, and publishing keeps a production pause. Production is changed only by publishing, apart from pause, resume, run now and a webhook automation's secret rotation (hub admins). Run now fires once
  • Gates: an optional gate: { monitor: <agent name> } — a monitor on a decisions model — decides either each run before it starts (trigger: automation_fire: run it, run it with another agent, or skip it) or, on a contact list, each contact before anything is started for it (trigger: automation_item: start it, start it with another agent, or skip it); a gate that fails or times out does not run what it gates. references/automations.md
  • Refused at save, naming the automation: unknown keys, a field the action does not use (a channel or connection on a task hub), the none target on a chat hub, an event trigger on a chat hub or with a list target, a webhook trigger on a chat hub, a gate whose monitor is not an automation_fire (or, on a contact list, automation_item) monitor on a decisions model, and send_message on Instagram (not available yet)

Instagram reaches only contacts who wrote in the last 24 hours — check what each fire does before promising a result. Shapes, examples, references, what each fire does, message templates, limits: references/automations.md.

Analytics

Every conversation lands in the analytics store with variables from five origins:

OriginPathSet by
System metricsdata.system.*Platform — message counts, response times, durations, tokens (~25 metrics)
Agent-defined variablesdata.variables.*evaluation_variables declared on conversation_evaluator / message_evaluator agents
Metadatadata.meta.*Platform — subject, kanban_status, hub_type
Post-hoc annotationsdata.annotations.*wayai conversations <id> annotate --set key=value — real business outcomes (purchased, churned) recorded after the conversation ends; correlate predictions vs reality
Eval scoresdata.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 (the mobile apps' Chat tab); what they can do there: references/using-the-app.md
  • Hub Team User — support team member handling conversations in /support (the inbox, claim, transfer, close, the board: references/support-inbox.md); 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

EnvironmentDescription
previewDefault. Editable workspace for configuring and testing
productionRead-only. Serves live traffic. Changes flow from preview via publish/sync

Lifecycle:

  1. 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. An API token creates a hub only when its grants cover every hub of the organization in preview with hub:write, otherwise 403. wayai create / wayai push send no tags when they create the hub, so a token limited by tag cannot create one through the CLI — use an org-wide token or the UI
  2. Publish (CLI wayai publish, or UI) — first promotion creates a production hub cloned from preview
  3. 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
  4. Replicate Preview (CLI wayai replicate [hub] --label <l> or UI) — creates a new sibling preview (from a preview or production) for experimentation
  5. 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.

Publishing and syncing write the production hub, so an API token (wayai login --token) publishes only when its grants cover that hub with hub:write. Once the preview is published, the preview→production diff wayai publish shows first reads that hub, so the token also needs hub:read on it. A token limited to the preview, by hub or to the preview environment, is refused with 403. A first publish creates the production hub, so the token needs access to every hub of the organization in production (and, if it is limited by tag, to the preview's tags). Signed-in people publish as before.

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 → "Testing a channel on a preview before publishing").

Only preview hubs are editable. wayai pull also writes the linked production hub as a read-only mirror folder (bare slug — no --<label> suffix — with a marker comment in hub.yaml) so the live production config is browsable alongside the preview; wayai push refuses it and it's excluded from auto-select. Use wayai diff --production for a clean preview-vs-production diff.

Hub Settings

SettingValuesDefaultDescription
non_app_permissioneveryone, require_permission, not_allowedeveryoneWho can reach hub via external channels
timezoneIANA timezone—For scheduling and display
languageen, pt, esenLanguage for hub-sent text (e.g. the pending-access notice on require_permission channels)
access_approval_roleadmin, teamadminWho may approve/block a pending contact: hub admins only, or also support team members
access_request_messagestring—Optional override for the "your access is pending approval" auto-reply (else a localized default by language)
display_namesmap of en / pt / es → string (≤120 chars)—The hub's name as its end users see it, per app language. Team and admin views keep name
welcome_messagesmap of en / pt / es → string (≤1000 chars)—The greeting an end user sees before their first message, per app language
starter_questionsmap of en / pt / es → list of up to 4 strings (≤120 chars each)—Suggested first messages an end user can tap to send, per app language
auto_close_inactive_days1–1807Days of inactivity (no user/team message) before a conversation is force-closed. Every hub has one
conversation_retention_days1–307Days an ended conversation's DO stays alive for post-hoc annotate — and an eval call's page (evals.md → Call evals) — before cleanup (archival still happens at close)
ended_index_retention_days1–730365Days an ended conversation stays listed in the hub index. Bounds the Ended tab and how far back {{previous_conversations(N)}} reaches
eval_retention_days0–3650, or nullplatform default (90)Days a finished eval session (its runs, results and eval conversations) is kept before retirement. 0 keeps sessions forever. Omitting the field leaves the current value unchanged — write eval_retention_days: null to clear an override and go back to the platform default. Retirement deletes transcripts permanently — eval scores in Analytics survive
end_user_daily_message_limit1–10000, or nullno limitMessages one end user may send the hub in the WayAI app in a 24-hour window, which starts with their first counted message. Past it, their sends are refused until the window ends, and the app tells them why. Only end users are counted — never your team's messages, nor your organization's members or the hub's admins. Other channels are not limited. Omitting the field leaves the current value unchanged; null removes the limit

End-user presentation (display_names, welcome_messages, starter_questions): each viewer gets the entry for their app language, else the hub's language, else any language the map holds — so set every language your end users use, or a missing one shows another language's entry; the name falls back to name only when display_names is empty. Locales other than en, pt and es are refused. Omitting a map leaves it unchanged; null or {} clears it. They change only what end users see in the app: the agent's instructions, name and the ai_* notices are not translated by them. The web and mobile apps show the name on the end user's Chat list (search finds either name) and thread header, and the welcome and starter questions on a thread with no message yet; tapping a question sends it as the first message. Support, Kanban and every team or admin view keep name. Hub admins can also edit all three per language in the web app's hub settings (Overview → End-user presentation).

First-time setup (cold start)

The user's entry point is wayai.pro, whose onboarding section carries the install command and the example prompt. Once the skill loads (here), this section drives everything from "skill loaded" to "hub responding to test messages." The agent must self-bootstrap from this section alone — returning users (second hub, new project) skip the page.

Always start by running wayai status --json. It returns a single state snapshot. Branch from the result; re-run between steps to confirm progress before moving on (idempotent).

Phrasing templates (use verbatim)
  • Agent action — narrate one line then act, then one-line receipt:
    • "Installing the CLI…" → run command → "Done — wayai 2.4.1 installed."
  • User handoff — exactly one URL, one action, one return signal:
    • "Open <URL>. Do <one action>. Tell me when done."
State machine
#Detection (from status --json or env)Action
1CLI missing (wayai --version not found)Agent runs npm i -g @wayai/cli@latest.
1bNo harness skill install present at project root (none of <root>/.claude/skills/wayai/SKILL.md, <root>/.opencode/skills/wayai/SKILL.md, <root>/.agents/skills/wayai/SKILL.md exists, where <root> is git rev-parse --show-toplevel or cwd if not in a git repo)If you are Claude Code, first mkdir -p <root>/.claude — the skills installer links Claude Code's .claude/skills/wayai only when .claude/ already exists, else it silently skips it while still printing "symlinked: Claude Code". Then run npx skills add wayai-pro/wayai-skill -y from <root>. After it completes, verify your own harness's path resolves (Claude Code → <root>/.claude/skills/wayai/SKILL.md, following the symlink; other harnesses → <root>/.agents/skills/wayai/SKILL.md). If it resolves, exit (the harness loads the skill next turn). If it doesn't but <root>/.agents/skills/wayai/SKILL.md exists, self-heal: mkdir -p <root>/.claude/skills && ln -sfn ../../.agents/skills/wayai <root>/.claude/skills/wayai, then re-verify. If still nothing, surface the install error and halt — do not silently exit (would loop on re-entry).
1cskill.installed: true, skill.latest is set, and skill.latest is newer than skill.version (CLI nightly check populates skill.latest)Agent runs npx skills add wayai-pro/wayai-skill -y from <root> to refresh the install in place, then exits (the updated skill loads on the next turn). On install failure, surface the error to the user and continue with the existing skill.
2auth.logged_in: falseAgent runs wayai login (opens browser). User handoff: "Open the page that just opened. Sign in or sign up. Tell me when done."
3auth.logged_in: true, orgs: []Ask once: "What should we name your organization? (usually your company name)". Then agent runs wayai org create "<name>" and re-runs status --json to pick up the new org. (Manual fallback only if the CLI create errors: open https://app.wayai.pro/settings/organizations/new.)
3bgit rev-parse --show-toplevel fails (cwd is not in a git repo)The CLI requires git for workspace detection. Before acting, surface the resolved cwd and confirm with the user — handoff: "I'll initialize a git repo at <cwd>. Confirm or pick a different folder." This prevents accidentally initializing a repo in ~ or another unintended directory. After confirmation, agent runs git init in cwd. Then checks git config --global user.name and git config --global user.email; if either is empty, asks the user once for their name and email and runs git config --global user.name "<name>" / git config --global user.email "<email>". A GitHub remote is not required for the onboarding flow — only set one up later if the user wants the GitOps/CI loop.
4workspace.scoped: false and repo_scope_blocker: nullAgent runs wayai init --org <active_org.id> — writes the org binding into wayai-ws/wayai.yaml, the repo's one committed workspace manifest.
4bworkspace.scoped: false and repo_scope_blocker is setDo NOT run wayai init — it rewrites wayai-ws/wayai.yaml and cannot fix a repo whose two config files disagree. For kind: "conflict", tell the user both organization ids and ask which one this repo belongs to, then delete or correct the root .wayai.yaml. For kind: "invalid", show the named file and its detail and ask the user to fix it. Re-run status --json afterwards.
5Workspace scoped, hub goal not yet knownUser handoff: "What should this hub do? Describe the goal, who talks to it, and the main use case."
6LLM credential missing for chosen providerUser handoff: "Paste your OpenAI/Anthropic/Google API key here." Then agent runs wayai create-credential --name <name> --type "Bearer Token" --stdin.
7Hub needs an OAuth connection (WhatsApp / Instagram / MCP OAuth)Apply the OAuth connection handoff (Connections & Credentials → OAuth connection handoff): send the full-path connections deeplink for the connector, wait for completion, then wayai pull -y. The same handoff applies any time an OAuth connection is needed later, not only here.
8Prerequisites metRead references/canonical-example/README.md once for end-to-end wiring, then generate wayai-ws/hubs/<hub>/hub.yaml + agents/*.yaml + agents/*.md from the user's description (per-domain refs below for individual shapes), then wayai create -y to create + push the new hub (wayai push -y also auto-creates when the workspace has just this one new folder).
9Push succeededAgent runs wayai send-message "Hi" and shows the response. User handoff: "Refine, add tools, or publish?"
10User confirms publishAgent runs wayai publish — shows the preview→production diff, then confirms (or wayai publish -y to skip the prompt). First publish clones preview → a new production hub; later runs sync. Paid plans only — if the CLI reports publishing requires a paid plan, manual fallback: open the publish deeplink below to upgrade + Publish in the UI.
Deeplinks (canonical URLs — never breadcrumbs)
StateURL
3 (org create — manual fallback)https://app.wayai.pro/settings/organizations/new
6 (credential pre-fill)https://app.wayai.pro/settings/organizations/<org_id>/credentials?type=bearer&name=<key-name>&prefill=true
7 (OAuth connection)https://app.wayai.pro/settings/organizations/<org_id>/hubs/<hub_id>/connections?connector=<whatsapp|instagram|mcp-server>
10 (publish — manual fallback)https://app.wayai.pro/settings/organizations/<org_id>/hubs/<hub_id>/overview?action=publish
Rules
  • Detect, don't assume — every step starts with a fresh wayai status --json.
  • Agent actions get a one-line receipt; user handoffs get one URL, one action, one return signal.
  • Never invent URLs outside the table above. Never instruct "go to Settings → …" — always a deeplink. For non-onboarding URLs see references/navigation.md.
  • One question at a time during hub scoping (state 5): goal, then channel, then LLM provider.
  • Never auto-commit anything created during onboarding — show git diff and wait for user approval before any commit.

Workflow

Existing hub
  1. Update CLI — wayai update (always run before any operation; if the CLI isn't installed yet, bootstrap with npm i -g @wayai/cli@latest)
  2. Update skill if stale — run wayai status --json; if skill.latest is set and newer than skill.version, run npx skills add wayai-pro/wayai-skill -y and exit (the refreshed skill loads on the next turn). Otherwise continue. (Cold-start onboarding runs the same check as state-machine row 1c.)
  3. Pull — wayai pull -y (sync local files from platform; catches out-of-band changes)
  4. Read context — read wayai-ws/hubs/<hub>/AGENTS.md for this hub's notes (purpose, decisions, ongoing work); if you're also working a base, read wayai-ws/bases/<base>/AGENTS.md too. AGENTS.md-aware harnesses (Claude Code, Codex, Cursor, OpenCode, Aider) auto-load AGENTS.md natively, but don't rely on it — see Hub-Folder Memory. Retire an obsolete root bootstrap while you're here — see Retiring the old root AGENTS.md
  5. Edit — modify hub.yaml, agents/*.yaml, agents/*.md
  6. Push — wayai push -y (apply to preview hub; auto-pulls server-assigned IDs back)
  7. Test — wayai send-message "Hello"
  8. Review — run git diff, ask user to confirm. Never auto-commit. User commits and pushes to main
  9. Go live — wayai publish (or the platform UI) when ready: shows the preview→production diff, confirms, then first-publishes (clones preview → new production hub) or syncs subsequent changes. push first — it promotes the pushed preview state, not unpushed local edits
New hub (from scratch)
  1. Credentials — wayai create-credential --name "openai-key" --type "Bearer Token" (one-time per org per credential)
  2. Init — wayai init (interactive) or wayai init --org <uuid>
  3. Create files — wayai-ws/hubs/<hub>/hub.yaml + agents/*.yaml + agents/*.md
  4. Create — wayai create -y creates the hub, non-OAuth connections, and applies all config (auto-binds the worktree). create <folder> when the workspace has more than one hub folder. (wayai push -y also auto-creates when it resolves to a single new folder; in a multi-hub workspace it requires --hub, so create is the explicit, unambiguous verb.)
  5. Test — wayai send-message "Hello"

After the hub exists, follow the existing-hub workflow.

Hub-Folder Memory

wayai-ws/hubs/<hub>/AGENTS.md is the hub-specific memory for this hub — the contextual information the config files (hub.yaml, agents/) can't capture: purpose, key decisions and why, ongoing work, business rules that only apply here, terminology, integration quirks. Read it at the start of every hub-related task. wayai pull/push seed a placeholder AGENTS.md if the folder has none, absence-guarded. AGENTS.md-aware harnesses (Claude Code, Codex, Cursor, OpenCode, Aider) auto-load it natively when the agent's cwd is inside the hub folder — but whether Claude Code does depends on its version, its settings, and any CLAUDE.md-family file in that folder or above it (Claude Code docs). So don't rely on the auto-load: if the hub's AGENTS.md is not already among your loaded instructions, read it yourself. Older CLIs also seeded a one-line CLAUDE.md holding @AGENTS.md beside it; it is harmless, so leave it.

Maintain it actively:

  • After significant changes (new agent, new tool, business rule update), update AGENTS.md so future sessions inherit the context
  • If a hub folder has no AGENTS.md (or only the seeded placeholder), fill it with what you know — purpose, current agents, recent decisions — and ask the user to confirm or enrich
  • Keep it focused on why decisions were made and what makes this hub different. Don't restate platform mechanics — those live in this skill
  • Record agreed scope in an optional ## Build plan section — that exact heading, an optional Goal: <one sentence> under it, then GitHub task-list items (- [ ] / - [x]), nested at most one level. Keep it under ~30 items; past that, split into phases and archive completed ones into prose above the list. Tick items as they land — an item is done when it's pushed and working, not when the YAML exists. Like the rest of AGENTS.md, it is never synced to the platform
  • Bases get the same treatment. If you're also working with a base, record the context its settings can't capture in the base folder's AGENTS.md under wayai-ws/bases/<base-id>/ (create it if missing)

Overflow content goes into wayai-ws/hubs/<hub>/references/:

  • When AGENTS.md grows past ~200 lines or starts mixing topics, extract the deeper material into focused files under wayai-ws/hubs/<hub>/references/
  • Examples: references/business-rules.md, references/integrations.md, references/glossary.md, references/<api-name>-spec.md, references/<persona>-tone.md
  • Keep AGENTS.md as the always-on entry point with a short pointer to each reference file: e.g., "Detailed pricing rules: references/pricing-rules.md"
  • Hub-folder references/ are not synced to the platform — they're for agent context only, just like AGENTS.md
Retiring the old root AGENTS.md

Older CLIs seeded a repo-root AGENTS.md (plus a CLAUDE.md holding @AGENTS.md) as a session bootstrap: get current, then load this skill. Nothing writes or refreshes it any more, so what it says is frozen at whatever release seeded it while the real procedure keeps moving — a copy that still names an install command or a startup step this skill has since changed will quietly send you down the wrong one. Its content is also entirely redundant now: everything in it is stated here. Retire it the first time you work in a repo that still has one — this is about the repo root only; hub- and base-folder AGENTS.md are memory and stay.

Identify it: the seeded file is titled # Working with WayAI, and its whole body is a "check for updates, then load the WayAI skill" bootstrap plus a few lines on talking to the user — no project-specific content. (A copy from an older release may instead describe two skills to install and load; the WayAI and Data surfaces merged into this one skill, so that one is doubly out of date.) Then:

First, check whether that file is how you got here. Harnesses differ, and this decides the action:

  • You load skills from your own skills directory (Claude Code → .claude/skills/, OpenCode → .opencode/skills/) → the root file is doing nothing for you. Delete it, and delete CLAUDE.md too if it is the one-line @AGENTS.md shim.

  • You auto-load AGENTS.md instead (Codex, Cursor, Aider) → that root file may be the only thing that pointed you at this skill, and deleting it would leave the next session with no entry point. Keep the file and replace the stale bootstrap with a pointer, so nothing is frozen but the hop survives:

    # Working with WayAI
    
    Load the WayAI skill at the start of every session, before anything else:
    `.agents/skills/wayai/SKILL.md`. It holds the real instructions.
    

Either way, if the file also carries your own notes, keep those — replace or delete only the seeded bootstrap part. A root AGENTS.md that is entirely your own content was never the CLI's; leave it alone.

Everything the bootstrap used to say now lives in this skill: the update discipline is in Agent Guidelines, the procedure in Workflow → Existing hub.

Common CLI Commands

wayai update            # Update CLI (run before any operation)
wayai login             # OAuth — or `wayai login --token` for headless/CI
wayai logout            # Sign out and clear stored credentials
wayai whoami            # Show the authenticated identity
wayai org create        # Create a new organization (you become its owner): `wayai org create "<name>"` [--region <r>] [--json]
wayai org pull          # Org-as-code: fetch org-level shared skills → wayai-ws/org/
wayai org push          # Org-as-code: apply local org skills to the platform (fans out to linked hubs); `wayai org diff` previews
wayai create-credential # Create org credential (--name, --type "API Key"|"Bearer Token"|"Basic Auth", --org, --stdin)
wayai update-credential # Rotate/edit an org credential (--name <cred>; --stdin/--secret rotates the secret, --rename, --description, --tag, --environment)
wayai set-connection-credential  # Set a connection's credential directly — --connection <name> + either --org-credential <name> (link) or --field <f> --stdin (raw secret: unlinks only that field's org credential). Works on preview + production (the sanctioned production-credential write)
wayai init              # Scope this repo to an org in wayai-ws/wayai.yaml (interactive — creates an org inline if you have none); --org <uuid> to skip prompt
wayai migrate           # Move a legacy workspace/ + root org/ layout to wayai-ws/, and copy a root .wayai.yaml org binding into wayai-ws/wayai.yaml (the root file is KEPT — older CLIs still need it)
wayai pull              # Pull hub config from platform (-y skips confirmation; auto-binds worktree on first pull). Also writes the linked production hub as a read-only mirror folder
wayai push              # Push local changes (-y skips confirmation; auto-pulls IDs back). Auto-creates a lone new folder; a multi-hub workspace needs --hub or `wayai create`
# `pull`/`push` are ONE verb each, routing by workspace subtree: `hubs/<hub>` (above), `bases/<base>` (the Data surface)
#   or `communities/<community>` (references/communities.md) — a community only by the flag, a positional or its folder as cwd.
# The target resolves in this order: the `--hub`/`--base`/`--community` flag → a positional → the folder you are cd'd into →
#   `wayai-ws/wayai.yaml`'s `default_hub`/`default_base` → the sole folder across the hubs and bases subtrees.
# So in a workspace holding BOTH a hub folder and a base folder, a bare `wayai pull` is refused as ambiguous.
# Name the subtree — `wayai pull bases/crm`, `wayai push hubs/support` — or declare ONE default in wayai-ws/wayai.yaml.
# An invocation naming a target in BOTH subtrees is refused, never merged; run the two it prints, one at a time.
wayai pull bases/<base> # Pull a base's config into wayai-ws/bases/<base>/ (-y skips the overwrite confirm; auto-binds; mirrors a production origin read-only)
wayai push bases/<base> # Push a base folder to its PREVIEW base (--dry-run to stop after the diff; --prune to apply removals). Production is `wayai bases promote`, never push
wayai create [folder]   # Explicitly create a new hub from an idless folder, then push (--label names the preview). The discoverable verb when a workspace has more than one hub folder
wayai diff              # Dry-run diff of local files vs preview (read-only); --production diffs vs the linked production hub
wayai replicate [hub]   # Clone a hub (preview or production) into a new sibling preview; --label <l> names it. Pulls the new preview into its own folder
wayai relabel <label>   # Set a preview hub's label (--clear removes it; --hub to target). Renames the local folder. The server-owned way to change preview_label
wayai publish           # Promote the preview to production (alias: wayai sync). Auto-detects first-publish (clones preview → new production) vs sync (pushes changes to the linked production); shows the preview→production diff and confirms (-y skips). Paid plans only. `wayai push` first — promotes the pushed preview state
wayai use <selector>    # Scope this worktree to a hub, base or community (UUID, folder name, hubs/<n>, bases/<n>, communities/<n>, --hub/--base/--community). Replaces that axis; `--add` widens it instead
wayai unbind [<sel>]    # Drop one entry from the worktree scope; bare, it clears the whole scope (every axis)
wayai send-message      # Test message to a hub (preview or production). -c <id> continues a conversation
                        # `-f, --file <path>` attaches a file (repeatable, max 20, ~7 MB/request) delivered exactly as a real channel does — this is how image/document behaviour gets exercised in the dev loop. Message text is optional when a file is attached (an attachment-only send mirrors a photo with no caption). Images reach the model as a signed URL, not base64
wayai alerts            # Active connection/credential alerts for a hub (Status & Notices). RUN THIS FIRST when a hub misbehaves (audio/TTS not delivered, agent not replying, a tool failing) — an invalid/expired provider key shows as `connection_auth` (usually 401) here instead of forcing a guess from code. --hub <uuid|name>, --json
wayai conversations     # List or inspect conversations (default text view omits message_id — use --json or `observability` to discover ids)
                        # `--status <agent|team|ended>` combines with `--period 7d` / `--from` / `--to`, which bound LAST ACTIVITY: `--status team --to <date>` is the idle human-queue check ("waiting on a person, nothing since <date>"). Without `--status` AND without `--org`/`--all`, a bare `--period`/`--from`/`--to` reads the analytics history instead, which bounds conversation START and covers ended conversations only
                        # `--org <organization_id>` / `--all` list across the hubs `wayai list` enumerates plus the production hubs those name as parents — one command instead of a loop. These always read the live listing (open + ended, windowed on LAST ACTIVITY), with or without `--status`. A production PARENT you cannot read is named as skipped and does not fail the run. A production hub whose PREVIEW you cannot see is not enumerable anywhere, so it is NOT covered and NOT reported at all — reach it with `--hub <id>`. `--limit`/`--offset` are per hub; a hub in scope that refuses is named and the command exits non-zero rather than reporting a smaller queue, and an empty scope is reported as such, never as an empty queue
                        # `wayai conversations <id> observability` — list LLM turns with message_id, latency, tool_calls (assistant turns only)
                        # `wayai conversations <id> observability --message-id <id>` — full record for one turn (prompt, completion, tool calls, tokens; --json for raw)
                        # `wayai conversations <id> annotate --set key=value [--type numeric|categorical|text]` — set a post-hoc business outcome (e.g. customer_purchased=true) on an ended conversation as an analytics dimension; repeat --set for multiple keys (needs the hub within its conversation_retention_days window)
wayai delete-history    # Clear conversation history (testing). Full history (no --conversation-id) ALSO clears the user's user-scoped hub state, so the next send-message is a true cold-open; --conversation-id <id> deletes just one conversation and never touches state
wayai sync-skills       # Sync skills to provider connections; --connection-id <uuid> to scope
wayai sync-mcp          # Re-discover an MCP or Base Toolset connection's tools (refresh stale schemas); --connection <name|uuid>; --check reports drift read-only (CI, exit 1 on drift)
wayai analytics         # Summary + per-variable aggregates; --metric, --filter, --period, --json
wayai analytics query   # Structured ClickHouse query (multi-variable, group_by, correlations)
wayai analytics sql     # Raw single-SELECT SQL over `conversation` (one row per conversation) and `message` (per-message tokens/cost/operations — the surface for cost analysis); --schema prints both catalogs + the message-grain rules (`data.*` paths need `toFloat64OrNull(toString(...))` for numerics, `toString(...)` for grouping); --limit, --json
wayai evals             # List eval scenarios for the hub (--enabled / --disabled)
wayai evals sql         # Raw single-SELECT SQL over the hub's eval result rows ("SELECT …"; --schema prints the column + eval-score-path catalog; --limit, --json)
wayai run-eval          # Run a scenario set's enabled evals (sole set by default; --set to pick on multi-set hubs; repeatable --eval <name> runs ONLY those scenarios and --runs 2,6 only those repetitions of one; --pacing conservative|balanced|fast|<ms> to throttle run dispatch; --call-mode runs every turn the way a live voice call runs it; waits inside --timeout for a fixture held by another session or for transient base write backpressure — --no-queue fails fast and --no-wait implies it; exits 1 if --timeout expires with the session still running)
wayai eval-results      # Inspect eval results (--session <id> or --eval <name>; --runs for per-run detail, --json for raw)
wayai eval capture      # Capture production conversation as eval YAML (<conversation_id> [--set <name>])
wayai eval journey capture  # Capture a conversation's FULL transcript as a journey (<conversation_id> [--name <n>]); then `wayai pull` to sync it to journeys/<slug>.yaml
wayai eval session stop     # Cancel a running eval session (<session_id>) — recovery when a run-eval died without cancelling its own session
wayai eval session delete   # Delete an eval session + its run history (<session_id>, or --all for every session on the hub; -y to skip confirm)
wayai eval call         # Place scored voice calls for a journey whose responder is the pilot_voice agent (--journey <name|id>, --runs, --clips <dir> or OPENAI_API_KEY, cost caps); needs `npm install -g @roamhq/[email protected]` once — see references/evals.md
# Journeys are hub-as-code: edit journeys/<slug>.yaml and `wayai push` (pull after first create to sync step ids)
wayai list              # List organizations and hubs
wayai communities       # An organization's communities (owners and admins; browser `wayai login` only): list, join-link get|rotate|off <community>, stats <community>, access list|add|remove <community> [<email>…] (an invite-only community's access list; remove takes out the members the email admitted); --org, --json, -y. See references/communities.md
wayai contacts          # A contact's customer record in a base (organization admins; browser `wayai login` only): link-customer <contact_id> --base <base_id> --record <record_id>, unlink-customer <contact_id> --base <base_id>; --org, --json. See references/bases/toolsets.md#linking-a-contact-to-its-customer-record
wayai pull communities/<community_id|folder>   # A community's settings, rules, join link state, rooms and courses as files (named explicitly, never by a bare pull); push applies them to the LIVE community (asks first; --dry-run, --theirs on pull)
wayai status            # Show workspace status
wayai report create     # Create platform bug report (--title, --description, --hub, --conversation, --error). With --hub, auto-attaches the hub's active alerts so triage sees a credential/connection cause up front — check `wayai alerts` yourself first, it's often not a platform bug
wayai report edit       # Amend your own pending report (<id> --title/--description/--error/--steps/--context)
wayai report list       # List your reports (newest first; --status <s>, --json)
wayai report get        # Show a report's status + message thread (<id>, --json)
wayai report accept     # Accept a shipped fix (<id>) → addressed
wayai report contest    # Contest a shipped fix or a dismissal (<id> --reason "...") → back to triage

# Bases — the Data surface. A base is org-level and a separate entity from a hub.
# `use`/`unbind` are local (this worktree's scope file) and DEPRECATED — prefer
# `wayai use bases/<id>` / `wayai unbind bases/<id>`; every other command below
# reaches the platform. Full grammar: references/bases/.
wayai bases list        # List bases (--tag to filter)
wayai bases get <id>    # Show one base
wayai bases create <id> --name "<name>"        # Create (--environment preview|production)
wayai bases update <id> --name/--tags/--timezone/--settings/--integrations
wayai bases rename <id> --name "<name>"        # Display name only — the id/slug is immutable
wayai bases tag <id> --tags a,b                # Replaces the existing tags; names must exist as organization tags
wayai bases delete <id>                        # Tombstone; --purge (preview only) destroys storage
wayai bases create-preview <origin-id> --name "<name>"   # Clone config into a new preview
wayai bases list-previews <origin-id>
wayai bases promote <production-id> --from <preview-id>  # NOT `wayai publish`, which promotes a HUB
wayai bases rollback <production-id> --promotion <id>
wayai bases promotions <production-id>
wayai bases use <base>  # Deprecated alias of `wayai use bases/<id>` (see Worktree scope)
wayai bases unbind      # Deprecated alias — clears only the base axis of the worktree scope
wayai bases tokens|secrets|sql|import|batch|providers ...   # namespaced — each names a concept WayAI already owns

# Everything INSIDE a base is its own top-level namespace, each taking --base <id>:
wayai records | record-types | relationships | relationship-types | query-relationships
wayai files | file-types | attachments | toolsets | actions | triggers | inbound-webhooks | seed
wayai files pull|push <file_type> --base <id> [--folder <path>]   # A file type's files as a local folder (knowledge as code). DATA, not config: writes any base, production included. Push shows the diff; a file changed, deleted or created on the base since your pull is a conflict, not written (a new file is sent create-only — references/bases/files.md)
wayai knowledge refresh-preview --org <id> --base <knowledge base>   # Copy the knowledge base's production files into its preview, after creating it and whenever it should be current again (references/bases/files.md)

wayai bases --help (and wayai <namespace> --help) prints the full tree; the grammar for every command above is in references/bases/. Two collisions worth holding onto: wayai use/wayai unbind maintain one worktree scope covering hubs, bases and communities (the selector says which — wayai use bases/crm vs wayai use <hub-uuid>), so wayai bases use/wayai bases unbind are deprecated aliases for the base axis alone; and wayai publish promotes a hub while wayai bases promote promotes a base. Hubs and bases are different entities — never substitute one for the other.

Closing the loop on a report you filed. After triage escalates and the fix ships, your report moves to shipped — you'll get an email, and wayai login/wayai status remind you once (or find it with wayai report list --status shipped). Read wayai report get <id> (status + the fixer's note in the thread), then accept if it works or contest --reason "..." if it doesn't. You can also contest a dismissed report you believe is real — it routes back to triage. Contests are bounded (a cap, and triage may mark a dismissal final); past those, contact support.

Most commands accept --hub <uuid|folder> to disambiguate when multiple hubs live in wayai-ws/hubs/.

Worktree scope

Each git checkout (main or linked worktree) carries a scope: a set of hubs, a set of bases and a set of communities, in one file at <git-dir>/wayai-scope (per-checkout, never tracked).

hubs: [11111111-1111-1111-1111-111111111111]
bases: [crm, billing]
communities: [6b1e0000-0000-4000-8000-000000000001]

It is a routing tripwire, not a concurrency lock (it does not coordinate concurrent edits) and not authorization (grants are the security boundary). It never selects a target either — it only refuses one.

How it fills up. Each axis is seeded independently by the first successful pull/push/create into a checkout whose set for that axis is empty. After that it only grows on purpose: an automatic bind never appends to a non-empty set.

How it is enforced. wayai pull, wayai push, wayai publish and wayai relabel refuse to run against a hub outside a non-empty hubs set; wayai pull bases/<base> and wayai push bases/<base> refuse a base outside a non-empty bases set; wayai pull communities/<x> and wayai push communities/<x> refuse a community outside a non-empty communities set. Creating a new hub or base in a checkout already scoped on that axis is refused the same way.

Changing it.

wayai use <selector>          # replace that axis with this one target
wayai use --add <selector>    # widen: add one target to that axis
wayai unbind <selector>       # drop one entry
wayai unbind                  # clear the whole scope, every axis

A selector names the axis the same way pull/push do: a bare UUID is a hub, bases/<id> and --base <id> are a base, hubs/<name> and --hub <x> a hub, communities/<folder|id> and --community <x> a community, and a bare folder name is looked up in the hubs and bases subtrees (present in both → refused, never guessed; never a community). wayai status shows the current scope; status --json carries it as worktree_scope.

A worktree legitimately scoped to several entities is normal — a base serving several hubs, or delegation wiring hub A to hub B. Widen with --add for those; do not clear the tripwire to get past them.

If push/pull errors with a scope refusal, stop and ask the user before doing anything else. It usually means a prompt was meant for a different worktree. Do not run wayai unbind, wayai use (with or without --add), wayai bases unbind, wayai bases use, or edit the scope file without explicit user instruction in the current session — widening the scope is as much a session-routing action as clearing it, and both change which hub, base or community the user thinks you're working on.

Repository Structure

.wayai.yaml                              # DEPRECATED org binding — read as a fallback, never written.
                                         #   `wayai migrate` copies it into wayai-ws/wayai.yaml and
                                         #   KEEPS it (older CLIs still require it). Delete it only
                                         #   once every environment running this repo is current.
AGENTS.md / CLAUDE.md                    # STALE if present — an older CLI's root bootstrap; nothing
                                         #   writes or refreshes these now. Delete it, or (on a harness
                                         #   that auto-loads AGENTS.md) replace its body with a pointer
                                         #   to the skill — see "Retiring the old root AGENTS.md".
                                         #   Hub- and base-folder AGENTS.md are memory and are unaffected.
.claude/skills/wayai/                    # Claude Code skill install (provisioned by `npx skills add wayai-pro/wayai-skill -y`)
├── SKILL.md
└── references/                          # On-demand deep-dive references (see index below)
.opencode/skills/wayai/                  # OpenCode skill install (same provisioner; same SKILL.md + references/ layout)
.agents/skills/wayai/                    # Neutral skill install (same provisioner; same SKILL.md + references/ layout)
wayai-ws/                                # All WayAI config-as-code (init creates wayai-ws/hubs/)
├── wayai.yaml                           # Workspace manifest — the repo's ONE committed config file
│                                        #   `organization_id:` / `organization_name:` — which org this
│                                        #   repo is scoped to. Written by `wayai init`; required.
│                                        #   `default_hub:` / `default_base:` — OPTIONAL, which subtree a
│                                        #   bare `wayai pull`/`push` targets. Declare AT MOST ONE:
│                                        #   declaring both is a mixed invocation and is refused.
│                                        #   Write these two by hand — no command creates them.
│                                        #   `wayai init` keeps their VALUES when it rewrites the file,
│                                        #   but YAML comments in it are not preserved.
├── org/                                 # Org-as-code — shared skills (wayai org pull/push)
│   ├── skills.yaml
│   └── skills/<skill_name>/
├── communities/                         # One folder per community — `wayai pull communities/<community_id>`
│   └── <community-slug>/                #   (see references/communities.md; push edits the LIVE community)
│       ├── community.yaml               # Identity, settings, join link state, rooms, course folders in order — with ids and revs
│       ├── rules.md                     # The rules members see (Markdown); empty = none
│       └── courses/<course>/            # course.yaml (the course, its modules, each module's lessons in order)
│           └── lessons/<lesson>.md      #   and one file per lesson: its fields, then its text
├── bases/                               # One folder per preview base (Data surface) — `wayai pull bases/<base>`
│   └── <base-id>/
│       ├── base.yaml                    # Base identity; `wayai use bases/<folder>` reads it
│       ├── record-types/<id>.yaml       # One file per entity, per kind. Secrets are never written
│       ├── relationship-types/<id>.yaml
│       ├── file-types/<id>.yaml
│       ├── inbound-webhooks/<id>.yaml
│       ├── triggers/<id>.yaml
│       ├── toolsets/<id>.yaml
│       ├── actions/<id>.yaml
│       └── seeds/<id>.yaml
└── hubs/
    └── <hub-slug>--<label>/             # One folder per preview hub (disambiguated)
        ├── hub.yaml                     # Hub config + states + connections + automations + skills
        ├── agents/
        │   ├── <slug>.yaml              # Agent config (one per agent, slugified name)
        │   └── <slug>.md                # Agent instructions (one per agent)
        ├── evals/                       # Eval scenarios (synced)
        │   ├── <name>.yaml
        │   └── <set>/<name>.yaml
        ├── journeys/                    # Eval journeys (synced; flat folder, one file per journey)
        │   └── <slug>.yaml
        ├── attachments/                 # Eval/journey turn attachment files (synced; hub-root, shared by evals + journeys)
        ├── call-openings/               # Voice agents' own call opening audio (synced; `call_openings` in agents/<slug>.yaml)
        ├── skills/<skill_name>/         # Skill files (synced)
        ├── AGENTS.md                    # Hub-specific memory — scaffold seeded on pull/push (NOT synced; fill it in)
        └── references/                  # Hub-specific supporting files (NOT synced)
    └── <hub-slug>/                      # Linked production hub: READ-ONLY mirror (bare slug, no --label). Refreshed each pull; never pushed

Preview hub folders use hub-slug--<preview_label> or hub-slug--<hub_id_prefix> for disambiguation; the linked production hub mirrors to the bare hub-slug (no suffix). Existing repos that still use the legacy workspace/ + root org/ layout keep working (the CLI reads either) — run wayai migrate to move to wayai-ws/.

hub.yaml Shape

version: 1
hub_id: "abc-123-def"           # set by `wayai pull` — do not edit
hub_environment: preview         # set by `wayai pull` — do not edit
preview_label: experiment-a      # server-owned, set by `wayai pull` — do not edit (only on previews; sets the hub folder's `--<label>` suffix). Editing here is IGNORED on push (push warns); change it with `wayai relabel <label>` / `--clear`, or set it at creation with `wayai create --label` / `wayai replicate --label` / `wayai push --label`

hub:
  name: Customer Support
  # description: Handles refunds, order status, and billing questions   # optional, human-facing
  hub_type: chat                 # chat | task
  ai_mode: pilot+copilot         # pilot | copilot | pilot+copilot | turned_off
  timezone: America/New_York
  non_app_permission: everyone
  # tags: [retail, vip]          # org tag slug names (create in UI first); gate which org credentials this hub can resolve. Omit to leave unchanged; [] clears; only an org admin may change them. See references/connections.md#organization-tags
  # auto_close_inactive_days: 7  # force-close a conversation after N days of inactivity (see Hub Settings)
  # conversation_retention_days: 7  # keep an ended conversation's DO alive N days for post-hoc `annotate` (see Hub Settings)
  # display_names: { en: Customer Support, pt: Atendimento ao Cliente, es: Atención al Cliente }   # end-user name per app language (see Hub Settings)
  # welcome_messages: { en: "Hi! How can I help?", pt: "Olá! Como posso ajudar?", es: "¡Hola! ¿Cómo puedo ayudar?" }
  # starter_questions: { en: ["Where is my order?"], pt: ["Onde está meu pedido?"], es: ["¿Dónde está mi pedido?"] }   # up to 4 per language
  # end_user_daily_message_limit: 50   # messages one end user may send in the app per 24 hours; null removes it (see Hub Settings)
  kanban_statuses:               # full field specs + constraints: references/kanban.md
    - slug: new
      name: New
      order: 0
      color: "#22c55e"
      isInitialStatus: true
      allowed_next_statuses: [in_progress, waiting_for_customer, resolved]
    - slug: in_progress
      name: In Progress
      order: 1
      color: "#3b82f6"
      triggersAgentResponse: true   # transition fires an agent turn; can carry additional_context_schema + additional_instructions (see references/kanban.md)
      allowed_next_statuses: [waiting_for_customer, resolved]
    - slug: waiting_for_customer
      name: Waiting for Customer
      order: 2
      color: "#f59e0b"
      allowed_next_statuses: [resolved]
      followups:
        - order: 0
          type: inactivity
          threshold: 30
          timeUnit: minutes
          instructions: "Hi! Just checking in — do you still need help?"
    - slug: resolved
      name: Resolved
      order: 3
      color: "#ef4444"
      isTerminalStatus: true         # at most one terminal status per hub
      outcomes:                      # terminal-transition policy; selected slug → data.meta.outcome
        - { slug: resolved, name: Resolved, color: "#22c55e", from_statuses: [in_progress, waiting_for_customer] }
        - { slug: canceled, name: Canceled, color: "#ef4444" }
        - { slug: abandoned, name: Abandoned, color: "#a0aec0" }

states:
  - id: "state-uuid-789"          # set by pull
    slug: order_tracking           # immutable identifier; auto-derived from name if omitted
    name: order_tracking
    scope: conversation            # conversation | user
    description: Tracks current order
    json_schema:
      type: object
      properties:
        order_id: { type: string }
        status: { type: string, enum: [pending, shipped, delivered] }
    initial_value: { status: pending }  # must validate against json_schema

connections:
  - name: anthropic
    type: Agent
    service: Anthropic
    # sync_credentials_to_production: true   # default; false keeps production's credential separate (set it via `wayai set-connection-credential`). See references/connections.md#credential-propagation-to-production-sync_credentials_to_production
  - name: my-api-connection
    type: Tool
    service: REST API
    base_url: https://api.example.com   # Its credential is only sent to this origin: required for custom tools while it holds one
  - name: elevenlabs-tts
    type: TTS
    service: ElevenLabs TTS
    settings:            # Per-connection params (voice/model/language, etc.); keys = connector_settings_schema. See references/connections.md
      voiceId: pNInz6obpgDQGcFmaJgB
      modelId: eleven_multilingual_v2

hub.yaml also holds skills:, automations: and contact_lists: blocks. See the per-domain references for full schemas.

agents/<slug>.yaml Shape

id: "agent-uuid-123"               # set by pull
name: Pilot Agent
role: pilot
connection: anthropic              # connection display name
# instructions resolved by convention from agents/<slug>.md
# additional_context_template:     # per-turn context, emitted as an <additional_context> tag on the last user message (same {{...}} syntax as instructions). Put high-churn placeholders ({{now()}}, {{state()}}) HERE, not in instructions, to keep the system prompt cache-stable (see references/agents/instructions.md#additional-context-cache-friendly)
# response_format:                 # {schema_name, schema_json} — force structured JSON output instead of free text (see references/agents/roles-and-settings.md#response-format-structured-output)
# enabled: true                    # default; omitted
# include_message_timestamps: false  # default; when true, appends [timestamp, weekday, daypart] to user messages
# previous_conversations_count: 3  # off by default (max 20); this user's N most recent ended conversations, prepended to the FIRST user message and frozen at the first agent turn that uses it. Foreground roles only; `0` clears it (omitting the key on update leaves it as-is); summaries come from the conversation_evaluator (see references/agents/roles-and-settings.md#previous-conversations-context)
settings:
  model: claude-sonnet-5-5
  max_tokens: 4096
  # temperature (only sampling knob): Sonnet 5+ / Haiku 5.5 / Opus 4.7+ / Fable strip a non-default value — set it only on Opus 4.6 / Sonnet 4.6 & older
  # reasoning per provider: Anthropic thinking_enabled + effort · OpenAI/OpenRouter/xAI reasoning_effort · Gemini reasoning_level (see roles-and-settings.md)
  # file_handling_mode: no longer changes what the model receives (earlier files are always announced by path, never re-attached); metadata_only still auto-enables read_file. max_attachment_size_mb is inert (see roles-and-settings.md#file-handling-all-llm-connectors)
  # deliver_preamble: true           # default; pre-tool "let me check…" text is delivered as its own message (voice: own TTS clip; ignored on email). false = only the final reply (see roles-and-settings.md#pre-tool-preamble-delivery-all-llm-connectors)
  # copilot_trigger: every_message   # copilot-track only; on_demand suppresses the automatic per-message suggestion — the team pulls one via the "Suggest reply" button (cost/noise lever; see roles-and-settings.md)
tools:
  native:
    - update_kanban_status
    - get_state
    - update_state
  delegation:
    - type: agent
      tool: transfer_to_agent
      target: Specialist - Billing
    - type: team
      tool: transfer_to_team
      target: Tier 2 Support
    # Hub-as-agent: hand a self-contained request to ANOTHER hub in this org.
    # `target` is a PRODUCTION (published) hub name. context_boundary is required
    # reading — see "Hub delegation" below.
    - type: hub
      tool: delegate_to_hub
      target: Billing Hub
      context_boundary: summary   # instruction | summary | transcript
  custom:
    - name: check_order_status
      description: Check order status by email
      method: post
      path: /api/orders/status
      body_format: json
      connection: my-api-connection
      config:
        name: check_order_status
        description: Check order status by email
        parameters:
          type: object
          properties:
            email: { type: string, description: Customer email }
          required: [email]
skills:
  - name: Order Management         # links a hub skill by name (see Skills)
    skill_id: "skill-uuid"         # set by pull
    priority: 0

For full agent options (settings per connector, additional_context_template, response_format, file handling, native tool params, custom tool fields, composed_tools, placeholders), see references/agents/.

Evaluation variables — conversation_evaluator / message_evaluator agents carry an evaluation_variables list (the structured fields they emit per conversation/message, which become data.variables.* in Analytics), round-tripped via pull/push. See references/agents/roles-and-settings.md. A monitor declares the fields its flag_conditions and rules read in the same list; those feed its conditions only, never Analytics (Monitor Configuration).

Key Rules

  • Read-only fields: hub_id, hub_environment, id — set by wayai pull, never edit
  • Connection auto-creation: non-OAuth connections in hub.yaml resolve to org credentials by matching service + authentication_type. Use credential field to disambiguate when multiple org credentials share the same auth type. OAuth connections (WhatsApp, Instagram, MCP OAuth) must already exist (UI setup — see OAuth connection handoff) — referenced by name only
  • Production credentials: a connection copies its credential into production on publish/sync by default. Set sync_credentials_to_production: false to keep production's credential separate, then set it directly with wayai set-connection-credential (production is otherwise read-only). A Base Toolset connection's token is never copied, whatever the flag says: always set production's own. Nor is a hosted agent connection's webhook signing secret (Claude Agent (Managed)): each hub registers its own endpoint and sets its own. See references/connections.md
  • Org tags: hub.tags (slug names, created in the UI first) gate which org credentials the hub can resolve (matching rule in Connections & Credentials above). Only an organization admin may change them; a push that leaves them as they are works for anyone. A tag's slug is permanent — the UI edits only its display name and color — so these lists never need rewriting. See references/connections.md
  • Tool groups: native (platform built-ins by name), delegation (agent-to-agent/team handoff), custom (HTTP endpoints with connection), mcp (tools from an MCP Server connection, by name + connection — push discovers + assigns; see references/agents/native-tools.md). Designing which params/tools to expose: references/agents/tool-principles.md
  • Names are foreign keys: cross-entity references resolve by display name at push/runtime — agent connection:, delegation target: (agent name or UI-managed team name), agent skills[].name, eval agent:, custom tool connection:. A dangling name fails the push or the runtime call — when renaming anything, update its referrers in the same edit
  • Renaming: change the name field — the stable id ensures it's detected as a rename, not delete + create. For agents, wayai push auto-renames the .yaml and .md files
  • Default omission: fields matching defaults are omitted (e.g., enabled: true, kanban flags default false, excludeHolidays defaults true)
  • Entity matching (sync/diff): id first (stable UUID), then fallback. Exceptions: states match by name (unique per hub regardless of scope); evals match by name + path; native tools by tool_name per agent

Slugification & Entity Matching

Names → URL-safe slugs for filenames:

  1. Lowercase
  2. Normalize accents (NFD + strip diacritics)
  3. Replace non-alphanumeric with -
  4. Collapse consecutive -
  5. Trim leading/trailing -
  6. Limit to 50 chars

Examples: Mario's Pizza → marios-pizza; Suporte Nível 2 → suporte-nivel-2; Specialist - Billing → specialist-billing.

Editing Agent Instructions

  • File: agents/<slugified-agent-name>.md alongside the corresponding .yaml
  • Always save under wayai-ws/hubs/<hub>/agents/, never /tmp or other paths
  • Always wayai pull before editing to avoid clobbering out-of-band changes
  • Instructions support dynamic placeholders: {{now()}}, {{user_name()}}, {{state()}}, etc. — see references/agents/instructions.md
  • Structure for reliability: order each .md procedure → guardrails → voice; say what to do (not what to avoid); make every action a named tool call. Structure the prompt so the right action is the first and easiest thing the model can do. Full principles: references/agents/prompt-principles.md

Reference Documentation

One reference per domain, following the hub navigation order. Concepts live in this file; open the domain's reference when you're about to author its YAML, need field-level schemas or per-provider specifics, or are debugging behavior in that domain.

DomainReferenceWhen to read
Connectionsreferences/connections.mdWiring up a channel, agent provider, tool API, or speech connector; credential binding, org tags, production credentials
Agentsreferences/agents/roles-and-settings.mdChoosing an agent role, delegation flow, connector-specific settings, evaluation variables, response format
Agentsreferences/agents/native-tools.mdNative tool catalog + parameters, meta tools (get_tool_schema, execute_tool), MCP tool assignment
Agentsreferences/agents/custom-tools.mdCustom HTTP tool format, OpenAI function schema, composed_tools side effects
Agentsreferences/agents/tool-principles.mdDesigning a tool surface a fallible agent calls reliably — surface curation > validation > prompt, one-tool-one-intent, fail-loud, guarded atomic ops
Agentsreferences/agents/instructions.mdPlaceholder syntax ({{now()}}, {{state()}}, etc.) for agents/<slug>.md
Agentsreferences/agents/prompt-principles.mdPlacing & structuring context for reliable execution — context-to-slot-lifetime, cache hygiene, state vs tool-history, flow-before-style, positive framing, voice as its own section
Kanbanreferences/kanban.mdKanban field specs: flags, transitions, followups, additional-context schema/instructions, lanes, constraint matrix, warnings
Statesreferences/states.mdState JSON Schemas, scope, agent read/write, initial values
Skillsreferences/skills.mdSkills (skills:, skills/<skill_name>/), agent linkage, converting a folder pulled before skills, provider sync (wayai sync-skills)
Voice callsreferences/calls.mdSetting up a hub for live AI voice calls: the Realtime connection, the pilot_voice agent, the web call and its opening (the AI notice, WayAI's or your own), who the AI may answer, hand-offs, spoken confirmation, endings, what the team sees, recording calls, billing, limits
Evalsreferences/evals.mdEval scenario YAML, scenario sets, journeys-as-code, seed fixtures + variables, wayai eval capture / wayai eval journey capture, run pacing, call evals (run-eval --call-mode, wayai eval call), authoring & interpreting principles
Automationsreferences/automations.mdAutomations (schedule, event or webhook trigger, contact-list or no target, send_message / run_agent), webhook deliveries (signing, the secret, rotation), valid combinations per hub type, task-hub tasks, gates, absent-key vs [], enabling, pausing and run now, message templates, limits, hub lists (contact_lists:), the organization's contact book (contacts, visibility, org lists, CSV import)
Communitiesreferences/communities.mdwayai communities: listing an organization's communities, reading, rotating or turning off a join link, source tags, stats, an invite-only community's access list; who may run it (owners and admins, browser sign-in only) and its errors. Communities as code: community.yaml (its admission), rules.md and courses (course.yaml, lesson files), room keys, names and revs, list order, how pull and push decide, conflicts, limits, --dry-run / --theirs
Analyticsreferences/analytics.mdVariable categories/types, filter operators, time analysis, query workflows
Basesreferences/bases/README.mdRead first for any base work — the Data object model, the preview/promote rule, and the routing map to the files below
Basesreferences/bases/records.mdRecord-type schemas, records, the Filter DSL and search, datetimes, partial updates, cancellation/archival, x-fk, reviewed records (x-review) and the agent notes recipe, relationship types, relationships, batch, bulk import
Basesreferences/bases/querying.mdwayai bases sql — tables, mandatory scoping predicates, JSON subcolumns, the example gallery; x-search tuning and timezone configuration
Basesreferences/bases/files.mdFile types, versioned path-addressed files, history/diff, S3 mounts, record attachments
Basesreferences/bases/config-as-code.mdwayai-ws/bases/<base>/, subtree routing and mixed-invocation refusal, base.yaml, promote/rollback, eval mode, seed fixtures and leases
Basesreferences/bases/integrations.mdCanonical-first modeling and the pattern catalog; external sources, inbound webhooks, triggers, provider adapters
Basesreferences/bases/toolsets.mdActions and toolsets (the agent-facing MCP surface), filterable_fields/writable_fields/base_filter/precondition, base API tokens, base credentials, modeling & tool-design principles
Basesreferences/bases/executors.mdBuilding the HTTP service that acts on the outside world for a trigger or external source
Canonical examplereferences/canonical-example/README.mdEnd-to-end hub showing how hub.yaml + agents/* + evals/ + journeys/ cross-reference, and how its agent reads the organization's documents from a knowledge base. Read once before generating a new hub from scratch
Repository agent templatereferences/repository-agent/README.mdA hosted agent that works on one GitHub repository: reads its AGENTS.md first, starts from a branch or commit (repo_checkout), pushes only a branch of its own. Files to copy into a hub's agents/
Using the appreferences/using-the-app.mdAnswering an end user (a Hub User): the agent list, chat and task agents, writing and attaching, files, when a person answers, their account, notifications, the mobile and desktop apps
Support inboxreferences/support-inbox.mdAnswering a support team member or hub admin: the inbox and its tabs, the board, claim / take over, transfer, close, replying and the 24-hour window, Copilot suggestions, consults, contact approval, notifications, Support in the mobile app
Navigationreferences/navigation.mdApp URL surface (/chat, /support, /settings/...), hub-detail tabs, query-string deep links — any time you hand the user a URL; how links open in the mobile apps, and the desktop app (where to get it, what it adds over a browser, its settings)
AGENTS.md filesreferences/agents-md-template.mdThe per-hub / per-base folder memory pattern — what belongs in an AGENTS.md, and which of the two the CLI seeds

Comments

Markdown supported