moaca-ai
v0.0.1
Published
Moaca is a wrapping coding agent, making different agent harnesses accessible through a single unified API, session format, and SDK interface. It is _not_ an orchestrator in the sense of managing multi-agent workflows in sandboxes and sits a layer below o
Readme
M.O.A.C.A. – Mother Of All Coding Agents
Moaca is a wrapping coding agent, making different agent harnesses accessible through a single unified API, session format, and SDK interface. It is not an orchestrator in the sense of managing multi-agent workflows in sandboxes and sits a layer below orchestrators like Fabrik.
[!WARNING] This project is currently in private alpha and not available for public use yet (which is why its currently only re-exporting a private package). The full implementation and source will be available here soon. If you're interested in early access or contributing, please reach out!
Table of Contents
Overview
Modern AI coding agents all store session history differently. GRIPS unifies them under one schema and one API:
- One schema for nodes (messages, tool calls, compactions, checkpoints, config changes, and custom events)
- One tree model for branching, forking, and navigating session history
- One streaming format for real-time output (text, reasoning, tool calls, tool execution stdout/stderr)
- One SDK to list, read, create, fork, send messages to, and listen to sessions across every supported harness
- One CLI to inspect and manage sessions from the terminal
- One React library to render session trees, tables, paths, and streaming UIs
Packages
Package Dependency Graph
cli ──> sdk ──> harness ──> format
react ───> format| Package | Purpose | Tech |
|---------|---------|------|
| @grips/format | Pure types and schemas — zero I/O | Effect-TS Schema |
| @grips/harness | Per-agent adapters (Pi, OpenCode, Claude Code, Codex, Cursor) | Effect-TS |
| @grips/sdk | Unified consumer API via Effect Layer and Service | Effect-TS |
| @grips/cli | Terminal interface and TUI | Effect-TS + citty |
| @grips/react | React components and hooks for rendering sessions | React + Tailwind |
| website | Marketing and documentation site | Vite + React |
@grips/format
Pure types and schemas. No I/O, no Effect runtime.
Defines the canonical data model used by every other package.
Core types:
Node— Discriminated union of all node types:message— User / assistant / system messages with usage metadatatool_execution— Tool call with arguments, output, and exit codecompaction— Session compaction / summarization eventbranch_summary— Summary of a branched conversation pathcheckpoint— Code snapshot checkpointconfig_change— Agent configuration changecustom— Extensible custom event type
Session— Session metadata (id, title, cwd, source harness, timestamps)SessionListItem— Lightweight session list entryTree— Built from an array of nodes; providesgetPath,getChildren,findLeaves,validateStreamEvent— Real-time streaming events (text.start,text,tool_call.delta,tool_execution.stdout,done,error, etc.)
Validation:
import { buildTree } from "@grips/format";
const tree = buildTree(nodes);
const result = tree.validate(); // { valid, brokenReferences, orphanedNodes, cycles }@grips/harness
Per-harness adapters that bridge native agent storage and runtime APIs into the GRIPS format.
Each supported agent has two implementations:
- SessionSDK — Read/write session storage (
list,get,create,append,delete,fork) - RuntimeSDK — Drive a live agent (
send,listen,stop)
Supported harnesses:
| Harness | SessionSDK | RuntimeSDK |
|---------|------------|------------|
| Pi (pi) | PiSessionSDK | PiRuntimeSDK |
| OpenCode (opencode) | OpenCodeSessionSDK | OpenCodeRuntimeSDK |
| Claude Code (claude-code) | ClaudeCodeSessionSDK | ClaudeCodeRuntimeSDK |
| Codex (codex) | CodexSessionSDK | CodexRuntimeSDK |
| Cursor (cursor) | CursorSessionSDK | CursorRuntimeSDK |
ACP Harness base class:
Agents that expose a JSON-RPC stdio interface can subclass AcpHarness in runtime-sdk/acp.ts to inherit standardized process handling.
Error types:
SessionSDKError/RuntimeSDKError— Tagged errors from adapter operationsAdapterError— Harness not found or misconfigured
@grips/sdk
Unified consumer API. All harnesses, one interface.
The GripsClient is an Effect Service that aggregates every registered harness and routes calls to the right adapter automatically.
Registry services:
SessionSDKRegistry— HoldsSessionSDKinstancesRuntimeSDKRegistry— HoldsRuntimeSDKinstances
Client API:
import { GripsClient } from "@grips/sdk";
// List sessions across all harnesses
yield* GripsClient.list({ cwd: "/project", harness: "opencode", limit: 10 });
// Get a session and its tree
const { session, tree } = yield* GripsClient.get("opencode:session-123");
// Create a new session
yield* GripsClient.create({ harness: "claude-code", title: "Fix login bug" });
// Send a message and receive a stream of events
const stream = yield* GripsClient.send("pi:session-456", { content: "Refactor auth.ts" });
// Listen to an already-running session
const liveStream = yield* GripsClient.listen("codex:session-789");
// Fork a session from a specific node
yield* GripsClient.fork("cursor:session-abc", "node-xyz", { title: "Exploration branch" });
// Stop a running session
yield* GripsClient.stop("opencode:session-123");Session IDs are namespaced: {harness}:{nativeId}. The client parses the harness prefix and routes to the correct adapter.
@grips/cli
Terminal interface and interactive TUI for managing sessions.
Commands:
# Interactive TUI (default when no args provided)
grips
# List sessions
grips sessions list [--cwd DIR] [--harness NAME] [--json]
# Show a session
grips sessions get <id> [--json | --tree | --nodes] [--thinking]
# Create a session
grips sessions create [--harness NAME] [--cwd DIR] [--title TITLE] [--json]
# Delete a session
grips sessions delete <id>
# Fork a session from a node
grips sessions fork <id> <nodeId> [--title TITLE] [--json]
# Send a message to a running session
grips send <id> --content "MESSAGE"
# Listen to a running session
grips listen <id>
# Stop a running session
grips stop <id>Auto-detection: The CLI automatically detects installed agents by checking for their executables (opencode, claude, codex, cursor-agent) or home-directory markers (~/.pi) and wires only the available harnesses into the Effect dependency layer.
@grips/react
React components, hooks, and headless primitives for rendering GRIPS sessions.
Exports:
/— Components + hooks + mock data/components— Styled UI components/headless— Unstyled, stateful primitives/hooks— Reusable state and stream logic
Components:
| Component | Description |
|-----------|-------------|
| SessionTree | Static hierarchical tree of nodes with type legend |
| InteractiveSessionTree | Expandable / collapsible interactive tree |
| SessionTable | Tabular session list with sorting and filtering |
| SessionHeader | Session metadata header (title, harness, timestamps) |
| SessionPath | Breadcrumb path from root to selected node |
| NodeDetail | Detail view for any node type with type-specific rendering |
| SessionView | Composite view combining tree, path, and detail |
Hooks:
| Hook | Purpose |
|------|---------|
| useTree | Tree state (expand/collapse, select, paths, visibility) |
| useStreamEvents | Collect stream events into state |
| useStreamContent | Accumulate streaming text / reasoning content |
| useStreamingNodes | Merge committed nodes with live draft nodes from a stream |
| useSelection | Single / multi selection state |
| usePath | Compute ancestor path for a node ID |
Headless primitives:
| Primitive | Purpose |
|-----------|---------|
| Tree | Render-agnostic tree component (pass your own node renderer) |
| SessionList | Render-agnostic list component |
Peer dependencies: react >=19, react-dom >=19, tailwindcss >=4
website
Vite + React marketing and documentation site.
Uses TanStack Router and Tailwind CSS. Built with vite build and previewed with vite preview.
Installation
This repository uses Bun workspaces.
# Install dependencies
bun install
# Type-check all packages
bun run typecheck
# Run tests
bun testUsing packages in your own project
# Install the SDK
bun add @grips/sdk
# Install React components
bun add @grips/reactQuick Start
CLI
# Start the interactive TUI
cd /your/project && grips
# List all sessions across all agents
grips sessions list
# View a session transcript
grips sessions get opencode:abc123
# View the branching tree
grips sessions get opencode:abc123 --tree
# Create a session
grips sessions create --harness claude-code --title "Bugfix"
# Send a message
grips send claude-code:xyz789 --content "Fix the race condition in queue.ts"SDK
import { Effect } from "effect";
import { GripsClient, SessionSDKRegistry, RuntimeSDKRegistry } from "@grips/sdk";
import { OpenCodeSessionSDK, OpenCodeRuntimeSDK } from "@grips/sdk";
const layer = Layer.mergeAll(
Layer.succeed(SessionSDKRegistry, { sdks: [new OpenCodeSessionSDK()] }),
Layer.succeed(RuntimeSDKRegistry, { sdks: [new OpenCodeRuntimeSDK()] })
).pipe(Layer.provide(GripsClient.Default));
const program = Effect.gen(function* () {
const sessions = yield* GripsClient.list({ limit: 5 });
const { session, tree } = yield* GripsClient.get(sessions[0].id);
const stream = yield* GripsClient.send(session.id, { content: "Hello" });
yield* Stream.runForEach(stream, (event) =>
Effect.sync(() => console.log(event.type))
);
});
Effect.runPromise(Effect.provide(program, layer));React Components
import { SessionTree, SessionView, useStreamingNodes } from "@grips/react";
import { mockNodes } from "@grips/react/data/mock";
// Static tree
<SessionTree nodes={mockNodes} />
// Full interactive view with streaming
function MyApp({ nodes, streamEvents }) {
const { nodes: liveNodes, draftNodeIds } = useStreamingNodes(nodes, streamEvents);
return <SessionView nodes={liveNodes} autoExpandIds={draftNodeIds} />;
}Architecture
The Streaming Draft Node Lifecycle
When driving a running agent through RuntimeSDK, the event stream merges live output into the tree:
- Stream Intake —
useStreamingNodesmerges committed nodes with incomingStreamEvents. - Draft Node Initialization — When an active event starts (e.g.,
text.start), a transient "draft node" is created with_draft: true. - Delta Merging — Incremental events continuously merge into the draft node's payload.
- Live Auto-Expansion — Draft node IDs are returned as
draftNodeIdsand mapped toautoExpandIdsin theTreecomponent, keeping the active streaming turn visible. - Commitment — On completion events (
text.done,tool_execution.end,done), the draft node closes. If the runtime appends it to storage, it becomes a permanent committed node.
Node Format
Every node shares a common shape:
interface Node {
id: string;
parentId: string | null;
timestamp: number;
type: "message" | "tool_execution" | "compaction" | "branch_summary" | "checkpoint" | "config_change" | "custom";
version?: number;
payload: /* type-specific */;
metadata: Record<string, unknown>;
}The parentId enables a full directed acyclic graph of session history, supporting branching, forking, and non-linear exploration.
Development
Scripts
| Script | Description |
|--------|-------------|
| bun run dev | Start dev servers (Turbo) |
| bun run typecheck | TypeScript check all packages |
| bun test | Run all tests |
| bun run fmt | Format with oxfmt |
| bun run lint | Lint with oxlint |
| bun run grips | Run the CLI locally |
Testing Conventions
- Colocation required — Test files (
*.test.ts,*.test.tsx) and Storybook stories (*.stories.tsx) must live in the same directory as the source file they cover. No__tests__/orstories/directories. - Unit tests:
vitest runinpackages/react/andpackages/website/;bun testelsewhere. - Storybook
play()tests are real tests and must pass.
Adding a New Harness Adapter
See AGENTS.md for the full workflow. The short version:
- Implement
SessionSDKinpackages/harness/src/session-sdk/my-agent.ts - Implement
RuntimeSDKinpackages/harness/src/runtime-sdk/my-agent.ts(or subclassAcpHarness) - Export both from
packages/harness/src/index.ts - Register in
packages/sdk/src/registry.ts - Add auto-detection in
packages/cli/src/layer.ts - Add colocated tests
Adding a New Node Type
See AGENTS.md for the full workflow. The short version:
- Define the payload schema in
packages/format/src/node.ts - Add streaming events to
packages/format/src/stream.tsif applicable - Add colors, background, and symbol in
packages/react/src/lib/node-rendering.tsx - Create a detail component in
packages/react/src/components/NodeDetail/details/ - Wire it into
NodeDetail.tsx - Add tests and stories
Contributing
- Follow the existing code style (oxfmt, oxlint).
- Keep tests and stories colocated with source files.
- Ensure all tests pass before submitting changes.
- Make minimal changes — prefer small, focused PRs.
License
MIT
