dcode

module
v0.19.0 Latest Latest
Warning

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

Go to latest
Published: Sep 2, 2026 License: MIT

README ΒΆ

dcode

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

Release Go Coverage Specs Contracts License

dcode mascot

The coding agent that measures its own behaviour β€” and publishes the score it has not earned yet.

Every terminal agent ships behaviour as prompt text and hopes. dcode ships it as contracts with thresholds, runs them against a real model, and writes down what came back β€” the 98% and the 35% alike.

Right now: 59 contracts declared, 20 actually measured. That ratio is on the front page on purpose. It is the most uncomfortable number in this repository and the only honest one.

Status. Published and installable. The daemon, the terminal client, the agent loop, the OS sandbox and the verification cycle all ship. Breaking changes are still expected β€” every contract removed, threshold lowered or tool description reworded is at least a MINOR, because the surface of this product is partly made of sentences.

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ ● dcode   MiniMax-M3   workspace-write   ctx 34%                       β”‚
β”‚                                                                        β”‚
β”‚ ─  you ─────────────────────────────────────────────────────────────── β”‚
β”‚   /loop add CPF validation to checkout                                 β”‚
β”‚                                                                        β”‚
β”‚ ─  done, proposed ──────────────────────────────────────────────────── β”‚
β”‚   tests    go test ./src/checkout/...     red    β€” must turn green     β”‚
β”‚   vet      go vet ./...                   green  β€” must stay green     β”‚
β”‚   sign?  [enter] accept   [e] edit   [esc] cancel                      β”‚
β”‚                                                                        β”‚
β”‚ ─  cycle 2 ─────────────────────────────────────────────────────────── β”‚
β”‚   ⏺ read   src/checkout/handler.go                     240 lines       β”‚
β”‚   ⏺ edit   src/checkout/validate.go                       +24 βˆ’2       β”‚
β”‚   ⏺ bash   go test ./src/checkout/...                  βœ“ 12 pass       β”‚
β”‚   done  tests βœ“   vet βœ“                          all criteria met      β”‚
β”‚                                                                        β”‚
β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚
β”‚ β”‚ β€Ί                                           ^B files   ^R sessions β”‚ β”‚
β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Three things, and the third is the product

It runs inside an OS boundary. Apple Seatbelt on macOS, bubblewrap and Landlock on Linux. Sandbox and approvals are separate axes, so it asks about what is different in kind β€” not about everything, until you switch asking off and the security model becomes decoration.

It speaks to any model, and knows the difference. Transport Γ— family: the wire format is reusable, the measured thresholds belong to the model. "OpenAI-compatible" describes serialization, not behaviour β€” treating one as the other applies one model's numbers to another.

It knows when it is done, and when it went backwards. You declare the finish line as commands, or let it propose them and sign off. The loop runs them, feeds the failing output back to the model, and when an attempt breaks something that was passing it rolls that attempt back and says so out loud:

changelog:
  (it printed nothing) test -f CHANGELOG.md

tests:
  --- FAIL: TestSlugify
      slug_test.go:8: got "A B"

That block is not a log line. It is the reminder the model receives on the next cycle. finishes-work-that-takes-more-than-one-cycle reads 100% today and read 70% at its first measurement, before the output travelled and before a round ceiling stopped ending runs that were still working.


Receipts

Behaviour you cannot assert, you measure. Every contract carries a threshold, a model, a date, a run count and a rate β€” and the record is a Go value the build reads, not a table someone keeps up to date.

Contract Rate Runs Model
qualifier-proposes-commands 98% 50 MiniMax-M3
names-the-child-that-did-not-answer 98% 50 MiniMax-M3
keeps-writing-that-must-cohere 96% 50 MiniMax-M3
floor-yields-to-user 96% 50 MiniMax-M3
floor-does-not-ask 94% 50 MiniMax-M3
states-unmet-on-stall 94% 50 MiniMax-M3
fixes-what-the-output-named 100% 20 MiniMax-M3
finishes-work-that-takes-more-than-one-cycle 100% 20 MiniMax-M3
qualifier-declares-regression 80% 20 MiniMax-M3
delegates-writing-when-disjoint 50% 50 MiniMax-M3
floor-says-it-once 50% 20 MiniMax-M3
floor-yields-to-project 70% 20 MiniMax-M3
floor-names-what-it-yields-to 35% 20 MiniMax-M3

The last two rows stay. They were one contract until 1 September, and its number was the product of theirs β€” 70% Γ— 35% read as 15%, which describes neither half and looks like a failure of both. A conjunctive judge reports a product, and a product is not a finding.

Apart, each says something. Obeying a project file runs at 70% where the same rule from the turn runs at 96%: twenty-six points that say position in the prefix is not precedence, and that needs a mechanism rather than better wording. Naming the instruction runs at 35%, and the doctrine asks for an override to be obeyed and stated once. Two different defects; fixing one moves neither.

Five times in one week a low score turned out to be the instrument, not the model β€” a judge matching on the wrong field, a round ceiling that ended runs still working, a criterion whose error message read as its own opposite. Each time the number looked like a statement about the model and was a statement about the scenario. That is what measuring buys you, and what a second opinion does not: a reviewer would have agreed with all five.

Fifty-four of the 59 need a model to answer at all; five are settled by assertion. The 34 contracts never run against one are the point of the badge. Each costs real calls to a real model, and the number only moves by spending them.


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 verification cycle

/loop is the whole difference between a chat and a harness. It takes a goal and runs until a declared finish line is met, rather than until the model says it is finished.

/loop specs/2026-08-25-home-page      # criteria read from tasks.md
/loop implement the customer registry # no criteria anywhere β€” it proposes them

Where the criteria come from decides very little β€” .dcode/done.toml, a spec folder, or a proposal you sign, and one cycle consumes all of them. What the cycle does is the part worth reading:

  • It runs every criterion before the work starts. A criterion that is already green must stay green; one that is red is the target; one that does not run at all is broken, written into done.toml commented out, and never counted as passing.
  • When a criterion fails, its output reaches the model β€” the tail of it, the failing assertion itself. A criterion that printed nothing has its command named instead, because "it failed" and "here is what it said" are different amounts of help.
  • When a cycle breaks something that was passing, that cycle is undone. Not the turn, the cycle: the person's /undo and the loop's rollback answer two different questions and take two different snapshots. The model is told which names went backwards, and that the same change will be undone the same way.
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
The boundary

By default the agent runs in workspace-write with on-request approvals: it may edit inside the workspace, and a read outside it stops and asks. Without an approver it denies, because with nobody to ask the only alternative is granting in silence.

Two things that sound like they should ask do not, and both are deliberate.

The network is granted by default (sandbox.allow_network, default true). A shell command is opaque, so bash declares the network on every call β€” there is no reading of the string that says whether this one reaches out. Asking about the declaration meant asking about every build, every test run and every commit, and wherever nobody was there to answer it denied the whole shell: an agent that can edit but never verify produces change nobody checked. Set it to false and the OS blocks the network instead, per command.

Some writes outside the workspace are granted too: /tmp, /private/tmp, /private/var/tmp, /dev, and the toolchain caches named one by one β€” GOCACHE, GOMODCACHE, CARGO_HOME, ~/.npm, ~/.m2, $TMPDIR. Compilers and test runners stage work there, and refusing them makes ordinary builds fail in ways that look like the sandbox is broken rather than doing its job.

So a turn that fetches a file from the internet and writes it to /tmp asks nothing, and then asking to read it back is the step that stops β€” which is what it looks like, and it is the honest tool being the one that is gated. bash cat /tmp/x would not have asked, because a shell command declares only the workspace and the network. Containment is identical either way; only the question differs.

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. The read asymmetry above is the same principle, and it is written down here rather than discovered from a prompt that did not appear.

[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

The conversation gets the terminal. The file column starts hidden and ^B summons it; the conversation list is an overlay on ^R, which is what that key means in the shell it was borrowed from. Every question opens with a rule, so a screen of scrollback has a boundary in it. Copy mode is ^O.

That shape came from a measurement rather than a preference. Replaying a real recorded session at four widths, the column and the panel took 61 of 132 columns and left 71 for the conversation, while the same session at 99 columns β€” where both disappeared β€” gave it 99. Widening the terminal made the text narrower.

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. A line starting with ! is not sent to the model: it runs, through the same tool and the same boundary, and the field says so from the first character.

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.


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. That is where the 58 contracts come from, and why measuring one costs money while asserting one costs a millisecond.

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.

Numbers here are counted, not typed

The state table in CHANGELOG.md and the badges above are read by a test that counts the tree: families, decision changelogs, contracts declared, contracts needing a model, contracts settled by assertion, and contracts ever actually measured β€” in both language editions, against the prose beside the table as well as the table itself. A number inherited from the previous release fails the build.

It is the defect this repository keeps finding in itself β€” a value copied from a truth that moves β€” and it was found sitting in the document that exists to prevent it.


Specs

Eighteen families. Each declares its regime, and the regime decides how it is verified.

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
loop-command mixed /loop, the sources of a DoneSet, the dedicated session
done-qualifier mixed proposing criteria, measuring them before the work, the signature
failure-feedback deterministic the failing criterion's output reaching the model
recoverable-cycle deterministic detecting a regression, undoing the cycle that caused it
working-defaults mixed the floor, and who may replace it
delegated-writing mixed when writing is split between children, and who reports
task-ledger mixed what is in flight, and what it cost
learned-memory mixed what the agent discovers, versioned where people read it β€” design only, not built

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.

Roadmap

Delivered
the protocol vocabulary, the event log, unix socket and SSE βœ…
the pure Assemble and compaction planning βœ…
provider β€” transport Γ— family, both dialects βœ…
policy and the OS sandbox βœ…
the seven tools, and the agent loop βœ…
behaviour, config, wiring, CLI βœ…
TUI client, commands, skills, reminders, signed distribution βœ…
/loop, the done qualifier, failure feedback, cycle rollback βœ…

Coverage is 93.4%, the gate is 90% aggregate and per package, and the suite runs under -race on macOS and Linux, gated on the union of the profiles.

What is open, in order of how much it bothers us.

  1. floor-yields-to-project at 15%. Project instructions do not govern the built-in floor the way a user's prompt does. Same rule, different place. It needs a mechanism.
  2. 40 declared contracts never measured. Each costs real model calls.
  3. 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.


Prior art

Four terminal coding agents got here first, and each is genuinely good at something different. The comparison is at the bottom rather than the top because it is credit, not a pitch.

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 β€” with the behaviour on top of it written down as something that can fail a build.


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, the done qualifier, the loop command
  behavior/       the prompt builder
  evals/          the behavioural contracts, their judges, and what was measured
  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

Still early β€” the specs move faster than an outside PR can track. What is most 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.

One theme, one branch, one PR. A theme is what fits in a PR title without the word "and". A defect found while doing something else gets its own branch.

Conventional Commits on every commit message and PR title. Breaking changes carry ! before the colon and explain the break in the body. Behaviour is part of the contract: a contract removed, a threshold lowered, or a tool description that changes meaning is at minimum a MINOR β€” SemVer reads signatures, and part of this surface is made of sentences.

Commits that change technical behavior must keep the corresponding spec in sync β€” a spec is never allowed to go stale relative to the code. Every change lands in CHANGELOG.md, on the branch that makes it.

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.

90% coverage gate, aggregate and 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.

make check   # lint, race, coverage gate, build β€” the whole gate
make test    # the suite alone
make eval    # the behavioural contracts, against a real model, for real money

make eval is not in make check on purpose, and nothing in make check even compiles it β€” so make eval-build runs in any PR that touches the contracts, or they rot in silence.

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.
loop/loopcommand
Package loopcommand is the /loop faΓ§ade over the agent loop's done definition.
Package loopcommand is the /loop faΓ§ade over the agent loop's done definition.
loop/qualifier
Package qualifier raises a definition of done when there is none to read.
Package qualifier raises a definition of done when there is none to read.
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.
workspace
Package workspace reads what a project declares about itself.
Package workspace reads what a project declares about itself.
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