Documentation
¶
Overview ¶
Package app wires the layers into a runnable agent.
It is the only place that reads the environment and touches the outside world on behalf of the core: everything below stays pure or injectable, which is what keeps the rest exactly testable.
Index ¶
- Constants
- Variables
- func CommitProposal(ctx context.Context, p *Proposal, run loop.CriterionRunner, ...) (string, error)
- func CredentialName(opts Options) string
- func DefaultSocketPath(env func(string) string) string
- func DoctrineAudit(s *Session) string
- func Families() []provider.Family
- func InstructionNotice(workspace string, foreign []string, toolNames []string) string
- func LookupCredential(roots config.Roots, opts Options) (secret, from string)
- func QualifyingTool(specDir string, into *Proposals) tools.DonePropose
- func Resolve(env func(string) string, workspace string) (config.Resolved, error)
- func StampGenerated(workspace string, sources, written []string)
- type ConsoleApprover
- type ConsoleEmitter
- type Daemon
- type DaemonOptions
- type DenyAll
- type HTTPTransport
- type Options
- type Proposal
- type Proposals
- type Session
- type StandingGrants
Constants ¶
const DoneFileName = "done.toml"
DoneFileName is where the definition of done is declared.
Under .dcode/, which DefaultRules already submits to write confirmation. An agent that can edit its own definition of done widens its own reach, and that is literally why that rule exists — so this needs no new policy, only the right location.
const GeneratedFile = "DCODE.md"
GeneratedFile is the one file dcode writes on the user's behalf and then hands over. Everything else in the workspace is theirs from the start.
Variables ¶
var ForeignDefault = []string{"AGENTS.md", "CLAUDE.md"}
ForeignDefault are the instruction files treated as a shared format, and therefore as candidates for translation.
DCODE.md is never here: it is the dcode-specific file, the output of the translation rather than an input to it.
Functions ¶
func CommitProposal ¶ added in v0.13.0
func CommitProposal(ctx context.Context, p *Proposal, run loop.CriterionRunner, timeout time.Duration) (string, error)
CommitProposal measures a recorded proposal and writes it into the spec folder, returning what a person reads.
This is the loop's half, and it runs OUTSIDE the qualifying turn on purpose. Measuring under read-only would call a criterion broken because the sandbox refused it a cache directory, and a proposal born with a false measurement is worse than none.
func CredentialName ¶
func DefaultSocketPath ¶
DefaultSocketPath resolves where the daemon listens.
Kept short deliberately: a Unix socket path is capped near 104 bytes on macOS, and the XDG state directory alone can exhaust that. Falling back to the temp directory with the uid keeps two users on one machine apart.
func DoctrineAudit ¶
DoctrineAudit renders where each doctrine section came from, and everything the overlay loader refused to do silently.
It is printed under --dump-prompt because a replacement nobody can see would be worse than the immutability it replaces: before the overlay existed, the prompt itself was the whole answer to "what is in force". Now it is not, and this is the rest of the answer.
func InstructionNotice ¶
InstructionNotice is what the session says at the start about instruction files it read that were not written for dcode.
It does NOT block. Requiring setup before answering "what does this function do" in a freshly cloned repository is the bureaucratic tool RN-9 names, and a gate that stops you is a gate you learn to walk through without reading.
The warning is enough, because the problem today is not that dcode cannot decide — it is that nobody knows this is happening.
func LookupCredential ¶
LookupCredential reads the stored key for these options.
A failure is silent on purpose: the store not being reachable is not a reason to refuse to start, and the turn that needs the key reports a clear auth error with a message that says where to set it.
func QualifyingTool ¶ added in v0.13.0
func QualifyingTool(specDir string, into *Proposals) tools.DonePropose
QualifyingTool builds the done_propose tool for a qualifying turn.
The turn runs in PLAN mode: read-only, and no approval to grant. The tool touches nothing — it records, and the loop measures and writes afterwards.
func Resolve ¶
Resolve builds the configuration chain and nothing else.
Split out from FromEnv because commands that are not a session — `update` is the one today — still have to read configuration through the same chain. A command reading os.Getenv directly is how `update.channel` came to be a declared key that a config file could never reach.
func StampGenerated ¶
StampGenerated records, inside a freshly generated DCODE.md, the digests of the instruction files it was generated from.
Without the record the divergence warning cannot exist. `/init` reads AGENTS.md and writes a DCODE.md from it; months later AGENTS.md changes, and the DCODE.md still says what the old one said — silently, because a generated file that has since been hand-edited looks exactly like one that is still current. RenderDigest, Diverged and the warning itself were all written; the marker was never put in the file, so Diverged could only ever answer "nothing changed".
Deterministic, and done by the product rather than asked of the model. A prompt requesting the marker would be a prompt hoping for it, and the digests have to be of the bytes actually read.
It runs after a turn, so failure here must stay quiet. This is bookkeeping, and a session that breaks because a comment could not be appended has traded a missing warning for a broken tool.
Types ¶
type ConsoleApprover ¶
ConsoleApprover asks on the terminal.
func (*ConsoleApprover) Approve ¶
func (a *ConsoleApprover) Approve(_ context.Context, req protocol.ApprovalRequest) (protocol.ApprovalDecision, error)
Approve prompts and reads a decision. Anything other than an explicit yes is a refusal: the safe answer must be the one that costs least effort.
type ConsoleEmitter ¶
ConsoleEmitter renders events for the development entry point.
Deliberately minimal: it exists so the core can be exercised before the TUI, and it holds no session state of its own, exactly like any other client.
type Daemon ¶
type Daemon struct {
// contains filtered or unexported fields
}
Daemon owns the server and the session manager.
func (*Daemon) Listen ¶
Listen binds the socket ahead of Serve, so a caller can report the address before the blocking call.
type DaemonOptions ¶
type DaemonOptions struct {
SocketPath string
MaxSessions int
EventRetention int
// RecordDir is where each session is written, one JSONL file per session.
// Empty turns recording off, and then retention is a hard horizon: a
// client away longer than it gets events_expired, and nobody can read the
// session afterwards either.
RecordDir string
// RecordBudget is how much history survives. Applied when a session opens
// rather than on a timer: nothing should be deleting a person's history
// while the program is not running.
RecordBudget session.PruneBudget
ApprovalTimeout time.Duration
Base Options
// Log receives operational notices. Nil silences them, which a test wants
// and a daemon must not.
Log func(string)
}
DaemonOptions configure the server process.
type DenyAll ¶
type DenyAll struct{}
DenyAll refuses every crossing. Used for non-interactive runs, where there is nobody to ask and granting in silence would be the only alternative.
func (DenyAll) Approve ¶
func (DenyAll) Approve(context.Context, protocol.ApprovalRequest) (protocol.ApprovalDecision, error)
Approve always denies.
type HTTPTransport ¶
type HTTPTransport struct {
// contains filtered or unexported fields
}
HTTPTransport speaks a wire format over HTTP with SSE. It knows nothing about families: a `if family == X` in here would collapse the two axes back into one, and the symptom only shows up at the third family.
func NewHTTPTransport ¶
func NewHTTPTransport(name, baseURL, apiKey string) *HTTPTransport
NewHTTPTransport builds a transport for a wire format.
func (*HTTPTransport) Do ¶
func (t *HTTPTransport) Do(ctx context.Context, wire provider.WireRequest) (<-chan provider.WireEvent, error)
func (*HTTPTransport) Name ¶
func (t *HTTPTransport) Name() string
type Options ¶
type Options struct {
Workspace string
Model string
Transport string
Family string
APIKey string
BaseURL string
SandboxMode policy.SandboxMode
Policy policy.ApprovalPolicy
Backend string
AllowNetwork bool
Parallel int
Limits loop.Limits
DumpPrompt bool
// Reminders switches the appended-notice channel on.
Reminders bool
// ShowReasoning forwards the model's thinking to clients.
ShowReasoning bool
// Instructions switches the reading of AGENTS.md and DCODE.md on. Off runs
// on the shipped doctrine alone, which is how one tells whether a behaviour
// comes from the user's instructions or from the product.
Instructions bool
// Skills switches progressive disclosure on. Off removes the index from the
// prefix, and no body is ever loaded.
Skills bool
// BudgetNotice switches the occupancy warning to the model on.
BudgetNotice bool
// VerifyCommand is the command that COUNTS as verification. Explicit,
// because "some bash ran" would count an `ls`.
VerifyCommand string
// DoneTimeout caps one criterion. A check that never finishes is not a
// check, and hanging the turn is worse than reporting the overrun.
DoneTimeout time.Duration
// DoneEnabled switches re-entry on unmet criteria on. Off restores the old
// behaviour: the turn ends when the model stops calling tools, met or not.
DoneEnabled bool
// Qualify makes this the session that works out what "done" means for
// LoopSpec instead of the one that does the work. Forces plan mode and
// offers done_propose; nothing else changes.
Qualify bool
// LoopSpec is a directory holding a tasks.md, read as this session's
// definition of done instead of done.toml. Empty is the ordinary case.
LoopSpec string
// Protect are globs added to whatever the spec declares as protected.
Protect []string
// DoneFile overrides where the definition of done is declared.
DoneFile string
// MaxStallCycles is how many cycles without progress end a turn.
MaxStallCycles int
// Delegate switches the read-only delegation tool on. Off removes it from
// the registry; nothing else changes.
Delegate bool
// Unreadable are paths this session may not read at all — a credential
// store put out of reach. Resolved at the edge, from configuration.
Unreadable []string
// Granted are unix sockets named as reachable, and Writable are paths
// named as writable outside the workspace. Both resolved at the edge.
Granted []string
Writable []string
// DelegateMaxIterations caps a child turn.
DelegateMaxIterations int
// DelegateMaxResultBytes caps the child's report.
DelegateMaxResultBytes int
// WorkspaceGates switches the inventory of the project's declared checks
// on. On by default: the probe reads two files, runs nothing, and costs one
// read at session open. A cheap probe that ships off is a probe nobody
// turns on.
//
// It exists for the repository with a seventy-target Makefile, where the
// cap still leaves a list nobody reads.
WorkspaceGates bool
// InstructionNotice switches the session-start warning about untranslated
// instruction files on. It warns; it never blocks.
InstructionNotice bool
// InstructionForeign are the files treated as a shared format, and so as
// candidates for translation. DCODE.md is never one of them.
InstructionForeign string
// Fetch switches the network tool on. Off by default: the network is the
// one capability whose absence nobody has to work around, so it is the one
// that earns an opt-in rather than an opt-out.
Fetch bool
// FetchMaxBytes caps a fetched document.
FetchMaxBytes int
// SymbolMaxMatches caps what symbol returns. Same ceiling as grep, and for
// the same reason: a symbol matching thousands of times is a badly chosen
// symbol, and returning all of it spends context without informing.
SymbolMaxMatches int
// EditEchoDiff decides when the diff of an edit goes back to the model.
// It always reaches the client, in every mode.
EditEchoDiff string
// DoctrineOverlay switches the reading of the user's doctrine/ directory
// on. Off runs on the shipped doctrine alone, which is how one tells
// whether a behaviour comes from the user's overlay or from the product.
DoctrineOverlay bool
// DoctrineDir overrides where the overlay is read from. Empty means
// doctrine/ under the user's config root — never the workspace (RN-11).
DoctrineDir string
// DoctrineMaxBytes caps each overlay file. Smaller than the instruction
// cap because this is the base layer, paid on every turn of every session.
DoctrineMaxBytes int
// Env is how the session reaches the environment. Carried on Options rather
// than read from the process, so a daemon serving several workspaces is not
// forced to share one view of it.
Env func(string) string `json:"-"`
// CredentialFrom records where the key came from, so `dcode config` can
// answer "which one is this" without ever printing it.
CredentialFrom string
// Rules ask a question the sandbox cannot, for paths and commands that are
// different in kind from ordinary work.
Rules policy.Rules
// History seeds the conversation, for a session continuing a recorded one.
//
// It is not compacted here. The engine checks at the top of its first
// iteration, before any request, so a seeded history that is too large is
// handled by the same code that handles one that grew — and compacting
// here would be a second implementation of the thing most worth having
// exactly one of.
History []ce.Message
// Steer hands the running turn what the person said without ending it.
//
// It rides here rather than as a parameter for the same reason History
// does: the engine is built before the session that owns the queue exists,
// so this is a closure bound late, exactly like the emitter and the
// approver. Nil is a session nobody can steer, which is every non-daemon
// path and was the only behaviour until now.
Steer func() string
// Memory reads what earlier sessions in this workspace learned. Off is the
// product from before this existed.
Memory bool
// MemoryMax is how many memories reach the prefix. See the .config spec:
// the default is a starting value, not a defended number.
MemoryMax int
// CredentialBackend selects the store. Empty chooses.
//
// Configuration rather than a per-command flag: a flag on the command that
// writes, and nothing on the commands that read, stores the secret
// somewhere nothing looks for it.
CredentialBackend string
}
Options are the resolved settings for one session.
func QualifyMode ¶ added in v0.13.0
QualifyMode forces plan mode on a qualifying session.
Exported so the eval harness takes the boundary from here rather than writing one of its own. A scenario that measured the qualifying turn under its own idea of the boundary would be measuring a different turn, and this package has been bitten four times by a copy of product text drifting from the product.
Not negotiable by the request, and separated out so it can be asserted: working out what "done" means is reading, and an agent that could write while deciding what it will be measured by can move the thing it is about to be measured against.
type Proposal ¶ added in v0.13.0
Proposal is a definition of done the model derived, recorded and not yet written down.
Recorded rather than written because the turn that produces it runs in plan mode: working out what you will be measured by is reading, and read-only denies every write with no exception. The loop takes it from here — it measures the criteria under the boundary the WORK will run under, which is also the only place they can actually run, and writes the file.
type Proposals ¶ added in v0.13.0
type Proposals struct {
// contains filtered or unexported fields
}
Proposals is where a qualifying session keeps what the model proposed.
One slot, replaced. A model that proposes twice has changed its mind, and keeping both would leave the loop choosing between them.
type Session ¶
type Session struct {
Engine *loop.Engine
Registry *tools.Registry
// State is the other half of Registry: every tool's Execute takes one, so
// a registry handed out without it cannot be called. It is also what owns
// the background processes, which is why "a process dies with its session"
// is a consequence of this chain rather than a cleanup step.
State *tools.State
Prompt string
Options Options
// Origins is where each doctrine section came from, and Notices is what
// the overlay loader refused to do silently. Both exist for the audit:
// an invisible replacement would be worse than the immutability it
// replaces (RN-12).
// Notice is what the session has to say at the start about instruction
// files written for another tool. Empty when there is nothing to say.
Notice string
Origins behavior.SectionOrigins
DoctrineNotice []behavior.Notice
// SkillNotice is what was trimmed or skipped while loading skills.
//
// Separate from DoctrineNotice because they answer different questions and
// a reader looking for one should not have to read past the other.
SkillNotice []behavior.Notice
// ContextWindow is what the provider reports for this model.
ContextWindow int
// Proposals is where a qualifying session keeps what the model proposed,
// until the loop takes it. Nil in every other session.
Proposals *Proposals
// Standing is what the user has already permitted. Carried on the session
// so the daemon attaches the same record the sandbox is asking, rather than
// loading a second copy that could answer differently.
Standing *StandingGrants
}
Session is a wired agent ready to take turns.
type StandingGrants ¶
type StandingGrants struct {
// Root is the USER's config root. Never the workspace: a record inside a
// project would let a repository arrive pre-approved.
Root string
Workspace string
// contains filtered or unexported fields
}
StandingGrants answers, for one workspace, which crossings the user has already permitted — and writes down the ones they permit from now on.
This is the layer that knows both sides: the session knows a crossing was declared, the config knows what the user has recorded, and neither should learn the other's vocabulary. Which boundaries are worth remembering is a product decision, and it lives here.
func NewStandingGrants ¶
func NewStandingGrants(root, workspace string) (*StandingGrants, error)
NewStandingGrants loads what the user has already permitted.
A record that cannot be read grants nothing and says so. Starting a session that silently permits more than the user agreed to is worse than refusing to start, and worse than asking again.
func (*StandingGrants) Granted ¶
func (s *StandingGrants) Granted(req protocol.ApprovalRequest) protocol.ApprovalDecision
Granted reports a decision the user already made for this crossing.
func (*StandingGrants) NetworkNow ¶
func (s *StandingGrants) NetworkNow() bool
NetworkNow reports whether the network is permitted at this moment.
Asked per command rather than read once, because the answer can arrive mid-session: the user is asked at the first crossing and says yes. A boundary fixed at startup would leave that answer with no effect until a restart.
func (*StandingGrants) Remember ¶
func (s *StandingGrants) Remember(req protocol.ApprovalRequest, d protocol.ApprovalDecision) error
Remember writes down an answer meant to outlive the session.
It is applied in memory first and persisted second, so a session whose disk write fails still honours the decision for the rest of its life. The user answered; losing the file costs them being asked again next time, which is the safe direction to fail in.