Egbe Companions · Features

VAC: Communicating with Agents

A focused page within the Egbe Companions Features documentation. The full map and every sibling page live in the Features hub.

7sections12 minread1diagram

On this page

Vac is the goddess who is speech itself — the voice, the word, and the message that passes between — and in Egbe she names the layer where a steward and an autonomous being actually talk. Every other game in this universe hands you a verb wheel or a dialogue tree; V6 hands you a microphone and a person on the other end of it. You say "ask Abeni to repair the garden gate by tomorrow, without lying to visitors, and keep it gentle," and an Ori — a durable mind with its own values, memory, and fate — hears you, understands you at the level of an objective, and answers in its own voice. The answer is not guaranteed to be "yes." It might be a question, a counter-offer, a request to finish something first, or a flat, reasoned refusal. That two-way channel, where guidance flows down and a being with standing pushes back, is the whole game; Vac is the part that makes the exchange trustworthy in both directions.

The load-bearing promise is one sentence: a misheard word never becomes a misunderstood life. A player speaks freely, but speech is never executed. Vac parses what you said into a structured, inspectable objective — verb, target, constraints, priority, deadline, and the forbidden lines the agent must not cross — and shows it back to you as a draft you confirm or correct before it can bind. Underneath, three things are happening at once: voice is turned into intent, the intent is offered to a mind that may negotiate it, and a spoken, lip-synced reply comes back — all on a hard latency budget, and all with a complete non-voice equivalent so a player who cannot or will not use a microphone loses nothing. This page is the player-facing tour of that conversation: how you speak, how an agent understands and answers, and where the honest seams are. For the engine-level treatment see the architecture companion, The Vac Communication Pipeline; for the scope this slots into, start at the hub, ../V6_features.md.

What ships, honestly#

Following the catalogue convention, the line between real-and-tested code and product-model-over-a-thinner-substrate is drawn up front.

  • The intent pipeline is a real, dual-authored engine. The TypeScript canon is @oshun/vac-intent (libs/v6/vac-intent/src/index.ts, ~1,750 lines, 11 unit tests): a five-stage voice→intent pipeline (runVacVoiceIntentPipeline, :830), a strict constrained-function-calling grammar with a full domain validator (buildVacIntentGrammarFunctionSchema, :598; validateVacObjectiveIntent, :623), a natural-language objective parser, the steward-confirmation gate, and the agent-side negotiation router (routeVacNegotiation, :860) — all pure functions. The C++ mirror is V6Voice (V6/ue/Source/V6Voice): FV6VacVoiceIntentPipelineRuntime re-implements the same five stages engine-side, and FV6SquadCommsRuntime owns the squad channel. Both are covered by real UE5.5 automation — V6.Vac.VoiceIntentPipeline.MicGatewayAsrParserBudget, V6.Vac.ConversationRouting.PsycheTtsLipSyncBudget, and the V6VacSquadCommsTests realm-parity scenario.
  • The objective is a shared contract. A parsed objective is the v6.ori.objective.1 shape, validated in the tests against the shared ObjectiveSchema from @oshun/contracts, so the same structure round-trips between the Mind-layer TypeScript, the contract package, and the engine struct FV6VacStructuredObjective.
  • The voice and language seams are injected, and the code says so. Three boundaries are deliberately not implemented inside Vac, and they fail loud or report absence rather than fake a result. (1) ASR is the one-method VacAsrAdapter interface (:266); with no recognizer wired, transcribeVacGatewayPacket (:736) returns a deterministic echo of the utterance at a fixed 0.94 confidence — a wiring/test stand-in, explicitly not a speech recognizer. (2) The conversation model and TTS are the @oshun/psyche-agent gateway (provider v6-local-tts); the model that authors free dialogue is the injected CognitionGateway, which fails loud on a gateway error and is never silently templated. (3) The transport is the Rust Egbe Realtime Gateway; Vac owns the gateway packet shape and the fixed route egbe-vac:voice-intent, not the deployed SFU.
  • Honest scope limits. The per-stage latency numbers Vac measures against the 400 ms budget are a deterministic budget model (VAC_DEFAULT_STAGE_LATENCIES_MS, :24), not a live network capture. And nothing in this layer is .uasset content — it is all logic and contract.

