Skip to content

add client-orchestrated agent onboarding - #6274

Draft
dnbrwstr wants to merge 21 commits into
developfrom
db/agent-onboarding-v2
Draft

add client-orchestrated agent onboarding#6274
dnbrwstr wants to merge 21 commits into
developfrom
db/agent-onboarding-v2

Conversation

@dnbrwstr

@dnbrwstr dnbrwstr commented Aug 13, 2026

Copy link
Copy Markdown
Contributor

Summary

Reworks agent onboarding so the client creates the first group and walks the user through a durable chat-based setup.

Changes

  • adds purpose, topic, and confirmation controls directly in chat
  • creates and opens the agent group from the client
  • adds the OpenClaw coordinator that schedules and posts the first notebook update
  • keeps onboarding choices and results in channel history
  • adds focused fixtures and tests for the complete flow

How did I test?

  • packages/api: 65 focused tests and TypeScript
  • packages/app: 7 focused tests and TypeScript
  • packages/shared: TypeScript
  • packages/openclaw: 1,376 tests and TypeScript
  • local iOS simulator with the onboarding sandbox

Risks and impact

  • Safe to rollback without consulting PR author? Yes
  • Affects onboarding, group creation, chat controls, notebook setup, and the OpenClaw plugin

Deployment

  • latest iOS Preview build: https://github.com/tloncorp/tlon-apps/actions/runs/31675363650
  • the hosted OpenClaw source build resolves workspace dependencies from npm latest; before hosted testing, publish an @tloncorp/api version that includes the new AgentOnboarding A2UI component and restart the assigned test ships on this branch

Rollback plan

Revert the merge. There is no database migration.

dnbrwstr and others added 21 commits August 17, 2026 01:25
The completion hooks fire from the extension entry while provisioning runs
in the lazy runtime module. The correlation crossed that split (sharedMap)
but @tloncorp/api's client is a module-level proxy, so listNotes/sendPost
ran against an unconfigured copy and threw "Client not initialized" — the
reveal, handoff and services beats never posted. Bind those calls to the
provisioning context.

Also name the notebook from its channel title; the ship dedupes slugs, so
slug-derived copy said "Updates 1" while the sidebar said "Updates".
insertChannels leaves currentUserIsMember out of its conflict-update set, so
the first row inserted decides it permanently. createChannel wins that race
for chat with a synchronous optimistic insert; createNotesChannel cannot — it
awaits a notebook create plus listing polls, so the %groups SSE update inserts
the row first, as a non-member.

Result: a notebook on the owner's own ship, open to the whole group, sat under
"Available Channels" behind a Join button — and it is where the agent writes
every entry. Correct it with a direct update, carrying the listing's own
membership answer, on both the create and adopt paths.
A bare Button can only carry a text label. The same card treatment the purpose
picker uses carries an icon, a title and a description, which is what this
needs: a link icon, "Connect External Services", "Bring your tools into
Tlonbot's context".

Adds 'Link' to the ChoiceIcon allowlist (type + runtime validator); the SVG
already exists in the icon set.
- services surface is a Choice with the Link icon, title and description
- reveal is followed by the handoff and services beats (3 posts, not 1)
- pacing asserts the contract (bracketed by presence, composed not flat,
  inside the clamp, longer message waits longer) instead of snapshotting the
  old flat 1250/1000, plus a jitter test pinning the ±20% range
Both controls hid themselves with opacity 0 while keeping their 44px box, so
every answered surface kept a hole where its control used to be — most
visibly under the topic pills, which sat above 44px of blank card forever
after being submitted.

The submit slot is still held open while the picker is answerable, so pills
don't jump when a first selection reveals the button; it collapses only once
the action is consumed and the reservation can never be filled again. A
consumed standalone Button now leaves the tree outright.
The accent is normally expressed only by the icon chip, so an option that
asks for one but has no icon rendered identically to a neutral card — the
accent silently doing nothing. The services card is exactly that case.

Carry it on the card's border instead. Deliberately not the background:
$blueSoft is a fixed light tint meant to back a dark icon glyph, so using it
as a card surface put white dark-theme text on rgb(229,244,255). The
multi-option pickers all have icons and are untouched.
Rotation already works — the recurring prompt tells the run to cover exactly
one topic, read the notebook to find the most recent, and take the next one,
with the topic in the title as the marker. What was missing is that nobody
told the owner. Picking three topics and getting one entry read as two topics
being dropped.

Name it at each point the expectation forms: the purpose card ("One idea each
morning, taking your topics in turn"), the topics picker ("one each morning,
rather than all at once"), the acknowledgement ("Music theory first, then
Architecture"), and the reveal ("Tomorrow: Architecture"). Digest and
research still combine topics, so their copy is unchanged.
The flow emitted no plugin-side telemetry at all — the rich traceOnboarding
helper lived on the superseded branch and did not carry over — so activation
rate, time-to-activation and per-step drop-off were all uncomputable. The
original failure took a screen recording to find rather than a dashboard.

Plugin: one 'TlonBot Onboarding Step' event per funnel step, per group —
intro_posted, purpose_picker_posted, purpose_chosen, topics_picker_posted,
provision_received, cron_created, first_run_enqueued, first_entry_revealed,
services_offered. Reporting is injected into the onboarding context the same
way log and presentation are, so this module never reaches for the telemetry
singleton, and a throw in reporting can't take a setup down.

Deliberately narrower than the event it replaces: that one carried a
coordinator state machine this flow doesn't have. Topic *count* only — the
topics are the owner's own words and don't belong in analytics.

provision_received fires once, after validation, with rejections reported as
the same step with outcome 'failed'; emitting on entry as well would have
counted a rejected provision twice at the widest part of the funnel.

Client: 'Agent Entry First Opened' completes the funnel. The plugin can only
report that it posted the entry; whether the owner opened one is the half only
the client sees. Counted once per group and persisted so it survives restarts.
@dnbrwstr
dnbrwstr force-pushed the db/agent-onboarding-v2 branch from 396f41a to fa5ef62 Compare August 17, 2026 05:31
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants