Metadata
IMPORTANT: The very first step should ALWAYS be validating this metadata section to maintain a CLEAN development workflow.
pull_request_title: "FROM feat/[issue#]-agent-marketplace TO development"
branch: "feat/[issue#]-agent-marketplace"
worktree_path: "$WORKSPACE/.worktrees/feat-[issue#]"
User Stories
- As a visitor, I want to fork/remix a public agent so that I can customize it for my own use and become an Orchestra user.
- As an agent creator, I want fork counts and discovery features so that my agents gain visibility and I can track adoption.
- As a website owner, I want to embed a public agent as a chat widget so that visitors can interact with AI on my site without leaving.
- As an agent owner, I want my agent's prompts to automatically improve over time so that conversations get better without manual tuning.
Summary
Every public agent is currently a dead end — users can chat with it but can't own it, customize it, or embed it on their site. There's no path from "I found a cool agent" to "I'm an Orchestra user." Meanwhile, conversation intelligence dies after each session — langmem is installed but disconnected from the conversation lifecycle.
This feature creates a self-reinforcing flywheel where published agents drive user acquisition (via fork/embed), forked agents accumulate usage data, and that data automatically improves agent prompts via background distillation.
The public agent infrastructure is ~80% built but has no viral loop. Fork/remix creates a GitHub-for-agents growth flywheel. The memory distillation loop rides alongside nearly free (langmem is already installed and unused). Workflows are deferred until marketplace demand validates them.
Visual Reference
Phased Implementation
Phase 1: Agent Fork/Remix (Highest immediate ROI)
Backend:
backend/src/services/assistant.py — Add fork() method: deep-copy public assistant, strip owner/published metadata, generate new UUID, set forked_from in metadata, increment fork_count on source
backend/src/schemas/entities/llm.py — Add fork_count: int = 0 to Assistant model, forked_from: Optional[str] to metadata, expose in PublicAssistant projection
backend/src/routes/v0/assistant.py — Add POST /assistants/public/{assistant_id}/fork (requires auth)
Frontend:
frontend/src/lib/services/agentService.ts — Add fork(assistantId) API call
frontend/src/pages/agents/public.tsx — Add "Remix this Agent" button; auth redirect flow
frontend/src/pages/Login.tsx / Register.tsx — Read remix query param, auto-fork post-auth
Phase 2: Agent Discovery Experience
Backend:
- Enhance
GET /assistants/public with sort_by param: fork_count, updated_at, published_at
- Add
tags: list[str] = [] to Assistant model, expose in PublicAssistant projection
Frontend:
- Add "Discover" tab in agents index showing public agents
- Agent cards: name, description, model, fork_count badge, "Remix" button
- Sort dropdown: "Most Remixed", "Newest", "Recently Updated"
Phase 3: Embeddable Agent Widget
Backend:
GET /assistants/public/{assistant_id}/embed — returns embed config JSON
POST /assistants/public/{assistant_id}/embed-token — signed JWT with agent_id + rate_limit + expiry
Frontend:
- New standalone Vite entry point:
frontend/src/embed/index.tsx
- Minimal chat widget (<50KB bundle): floating button + expandable chat panel
- Loads via
<script src="https://chat.ruska.ai/embed.js" data-agent-id="...">
- "Powered by Orchestra" footer linking to
/a/{agentId}
- Add "Embed" section in agent edit page for published agents with copy-paste snippets
Phase 4: Memory Distillation Loop
Post-Conversation Trajectory Extraction:
- Hook into
_persist_final_state() in backend/src/utils/stream.py (line 278)
- Extract
AnnotatedTrajectory from conversation messages with implicit quality signals
- Store in
(user_id, "trajectories", assistant_id) namespace via AsyncPostgresStore
- Add
extract_trajectory TaskIQ task (non-blocking, async after conversation)
Periodic Prompt Distillation:
- Extend
PromptOptimizer in backend/src/services/prompt/optimize.py with distill() method
- Retrieve recent trajectories, invoke
create_prompt_optimizer, store as new revision via PromptService.revision()
- Add
scheduled_prompt_distillation job type in backend/src/services/schedule.py (daily, per-user, per-assistant)
Key Integration Points
| File |
Function(s) |
Role |
backend/src/services/assistant.py |
fork() |
Deep-copy public assistant to user namespace |
backend/src/schemas/entities/llm.py |
Assistant, PublicAssistant |
Add fork_count, forked_from, tags fields |
backend/src/routes/v0/assistant.py |
Fork + embed endpoints |
New REST API surface |
backend/src/services/prompt/optimize.py |
PromptOptimizer.optimize() |
Wire to auto-generated trajectories |
backend/src/utils/stream.py |
_persist_final_state() (line 278) |
Hook point for trajectory extraction |
backend/src/controllers/llm.py |
_update_store() (line 49) |
Sync invoke hook point |
backend/src/services/schedule.py |
APScheduler infra |
Schedule distillation jobs |
backend/src/workers/tasks.py |
TaskIQ broker |
Run extraction as background task |
backend/src/services/db.py |
AsyncPostgresStore namespaces |
Store trajectories without new DB tables |
UI Integration Points
| Component / Route |
Change Type |
Description |
frontend/src/pages/agents/public.tsx |
Modify |
Add "Remix this Agent" button |
frontend/src/pages/Login.tsx / Register.tsx |
Modify |
Handle remix query param for post-auth auto-fork |
frontend/src/pages/agents/index.tsx |
Modify |
Add "Discover" tab with public agent cards |
frontend/src/pages/agents/edit.tsx |
Modify |
Add "Embed" section for published agents |
frontend/src/lib/services/agentService.ts |
Modify |
Add fork() API method |
frontend/src/embed/index.tsx |
New entry |
Standalone embeddable chat widget (<50KB) |
Storage
- Persistence layer: LangGraph
AsyncPostgresStore (existing)
- Namespaces:
("public", "assistants") for fork source; (user_id, "trajectories", assistant_id) for conversation trajectories
- Model pattern: Pydantic models following existing
Assistant / PublicAssistant patterns
- Embed tokens: Signed JWTs (no new DB table needed)
Architectural Decisions
- Source of truth: Backend — fork_count, tags, and trajectory data are server-persisted
- State management: React Query cache invalidation on fork/remix mutations
- Auth / scoping: Fork requires auth; embed uses signed JWT with rate limiting; trajectories are user-scoped
- Embed widget: Standalone Vite entry point, <50KB bundle, no user auth required (token-based)
- Memory distillation: Uses existing
langmem + PromptOptimizer — no new dependencies
- Phase 5 (Workflow Engine): Explicitly deferred until marketplace validates demand
Existing Code to Reuse
| What exists |
Where |
How to use |
PromptOptimizer.optimize() |
backend/src/services/prompt/optimize.py:72 |
Wire to auto-generated trajectories |
AnnotatedTrajectory format |
langmem.prompts.types (already imported) |
Structure extracted conversation data |
PromptService with revisions |
backend/src/services/prompt/__init__.py |
Store optimized prompts as new revisions |
_persist_final_state() |
backend/src/utils/stream.py:278 |
Hook point for trajectory extraction |
_update_store() |
backend/src/controllers/llm.py:49 |
Sync invoke hook point |
| APScheduler infra |
backend/src/services/schedule.py |
Schedule distillation jobs |
| TaskIQ broker |
backend/src/workers/tasks.py |
Run extraction as background task |
AsyncPostgresStore namespaces |
backend/src/services/db.py |
Store trajectories without new DB tables |
Development Setup
Dependencies
| Service |
Address |
Notes |
| Redis |
localhost:6379 |
Docker container |
| Postgres |
localhost:5432 |
Docker container |
Commands
# See CLAUDE.md for build, test, and dev commands
Wiki
⚠️ IMPORTANT: Wiki lives in a separate repo. Changes to @wiki must be committed directly to the wiki repo, not the main project repo.
Design Principles
- Simplicity is beauty, complexity is pain.
- ALWAYS look at the current codebase first — achieve the goal in the least amount of changes.
- TDD-first: write tests before implementation.
- Reuse existing infrastructure (langmem, AsyncPostgresStore, TaskIQ, APScheduler) — no new dependencies.
- Phase incrementally: each phase delivers standalone value.
Validation Tools
Acceptance Criteria
Phase 1
Phase 2
Phase 3
Phase 4
General
Metadata
User Stories
Summary
Every public agent is currently a dead end — users can chat with it but can't own it, customize it, or embed it on their site. There's no path from "I found a cool agent" to "I'm an Orchestra user." Meanwhile, conversation intelligence dies after each session — langmem is installed but disconnected from the conversation lifecycle.
This feature creates a self-reinforcing flywheel where published agents drive user acquisition (via fork/embed), forked agents accumulate usage data, and that data automatically improves agent prompts via background distillation.
The public agent infrastructure is ~80% built but has no viral loop. Fork/remix creates a GitHub-for-agents growth flywheel. The memory distillation loop rides alongside nearly free (langmem is already installed and unused). Workflows are deferred until marketplace demand validates them.
Visual Reference
Phased Implementation
Phase 1: Agent Fork/Remix (Highest immediate ROI)
Backend:
backend/src/services/assistant.py— Addfork()method: deep-copy public assistant, strip owner/published metadata, generate new UUID, setforked_fromin metadata, incrementfork_counton sourcebackend/src/schemas/entities/llm.py— Addfork_count: int = 0toAssistantmodel,forked_from: Optional[str]to metadata, expose inPublicAssistantprojectionbackend/src/routes/v0/assistant.py— AddPOST /assistants/public/{assistant_id}/fork(requires auth)Frontend:
frontend/src/lib/services/agentService.ts— Addfork(assistantId)API callfrontend/src/pages/agents/public.tsx— Add "Remix this Agent" button; auth redirect flowfrontend/src/pages/Login.tsx/Register.tsx— Readremixquery param, auto-fork post-authPhase 2: Agent Discovery Experience
Backend:
GET /assistants/publicwithsort_byparam:fork_count,updated_at,published_attags: list[str] = []toAssistantmodel, expose inPublicAssistantprojectionFrontend:
Phase 3: Embeddable Agent Widget
Backend:
GET /assistants/public/{assistant_id}/embed— returns embed config JSONPOST /assistants/public/{assistant_id}/embed-token— signed JWT with agent_id + rate_limit + expiryFrontend:
frontend/src/embed/index.tsx<script src="https://chat.ruska.ai/embed.js" data-agent-id="...">/a/{agentId}Phase 4: Memory Distillation Loop
Post-Conversation Trajectory Extraction:
_persist_final_state()inbackend/src/utils/stream.py(line 278)AnnotatedTrajectoryfrom conversation messages with implicit quality signals(user_id, "trajectories", assistant_id)namespace viaAsyncPostgresStoreextract_trajectoryTaskIQ task (non-blocking, async after conversation)Periodic Prompt Distillation:
PromptOptimizerinbackend/src/services/prompt/optimize.pywithdistill()methodcreate_prompt_optimizer, store as new revision viaPromptService.revision()scheduled_prompt_distillationjob type inbackend/src/services/schedule.py(daily, per-user, per-assistant)Key Integration Points
backend/src/services/assistant.pyfork()backend/src/schemas/entities/llm.pyAssistant,PublicAssistantbackend/src/routes/v0/assistant.pybackend/src/services/prompt/optimize.pyPromptOptimizer.optimize()backend/src/utils/stream.py_persist_final_state()(line 278)backend/src/controllers/llm.py_update_store()(line 49)backend/src/services/schedule.pybackend/src/workers/tasks.pybackend/src/services/db.pyAsyncPostgresStorenamespacesUI Integration Points
frontend/src/pages/agents/public.tsxfrontend/src/pages/Login.tsx/Register.tsxremixquery param for post-auth auto-forkfrontend/src/pages/agents/index.tsxfrontend/src/pages/agents/edit.tsxfrontend/src/lib/services/agentService.tsfork()API methodfrontend/src/embed/index.tsxStorage
AsyncPostgresStore(existing)("public", "assistants")for fork source;(user_id, "trajectories", assistant_id)for conversation trajectoriesAssistant/PublicAssistantpatternsArchitectural Decisions
langmem+PromptOptimizer— no new dependenciesExisting Code to Reuse
PromptOptimizer.optimize()backend/src/services/prompt/optimize.py:72AnnotatedTrajectoryformatlangmem.prompts.types(already imported)PromptServicewith revisionsbackend/src/services/prompt/__init__.py_persist_final_state()backend/src/utils/stream.py:278_update_store()backend/src/controllers/llm.py:49backend/src/services/schedule.pybackend/src/workers/tasks.pyAsyncPostgresStorenamespacesbackend/src/services/db.pyDevelopment Setup
Dependencies
localhost:6379localhost:5432Commands
# See CLAUDE.md for build, test, and dev commandsWiki
Design Principles
Validation Tools
agent-browserskill with screenshots to validate E2E. This validates test assumptions for completion promise.Acceptance Criteria
Phase 1
POST /assistants/public/{id}/forkcreates a user-owned copy withforked_frommetadatafork_countincrements on the source public assistantmake test— all existing + new tests passPhase 2
GET /assistants/public?sort_by=fork_countreturns correctly ordered resultstagsfield added and exposed in public assistant projectionPhase 3
embed.jsloads and renders chat widget on external HTML pagePhase 4
(user_id, "trajectories", assistant_id)namespaceGeneral
agent-browserCLI