The main agent had no way to give a specialist a piece of work and keep
track of it. message_bot is a fire-and-forget peer message whose work stays
hidden from the user, and handoff_to_bot ends the main bot's turn and only
works between members of one group. Delegation now runs the specialist's
work where the user follows the main agent and brings the result back so
the main agent can close the task.
- delegate_task({bot, title, brief, deadline?}) is offered to the main bot
only and refused unless the caller leads the main conversation. It
resolves the specialist by id or name across the user's spaces, creates a
DelegatedTask and posts a task card in the main conversation.
- A specialist from the main conversation's space joins it and works there
in a run with the new trigger "delegation", which the peer filters do not
hide, so its progress, asks and approvals appear in the main conversation
with the specialist as sender. A specialist from another space works in
its own chat; its written result is posted into the main conversation as
its message.
- The executor mirrors the run on its task (running, waiting, done, failed,
cancelled) after the run starts and when each attempt ends, and updates
the card in place through thread.message.updated. Done and failed tasks
wake the main bot in the main conversation with the result. The job
reconciler settles tasks whose run ended without the executor (stop,
clear, archive, deletion, crash).
- tasks.list({threadId?, status?}) returns the caller's tasks for the
current space.
- The task card shows who, what and status with Lingui labels; the brief
opens on demand. The message_bot card no longer shows the recipient's
unrelated overall status or hard-coded German copy.
- The scripted runtime understands "delegate to the bot named X: ..." and
the task wake prompt; activity rows name delegation runs by task title.
Tests: unit tests for the prompts, status mapping, card, reducer, tool
selection and scripted runtime; a Postgres suite for same-space, group,
other-space, ask and answer, failure, stop and replay; tasks.list in the
authorization suite; and an end-to-end spec for the whole flow.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MRtGG9ARfqqhma3t85Ro2u
An agent connection wakes a bot with another owner's message, and the reply
goes back to that owner. Such runs now get only the basics of the user block
(name, time zone, locale, language, style), like runs in shared messaging
channels, so confirmed facts such as health never reach another person's
agent.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MRtGG9ARfqqhma3t85Ro2u
Profile consolidation now passes the profile's time zone to the shared time
instruction, which takes the zone since every run states the time in the
user's zone. The semantic memory migration is renamed after what it creates.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MRtGG9ARfqqhma3t85Ro2u
Why: the user profile only knew what onboarding wrote, so people had to repeat themselves,
and semantic recall needed a hosted or separately run memory service. The profile now grows
from everyday conversations, and recall can run on the user's own server with a local
embeddings endpoint (for example llama.cpp). Both stay optional and are tested offline.
What:
- adapter-kit: EmbeddingProvider contract (describe, embed) and the profile.consolidate job
(payload schema and a replace-keyed job factory, one pending job per user).
- adapters: OpenAI-compatible embeddings (POST {baseUrl}/embeddings through the guarded
OpenAI-compatible fetch, batching, bounded responses, clear errors), a deterministic
emulator, an in-process /v1/embeddings server emulator, and a shared conformance suite.
- adapters: "builtin" semantic memory stored in Postgres (semantic_memory_items with Float[]
embeddings, cosine ranking in process, bot/shared/history scopes matching the existing
providers), registered in the memory provider factory and configured through
memory.connectProvider with an embeddings URL, model and optional API key. Private
endpoints require the deployment owner, like the other local memory providers.
- adapters: profile consolidation reads the user's settled app conversations since
UserProfile.consolidatedAt (never messaging channels), asks the main bot's model for strict
JSON facts and an updated summary, drops restatements (embedding cosine when built-in
memory is configured, normalized text otherwise), stores sensitive categories as pending,
and advances the cursor with a compare-and-set. A reconciler hook enqueues it at most daily
per active user; it skips under the scripted runtime and without a usable model.
- db: additive migration 20261002210400_ws4_semantic_memory.
- web: a "Built-in" option in Memory settings; AI data sharing names the embeddings server.
Tests: new embedding conformance, OpenAI-compatible wire, built-in provider and consolidation
unit tests; new Postgres suites for built-in memory (including a real loopback embeddings
server through the memory provider resolver) and for consolidation and its scheduling query,
both added to the integration harness. The settings E2E spec now opens the new form.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MRtGG9ARfqqhma3t85Ro2u
Runs dropped the bot's name, title and description whenever custom
instructions existed, always stated the time in UTC, and knew the user only
through free-form durable memory. Groups with a lead, like the main
conversation, still got the peer-group lines that forbid handing work back.
And every group follow-up run failed during setup: the inbound handoff lookup
selected a `bot` relation that Message does not have, which Prisma rejects.
- Instructions now open with the bot's identity, then its own instructions,
a main-agent role paragraph for the main bot, the current time in the
user's time zone with its offset (UTC when unknown), and a `<user>` block
built from loadUserContext: profile fields and the active facts visible in
the run's space, escaped, clipped and framed as data rather than
instructions. Runs that reply in a shared messaging channel get only the
name, zone, locale, language and style, matching how those runs already
leave out durable memory.
- run_subagent helpers receive the identity, time and user block through a
new optional AgentRunRequest.subagentContext instead of nothing.
- Groups with an active lead get lead and specialist lines and matching
inbound handoff instructions. Members may hand a stage back to the lead and
the lead may delegate the next stage; the bounce rules still apply between
members. Ordinary groups keep today's lines.
- The teammate directory is rendered only when the run has message_bot, which
group runs never have, and tool guidance names a tool only when the run has
it.
- The auto-review judge, history compaction and the team chat judge read the
time in the user's zone, and schedule_create defaults to it.
- The inbound handoff sender is read from the handoff block and looked up
among the user's bots; a Postgres test covers the follow-up run, the prompt
it receives, and handing the result back to the lead.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MRtGG9ARfqqhma3t85Ro2u
* fix(adapters): route evOo self-modification requests to code tools
Oos lacked product self-knowledge in their system instructions and were
steered toward browser/computer tools for UI text changes. Add explicit
guidance that evOo changes belong in source code (file/shell tools,
project/ when linked, cloud agent when configured), never via claude.ai
login or browser CSS injection.
Co-authored-by: cynarAI <cynarAI@users.noreply.github.com>
* fix(adapters): avoid space query that broke executor test mocks
Co-authored-by: cynarAI <cynarAI@users.noreply.github.com>
---------
Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: cynarAI <cynarAI@users.noreply.github.com>
* fix(ci): restore browser preview module and onboarding e2e
The web bundle imported a browser-preview store that was never added, so production builds and every Playwright signup failed before the shell rendered. Mobile check failed because Expo packages were a patch behind the SDK.
Restore the store, type the optional desktop bridge, and walk whichever onboarding step scripted CI actually opens.
Co-authored-by: cynarAI <cynarAI@users.noreply.github.com>
* fix(ci): stop the blank web client and the send 500
The core barrel pulled git checkout into the browser, so node:child_process crashed the page before sign-up. Serve that module from a node export instead.
Add the missing run tool-call budget columns that threads/send writes. Guard an indexed shell flag for typecheck, and expect the files key the desktop preload already exposes.
Co-authored-by: cynarAI <cynarAI@users.noreply.github.com>
* fix(ci): isolate pnpm setup and the takeover membership
The Team Computer takeover journey inserted a space member the
default-space trigger had already created, so shared Postgres rejected
the unique pair. Self-hosted jobs now keep a per-job pnpm home and
store, and artifact uploads no longer fail a job that already passed.
Co-authored-by: cynarAI <cynarAI@users.noreply.github.com>
* fix(ci): clear typecheck, lint, and unit failures
Inbound channel rosters can mark a participant list as authoritative.
Answered channel asks stay off the group, and clearing a thread waits
until delegated returns land. Lint, unit, and web e2e contracts follow
the current shell, composer, and settings.
Co-authored-by: cynarAI <cynarAI@users.noreply.github.com>
* fix(web): align e2e with the shipped shell and onboarding
Web E2E still expected older focus buttons, nested project rows, and
settings that never opened MCP. Persist the signed-in space, list
non-default spaces under Projekte, and keep the quote control on screen.
Co-authored-by: cynarAI <cynarAI@users.noreply.github.com>
* fix(web): open group reply from a revealed hover rail
The first user row covered its Reply control until the rail accepted
pointer events. The group transcript now reveals that rail, then replies.
Co-authored-by: cynarAI <cynarAI@users.noreply.github.com>
* fix(web): stop the shell render loop and settle computer and mention UI
A working bot rewrote preview state on every render, the computer menu never stayed still, and the mention picker did not expose its expanded state or option name.
Co-authored-by: cynarAI <cynarAI@users.noreply.github.com>
* fix(ci): stop idle probes hanging and accept a follow-up send
Background probes walked every process fd and timed out on the shared runner. Linux now tracks the marker with flock and /proc/locks. A new composer draft can send while an earlier send is still refreshing, and group members stay mentionable.
Co-authored-by: cynarAI <cynarAI@users.noreply.github.com>
* fix(adapters): detect background work when flock ids are padded
The idle probe compared /proc/locks with an unpadded device key, so majors
or minors under 16 never matched. It deleted the live marker and reported
idle while the command still held the lock. Probe by taking the lock, and
print the cancel key the way the kernel does.
Co-authored-by: cynarAI <cynarAI@users.noreply.github.com>
* fix(adapters): stabilize background-work probe tests on self-hosted CI
Clear stale marker files before each probe scenario and allow longer
polling so launch can acquire flock on busy persistent runners.
Co-authored-by: cynarAI <cynarAI@users.noreply.github.com>
* fix(web): do not block onboarding on spaces list
Render the first onboarding step from me and integration setup alone.
Resolve the optional skip-to-app redirect after that so Playwright
signups are not stuck on the loading shell when spaces.list is slow.
Co-authored-by: cynarAI <cynarAI@users.noreply.github.com>
* fix(ci): restore green gates after second main rebase
Re-apply biome formatting on Shell/SortableGroup and strict TypeScript
assertions in computer-idle tests dropped when skipping the duplicate
conflict-resolution commit.
Co-authored-by: cynarAI <cynarAI@users.noreply.github.com>
* fix(web): wait for onboarding bootstrap before E2E proceeds
Expose data-onboarding-step on the onboarding shell and have Playwright
wait until loading finishes. Ignore ad-hoc screenshot scripts on
self-hosted runners so they do not fail lint.
Co-authored-by: cynarAI <cynarAI@users.noreply.github.com>
* fix(web): fall back when onboarding bootstrap stalls
Race me/integration setup against a timeout and promote loading to the
emoji step after 20s so Playwright is not blocked on a hung RPC.
Co-authored-by: cynarAI <cynarAI@users.noreply.github.com>
* fix(web): render onboarding immediately for E2E signups
Start on the emoji step instead of a loading shell that waits on RPC.
Bootstrap can still redirect once me and integration setup resolve.
Co-authored-by: cynarAI <cynarAI@users.noreply.github.com>
* fix(ci): stabilize web e2e on self-hosted runners
Per-job pnpm stores broke node_modules symlinks when setup-node restored
cached installs, so Vite could not resolve react during dependency
optimization. Keep a runner-scoped store, clear stale Vite caches, and pin
Playwright to one worker in CI to reduce onboarding RPC contention.
Add Vite resolve aliases for react/react-dom, wait for an interactive
onboarding step in E2E helpers, and include the React hook smoke spec from
main.
Co-authored-by: cynarAI <cynarAI@users.noreply.github.com>
* fix(web): resolve React aliases through pnpm package layout
Root node_modules/react does not exist under pnpm, so Vite aliases pointed
at missing paths and broke production builds and the Playwright webServer.
Resolve react and react-dom from the web package and map jsx-runtime to the
actual entry files.
Co-authored-by: cynarAI <cynarAI@users.noreply.github.com>
* fix(core): type the lazy child_process spawn cache
Main's lazy require left spawn typed as null after assignment, which broke
Typecheck on the rebased branch.
Co-authored-by: cynarAI <cynarAI@users.noreply.github.com>
* fix(db): mock git-checkout export path in unit tests
Vitest resolves vi.mock against package exports; the .js suffix is not
declared for @rakazo/core/git-checkout.
Co-authored-by: cynarAI <cynarAI@users.noreply.github.com>
* fix(web): stabilize E2E after main sidebar and onboarding changes
Use first() for duplicate transcript lines, wait for interactive onboarding
before model-required assertions, and allow project sidebar to settle after
create. Extend the Playwright job timeout so the single-worker suite can finish.
Co-authored-by: cynarAI <cynarAI@users.noreply.github.com>
* fix(web): restore Projekte sidebar and integrations MCP entry
Use the default space for Unterhaltungen/Projekte separation instead of the
active space header, persist the bootstrapped space in localStorage, wire
SettingsOverlay to MCP management, and compare hover tones on inactive rows.
Co-authored-by: cynarAI <cynarAI@users.noreply.github.com>
* fix(web): satisfy biome format on homeSpaceId line
Co-authored-by: cynarAI <cynarAI@users.noreply.github.com>
* fix(web): align sidebar E2E with nested Projekte shell layout
Scope Unterhaltungen assertions to conversation rows only, target project-space
Chief actions under .sidebar-projekte, and stabilize hover-tone polling.
Co-authored-by: cynarAI <cynarAI@users.noreply.github.com>
* fix: satisfy lint after main merge (format and unused var)
Co-authored-by: cynarAI <cynarAI@users.noreply.github.com>
* fix(web): show project-space 1:1 bots in Projekte sidebar
Include lone bots in projectSidebarGroups, render bot rows in Projekte,
and stabilize E2E for nested sidebar layout and duplicate group entries.
Co-authored-by: cynarAI <cynarAI@users.noreply.github.com>
* fix(web): disambiguate duplicate group sidebar buttons in E2E
Co-authored-by: cynarAI <cynarAI@users.noreply.github.com>
* fix(web): align spaces and group-chats E2E with Projekte sidebar IA
Co-authored-by: cynarAI <cynarAI@users.noreply.github.com>
* fix(web): dedupe Projekte sidebar rows and stabilize E2E specs
- Deduplicate project space and group-chat entries in projectSidebarGroups
- Scope group-chats/spaces E2E clicks to first Projekte match after IA split
- Wait for shell ready before mention-picker keyboard test; restore main locators
- Fix PrivacySettingsOverlay lint and align biome schema with CLI 2.5.13
Co-authored-by: cynarAI <cynarAI@users.noreply.github.com>
* fix(web): restrict project drag handle and stabilize space E2E
- Move SortableGroup drag listeners to ProjectDragGrip only so sidebar clicks work
- Scope space delete actions to project-space rows; wait for menu in E2E
- Switch group settings E2E via direct navigation after unsaved draft check
- Merge main (DataTrustPanel) and fix Shell import order for CI lint
Co-authored-by: cynarAI <cynarAI@users.noreply.github.com>
* fix(web): stabilize message hover Escape assertion in E2E
Assert the More menu closes and the hover rail stays visible instead of
requiring focus restoration, which is flaky across Chromium builds.
Co-authored-by: cynarAI <cynarAI@users.noreply.github.com>
* chore: format apps/api/src/app.ts after main merge
Co-authored-by: cynarAI <cynarAI@users.noreply.github.com>
* fix(web): stabilize team-computer E2E send after bot switch
Wait for shell ready and use the Send button instead of Enter after
switching bots and closing the computer overlay. The prior run timed
out on threads/send with only an apps/api format commit in between.
Co-authored-by: cynarAI <cynarAI@users.noreply.github.com>
---------
Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: cynarAI <cynarAI@users.noreply.github.com>
python -m, stdin programs, and deno/bun eval or kill-named scripts could still tear down a managed graphical desktop after the shell guard tokenized the command.
Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: cynarAI <cynarAI@users.noreply.github.com>
The desktop-protection shell guard inspected shell syntax and nested
bash -c, but not script files passed to python, node, perl, ruby, or
php. Read small workspace scripts and reject kill-family contents, and
fall back to a kill/pkill/killall/xkill filename when the path cannot
be read.
Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: cynarAI <cynarAI@users.noreply.github.com>
The desktop-protection shell guard only walked nested shell -c programs,
so interpreter inline code could still invoke kill/pkill. Inspect python/
node/perl/ruby/php -c/-e/-r payloads for the same lifecycle kills.
Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: cynarAI <cynarAI@users.noreply.github.com>
* Voice UX: simple speak toggle, onboarding pick, chat voice change
Replace Kokoro-branded settings with a Kann sprechen / Spricht nicht toggle,
onboarding voice personalization, and local chat commands for voice changes.
German voice presets (Eva, Bernd) surface as friendly names; Kokoro stays
under the hood via KOKORO_BASE_URL.
Co-authored-by: cynarAI <cynarAI@users.noreply.github.com>
* Fix import order in voice onboarding and settings
Co-authored-by: cynarAI <cynarAI@users.noreply.github.com>
---------
Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: cynarAI <cynarAI@users.noreply.github.com>
* fix(adapters): stop desktop shell from inheriting host secrets
The this-mac shell spawned with the server process environment and ignored
the command environment, so Autopilot could read deployment credentials that
are not on the run redaction list. Drop credential-shaped host variables,
then apply the request environment.
Co-authored-by: cynarAI <cynarAI@users.noreply.github.com>
* style(adapters): biome format desktop-sandbox after env scrub
Co-authored-by: cynarAI <cynarAI@users.noreply.github.com>
---------
Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: cynarAI <cynarAI@users.noreply.github.com>
Extend the existing NewSpaceDialog project flow so an optional host
folder becomes the space workspace root. When .git metadata is present,
remote URL, default branch, and head commit are stored on the space.
Team Computer file APIs expose the linked folder under project/ without
a second VCS stack. Desktop adds a native folder picker; web falls back
to an absolute path field.
Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: cynarAI <cynarAI@users.noreply.github.com>
When a run worker loses its lease while the runtime yields no events
(long tool or model work), lease loss was only detected on the next
heartbeat renewal (up to 60s). A reclaimed worker could execute in
parallel during that window.
Add a dedicated lease poll that aborts stale workers within a few
seconds and call runtime.abort so pi-runtime stops promptly.
Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: cynarAI <cynarAI@users.noreply.github.com>
The executor loaded every agent secret in a Space and injected decrypted
values into shell environment variables for any member's bot run. Restore
agentSecretScopeForRun scoping accidentally dropped in 0a8a831.
Add an executor test that fails when findMany is scoped only by spaceId.
Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: cynarAI <cynarAI@users.noreply.github.com>
Team mode stores one disk per Space. computer/readFile and computer/files
only verified the caller owns the entry bot; an explicit bots/<otherBotId>/…
path let one member read another member's workspace. Wire existing path guards
into the API router and executor file/shell tools, and add a shared-Space
authorization regression test.
Co-authored-by: cynarAI <cynarAI@users.noreply.github.com>
Group intros were stamped as posted before the outbox drain ran. When
delivery enqueue failed or the intro row exhausted retries, the channel
stayed marked and no retry path existed — unlinked participants never saw
the onboarding message.
Stamp introPostedAt in the drain path after a successful provider send,
matching the messagingMirroredAt pattern from #42.
Co-authored-by: cynarAI <cynarAI@users.noreply.github.com>
spawnKey is unique per Space, not per user. createBot already rejects
cross-member replay, but spawnBot caught that error and returned the
other member's bot anyway. Match createBot by requiring existing.userId
=== spawnedBy.userId before treating a spawnKey conflict as idempotent.
Co-authored-by: cynarAI <cynarAI@users.noreply.github.com>
Team mode shares one disk per Space. export/bot walked the entire
team home, so a member exporting their own bot received every other
member's workspace files too.
Scope exportHome with the exporting bot's bots/<id>/ prefix and add
regression coverage for the shared-Space case.
Co-authored-by: cynarAI <cynarAI@users.noreply.github.com>
Computer-busy requeues previously retried forever via retryForever,
which could hang runs and burn worker cycles on a stuck computer.
Add a small retry policy helper that tracks consecutive busy waits in
an internal run.error marker, preserves that marker across lease claims,
caps retries at 24 with existing exponential backoff, and fails the run
with a clear message when the bound is reached. Database connection
pressure retries remain unbounded.
Co-authored-by: cynarAI <cynarAI@users.noreply.github.com>
Remove hostRoots from this-mac desktop sandbox so shell execution stays
inside the bot computer workspace, matching container path rules.
Add resolveDesktopExecuteCwd to map virtual /home/rakazo paths onto the
local home and reject host-absolute cwd requests via containment checks.
Co-authored-by: cynarAI <cynarAI@users.noreply.github.com>
mirrorMessagingOutbound always stamped messagingMirroredAt even when
mirrorRun returned early, so the job reconciler never retried completed
messaging runs whose channel or DM mirror was skipped or not yet durable.
Gate the recovery marker on mirror disposition (complete vs retry), only
set it for completed messaging runs, and leave it null when mirrorable
bot text still exists but was not written to the outbox.
Co-authored-by: cynarAI <cynarAI@users.noreply.github.com>
Apply the connect-invite at-most-once pattern to mirrored DMs, group
posts, channel invites, and intros. Provider send errors now keep the
outbox row claimed as sent without a handle instead of reverting to
pending and risking duplicate delivery.
Pre-send setup failures (thread resolution) still retry with backoff.
Pass outbox idempotency keys through to sendToThread for future provider
dedupe.
Co-authored-by: cynarAI <cynarAI@users.noreply.github.com>
Agent secrets are owner-managed via the API but the executor loaded every
secret in the Space and injected decrypted values into shell environment
variables for any member's bot run.
Co-authored-by: cynarAI <cynarAI@users.noreply.github.com>
Team mode shares one disk per Space. RPC and executor paths under
bots/<otherBotId>/ were reachable with any member-owned entry bot,
leaking another member's workspace files while same-user cross-bot
access stays intentional.
Add path guards, filter bot listings, regression tests.
Co-authored-by: cynarAI <cynarAI@users.noreply.github.com>
When a delegated bot finishes but its wake message was deleted (e.g.
thread clear), returnBotMessageOutcome used to mark botOutcomeReturnedAt
and drop the coordinator reply silently. Retry instead, and block
clearThread while completed bot_message runs still owe a return.
Co-authored-by: cynarAI <cynarAI@users.noreply.github.com>
Drain claims outbox rows as sent before the provider acks. Late ERROR
webhooks were regressing delivered rows to failed; late DELIVERED could
upgrade exhausted failures. Gate webhook updates by allowed source status.
Co-authored-by: cynarAI <cynarAI@users.noreply.github.com>
Cloud/public memory providers are owner-managed infrastructure. The RPC
read path is owner-gated separately; this aligns executor, history
compaction, and thread-clear purge with the same boundary so non-owner
members cannot load credentials or reach external endpoints at runtime.
Deployment-owner local/private providers remain available to all Space
members when the deployment owner configured them.
Co-authored-by: cynarAI <cynarAI@users.noreply.github.com>
Remove duplicate definitions from executor.ts and pi-runtime.ts that
already live in executor-*.ts and pi-model-config.ts. Import extracted
symbols with .js extensions and re-export the public executor/pi-runtime
surface for existing callers.
Export a few pi-model-config helpers required by pi-runtime (including
boundAgentToolResult and toolCallBudgetFor) and align beginToolCall queue
typing with AgentRuntimeEvent.
Co-authored-by: cynarAI <cynarAI@users.noreply.github.com>
- Remove pi-tool-utils.ts (too many dependencies on pi-runtime internals)
- Fix executor.test.ts to import from extracted modules
- Remove pi-tool-utils from index.ts exports
- Build verified green