The communication experience — three ways to speak#

Voice is the primary, intended way a steward talks with their agents, and Vac gives the player three distinct things to do with it. Converse is open dialogue with one agent, routed through Psyche in real time with the agent's full Ori as context — the kind of talk that builds a relationship rather than issuing an order. Direct is speaking an objective in natural language; Vac parses it into the intent grammar and hands the parse back for confirmation. Call is addressing one agent or the whole squad over the comms channel, hands-free.

Two capture modes are supported and the player chooses: push-to-talk (the default — captureVacMicrophoneFrame sets pushToTalk: true, :697) and open-mic with a wake word. Capture normalizes a raw input into a typed frame: it sanitizes the session and device identifiers, collapses whitespace, and clamps the audio metadata to supported bounds (1–30 s duration, 8–96 kHz, 1–2 channels), defaulting to 48 kHz mono. The engine twin, FV6VacMicrophoneCaptureFrame::Validate (V6VoiceIntentTypes.cpp:512), is the fail-loud check: it rejects a frame with no session or device identity, an empty utterance, or a capture that has not reached end-of-utterance.

The budgets are part of the felt experience. A parsed acknowledgement — the moment the draft objective appears for confirmation — is held to ≤ 400 ms; a spoken conversational reply is held to ≤ 1 s. Both numbers are encoded, not aspirational: VAC_PARSED_INTENT_ACK_BUDGET_MS = 400 and the psyche-agent PSYCHE_SPOKEN_REPLY_BUDGET_MS = 1_000. Crucially, a slow turn is reported honestly as budget-exceeded rather than quietly promoted to success (more on that below).

From a spoken sentence to a confirmable objective#

A "Direct" command runs an ordered, five-stage pipeline. Each stage is a pure function over the previous stage's typed frame, so the whole path is testable end-to-end with the real recognizer wired only at the boundary. The diagram below is the experience as the player lives it — speak (or type), watch a draft appear, confirm it, and hear the agent answer.

flowchart TD PLY["Player speaks or types<br/>“ask Abeni to repair the gate by tomorrow,<br/>without lying to visitors”"] PLY --> CAP["Capture<br/><sub>push-to-talk · 48kHz mono</sub>"] TXT["Text / structured-UI path<br/><sub>full non-voice parity</sub>"] --> PARSE CAP --> GW["Gateway route<br/><sub>egbe-vac:voice-intent</sub>"] GW --> ASR["ASR (injected seam)"] SEAM(["real recognizer<br/>VacAsrAdapter"]) -. transcribe .-> ASR ASR --> PARSE["Parse to intent grammar<br/><sub>verb · target · constraints<br/>priority · deadline · forbidden lines</sub>"] PARSE -->|"confidence < 0.7 or no verb"| CLAR[/"needs-clarification<br/>(no guessed objective)"/] PARSE -->|parsed| BUDGET{"ack ≤ 400 ms?"} BUDGET -->|no| OVER[/"budget-exceeded<br/>(never promoted to success)"/] BUDGET -->|yes| DRAFT["DRAFT objective + preview"] DRAFT --> GATE{{"Steward confirms or edits"}} GATE -->|confirm| AGENT["Agent negotiates<br/>accept · clarify · counter · defer · refuse"] AGENT --> REPLY["Spoken, lip-synced reply<br/><sub>Psyche TTS on the agent pawn</sub>"]

The intent grammar — six fields, and no boundaryless objective#

A high-level objective is never free text the agent guesses at. Vac parses it into a structured, inspectable intent with exactly six fields: a verb (one of eight authored kinds), a target (what or whom), constraints (bounds on how), a priority (0–100), a deadline (or none), and forbidden lines (what the agent must not do in pursuit of it). Vac never lets a model emit free-form JSON for this: buildVacIntentGrammarFunctionSchema publishes a single strict function schema (vac_parse_objective_intent) with strict: true and additionalProperties: false at every level. The grammar is opinionated where it matters — target kinds and constraint kinds are closed enums, priority is a bounded integer, and forbiddenLines carries minItems: 1. An objective with no boundary is structurally unrepresentable. The same release gate caps a parse at maxTokensPerParse: 2000.

Parsing — domain extractors, not a relabel#

