Back
Projects & practice
Building WishLive: A Multi-Agent Runtime
A 48-hour hackathon build of an event-driven multi-agent runtime with registry, workflow, execution, negotiation, settlement, and observability layers.
The Idea#
WishLive was a 48-hour hackathon build to test a specific system shape: agents have registry entries, workflows create work, a runtime executes tool calls, negotiation changes state, settlement requires explicit confirmation, and all of those actions emit typed events.
The project is useful as an architecture experiment, not as evidence of production scale. The counts below describe the implementation and seeded demo data.
Architecture: Six Layers, One Event Bus#
The implementation separates six responsibilities and uses the event bus for cross-layer activity:
flowchart TB
B[Event Bus] --> C[Concierge]
B --> S[Settlement]
B --> N[Negotiation]
B --> R[Runtime]
B --> W[Workflow]
B --> G[Registry]
U[User Wish] --> W
G --> W
W --> N
N --> R
R --> S
C -. observes .-> BmermaidEvery layer publishes typed events. The event history is the shared record used for tracing cross-layer activity, while each service still owns its local state.
Layer 1: Registry and discovery#
The registry is the DNS + service discovery for agents. Each agent registers with a typed card:
interface AgentCard {
agent_id: string;
type: 'musician' | 'venue' | 'manager' | 'audience' | 'business';
name: string;
skills: string[];
reputation: number;
// ...
}typescriptAgents send heartbeats every 60 seconds. If they miss, the registry marks them OFFLINE. The discover() method implements an A2A discovery protocol — one agent asks a manager agent to find matching candidates by skill, genre, city, or capacity.
The system seeds 57 agents across 6 types on startup. The onlineCount() method tracks availability by type in real time.
Key runtime behavior: ensureSeeded() auto-seeds on first access, so every service that depends on the registry gets a populated agent network without explicit initialization order.
Layer 2: Wish → Workflow Pipeline#
Users submit wishes: “I want to see Artist X in City Y on Date Z.” The WishWorkflowService:
- Aggregates wishes by cohort key (genre + city)
- Runs an agent to record the wish processing
- When a cohort reaches threshold (10 wishes), triggers demand creation
- The matching engine finds candidate musicians and venues from the registry
- Publishes demand events that drive the rest of the system
The threshold is a deliberate design choice — it prevents premature negotiation on insufficient demand.
Layer 3: Runtime loop#
This is the core. AgentRuntimeService.run() creates a session, then executes in a loop:
session started → agent thinks → tool execution → message → session completedplaintextflowchart LR
A[Session started] --> T[Agent thinks]
T --> Q{Tool needed?}
Q -->|yes| X[Execute tool]
X --> T
Q -->|no| M[Emit message]
M --> D[Session completed]mermaidThe runtime supports dual-mode execution:
- Real mode: Uses the Vercel AI SDK with
generateText(), passing runtime tools for discovery, pricing, proposals. The agent decides which tools to call based on the user message. - Simulated mode: Deterministic fallback when no AI provider is configured. Executes the same tool plan but returns structured mock responses.
The graceful degradation is intentional — simulated mode means I can demo the entire system without an API key. The switch happens transparently at the session level:
if (mode === 'real') {
try {
result = await generateWithAISDK(agent, input, session);
} catch (error) {
mode = 'simulated'; // graceful fallback
result = await executePlannedTools(agent, input, session, mode);
}
} else {
result = await executePlannedTools(agent, input, session, mode);
}typescriptEvery step publishes to the agent.runtime event stream — thoughts, tool calls, messages, session lifecycle. Langfuse telemetry is wired in for production observability.
Layer 4: Negotiation state#
The negotiation service models multi-agent coordination as explicit, stateful protocols:
create → propose → counter → accept → deal
↓
rejectplaintextEach negotiation has a workflowId, conversationId, and tracks all proposals. The runAutonomousNegotiation() method runs the full pipeline automatically:
stateDiagram-v2
[*] --> Proposed
Proposed --> Countered: counter
Countered --> Proposed: counter
Proposed --> Accepted: accept
Countered --> Accepted: accept
Proposed --> Rejected: reject
Countered --> Rejected: reject
Accepted --> Deal
Deal --> [*]
Rejected --> [*]mermaid- Matches top musician and venue candidates from demand
- Musician sends initial proposal with terms (venue fee, split percentage)
- Venue counters with adjusted terms
- Musician accepts
- A Deal is created, ready for settlement
The protocol is general enough for human-in-the-loop override — each step has API endpoints for manual accept/reject/counter.
Layer 5: Settlement boundary#
Settlement is where execution becomes real. The system supports dual settlement modes:
On-chain (production):
- Solidity smart contracts: Escrow (lock/release funds), TicketNFT (mint event tickets), AgentProfile (agent identity)
- Hardhat deployment to local or testnet
- Funds held in escrow until deal confirmed
Local (development):
- Deterministic hash-based transaction IDs
localTxHash()generates consistent 0x-prefixed hashes- Full simulation of the settlement flow without a blockchain
flowchart LR
D[Deal accepted] --> E[Create escrow]
E --> P[Payment or local settlement]
P --> X{Execution verified?}
X -->|yes| R[Release settlement]
X -->|no| H[Hold or dispute]mermaidThe critical design choice: human confirmation gates. Every settlement action requires explicit confirmation:
async createEscrow(input, options: { confirmed: boolean }) {
if (!options.confirmed) {
throw new SettlementError(409, "Human confirmation required");
}
// ...
}typescriptThis prevents autonomous agents from moving real money without oversight.
Layer 6: Concierge — The Observability Layer#
The concierge is an LLM-powered interface over the entire runtime. It reads event streams from all layers and answers:
- “What is happening right now?”
- “Why is the negotiation stuck?”
- “What happens next in this workflow?”
It supports both streaming and non-streaming responses, with Langfuse tracking on every interaction. When no AI provider is configured, it falls back to a rule-based simulator that summarizes system state from event data.
The Event Bus: Where Everything Connects#
All subsystems communicate through typed event streams. The bus has two implementations:
- MemoryEventBus: In-memory event history for development and tests
- RedisEventBus: Redis Streams implementation for persisted event history
Event types include:
agent.lifecycle— registration, heartbeat, offline transitionsagent.runtime— session lifecycle, thoughts, tool callsagent.task— A2A message passing between agentsnegotiation.events— proposals, counters, accepts, rejectionssettlement.events— escrow creation, fund release, ticket mintingwish.events/demand.events/matching.events— workflow pipeline
interface EventEnvelope {
id: string;
type: string;
source: string;
timestamp: number;
data: Record<string, unknown>;
metadata: {
traceId: string;
spanId: string;
};
}typescriptEvery event carries trace context, enabling end-to-end flow reconstruction.
Implementation notes#
The dual-mode runtime was useful because the same workflow could run with an AI provider or with deterministic simulated responses. That made local development possible without an API key. It does not prove that every agent system needs a simulator, but it gave this project a repeatable fallback path.
Typed events made debugging easier because runtime, negotiation, and settlement steps could be reconstructed from one event history. This only works if every state-changing path emits enough information and keeps trace identifiers intact.
Settlement required an explicit confirmation flag before escrow creation or release. That gate is intentionally simple. A real financial workflow would still need stronger authorization, idempotency, policy checks, and recovery handling.
The 57 seeded agents made demos start with usable registry data. They are fixtures, not evidence of marketplace activity.
The Stack#
- Runtime: TypeScript, Vercel AI SDK, Zod validation
- Events: Redis Streams (prod), in-memory (dev)
- Contracts: Solidity, Hardhat, viem
- Frontend: Next.js 14, Tailwind CSS
- Observability: Langfuse
- Infrastructure: Docker Compose, pnpm workspace
Scope#
WishLive is a hackathon prototype for testing event-driven coordination across registry, workflow, runtime, negotiation and settlement layers. The parts worth carrying into another project are the explicit state transitions, typed event envelopes, fallback execution mode and confirmation gate. Production use would still need load testing, stronger authorization, durable recovery tests and deployment-specific monitoring.
The code is at github.com/lora-sys/Hackthon ↗.
End of article · Back to writing
Comments
Comments are public and managed through GitHub Discussions.
Preparing the comment section…
Sign in with GitHub once the comment box is ready, or open the repository discussions directly. Open GitHub Discussions ↗Comments are published to the public discussions of the lora-sys/loraSys repository.