← All 21 apps

CandyCode

🤖 CandyCode

Terminal AI coding agent

inspired by charmbracelet/crush ai chat anthropic openai ollama

CandyCode code coverage

Terminal AI coding agent. Seven LLM providers (OpenAI, Anthropic, Claude Code, SGLang, Bedrock, Vertex, custom), twenty-eight built-in tools gated by hooks and a six-mode permission gate, workspace checkpoints with undo and rewind, a context engine that prunes and compacts on its own, sub-agents you can watch and message, memory, skills, MCP, workflows, SQLite sessions, a WebSocket server with a browser UI, and an Agent Client Protocol agent for editors — over a sugar-core render loop with Markdown replies. Runs offline out of the box.

Install

composer require sugarcraft/candy-code

Quickstart

composer install
./bin/candy-code

# With no configuration it runs the offline EchoProvider through the full
# engine — zero network, zero keys. Point it at a real model instead:
export CANDY_CODE_PROVIDER=openai
export OPENAI_API_KEY=sk-...
export CANDY_CODE_MODEL=gpt-4o     # optional; provider default otherwise
./bin/candy-code

# One-shot (never attaches to the TTY, never opens the alt-screen):
candy-code -p "explain the Width helper"
candy-code -p "audit this" --output-format json | jq -r .result

CANDY_CODE_PROVIDER accepts openai, anthropic, claude-code, sglang, bedrock, vertex or custom. To skip PHP SDKs entirely, set CANDY_CODE_BACKEND_CMD to a script that reads JSON history on stdin and writes the reply to stdout. See docs/ENVIRONMENT.md for the full environment-variable reference.

What's in the box

Seven providersOne ProviderInterface — OpenAI, Anthropic, Claude Code CLI, SGLang, Bedrock, Vertex, custom — with capability introspection, streaming, function calling, embeddings and per-model cost.
Bounded agent loopEngineBackend calls the provider, runs the returned tool calls through the hook gate, feeds results back, and repeats until the model answers without calling tools or maxSteps is hit.
Twenty-eight built-in toolsBash, Read, Edit, Write, ApplyPatch (multi-file patches, all or nothing), Glob, Grep, WebFetch, WebSearch, doctor, Skill, Lsp, Memory, RepoMap, Todo, Prune, Compress, Recall, Workflow, Team, AskUser, PlanExit, Task, the messaging tools SendMessage, Subagents and InterruptAgent, and BoardRead/BoardPost for parallel batches — path tools jailed to the project root, edits refused on a file that changed since it was read, plus every trusted MCP server's tools. Implement Tools\Tool for your own.
Hooks that can say noPre/post-tool-use guards returning allow / deny / modify / ask. Built-ins: ProtectFilesHook, ConfirmRemoveHook, AuditHook, plus BashEscapeDenyHook when a worktree root is set and PermissionGateHook last, which adapts the six-mode PermissionGate onto the same chain. YAML config and external ScriptHook supported, the latter selecting its outcome by exit code.
Progressive skillsFrontmatter SKILL.md files inject prompt context. The system prompt carries only name + description; the model pulls a full body through the Skill tool. 8 built-ins, plus ~/.candy-code/skills, <project>/.candy-code/skills and skills imported from Claude Code and opencode.
Sub-agents, teams & worktreesThe Task tool delegates to presets with their own model, tools, skills and permission mode — in parallel, nested, in the background or in a git worktree of their own, every run resumable; a preset model the provider does not serve falls back to the session's model. Each run gets a live line, an agents strip and an Agent View with its own transcript, where you can message, pause, cancel or send it to the background (Ctrl+X b). The agent messages, waits on and interrupts its sub-agents with SendMessage, Subagents and InterruptAgent, sub-agents reply to their parent, a parallel batch shares a board, and teammates claim tasks from a shared SQLite board through the Team tool.
WorkflowsWorkflowEngine runs multi-stage agent pipelines — sequential, fan-out parallel, chained pipeline, and task-then-verifier — from a PHP DSL or YAML, with SIGINT-safe pause and stage-granularity resume.
MCP (Model Context Protocol) client & serverMulti-server client over stdio + HTTP reading .mcp.json with ${VAR} interpolation, plus stdio/HTTP servers to host your own tools. Per-preset mcpServers allowlists are enforced, not decorative.
SQLite sessionsWAL-mode persistence of sessions, messages and tool calls with FK-enforced cascade, fork/rename/resume, and opt-in retention pruning that never drops a session you named.
Context engineCompaction tiers with absolute token caps and summaries requested ahead of need, in-turn pruning of stale tool output, the agent's own Prune/Compress/Recall tools, re-injection after a compaction, and recovery from overflows, cut-off replies and dropped streams.
Permissions & checkpointsSix permission modes (the TUI asks by default; Alt+M toggles plan mode and the model leaves it with PlanExit, asking you to approve its plan; auto puts security findings to you and can have the title model review the rest), argument-scoped rules; shell lines proven read-only — sed scripts that only read, simple awk programs, for/while read loops over read-only bodies — run unasked (permissions.autoAllowReadOnly); a remembers each part of a chain and answers at once, e edits the scope first, a grant ignores a leading in-project cd, and /permissions mode / /permissions revoke switch the mode and drop grants; an optional bubblewrap sandbox for Bash, and a per-turn workspace checkpoint behind /undo, /redo, /rewind and /diff, with opt-in auto-commit, post-edit lint, LSP diagnostics and auto-test.
MemoryNotes indexed into the prompt, a Memory tool with BM25 recall, auto-consolidation at the end of a turn, a periodic dream pass, and a git history you can restore from.
Server mode & web UIcandy-code serve runs sessions headless behind an authenticated WebSocket speaking candy-code.v1; the CandyCodeWeb browser UI and candy-code attach drive them. Remote access is gated by --allowed-host and a --allowed-ips client allow-list, and --allow-dir-browse (confined to --browse-root) lets a client pick a new session's project directory — as /new's folder picker does in the TUI. candy-code acp serves editors — Zed, JetBrains, Neovim — over the Agent Client Protocol on stdio.
Prompts from anywhere@file, @diff and @https://… attachments, $skill mentions for one turn, !command shell lines, opt-in AI! comments saved in your editor, /handoff into a fresh session seeded with a state summary, and /newrule to draft a project rule from the conversation.
Real Model–Update–View render loopA sugar-core Model/Program tree with buffer-diffed output: Markdown-rendered replies via SugarMarkdown, live tool-call lifecycle, unified diffs for Edit results, inline images via sugar-image, mouse zones and text selection via sugar-mouse, and a command palette whose fuzzy filter requires every typed character to match. A slash command is offered when any such alignment starts on its first character, so /ab still finds a__ab.