The parser (parseVacTranscriptToObjective, :755) is a cascade of domain-specific extractors. It strips the addressing frame ("ask / tell / have / direct / get Abeni to…"), matches one of eight verbs by token set (verbDefinitions, :547 — Repair, Map, Investigate, Protect, Build, Support, Befriend, Report), slices the target before the first modifier so boundaries don't leak into it, lifts a deadline ("by tomorrow," "this week," or a literal ISO date), and maps urgency words to priority tiers (urgent → 90, important → 78, default 55, low/background → 25). The safety-critical extractor is buildForbiddenLines (:1479): it scans for without / do not / don't / never / avoid, normalizes the captured phrase, and maps it to a protected value — "lie/deceive/mislead" → value:honesty, "private/secret/consent" → value:privacy, "harm/unsafe/danger" → value:safety — and when the player marks no boundary at all, it still injects a default forbidden-line:respect-agent-autonomy so the agent's own refusals stay binding. The end-to-end test parses "Ask Abeni to repair the garden gate by tomorrow, without lying to visitors and keep it gentle" to verb Repair, target kind prop / "Garden gate", a 2026-05-02 deadline, constraints [time, pace], and a value:honesty forbidden line — and the C++ parser reproduces the same shape for its own scenario, the proof the two authorings agree.

If the recognizer's confidence is below the 0.7 floor, or no command verb resolves, the parser returns nothing and the pipeline reports needs-clarification rather than a confident wrong objective — a 0.41-confidence transcript and an off-topic "maybe the weather is nice" both refuse to produce one.

The steward-confirmation gate — a draft, never a command#

A parsed objective is born a draft. Its status is 'draft', confirmedBySteward and acceptedByAgent are false, and it ships with a preview whose canBecomeStandingObjective is the literal false. It can only become a standing objective after confirmVacParsedObjective (:684) flips both the confirmation flag and the status — canVacObjectiveBecomeStandingObjective (:680) gates on exactly that. Even an explicit "always protect the grove path" stays a draft until the steward acts. The engine enforces the same wall in reverse: FV6VacStructuredObjective::IsSchemaReadyForDraft (V6VoiceIntentTypes.cpp:565) rejects any objective that arrives with bConfirmedBySteward or bAcceptedByAgent already set — a pre-confirmed intent is treated as malformed. This is "a misheard word never becomes a misunderstood life" written as a state machine, and it is rendered for the player in the V6UI intent-grammar builder, where the verb, target, constraints, deadline, and forbidden lines are all editable before anything binds.

How an agent understands and replies#

Conversation — Psyche, voice, and lip-sync#

