Ask AI
Ask AI Start conversation ↵

I'm an AI assistant with Grove's codebase and documentation in context.

Ask me anything about Grove.

EXAMPLE QUESTIONS

How the packages fit together

  • Grove has one engine and several clients. grove.core owns decisions while CI enforces dependency boundaries.
  • The daemon serves REST and SSE. The TUI, client SDK, MCP server and web dashboard expose the engine through different interfaces.

The package layout

grove/
├── core/
│   ├── __init__.py          # public exports
│   ├── config.py            # configuration cascade
│   ├── workspace.py         # workspace state
│   ├── git.py
│   ├── tmux.py
│   ├── mewbo.py
│   ├── store.py             # atomic storage
│   ├── manager.py           # orchestration
│   ├── registry.py
│   ├── activity.py
│   ├── sessions.py
│   ├── auth.py              # pairing
│   ├── mailboxes.py         # native coordination
│   ├── native.py            # steering clients
│   ├── native_launch.py
│   ├── native_worker.py
│   ├── paths.py
│   ├── errors.py
│   ├── contracts/
│   └── agents/
│       ├── native_owner.py
│       ├── native_claude.py
│       └── native_codex.py
├── daemon/                  # loopback FastAPI
├── client/                  # local PTY and SSH SDK
├── mcp/                     # tools over the SDK
└── tui/                     # Textual
    ├── cli.py
    ├── app.py
    ├── theme.py
    ├── _status.py
    ├── keys.py
    ├── screens/
    └── widgets/

webapp/                     # Next.js and its BFF
  • Public exports define the engine API. pyproject.toml declares the import contracts that CI checks.

The boundaries, enforced

  • Core cannot import UI packages. The daemon depends on core, never on clients.
  • The client SDK cannot import daemon or TUI internals. MCP reaches the daemon through GroveClient, including from another host.
  • include_external_packages = true extends checks to external imports. lint-imports enforces these boundaries on every push.

The public share boundary

  • Bearer authentication protects daemon routes except pairing, liveness and /public/{token}. A share token opens one workspace, never the fleet.
  • WorkspaceState.share_token is the publication state. Clearing it revokes the link without a second boolean to synchronize.
  • Public routes have a separate prefix. A route census prevents accidentally exposing an authenticated route.
  • grove.core.contracts.public explicitly selects public fields, including nested data. New fields stay private rather than escaping a redaction list.
  • grove.core.share_policy sets project policy. Expiry is fixed when a link is minted, while an optional hashed passcode is checked on every read.
  • Readers supply the passcode in a header. No anonymous grant, cookie or session needs storing.

Side effects at the edges

The contracts layer

  • grove.core.contracts owns branch intent, request envelopes, response views and shared palettes.
  • Values crossing client boundaries use Pydantic with extra="forbid". Internal state uses @dataclass(slots=True).

The agents layer

  • Adapters map Claude Code transcripts, Codex rollout files and Mewbo sessions onto AgentActivityState. The generic adapter deliberately does nothing.
  • The registry selects by kind. Adapters normalize protocol shape, never model behavior.

The native session control plane

  • Claude Code, Codex and OpenCode default to Grove owned native sessions. Their tmux pane runs grove-native-worker, which owns the provider channel.
  • The worker starts claude -p or codex app-server and registers through /mailboxes/connection SSE.
  • Steering reaches a native owner through OwnerSteerClient inside the daemon or DaemonSteerClient over HTTP.
  • Each sent or received frame becomes a timestamped stdout line. Dashboards expose this wire log as Stream.
  • Questions and stream facts enter the hook spool. ClaudeHook.drain folds them into the existing session sidecar rather than adding another activity reader.
sequenceDiagram
    participant Client as CLI / TUI / web
    participant Daemon as grove.daemon + coordinator
    participant Worker as grove-native-worker (tmux pane)
    participant Provider as claude -p / codex app-server
    participant Spool as hook spool → sidecar → activity
    Worker->>Daemon: register, hold /mailboxes/connection (SSE)
    Client->>Daemon: message / interrupt / set model / answer
    Daemon->>Worker: control frame over the SSE stream
    Worker->>Provider: provider frame on stdin
    Provider-->>Worker: stream event or JSON-RPC frame on stdout
    Worker->>Worker: one timestamped line per frame
    Worker->>Spool: *.ask.json, *.facts.json
  • native: false, claude-terminal or codex-terminal keeps the interactive agent UI. Grove steers it through pasted keystrokes.

The observability spine

  • ActivityService combines agent state, tmux output and git changes. The TUI consumes deltas directly and the daemon streams them over SSE.
  • RepoRegistry keeps one manager per repository without repeatedly resolving configuration.
  • SessionExplorer discovers sessions across the repository and its worktrees for CLI, daemon and web consumers.

Dependencies flow inward

  • Clients depend on the engine. Reverse dependencies couple internal helpers to callers and invite circular imports.
flowchart LR
    Browser([browser / phone]) -.http.-> BFF([webapp BFF])
    BFF -.http + SSE.-> Daemon([grove.daemon])
    TUI([grove.tui]) --> Core([grove.core])
    MCP([grove.mcp]) --> Client([grove.client])
    Client -.http.-> Daemon
    Daemon -.REST + SSE.-> Core
    Core --> Git([core.git])
    Core --> Tmux([core.tmux])
    Tmux --> Worker([grove-native-worker])
    Worker -.stdio.-> Provider([claude -p / codex app-server])
    Worker -.SSE + http.-> Daemon
    Core --> Native([core.native*])
    Core --> Mewbo([core.mewbo])
    Core --> Store([core.store])
    Core --> Contracts([core.contracts])
    Core --> Agents([core.agents])

See also