dcode

module
v0.1.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Aug 20, 2026 License: MIT

README ΒΆ

dcode

πŸ‡§πŸ‡· VersΓ£o em portuguΓͺs

License Go Status Specs Coverage

dcode mascot

Dreibox Code β€” an agentic coding harness for the terminal, written in Go.

Status: alpha. The core is implemented and tested β€” the agent reads, edits, searches and runs commands inside an OS-enforced sandbox. There is no TUI yet and no released binary; build it yourself with make build. Expect breaking changes.

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ ● dcode  MiniMax-M3  workspace-write  ctx 34%β”‚ PLAN                   β”‚
β”‚                                              β”‚                        β”‚
β”‚ β–Έ Add CPF validation to checkout             β”‚ βœ“ 1 Map the flow       β”‚
β”‚                                              β”‚ βœ“ 2 Find existing checkβ”‚
β”‚   Found the flow. Validating before persist. β”‚ β–Έ 3 Implement CPF      β”‚
β”‚                                              β”‚   4 Cover with tests   β”‚
β”‚   ⏺ read  src/checkout/handler.go   240 linesβ”‚ ⊘ 5 Run the suite      β”‚
β”‚   ⏺ edit  src/checkout/validate.go    +24 βˆ’2 β”‚     β”” missing dep      β”‚
β”‚     β”‚ + func validateCPF(doc string) error { β”‚                        β”‚
β”‚   ⏺ bash  go test ./src/checkout/  βœ“ 12 pass β”‚ 2 of 5 Β· 1 blocked     β”‚
β”‚                                              β”‚                        β”‚
β”‚ β€Ί _                                          β”‚ [p] hide               β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Why another one

Four terminal coding agents already do this well, and each is genuinely good at something different:

Language License Strongest at Weakest at
Claude Code TS + Rust source-available context engineering, tool design cold start, single provider
Codex CLI Rust Apache-2.0 OS-level sandboxing, governance provider lock-in
opencode TypeScript MIT 75+ providers, extensibility runtime weight
jcode Rust MIT startup latency, RAM per session no sandbox at all

The gap is the intersection none of them occupy: session density and a real OS-enforced sandbox and provider neutrality. jcode has the performance and no sandbox. Codex has the sandbox and is tied to one provider. opencode has the neutrality and the heaviest runtime of the four.

That intersection is what this project is for.


Design decisions

Five decisions, recorded before any code. Each is a load-bearing constraint on everything that follows.

Go for the core β€” chosen over Rust and TypeScript

Go gives roughly 90% of Rust's performance profile with the best CLI/TUI tooling of any language, a concurrency model that maps directly onto the problem (N sessions, SSE streaming, PTY multiplexing), and the deepest contributor pool in this specific domain.

The honest version: Go and Rust scored within noise of each other. The weighted matrix that produced this decision separated them by single digits on a 115-point scale, which is not enough resolution to call one correct. Go's accepted cost is GC pressure under many concurrent sessions β€” which attacks exactly the thesis above, so a per-session memory target ships as a regression test from day one.

Sandbox and approvals are separate concerns β€” borrowed wholesale from Codex
  • Sandbox is a technical boundary enforced by the kernel β€” read-only, workspace-write, full-access. Apple Seatbelt on macOS, bubblewrap and Landlock on Linux, Windows Sandbox on Windows.
  • Approval policy is an authorization decision, orthogonal to the boundary β€” untrusted, on-request, never.

Keeping them separate is what reduces approval fatigue. Harnesses that conflate the two ask too often, users disable prompting entirely, and the security model becomes decorative. That is the real failure mode β€” not the sophisticated attack, but the exhausted user.

Append-only context β€” the highest-leverage performance decision

The context prefix is never mutated between turns. Editing anything early in the conversation invalidates the whole KV cache and re-bills the full prompt, in both latency and money.

Consequences most harnesses get wrong:

  • MCP tool schemas are advertised at startup from cache. A server that connects on turn 5 and injects tool definitions invalidates the cache for the entire session.
  • No timestamps, token counters, or volatile state in the system prompt.
  • Compaction is rare and block-wise, never incremental.
Client-server from the first commit β€” cheapest now, most expensive to retrofit

The core runs as a local daemon; the TUI is just one client. Desktop apps, IDE extensions, shared sessions and remote execution all fall out of it, and none of them fit into a TUI monolith.

Provider-agnostic, with a real adaptation layer β€” transport Γ— family

Not just endpoint swapping. Two orthogonal axes:

  • Transport is the wire format (openai, anthropic). Reusable, carries no thresholds.
  • Family is the adaptation β€” system prompt, tool schema, edit strategy. Carries the measured behavioral thresholds and the per-model turn limits.

MiniMax M3 forced this: it speaks both dialects, so a single axis would mean duplicating the whole family. The safety consequence matters more than the code shape β€” "OpenAI-compatible" describes serialization, not behavior, so treating a wire format as a family would apply one model's measured thresholds to another.


Architecture

flowchart TB
    subgraph clients[Clients]
        TUI[TUI]
        IDE[IDE ext Β· future]
        DESK[Desktop Β· future]
    end

    clients -->|HTTP + SSE over unix socket| API

    subgraph daemon[dcode daemon]
        API[protocol Β· session Β· event log]
        LOOP[agent loop]
        CTX[context engine]
        BEH[behavior Β· prompt]
        TOOLS[tools]
        POL[policy]
        SBX[sandbox]
        PROV[provider]

        API --> LOOP
        LOOP --> CTX
        CTX --> BEH
        LOOP --> PROV
        LOOP --> TOOLS
        TOOLS --> POL
        POL --> SBX
    end

    PROV -->|transport Γ— family| MODEL[(LLM)]
    SBX -->|seatbelt Β· bwrap| OS[(OS boundary)]

The session is an append-only event log. Resume, multi-client attach and session density all fall out of that one primitive β€” the same principle as the model context, one layer up.


Stack

Concern Choice Why
Language Go 1.25+ see design decisions
TUI bubbletea/v2 Β· lipgloss/v2 Β· bubbles/v2 best TUI tooling of any language
Config pelletier/go-toml/v2 typed, commentable, no indentation traps
Sandbox exec of sandbox-exec / bwrap + golang.org/x/sys no cgo, keeps the static binary
gitignore boyter/gocodewalker the dedicated libs are abandoned since 2018–2021
IDs oklog/ulid/v2 time-ordered, filename-safe
Transport stdlib net/http HTTP+SSE over a unix socket needs nothing more

Two deliberate non-choices:

  • No gRPC. No codegen step, reachable from a future web surface, debuggable with curl --unix-socket. The bottleneck is the model, not serialization β€” optimizing the wire would be optimizing the wrong place.
  • No bundled tooling. grep and glob are native Go. The user's own toolchain β€” tests, linters, formatters β€” runs through bash with whatever the machine already has. Bundling a linter would fight the project's own config.

How this is built

Spec-driven development using the RPI protocol β€” four .spec.md files sharing a timestamp prefix, with strict precedence:

File Role Rule
.r.spec.md Research β€” context, user stories, business rules Absolute truth. If code contradicts it, the code is wrong.
.p.spec.md Planning β€” schemas, contracts, types Use exactly the names and types defined.
.config.spec.md Config β€” env vars, flags, infra constants No new env var in code without an entry here.
.i.spec.md Implementing β€” ordered execution checklist Follow the order.

Precedence: .r > .p/.config > .i.

The interesting part: specs for non-deterministic behavior

A harness has a problem a CRUD app does not β€” its most important behavior is mediated by a language model. You cannot write this as a schema:

when an edit fails on an ambiguous match, the agent re-reads the file instead of retrying blind

So every .r.spec.md declares which regime its scope operates in β€” deterministic, model-mediated, or mixed β€” and that declaration decides how it is verified: assertion in go test, or a measured threshold over fixtures.

The corollary is an architectural goal, not an accident: push as much behavior as possible across the line into the deterministic side. If context assembly is a pure function of session state, it is exactly golden-testable β€” and append-only context makes that natural, because the prefix is a pure function of history.

The same principle governs where a behavior rule lives. A rule that can be enforced in code does not belong in a prompt at all; prompt is for what cannot be structurally enforced. And tool error messages are a behavior surface, not diagnostics β€” they are where recovery is taught, at the only moment it is relevant, at zero cost until it happens.

Details in docs/conventions/SDD-HARNESS.md.


Specs

Spec Regime Covers
client-server-protocol deterministic HTTP+SSE over unix socket, event log, approval flow
context-engine deterministic the pure Assemble, compaction planning
provider-adapter mixed transport Γ— family, error classes, retry
agent-loop mixed the turn cycle, limits, parallel tools, recovery
sandbox-policy deterministic the two orthogonal axes, OS enforcement
tool-suite deterministic read, write, edit, glob, grep, bash, plan
behavior-definition mixed prompt layers, reminders, intrinsic planning
configuration deterministic XDG layout, precedence chain, commands
client-tui deterministic layout, plan panel, approval modal
distribution deterministic install, signed release, update
learned-memory mixed what the agent discovers, versioned where people read it β€” design only, not built

Installing

# the install script
curl -fsSL https://raw.githubusercontent.com/aguinelo/dcode/main/install.sh | sh

# from source
go install github.com/aguinelo/dcode/cmd/dcode@latest
What the install script checks

Nothing has to be installed first. Of rustup, bun, deno, nvm, k3s and uv, not one requires an external verification tool, and a first install is the worst moment to ask.

The SHA-256 always. A failure installs nothing and leaves nothing behind.

Source of the expected digest Covers Needs
the digest the script carries a substituted release nothing
checksums.txt from the release a corrupted or truncated download nothing
the cosign signature over that file a substituted release cosign, if you have it

The carried digest is the one worth explaining. checksums.txt travels from the same host as the tarball, so on its own it cannot catch a swapped release β€” whoever replaces one replaces the other, and the pair stays consistent with itself. The digest in the script arrived by a different route: it is committed to main, where a release asset can be replaced leaving no public trace and a line in a tracked file cannot, because changing it is a commit.

A substituted release is covered by the carried digest or by the signature, and either is enough. So the script says nothing at all when either held β€” telling you the signature went unchecked while the check that matters passed by a route that does not depend on it is noise dressed as diligence. When neither covered it, it says so, and points at the installer that carries this release's digests. Never at a package to install.

DCODE_VERSION pins a version and DCODE_INSTALL_DIR chooses where. An installer carries exactly one release's digests, so a pinned install of another version is that version's own installer: https://github.com/aguinelo/dcode/releases/download/vX.Y.Z/install.sh.

dcode update installs a newer release on request, and never on its own, and needs no extra package either. It applies the same rule as the install script β€” the carried digest or the signature, either is enough β€” by reading the digests from the installer on main. It then checks the downloaded binary actually runs, and only then swaps it, so every failure leaves the working binary untouched.

One difference: where the install script warns, update refuses. There is a working binary on the machine, so stopping costs a version and keeps everything.

From source, for working on dcode itself
make install          # runs the gate, then installs to ~/.local/bin
make install-fast     # skips the gate, for the edit loop
make uninstall

DCODE_INSTALL_DIR chooses somewhere else. A local build says so in its own version string β€” 0.0.0-dev+a91f2c4.dirty β€” because a binary that presents itself exactly like a published release is how a bug report costs an hour finding out it was never the published code.

For the same reason dcode update refuses to replace a local build: one is normally ahead of the last tag, so installing the latest release would be a downgrade wearing the word "update". --force lifts the refusal.

Running it

export DCODE_API_KEY=...

dcode                              # the terminal interface
dcode "add a test for the parser"  # one task, one exit code β€” for scripts and CI
dcode serve                        # the daemon, for clients that outlive one terminal
dcode tui --socket /path/to.sock   # attach a client to a running daemon

dcode tui attaches to a daemon when one answers and otherwise starts its own in-process. The client speaks the protocol either way β€” the embedded daemon is a deployment detail, not a second code path β€” so a session that outgrows one terminal moves to dcode serve without the client changing anything.

The credential
dcode login                    # read from a prompt that does not echo
dcode login --family claude    # a second key, for another family
dcode config                   # what is stored, masked, and where from
dcode login --reveal           # print it in full, on purpose

The key is never taken as an argument β€” an argument lands in shell history and is visible in ps. It is stored in the OS keychain where there is one and in a 0600 file where there is not, one credential per model family, chosen by credential.backend so that what writes and what reads always agree. DCODE_API_KEY still wins over anything stored.

config.toml refuses anything shaped like a secret, in any section, because that file is meant to be versioned and synced.

Two commands need no key, and they are the audit pair:

dcode --dump-prompt          # exactly what would be sent to the model
dcode --config model.name    # a setting's effective value, and where it came from

By default the agent runs in workspace-write with on-request approvals: it may edit inside the workspace, and anything crossing that boundary β€” a write outside it, or the network β€” stops and asks. Without an approver it denies, because with nobody to ask the only alternative is granting in silence.

Inside the workspace a short list of rules asks about the things that are different in kind from ordinary work β€” writing .git/** (a hook runs on the next commit, outside the sandbox) or .dcode/** (the agent's own configuration), and reading a secret (which sends it to the model provider). They are attention, not containment: a command pattern is avoided by bash -c, and what actually contains the agent is the sandbox.

[rules]
confirm_write   = [".git/**", ".dcode/**"]
confirm_read    = [".env", "**/*.pem"]
confirm_command = ["rm -rf*"]

A configured list replaces the default rather than adding to it, and dcode config rules.confirm_write shows what is in force and where it came from.

Configuring it

Everything is optional; the defaults are the product. Files live under $DCODE_HOME, or the XDG directories when it is unset.

$DCODE_HOME/
  config.toml     settings β€” never credentials, which come from the environment
  AGENTS.md       instructions shared with other agent tools
  DCODE.md        instructions for dcode alone; wins where they disagree
  commands/       your own /commands β€” markdown with frontmatter
  skills/         guidance loaded only when its trigger fires

A workspace carries the same set under <workspace>/.dcode/, and its values win. An unknown key in config.toml is an error rather than a warning: a typo that is silently ignored is the most frustrating configuration bug there is.

Inside the interface

While the model thinks, the last few lines of its reasoning stream dimmed on screen β€” the only answer to "is it going somewhere sensible". Once it acts, that collapses to ✻ thought for 4.2s Β· Tab, because on a tool-calling turn the thinking runs five to ten times the length of the answer and would bury the result it led to. It never enters the history the model is sent. behavior.show_reasoning = false turns it off.

/help lists everything. /plan shows the plan in full, /config <key> answers where a setting came from, /model <name> and /clear open a fresh session β€” the system prompt is part of the prefix, and the prefix cannot be rewritten. /init writes DCODE.md for the repository from what is already in it.

Typing while a turn is running queues the message; the queue drains as one turn when the session goes idle. ^C interrupts the turn rather than quitting. In the approval modal, Enter denies.


Roadmap

Phase Delivers Status
0 go.mod, CI with -race, coverage gate, CGO_ENABLED=0 βœ…
1 protocol type vocabulary βœ… 100% covered
2 context engine β€” the pure Assemble βœ… 99%
3 provider β€” transport Γ— family, both dialects βœ… 91%
4 policy and OS sandbox βœ… 92% / 94%
5 the seven tools βœ… 94%
6 the agent loop βœ… 98%
7 behaviour, config, wiring, CLI βœ… 91%
8 event log, unix socket, SSE, reference client βœ… 95% / 92% / 93%
9 TUI client, commands, skills, reminders, distribution βœ… 95% / 96% / 92%

The aggregate gate is 95% and every package clears its own 90% floor; the suite runs under -race on macOS and Linux.

Beyond MVP: multiple providers, MCP, plugins, session sharing, desktop, IDE.

Self-hosting milestone. A pull request to dcode written end to end by dcode, passing review and the coverage gate with no manual edits. It is the best eval the project has: its own test suite and review checklist become the fitness function. Its bias mitigation is mandatory β€” keep a non-Go codebase in the eval fixtures, or the agent gets excellent at Go and mediocre elsewhere without the metric noticing.


Repository layout

docs/
  conventions/            bilingual β€” X.md is English, X.pt-BR.md is Portuguese
    LANGUAGE.md           the bilingual policy itself
    SDD-HARNESS.md        applying spec-driven development to a harness
    TESTING.md            TDD, bug reproduction rule, coverage gate
    GO-CODE-REVIEW.md     Go review checklist, with product-specific checks
  brand/                  bilingual β€” mascot, logomark, palette, voxel maps
  specs/                  Portuguese only β€” see LANGUAGE.md section 3
    architecture/         cross-cutting specs
    domains/              feature specs
internal/
  protocol/       the wire vocabulary, no logic and no I/O
  contextengine/  the pure Assemble β€” where ADR-03 lives or dies
  provider/       transport Γ— family, replay transport, credential redaction
  policy/         the two orthogonal axes, pure decision table
  sandbox/        seatbelt and bubblewrap, driven as binaries
  tools/          read write edit glob grep bash plan
  loop/           the turn cycle
  behavior/       the prompt builder
  config/         roots, precedence chain, config.toml, commands, instructions
  session/        the append-only event log, approvals, the session manager
  server/         the daemon: unix socket, protocol routes, SSE
  tui/            the terminal client β€” a pure reducer and a pure renderer
  update/         signature and checksum verification, atomic binary swap
  app/            the only package that reads the environment
pkg/client/       the reference client, and the first consumer of the protocol
cmd/dcode/        argument parsing and printing
install.sh        verifies before it installs, or installs nothing

Contributing

Too early for code contributions β€” the specs are still moving.

What is useful right now is argument. If a design decision above looks wrong, open an issue and say why. The reasoning is written down precisely so it can be attacked: every decision has a stated cost, and the Go-versus-Rust call in particular was close enough that new information could flip it.

Workflow

GitHub Flow. main is always deployable. Work happens on short-lived branches cut from main and returns through a pull request.

main ──┬─────────────────────────┬──▢
       └── feat/event-log ── PR β”€β”˜

Branch names follow the change type: feat/, fix/, docs/, chore/, refactor/. For spec-driven work, use the spec slug without its timestamp β€” feat/client-server-protocol.

Conventional Commits on every commit message and PR title. Breaking changes carry ! before the colon and explain the break in the body. For anything marked stable in a .p.spec.md, a breaking change also requires a changelog/ entry and a major version bump.

Commits that change technical behavior must keep the corresponding spec in sync β€” a spec is never allowed to go stale relative to the code.

Authorship. Commits carry a single author and no co-author trailers. Every commit is attributable to one person; tooling that assisted does not get a byline.

Testing

TDD. Test first, watch it fail, then write the code. A test that has never been seen red is not a safety net.

Every bug gets a reproducing test β€” before the fix. Reproduce it in a failing test, confirm it fails for the reported symptom, then fix it, and the same test passes unmodified. A fix: PR without a new test is blocked. Regression tests are permanent.

95% aggregate coverage gate, 90% per package, with an explicit denominator: deterministic code in internal/ and pkg/. Generated code, main wiring, and model-mediated paths are excluded β€” the last because it cannot be verified by assertion at all, only by measured thresholds over fixtures. That exclusion is deliberate pressure in the right direction.

The gate is a floor, not a target. A test that exercises a line without asserting anything is a review finding even when coverage is green.

Full rules in docs/conventions/TESTING.md.

Language

This project is bilingual. English is canonical and takes the bare filename; Portuguese carries a .pt-BR suffix. The README and everything under docs/conventions/ exist in both, cross-linked at the top.

Two deliberate exceptions: specs are Portuguese only, because RPI makes .r.spec.md the absolute truth and that rule needs exactly one source of truth β€” a drifted spec is worse than a missing one because it still looks authoritative. Commits and code comments are English only, because changelog tooling assumes a single language.

Full policy in docs/conventions/LANGUAGE.md.


Brand

The mascot is three stacked boxes; the logomark is a D built from the same three boxes. Its eye is the ⏺ that marks every execution line in the TUI, so the mark repeats itself on screen instead of being applied on top.

Designed as three pieces that stack β€” the name becomes the object, and it prints without supports. Files, palette and voxel maps in docs/brand/.

License

MIT β€” the same license as opencode and jcode.

Directories ΒΆ

Path Synopsis
cmd
dcode command
Command dcode is the agentic coding harness.
Command dcode is the agentic coding harness.
internal
app
Package app wires the layers into a runnable agent.
Package app wires the layers into a runnable agent.
behavior
Package behavior builds the system prompt.
Package behavior builds the system prompt.
config
Package config resolves where files live and what the effective settings are.
Package config resolves where files live and what the effective settings are.
contextengine
Package contextengine assembles the message list sent to the model.
Package contextengine assembles the message list sent to the model.
credential
Package credential stores model credentials outside the files a user syncs.
Package credential stores model credentials outside the files a user syncs.
evals
Package evals measures the behavioural contracts declared in the `.p` specs.
Package evals measures the behavioural contracts declared in the `.p` specs.
loop
Package loop is the turn cycle: it orchestrates the context engine, the provider, the tools and the sandbox.
Package loop is the turn cycle: it orchestrates the context engine, the provider, the tools and the sandbox.
memory
Package memory reads what the agent noted for itself in a previous session.
Package memory reads what the agent noted for itself in a previous session.
policy
Package policy decides what an agent may do, and what needs consent first.
Package policy decides what an agent may do, and what needs consent first.
protocol
Package protocol is the shared vocabulary between the dcode daemon and its clients.
Package protocol is the shared vocabulary between the dcode daemon and its clients.
provider
Package provider is the layer between dcode and the language models.
Package provider is the layer between dcode and the language models.
sandbox
Package sandbox enforces the boundary the policy package decides on.
Package sandbox enforces the boundary the policy package decides on.
server
Package server exposes sessions over HTTP with SSE, on a Unix domain socket.
Package server exposes sessions over HTTP with SSE, on a Unix domain socket.
session
Package session owns the server-side session: its append-only event log and its state machine.
Package session owns the server-side session: its append-only event log and its state machine.
specguard
Package specguard checks that every invariant a spec declares is claimed by a test that exists.
Package specguard checks that every invariant a spec declares is claimed by a test that exists.
tools
Package tools implements what turns dcode into a coding agent rather than a chat: reading, searching, editing and running.
Package tools implements what turns dcode into a coding agent rather than a chat: reading, searching, editing and running.
tui
Package tui is the terminal client.
Package tui is the terminal client.
update
Package update installs a newer dcode over the running one.
Package update installs a newer dcode over the running one.
vcs
Package vcs reads the state of the repository the agent is working in.
Package vcs reads the state of the repository the agent is working in.
version
Package version reports what build this is.
Package version reports what build this is.
pkg
client
Package client is the reference client for the dcode protocol.
Package client is the reference client for the dcode protocol.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL