Dagu harness
Dagu custom harness
The CLI specification and its native command asset define the command contract. This guide explains its use.
The Dagu custom harness starts one t3c run process, supplies its prompt on
stdin, and consumes the final assistant message on stdout. Progress and thread
identity go to stderr. The exit code preserves run, request, transport and
credential outcomes; 124 belongs to Dagu.
binary: t3c
prefix_args: [run]
prompt_mode: stdin
with:
model: gpt-6-luna
project-path: /workspace/project
timeout: 5m
Dagu supplies T3_TOKEN and T3_SERVER_URL through masked secrets. The CLI
resolves these through the library profile module. An explicit --server or
--credentials overrides the corresponding environment input. T3_TOKEN alone
uses the current profile’s server. The process uses the same profile and
credential contract as other library consumers.
Pushback continues the existing conversation with t3c run --thread <id> and
a new stdin prompt. Thread ids emitted on stderr remain available when the
process detaches, times out, blocks on a request, or loses its connection.
Without --timeout, run waits without a run deadline. The Dagu timeout and
the CLI run timeout are separate controls. CLI timeout interruption uses the
library’s interrupt operation with holdQueue; Dagu can also terminate the
process with a signal. A signal reports exit 4 and the first signal requests
interruption. User-input requests require explicit responses through
thread respond; an automated harness does not invent answers.
Run the committed fixtures
The repository provides new-run and
continuation fixtures. They require Dagu
with harness.run support, an installed matching CLI/library consumer with its
node_modules/.bin first on PATH, an available protocol 2 server, and provider
access for gpt-6-luna. The recorded fixture verification used Dagu 2.20.1,
Node 24.22.0 and stock T3 0.1.0. These are the tested versions.
Supply SAMPLE_T3_TOKEN and SAMPLE_T3_SERVER_URL to the Dagu process through
your existing environment setup. The fixtures resolve these as masked
T3_TOKEN and T3_SERVER_URL secrets. Keep their values out of command
arguments, YAML and collected output. For isolated testing, use an owned server,
profile and project directory, a fresh external Dagu home, and an environment
without inherited operational DAGU_*, T3_LIVE_*, direct T3_TOKEN,
T3_SERVER_URL or T3CODE_HOME overrides. The setup supplies the two
SAMPLE_* environment inputs in memory before invoking Dagu.
From the repository root, copy the fixtures into the owned Dagu home. The
example uses an existing absolute project directory /workspace/sample;
replace that JSON value with your owned project directory. Pass each JSON
object as one argument.
dagu_home=$(mktemp -d /tmp/sample-dagu-XXXXXX)
cp scripts/dagu/verification.yaml "$dagu_home/sample-success.yaml"
cp scripts/dagu/continuation.yaml "$dagu_home/sample-continuation.yaml"
cp scripts/dagu/verification.yaml "$dagu_home/sample-timeout.yaml"
dagu --dagu-home "$dagu_home" validate "$dagu_home/sample-success.yaml"
dagu --dagu-home "$dagu_home" start --run-id sample-success \
--params '{"PROJECT":"/workspace/sample","PROMPT":"Reply SAMPLE only.","TIMEOUT":"120s"}' \
"$dagu_home/sample-success.yaml"
dagu --dagu-home "$dagu_home" history sample-success --format json
The new-run fixture requires PROJECT; its blank default is not a project.
The harness sends the prompt on stdin and invokes t3c run --model gpt-6-luna --project-path <project> --timeout 120s. Success yields SAMPLE on stdout,
a Thread: identity on stderr, and a successful Dagu step and run.
Read that new thread identity from stderr. Replace <thread-id> in the next
JSON object with it. Continuation requires THREAD and omits PROJECT and
project-path so that the conversation retains its workspace.
dagu --dagu-home "$dagu_home" validate "$dagu_home/sample-continuation.yaml"
dagu --dagu-home "$dagu_home" start --run-id sample-continuation \
--params '{"THREAD":"<thread-id>","PROMPT":"Pushback: replace the answer with NEXT only.","TIMEOUT":"120s"}' \
"$dagu_home/sample-continuation.yaml"
dagu --dagu-home "$dagu_home" history sample-continuation --format json
Expect NEXT on stdout and the same thread identity. Dagu can warn that the
continuation has no working directory; the CLI uses the thread’s workspace.
The recorded library assertions verified two distinct completed runs in that
thread.
The timeout case uses the new-run fixture and the same existing project:
dagu --dagu-home "$dagu_home" validate "$dagu_home/sample-timeout.yaml"
dagu --dagu-home "$dagu_home" start --run-id sample-timeout \
--params '{"PROJECT":"/workspace/sample","PROMPT":"Use the shell tool to sleep 30 seconds, then reply SAMPLE only.","TIMEOUT":"1s"}' \
"$dagu_home/sample-timeout.yaml"
dagu --dagu-home "$dagu_home" history sample-timeout --format json
Default timeout interruption returns CLI exit 6, empty stdout, a thread
identity and the final stderr line t3c: Timed out; interruption requested..
Dagu records a failed step with exit status 6 in its error and returns outer
launcher exit 1 for the failed DAG. These exits are distinct; neither is
124. No Dagu timeout_sec is added. The recorded fixture verification did
not separately exercise detach exit 5.
These fixtures invoke the CLI. They do not start a server, issue credentials, or assert server projections. The recorded verification used a separate test launcher and the installed library to check prompt/model/project linkage, thread identity, completed runs and interrupted timeout state. That launcher is not a tracked repository tool. See testing design for the verification and cleanup boundary.
Keep only sanitized invocation, stdout/stderr and run records. An entire Dagu home includes generated authentication material and is not an evidence bundle. After testing, stop or delete owned subject threads through the library, close the client, stop the owned server, and remove the owned Dagu, server, profile and project roots. Keep sanitized evidence separately. Use a fresh home and new subjects for each attempt.