Source & demos

Try the quickstart →

Key Classes

ClassMethodDescription
ProviderInterfacecomplete(CompleteRequest): CompleteResponseThe single LLM abstraction — one batch completion
ProviderInterfacecompleteStream(CompleteRequest): \GeneratorSame call, yielded as streaming chunks
ProviderInterfacesupportsStreaming(), supportsVision(), contextWindow(), costPer1kTokens(model, direction)Capability + cost introspection, so callers do not guess
EngineBackendnew(provider, model)Factory — bridges the chat Backend seam to the agent engine
EngineBackendwithTools(tools), withHooks(manager), withSkillRegistry(registry), withMaxSteps(n)Fluent wiring of the agent loop (immutable — each returns a new instance)
EngineBackendcomplete(history, ?onToken, ?onEvent): MessageRun one bounded agentic turn, emitting tool-lifecycle events
HookManagerregisterBuiltIns()Registers ProtectFilesHook, ConfirmRemoveHook, AuditHook
HookManagerpreToolUse(HookContext): HookResultGate a tool call — allow / deny / modify / ask
HookManagerresolveAsk(ask, approved, feedback)Settle an answered ASK into a final allow or deny
SkillRegistryfindForPrompt(prompt): arrayMatch discovered SKILL.md files to the current turn
SkillRegistrygetForPaths(paths): arrayPath-scoped skills for files a tool just touched
SkillRegistryisAutoInvocable(name), isUserInvocable(name), isContextFork(name)Frontmatter flag enforcement, not decoration
AgentPreset__construct(name, description, tools, model, permissionMode, skills, mcpServers, …)Readonly sub-agent descriptor — model, tool allow/deny, skills, MCP allowlist, effort, isolation
McpClientstartServers(), stopServers()Launch / tear down the configured stdio + HTTP MCP servers
McpClientlistTools(), callTool(server, tool, args), callToolByName(tool, args)Discover and invoke MCP tools, filtered by the active preset's allowlist
PermissionGateevaluate(ToolCall): PermissionDecisionEvaluate a call against the active mode, with a mode-independent rm-rf circuit breaker
PermissionGatemode(): PermissionModeOne of default, accept-edits, plan, auto, dont-ask, bypass-permissions
WorkflowEnginerun(workflowPath, context): WorkflowResultExecute a YAML multi-stage agent pipeline
WorkflowEnginerunFromPhp(workflowClass, context): WorkflowResultSame, from the PHP builder DSL
WorkflowEnginepause(id), resume(id), getStatus(id)Stage-granularity pause / resume across a signal
SessionStorecreateSession(id, provider, model, ?systemPrompt, ?name)Open a session row in the SQLite (WAL) store
SessionStoreaddMessage(sessionId, message), addToolCall(sessionId, messageId, toolCall)Append a turn and its tool calls, FK-enforced
SessionStorelistSessions(limit), forkSession(id), renameSession(id, name)Back the /sessions, /fork and /rename commands
SessionStorepruneSessions(daysOld, ?exemptSessionId): intOpt-in retention sweep; never drops a named or resuming session

Demos.

VHS-recorded GIFs of every example shipped with the app. Regenerated automatically on every push that touches the source.

Chat

Chat

Full chat session with scrollback + Markdown-rendered replies.