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@dbd0dfd9459bc0f6d55fabaa7471eed4f9df2c93Entrypoint
Return Type
Dagmar !Arguments
| Name | Type | Default Value | Description |
|---|---|---|---|
| project | Directory | - | The target Project's source directory (per-Project binding seam). |
Example
dagger -m github.com/denkhaus/dagmar@dbd0dfd9459bc0f6d55fabaa7471eed4f9df2c93 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
| Name | Type | Default Value | Description |
|---|---|---|---|
| source | Directory ! | - | source is the project source directory (read-only). The Adjudicator reads files from here to investigate the root cause of the disagreement. |
| gateResult | String ! | - | gateResult is the gate’s outcome: “green” or “red” plus which checkables failed (if red) and their failure messages. |
| reviewResult | String ! | - | reviewResult is the reviewer’s outcome: “approve” or “veto” plus the reviewer’s rationale. |
| taskContext | String ! | - | taskContext is the original issue text / task description the coder was asked to implement. |
| model | String | "anthropic/claude-sonnet-4" | model is the LLM model identifier (e.g. “anthropic/claude-sonnet-4”). The Adjudicator needs strong reasoning (ADR-0023 D6). |
| maxApicalls | Integer | 30 | maxAPICalls bounds the LLM API calls for this adjudication Run. Higher than the prompter (adjudication may require deeper investigation of source |
| moduleRef | String | ".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@dbd0dfd9459bc0f6d55fabaa7471eed4f9df2c93 call \
adjudicate --source DIR_PATH --gate-result string --review-result string --task-context stringfunc (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
| Name | Type | Default Value | Description |
|---|---|---|---|
| source | Directory ! | - | source is the workspace Directory — the project source the agent works on (clone from ADR-0020 D1: dag.Git(repoURL).Branch(branchName).Tree()). |
| promptFile | File ! | - | promptFile is the resolved prompt .md (ADR-0005 cross-store merge, pre-computed by the controller). The agent receives this via WithPromptFile. |
| model | String | "anthropic/claude-sonnet-4" | model is the LLM model identifier (e.g. “anthropic/claude-sonnet-4”). |
| maxApicalls | Integer | 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. |
| moduleRef | String | ".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@dbd0dfd9459bc0f6d55fabaa7471eed4f9df2c93 call \
code --source DIR_PATH --prompt-file file:pathfunc (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
| Name | Type | Default Value | Description |
|---|---|---|---|
| cluster | String | "dagmar-spike" | name of the throwaway k3s cluster |
Example
dagger -m github.com/denkhaus/dagmar@dbd0dfd9459bc0f6d55fabaa7471eed4f9df2c93 call \
deploy-enginefunc (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
| Name | Type | Default Value | Description |
|---|---|---|---|
| after | Directory ! | - | after is the post-Loop workspace (Code’s return value). |
| before | Directory ! | - | before is the pre-Loop workspace (the original clone). |
Example
dagger -m github.com/denkhaus/dagmar@dbd0dfd9459bc0f6d55fabaa7471eed4f9df2c93 call \
diff --after DIR_PATH --before DIR_PATHfunc (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
| Name | Type | Default Value | Description |
|---|---|---|---|
| cluster | String | "dagmar-spike" | name of the throwaway k3s cluster |
Example
dagger -m github.com/denkhaus/dagmar@dbd0dfd9459bc0f6d55fabaa7471eed4f9df2c93 call \
probefunc (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
| Name | Type | Default Value | Description |
|---|---|---|---|
| mode | String ! | - | which leg of the test to run: write | readsame | readdiff |
Example
dagger -m github.com/denkhaus/dagmar@dbd0dfd9459bc0f6d55fabaa7471eed4f9df2c93 call \
probe-cache --mode stringfunc (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@dbd0dfd9459bc0f6d55fabaa7471eed4f9df2c93 call \
probe-netfunc (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
| Name | Type | Default Value | Description |
|---|---|---|---|
| source | Directory ! | - | source is the project source directory (read-only). The prompter reads files from here to ground the synthesized prompt in real project context. |
| phase | String ! | - | phase selects which meta-prompt to use: “pre-code” (coder) or “pre-review” (reviewer). |
| taskContext | String ! | - | taskContext is the issue text / task description from the orchestrating Run. |
| model | String | "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). |
| maxApicalls | Integer | 10 | maxAPICalls bounds the LLM API calls for this synthesis Run. Low budget — prompt synthesis is well-bounded (ADR-0023 D1). |
| moduleRef | String | ".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@dbd0dfd9459bc0f6d55fabaa7471eed4f9df2c93 call \
prompt --source DIR_PATH --phase string --task-context stringfunc (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
| Name | Type | Default Value | Description |
|---|---|---|---|
| image | String ! | - | Base OCI image for the Sandbox container. |
| workingDir | String | - | 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@dbd0dfd9459bc0f6d55fabaa7471eed4f9df2c93 call \
sandbox --image stringfunc (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
| Name | Type | Default Value | Description |
|---|---|---|---|
| cluster | String | "dagmar-spike" | name of the throwaway k3s cluster |
Example
dagger -m github.com/denkhaus/dagmar@dbd0dfd9459bc0f6d55fabaa7471eed4f9df2c93 call \
upfunc (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@dbd0dfd9459bc0f6d55fabaa7471eed4f9df2c93 call \
sandbox --image string \
containerfunc (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()
}