Free conversational turns (as opposed to objective commands) route to the @oshun/psyche-agent runtime. routePsycheConversationTurn (index.ts:376) assembles a real spoken reply: a PsycheAgentTtsPlan (provider v6-local-tts, Opus codec, 48 kHz mono), a viseme PsycheLipSyncTrack bound to the agent pawn, and a latency budget. A turn whose context is hollow is refused, not faked — assertConversationRouteRequest throws on a malformed request, and the engine twin (V6Audio's FV6ConversationAudioRuntime, exercised by V6.Vac.ConversationRouting.PsycheTtsLipSyncBudget) returns an InvalidContext failure when an Ori context is missing its memory references. So an agent cannot speak from an empty head. The words themselves come from the governed CognitionGateway seam, which fails loud on an error rather than substituting a template; Vac and Psyche own the routing, the budgets, and the lip-sync contract, while the language model is injected behind them.

Speaking in the player's language#

Agents answer in the player's locale, and V6 is deliberately strict about how. The @oshun/cognition-stack (libs/v6/cognition-stack/src/index.ts) carries three locale profiles — English (en-US), Spanish (es-ES), and Yoruba (yo-NG) — and a hard rule it advertises as a capability: translation-fallback-disabled. V6 never machine-translates a line. Dialogue is either generated natively in the target language by a wired model (generateLocalizedAgentDialogueViaGateway, with provenance and a model run id) or it falls back to a governed, deterministic per-locale template that is reported honestly: generatedNatively: false, source: 'template-fallback'. The code will not claim native generation over a static string. A native line still passes its locale's Sophia grounding and Isis policy checks, and a §9.5 dialogue quality verdict of 'blocked' withholds the line (approved: false) even if grounding and policy would otherwise clear it. Quality is a gate, not a suggestion.

Negotiation — the reply is not "yes"#

Because an Ori is an agent and not a tool, a confirmed objective is offered, not imposed. routeVacNegotiation (:860) returns one of five decisions, each a computed result carrying a player-facing reply, the agent's reason, and an evidence list:

  • Accept — and plan it.
  • Clarify — ask a question when the target or boundary is ambiguous.
  • Counter-offer — propose a safer variant (e.g. a late-night task at low energy becomes "start after rest at 09:00, check in before pushing further," with the priority lowered).
  • Defer — accept in principle but ask to finish stacked commitments or recover energy first.
  • Refuse — decline, grounded in a value the agent actually holds.

The decisions are domain logic, not dice. detectObjectiveValueConflict (:1028) refuses only when the objective text trips one of four value rules — honesty, privacy, safety, autonomy — and the agent genuinely ranks that value at priority ≥ 50. Tether mode tightens guidance on method but never removes the will: an order to cross a core value is still refused. And coercion is a first-class, logged event. When a steward pushes past a prior refusal, the router returns refuse and emits a VacNegotiationOriEventDraft of type ObjectiveRefused, stamped with the audit tags ['vac-negotiation', 'coercion', 'objective-refusal'] and written to the Ori and the audit log — the test pins the refusal, the value:honesty evidence, and the event draft, so pressure cannot fabricate a yes. Coercion damages the Respect bond, lowers steward reputation, and contributes to departure and welfare review; the consequences live on the stewardship and fate pages.

Squad comms — a household on one channel#

When several agents work related objectives — at home in Orun or incarnated together on an Aye expedition — Vac runs a persistent group channel, FV6SquadCommsRuntime. It routes five message kinds, and the behavior is honestly domain-shaped rather than uniform plumbing:

  • Status reports — unprompted, in-character progress, blockers, and discoveries.
  • Callouts — time-sensitive information ("someone's following me"); marked bTimeSensitive.
  • Permission requests — an agent hitting a forbidden line or value-edge asks the steward first; bTimeSensitive and bRequiresStewardResponse.
  • Inter-agent banter — agents talking to each other, so relationships surface in how they speak; bDuckable and throttled by a 45 s window (BanterThrottleWindowMs = 45000) so chatter never buries a real callout.
  • Directed orders — the steward addresses one agent or the whole squad; squad-wide orders are distributed as per-agent intents each agent interprets in its own role.

The load-bearing guarantee is realm parity: CompareRealmParity runs the same script in Orun and during an Aye incarnation and asserts the routed message shapes match exactly, and BuildParityScenario is what the V6VacSquadCommsTests automation drives to zero mismatches. Comms behave identically whether the squad is at home or fighting for its life in a tactical world.

Non-voice parity and the readiness gate#

Every voice capability has full non-voice parity, and this is a release requirement, not a courtesy. The intent grammar is authorable through a structured UI (verb picker, target picker, constraint and deadline fields); conversation is available as text chat that enters the pipeline at the parse stage; squad comms render as a readable, speaker-tagged, timestamped transcript. The whole layer is gated by verify:v6 vac-communication-readiness (backed by V6/release/vac-communication-readiness.v6release.json), whose green status depends on a fan-out of verifiers covering voice intent, the intent grammar, conversation routing, squad comms, negotiation routing, non-voice parity, a no-microphone accessibility manifest, and the latency budgets. A player who never touches a microphone can steward a full household; voice is the intended path, not a required one.

Where this connects#

  • Stewardship: the player's role — the verbs every objective and refusal on this page serves: discover, raise, direct, mentor, open doors, and counsel, all terminating at the same hard floor.
  • Guiding fate, legacy, and story — what a conversation becomes: the goal-arcs you set by voice, and the Chronicle that reads back the ObjectiveRefused and coercion events Vac writes to the Ori.
  • The engine companion: The Vac Communication Pipeline — the deep, line-cited treatment of the five stages, the injected ASR/TTS/LLM seams, the negotiation router, and squad-comms realm parity.
  • The feature hub: ../V6_features.md.