Dagger
Search

dagmar

This is dagmar's own Dagger module (seed cbb8 spike: engine tenancy & Run concurrency).
It prototypes dagmar's Hybrid-C topology hermetically: an in-cluster Dagger engine
serving agent pods, brought up inside an isolated k3s cluster that itself runs inside
the OUTER Dagger engine (Docker Desktop). The outer engine runs this module; the inner
engine (deployed into k3s) is the system under test. Never touches the production
netcup cluster.

Installation

dagger install github.com/denkhaus/dagmar@6a11c4bf000c69a12aa7b6f24c8e613aff333dcb

Entrypoint

Return Type
Dagmar !
Arguments
NameTypeDefault ValueDescription
projectDirectory -The target Project's source directory (per-Project binding seam).
Example
dagger -m github.com/denkhaus/dagmar@6a11c4bf000c69a12aa7b6f24c8e613aff333dcb call \
func (m *MyModule) Example() *dagger.Dagmar  {
	return dag.
			Dagmar()
}
@function
def example() -> dagger.Dagmar:
	return (
		dag.dagmar()
	)
@func()
example(): Dagmar {
	return dag
		.dagmar()
}

Types

Dagmar 🔗

Dagmar is dagmar’s main Dagger object (auto-named from the module). It is the primary entry point into dagmar’s Dagger functionality AND the per-Project binding seam: the New constructor binds the target Project once, and every method (Run, Sandbox, Gate, …) reuses that bound state (ADR-0010 §5). Project Hook Services (issues, memory, prompts) are exposed as native Dagger functions via WithMainModule(), not Go ports (ADR-0018).

adjudicate() 🔗

Adjudicate is dagmar’s adjudicator-loop entry point (ADR-0023 D4). When the deterministic Gate and the Reviewer-LLM disagree (gate green + reviewer veto, or gate redapprove), the Adjudicator resolves the conflict — it is the final automated decision maker before human escalation.

The Adjudicator is read-only: it reads source, issues, and memory to investigate the disagreement, but modifies nothing. It returns a structured verdict string naming one of three resolution paths: reviewer-wrong (calibrate reviewer, proceed), gate-wrong (coder repairs the gate checkables, full re-run), or escalate (unresolvable, human needed).

Unlike Prompt/Code, the Adjudicator does NOT use a chained prompter — its instructions come directly from the adjudicator meta-prompt (prompts.AdjudicatorMetaPrompt). The controller dispatches this via dagger call -m .dagger adjudicate --source <dir> .... Delegates to app.Adjudicate.

The args are primitives + Dagger types because Dagger codegen requires main-package types only. The app layer builds the read-only Env, sends the meta-prompt + disagreement context, and drives the Loop (ADR-0010 §3: Tier A direct).

Return Type
String !
Arguments
NameTypeDefault ValueDescription
sourceDirectory !-

source is the project source directory (read-only). The Adjudicator reads files from here to investigate the root cause of the disagreement.

gateResultString !-

gateResult is the gate’s outcome: “green” or “red” plus which checkables failed (if red) and their failure messages.

reviewResultString !-

reviewResult is the reviewer’s outcome: “approve” or “veto” plus the reviewer’s rationale.

taskContextString !-

taskContext is the original issue text / task description the coder was asked to implement.

modelString "anthropic/claude-sonnet-4"

model is the LLM model identifier (e.g. “anthropic/claude-sonnet-4”). The Adjudicator needs strong reasoning (ADR-0023 D6).

maxApicallsInteger 30

