Neocortex
IntegrationsGodot SDK

Group Chat

Put several AI characters in one conversation in Godot 4 with the Neocortex Group Director, each with its own voice, joining, leaving and talking to each other.

Pro or Team plan

A cast of two or more characters needs a Pro or Team plan. A cast of one works like a normal single character chat on any plan.

NeocortexGroupDirector runs one conversation across several characters. Each character keeps its own NeocortexSmartAgent, with its own voice, animation and signals. The director sends one turn for the whole cast and plays every reply through the character that said it, one after the other. The cast can be up to three characters.

How the server runs a group scene is covered in Web API Group Chat.

Setting up

Add the characters

Give each character its own NeocortexSmartAgent and pick a character in its character_id dropdown. Each agent plays its own lines, so give each one its own AudioStreamPlayer3D if you want the voices to come from the right place.

Add the director

Add a NeocortexGroupDirector node and drag the agents into its agents array.

The director owns the microphone for the whole scene, so it turns auto_voice_input off on every agent in the cast. Without that, each character would answer the player on its own.

Add the UI (optional)

Add a NeocortexGroupChatUI beside the director, with a chat panel, a text input and a thinking indicator under it. It finds the director and its widgets by itself. Every speaker's turn comes back as a bubble with the character's name.

Use one NeocortexGroupChatUI for the whole cast. Don't also give each character a NeocortexChatUI, or every reply shows up twice.

Talking to the group

CallWho answers
send(message)The cast decides who answers, up to max_turns speakers.
send_to(agent_or_id, message)That character. Pass an agent or a character id.
continue_scene()Nobody is addressed: the characters talk among themselves.
send_audio(wav)Transcribes the player's recording, raises player_speech, then runs it like send.
group_scene.gd
extends Node

@onready var director: NeocortexGroupDirector = $NeocortexGroupDirector
@onready var aria: NeocortexSmartAgent = $Aria/NeocortexSmartAgent

func _ready() -> void:
	director.turn_started.connect(func(): input_box.editable = false)
	director.turn_finished.connect(func(): input_box.editable = true)
	director.speaker.connect(func(message: NeocortexGroupMessage):
		var text := ""
		for line in message.lines:
			text += line.text
		subtitles.show_line(message.name, text))
	director.request_error.connect(func(error: NeocortexApiError):
		if error.is_forbidden():
			print("Group chat needs a Pro or Team plan"))

	director.send("Hi everyone, introduce yourselves")

func ask_aria() -> void:
	director.send_to(aria, "Aria, what do you think?")

A turn plays out in order. turn_started fires first, then group_response_received with the whole turn, then speaker before each character speaks its part. Each agent raises its own chat_line_started, emotion_changed and reply_finished as it speaks, so your animation code works the same as for a single character. turn_finished fires once everyone is done, and also when a turn could not run.

Input sent while a turn is running is ignored with a warning. Lock your input on turn_started and open it again on turn_finished.

max_turns (1 to 5, default 5) caps how many characters speak in one turn.

Actions in a group

Actions are registered on each agent, not on the director. When a character asks for an action in its part of the turn, its own agent runs the handler.

Per-character actions
aria.register_action("WAVE", func(action: NeocortexChatAction):
	aria_animation.play("wave")
	return aria_animation.animation_finished)

Changing the cast

Characters can join and leave while the conversation runs:

A character walks in
func on_bram_arrived() -> void:
	director.add_agent(bram)
	director.continue_scene() # the others notice the newcomer

remove_agent(agent) takes a character out of the cast. An ambient turn with continue_scene() right after a change lets the cast react straight away.

History and sessions

The director keeps one shared session in session_id. It starts empty and is set by the first turn. It lives in memory only, so store it yourself if you want to resume the scene later. clear_session() starts a new conversation on the next turn.

await director.get_history(limit, before) loads the shared transcript, every speaker oldest first, and raises history_received. It returns null before the first turn.

The full list of members and signals is in the API Reference. The Group Chat sample shows a live roster, with characters walking in and out.

On this page