maxAPICalls bounds the LLM API calls for this adjudication Run. Higher than the prompter (adjudication may require deeper investigation of source

moduleRefString ".dagmar"

moduleRef is the project module reference (the Project CR’s moduleRef). Registers dagmar-issues + dagmar-memory as LLM-Tool hooks via WithMainModule. Defaults to “.dagmar” (dagmar dogfooding itself).

Example
dagger -m github.com/denkhaus/dagmar@6a11c4bf000c69a12aa7b6f24c8e613aff333dcb call \
 adjudicate --source DIR_PATH --gate-result string --review-result string --task-context string
func (m *MyModule) Example(ctx context.Context, source *dagger.Directory, gateResult string, reviewResult string, taskContext string) string  {
	return dag.
			Dagmar().
			Adjudicate(ctx, source, gateResult, reviewResult, taskContext)
}
@function
async def example(source: dagger.Directory, gateresult: str, reviewresult: str, taskcontext: str) -> str:
	return await (
		dag.dagmar()
		.adjudicate(source, gateresult, reviewresult, taskcontext)
	)
@func()
async example(source: Directory, gateResult: string, reviewResult: string, taskContext: string): Promise<string> {
	return dag
		.dagmar()
		.adjudicate(source, gateResult, reviewResult, taskContext)
}

code() 🔗

Code is dagmar’s coder-loop entry point (Phase 2 cognition, ADR-0021 D1). It constructs the Env, drives the LLM Loop, and returns the modified workspace Directory. The controller dispatches this via dagger call -m .dagger code --source <dir> --prompt-file <md>. Delegates to app.Code.

The args are primitives + Dagger types (Directory, File) because Dagger codegen requires main-package types only. The app layer builds the Env + LLM + Loop from these (ADR-0010 §3: Tier A direct). The prompt file is pre-composed by the controller (ADR-0005 merge).

Return Type
Directory !
Arguments
NameTypeDefault ValueDescription
sourceDirectory !-

source is the workspace Directory — the project source the agent works on (clone from ADR-0020 D1: dag.Git(repoURL).Branch(branchName).Tree()).

promptFileFile !-

promptFile is the resolved prompt .md (ADR-0005 cross-store merge, pre-computed by the controller). The agent receives this via WithPromptFile.

modelString "anthropic/claude-sonnet-4"

model is the LLM model identifier (e.g. “anthropic/claude-sonnet-4”).

maxApicallsInteger 100

maxAPICalls bounds the LLM API calls for this Run (token/cost cap, ADR-0021 D4). Engine-enforced hard stop: when exhausted, the Loop terminates.

moduleRefString ".dagmar"

moduleRef is the project module reference (the Project CR’s moduleRef). Defaults to “.dagmar” (dagmar dogfooding itself).

Example
dagger -m github.com/denkhaus/dagmar@6a11c4bf000c69a12aa7b6f24c8e613aff333dcb call \
 code --source DIR_PATH --prompt-file file:path
func (m *MyModule) Example(source *dagger.Directory, promptFile *dagger.File) *dagger.Directory  {
	return dag.
			Dagmar().
			Code(source, promptFile)
}
@function
def example(source: dagger.Directory, promptfile: dagger.File) -> dagger.Directory:
	return (
		dag.dagmar()
		.code(source, promptfile)
	)
@func()
example(source: Directory, promptFile: File): Directory {
	return dag
		.dagmar()
		.code(source, promptFile)
}

deployEngine() 🔗

DeployEngine deploys the singleton Dagger engine as a privileged DaemonSet into k3s and reports the Ready engine pod. (cbb8 spike — the nesting test.)

Return Type
String !
Arguments
NameTypeDefault ValueDescription
clusterString "dagmar-spike"

name of the throwaway k3s cluster

Example
dagger -m github.com/denkhaus/dagmar@6a11c4bf000c69a12aa7b6f24c8e613aff333dcb call \
 deploy-engine
func (m *MyModule) Example(ctx context.Context) string  {
	return dag.
			Dagmar().
			Deployengine(ctx)
}
@function
async def example() -> str:
	return await (
		dag.dagmar()
		.deployengine()
	)
@func()
async example(): Promise<string> {
	return dag
		.dagmar()
		.deployEngine()
}

diff() 🔗

Diff computes the difference between a pre-Loop and post-Loop workspace (ADR-0021 D8). The controller calls this after Code() to extract the agent’s changes for the PR flow (ADR-0020 D3). Returns a Directory containing only the changed files.

Return Type
Directory !
Arguments
NameTypeDefault ValueDescription
afterDirectory !-

after is the post-Loop workspace (Code’s return value).

beforeDirectory !-

before is the pre-Loop workspace (the original clone).

Example
dagger -m github.com/denkhaus/dagmar@6a11c4bf000c69a12aa7b6f24c8e613aff333dcb call \
 diff --after DIR_PATH --before DIR_PATH
func (m *MyModule) Example(after *dagger.Directory, before *dagger.Directory) *dagger.Directory  {
	return dag.
			Dagmar().
			Diff(after, before)
}
@function
def example(after: dagger.Directory, before: dagger.Directory) -> dagger.Directory:
	return (
		dag.dagmar()
		.diff(after, before)
	)
@func()
example(after: Directory, before: Directory): Directory {
	return dag
		.dagmar()
		.diff(after, before)
}

probe() 🔗

Probe validates Research Q3: can a Dagger CLIENT reach the singleton (nested) engine via kube-pod://? Deploys the engine, then runs dagger core version in a client container pointed at the inner engine through _EXPERIMENTAL_DAGGER_RUNNER_HOST=kube-pod://… A version reported from the inner engine proves the singleton engine serves clients — the precondition for multi-tenancy on one engine.

Return Type
String !
Arguments
NameTypeDefault ValueDescription
clusterString "dagmar-spike"

name of the throwaway k3s cluster

Example
dagger -m github.com/denkhaus/dagmar@6a11c4bf000c69a12aa7b6f24c8e613aff333dcb call \
 probe
func (m *MyModule) Example(ctx context.Context) string  {
	return dag.
			Dagmar().
			Probe(ctx)
}
@function
async def example() -> str:
	return await (
		dag.dagmar()
		.probe()
	)
@func()
async example(): Promise<string> {
	return dag
		.dagmar()
		.probe()
}

probeCache() 🔗

ProbeCache is the dagmar-d8f0 spike: it empirically validates that a Dagger engine isolates cache by volume NAME (the ADR-0008 §3 design assumption — “Dagger isolates cache by volume name; cache poisoning across Projects is prevented as long as Projects use distinct cache-volume names”). It is run as THREE separate dagger call probe-cache --mode ... invocations — i.e. three separate client sessions against one engine, the cheapest faithful analogue of “two client pods on the singleton engine”:

dagger call probe-cache --mode write     # write MARKER_A into cache volume "…-A"
dagger call probe-cache --mode readsame  # read volume "…-A"  (expect MARKER_A → shares)
dagger call probe-cache --mode readdiff  # read volume "…-B"  (expect EMPTY   → isolates)

If readsame sees the marker AND readdiff does not, name-based isolation is CONFIRMED (the ADR-0008 §3 claim holds locally); the remaining cross-Project concern is then purely the controller’s allocation of distinct names (a control-plane guarantee). LLM-free. (cbb8/d8f0-style spike; to be refactored into a platform workflows/ package if/when one is introduced — the gate-family workflows/ moved to the .dagmar project module at ADR-0014.)

Return Type
String !
Arguments
NameTypeDefault ValueDescription
modeString !-

which leg of the test to run: write | readsame | readdiff

Example
dagger -m github.com/denkhaus/dagmar@6a11c4bf000c69a12aa7b6f24c8e613aff333dcb call \
 probe-cache --mode string
func (m *MyModule) Example(ctx context.Context, mode string) string  {
	return dag.
			Dagmar().
			Probecache(ctx, mode)
}
@function
async def example(mode: str) -> str:
	return await (
		dag.dagmar()
		.probecache(mode)
	)
@func()
async example(mode: string): Promise<string> {
	return dag
		.dagmar()
		.probeCache(mode)
}

probeNet() 🔗

ProbeNet is the dagmar-911b trust-zone spike: it empirically tests whether a Dagger container exec has outbound network access by default. Dagger v0.21.8 exposes NO per-exec no-network option (ContainerWithExecOpts has no network/egress field). This establishes the residual-risk fact that ADR-0011 consciously accepts: tool-set exclusion is NOT a hard network guarantee (a raw exec path can still reach the network). LLM-free. (cbb8-style spike; to be refactored into a platform workflows/ package if/when one is introduced — the gate-family workflows/ moved to the .dagmar project module at ADR-0014.)

Return Type
String !
Example
dagger -m github.com/denkhaus/dagmar@6a11c4bf000c69a12aa7b6f24c8e613aff333dcb call \
 probe-net
func (m *MyModule) Example(ctx context.Context) string  {
	return dag.
			Dagmar().
			Probenet(ctx)
}
@function
async def example() -> str:
	return await (
		dag.dagmar()
		.probenet()
	)
@func()
async example(): Promise<string> {
	return dag
		.dagmar()
		.probeNet()
}

prompt() 🔗

Prompt is dagmar’s prompter-loop entry point (ADR-0023 D1). It synthesizes a tailored prompt for the Coder or Reviewer by running a short LLM loop that reads project source, issues, and memory. The synthesized prompt is returned as a string — the controller forwards it as –prompt-file to the subsequent Code or Review Run.

The args are primitives + Dagger types because Dagger codegen requires main-package types only. The app layer builds the read-only Env, selects the meta-prompt by phase, and drives the Loop (ADR-0010 §3: Tier A direct). Delegates to app.Prompt.

Return Type
String !
Arguments
NameTypeDefault ValueDescription
sourceDirectory !-

source is the project source directory (read-only). The prompter reads files from here to ground the synthesized prompt in real project context.

phaseString !-

phase selects which meta-prompt to use: “pre-code” (coder) or “pre-review” (reviewer).

taskContextString !-

taskContext is the issue text / task description from the orchestrating Run.

modelString "anthropic/claude-sonnet-4"

model is the LLM model identifier (e.g. “anthropic/claude-sonnet-4”). The prompter may use a smaller/faster model — synthesis is well-bounded (ADR-0023 D6).

maxApicallsInteger 10

maxAPICalls bounds the LLM API calls for this synthesis Run. Low budget — prompt synthesis is well-bounded (ADR-0023 D1).

moduleRefString ".dagmar"

moduleRef is the project module reference (the Project CR’s moduleRef). Registers dagmar-issues + dagmar-memory as LLM-Tool hooks via WithMainModule. Defaults to “.dagmar” (dagmar dogfooding itself).

Example
dagger -m github.com/denkhaus/dagmar@6a11c4bf000c69a12aa7b6f24c8e613aff333dcb call \
 prompt --source DIR_PATH --phase string --task-context string
func (m *MyModule) Example(ctx context.Context, source *dagger.Directory, phase string, taskContext string) string  {
	return dag.
			Dagmar().
			Prompt(ctx, source, phase, taskContext)
}
@function
async def example(source: dagger.Directory, phase: str, taskcontext: str) -> str:
	return await (
		dag.dagmar()
		.prompt(source, phase, taskcontext)
	)
@func()
async example(source: Directory, phase: string, taskContext: string): Promise<string> {
	return dag
		.dagmar()
		.prompt(source, phase, taskContext)
}

sandbox() 🔗

Sandbox realizes an isolated, credentialed execution slot (a Dagger Container — Tier A, used directly; ADR-0010 §3). This is the v0 vertical proving the layout seams (functional core -> app Tier-A-direct -> main delegation -> a chainable custom return object) without an LLM call. Delegates to app.BuildSandbox.

NOTE: the args are primitives (not a domain.SandboxSpec) because Dagger cannot code-generate for a foreign (non-main-package) input type. The pure domain.SandboxSpec is constructed at this seam from the primitives; domain stays Dagger-free and unit-tested (ADR-0010 §3).

Return Type
Sandbox !
Arguments
NameTypeDefault ValueDescription
imageString !-

Base OCI image for the Sandbox container.

workingDirString -

Working directory inside the Sandbox (empty = image default). Named workingDir, not workdir, to avoid a CLI flag collision with *dagger.Container’s own workdir field.

Example
dagger -m github.com/denkhaus/dagmar@6a11c4bf000c69a12aa7b6f24c8e613aff333dcb call \
 sandbox --image string
func (m *MyModule) Example(image string) *dagger.DagmarSandbox  {
	return dag.
			Dagmar().
			Sandbox(image)
}
@function
def example(image: str) -> dagger.DagmarSandbox:
	return (
		dag.dagmar()
		.sandbox(image)
	)
@func()
example(image: string): DagmarSandbox {
	return dag
		.dagmar()
		.sandbox(image)
}

up() 🔗

Up brings up an isolated k3s cluster inside Dagger and proves the API is reachable.

First checkpoint of the cbb8 spike: validates that k3s-in-Dagger works on this host (nested privileges / cgroup v2) before we deploy the Dagger engine DaemonSet into it.

Return Type
String !
Arguments
NameTypeDefault ValueDescription
clusterString "dagmar-spike"

name of the throwaway k3s cluster

Example
dagger -m github.com/denkhaus/dagmar@6a11c4bf000c69a12aa7b6f24c8e613aff333dcb call \
 up
func (m *MyModule) Example(ctx context.Context) string  {
	return dag.
			Dagmar().
			Up(ctx)
}
@function
async def example() -> str:
	return await (
		dag.dagmar()
		.up()
	)
@func()
async example(): Promise<string> {
	return dag
		.dagmar()
		.up()
}

Sandbox 🔗

Sandbox is the Dagger object returned by Dagmar.Sandbox — a thin, chainable wrapper over the realized Container. Exported methods on it become callable Dagger functions.

container() 🔗

Container returns the underlying Dagger Container (Tier A).

Return Type
Container !
Example
dagger -m github.com/denkhaus/dagmar@6a11c4bf000c69a12aa7b6f24c8e613aff333dcb call \
 sandbox --image string \
 container
func (m *MyModule) Example(image string) *dagger.Container  {
	return dag.
			Dagmar().
			Sandbox(image).
			Container()
}
@function
def example(image: str) -> dagger.Container:
	return (
		dag.dagmar()
		.sandbox(image)
		.container()
	)
@func()
example(image: string): Container {
	return dag
		.dagmar()
		.sandbox(image)
		.container()
}