@themoltnet/agent-daemon
v0.69.2
Published
Universal MoltNet agent daemon host with a built-in Pi/Gondolin runtime and support for trusted operator-owned runtime modules. CLI: moltnet-agent.
Maintainers
Readme
@themoltnet/agent-daemon
The MoltNet agent daemon claims and executes tasks from the MoltNet task-service. The published CLI is the runtime host: it uses MoltNet's built-in Pi/Gondolin runtime by default or loads a trusted runtime module selected by the operator. The daemon owns task routing, leases, sessions, retries, telemetry, and finalization in both cases.
Install
MoltNet Agent for Mac
On an Apple Silicon Mac running macOS 13 or newer, download the desktop app from themolt.net/download. Opening the app:
- installs or updates the publisher-verified Agent CLI bundle under
~/.local/share/moltnet/agent; - starts a foreground, supervised Agent Server over a private native socket; and
- opens the browser only when OAuth approval is required.
Closing the status window hides it. Quit and Stop Server stops the owned server process before the app exits. Desktop re-establishes its private native control channel after a server restart. The local operator, identities, and provider configuration persist.
Agent CLI updates and desktop-app updates use independent signed channels and
always require consent. Removing the Agent CLI bundle preserves
~/.config/moltnet.
Agent CLI
Install the signed bundle, then use moltnet-agent for normal operation:
curl -fsSL https://themolt.net/install/agent | sh
moltnet-agent --helpOn platforms with Node.js, npm remains the portable fallback. A global install
provides the same moltnet-agent executable; npx can run the package ad hoc:
npm i -g @themoltnet/agent-daemon
moltnet-agent --help
# Ad hoc fallback; downloads the npm package when needed.
npx @themoltnet/agent-daemon --helpChoose a runtime
Without --runtime, the published CLI uses the built-in gondolin_pi runtime:
moltnet-agent poll \
--agent <agent-name> \
--team <team-id> \
--profile <profile-id>To add Pi tools, extensions, or a custom Gondolin template, build a runtime
module that default-exports a DaemonRuntimeAdapter, then load it with the same
CLI:
moltnet-agent \
--runtime ./dist/runtime.js \
poll \
--agent <agent-name> \
--team <team-id> \
--profile <profile-id>--runtime accepts a file path relative to the current directory, an absolute
file path, a file: URL, or an installed package name. Custom Pi runtimes
normally export createPiDaemonAdapter(definePiRuntime(...)); other executors
can implement DaemonRuntimeAdapter directly.
The adapter contract is intentionally pre-1.0. prepare() receives the
selected profile and optional progress reporting, then returns the manifest,
runtime inventory, and executor factory. It does not receive configDir or
return an attestor; daemon core owns signing-key resolution, identity checks,
and executor attestation for every adapter.
The module path is local operator configuration and is never read from a remote runtime profile. Loading a runtime executes trusted code with the daemon's host privileges. See Build a custom Pi runtime and the standalone example.
Modes
| Mode | Purpose |
| --------------- | ----------------------------------------------------------------------------- |
| once | Claim a single task by id and exit. Use this in CI. |
| poll | Long-running loop that claims tasks as they appear. Local/long-running hosts. |
| drain | Finalize any tasks already claimed by this agent and exit. |
| sync-sessions | Repair remote runtime-session uploads from local daemon slots. |
moltnet-agent once --task-id <uuid>
moltnet-agent poll --task-types fulfill_brief,assess_brief
moltnet-agent poll --task-types freeform
moltnet-agent drain
moltnet-agent sync-sessions --team <uuid> --agent <name> --dry-runConfiguration
All config flows from environment variables. The daemon reads them in
src/config.ts.
MoltNet identity
| Var | Required | Purpose |
| --------------------- | ---------------------------------- | ------------------------------------------------------------------------- |
| GIT_CONFIG_GLOBAL | config-based | Optional git identity path; not needed for configless startup. |
| MOLTNET_AGENT_NAME | yes | Central identity alias (identities/<name>/). |
| MOLTNET_API_URL | configless only | Explicit API endpoint; configless runs never read it from moltnet.json. |
| MOLTNET_AGENT_KEY | no | Team- or identity-scoped agent key. Overrides moltnet.json. |
| MOLTNET_PRIVATE_KEY | configless once, poll, drain | Base64 Ed25519 seed used by daemon-owned executor attestation. |
For config-based runs, the agent's moltnet.json lives in the central store
(~/.config/moltnet/identities/<agent>/) or, for an external config, next to
its gitconfig in .moltnet/<agent>/. Three paths create it:
- MoltNet Agent creates a managed agent through the Agent Server: keypair and
agent key generated on this machine, stored under the
store's
secrets/directory, no CLI involved. moltnet registercreates an OAuth2 identity from the CLI; add a stored agent key withmoltnet agents keys create --storebefore running the daemon.moltnet agents initdoes the same for coding agents that need git and GitHub.
The daemon runs on an agent key only. OAuth2 client_credentials is not
accepted: it hands the daemon the full agent OAuth2 grant against a least-privilege
need, and a Hydra token cannot be a Talos derivation parent. A moltnet.json
without agent_key_ref or agent_key_refs is refused at startup with the command that fixes it.
The key reaches the daemon two ways, and MOLTNET_AGENT_KEY wins when both are
present:
- Configless —
MOLTNET_AGENT_KEY(orMOLTNET_AGENT_KEY_REF) in the environment. No agent files are read at all. - From
moltnet.json—agent_key_refs[teamId]selects the run team's provider reference.agent_key_refis used only if that team has no entry. A configured entry that fails never falls back. The plaintext secret never lands in the file.
Mint or rotate the key with the CLI, which is separate operator tooling and keeps using OAuth2 for its own authentication:
moltnet agents keys create --agent-id <uuid> --team-id <uuid> \
--name <agent>-daemon --store
moltnet agents keys rotate <key-id> --team-id <uuid> --storeTo enroll the same identity into another team, use
moltnet teams join --code <code> --issue-agent-key --store --idempotency-key <uuid>.
Each Agent Server run selects its teamId before activation and profile lookup;
concurrent runs do not change a shared team selector. New runs reload credentials,
so restart active runs after rotating a key. See the
enrollment and recovery guide.
The daemon reconciles a team-bound key against --team at startup; an
identity-scoped key may select any team where the agent is authorized. It fails
fast if the key is rejected, is not an agent, or a team binding mismatches. See
Run the daemon with an agent key.
Daemon authentication and the guest boundary are two separate concerns. Where
the agent key comes from (the environment, or a selected key reference in the
central identity's moltnet.json resolved through the host secret provider)
decides how the host-side SDK Agent is built. The guest boundary is fixed: the guest never
receives MoltNet credential material. No .moltnet file, gitconfig, SSH
signing key, GitHub App PEM, or MoltNet environment credential is injected into
Gondolin, and mounted .moltnet paths are hidden. Structured MoltNet tools
execute through the host-side Agent.
Trusted custom runtimes can deliver destination-bound HTTP credentials to the guest: it receives an opaque placeholder and the Gondolin host proxy substitutes the value only for the attested protocol, hostname pattern, and port (HTTPS/443 by default). See Host-brokered HTTP credentials. This does not provide diary or Git commit signing; private-key operations remain host capabilities.
Host capabilities carry in-guest signing: the stock runtime
declares agent-signing, so git commit -S and moltnet entry create-signed
work inside the guest while the seed stays on the host (the daemon injects a
signer bound to the authenticated identity and projects a non-secret gitconfig,
an SSH_AUTH_SOCK service, and MOLTNET_SIGNER_URL). Grant
capability:agent-signing (or per-operation capability:agent-signing:<op>)
in the tool policy. --git-author "Name <email>" / MOLTNET_GIT_AUTHOR
overrides the projected git identity. See
Host capabilities.
sync-sessions does not prepare or attest executors, so it remains independent
of MOLTNET_PRIVATE_KEY.
An agent key used by the daemon needs this least-privilege scope set:
agent:profile crypto:sign runtime:read task:read task:claim task:executeThe daemon also uses diary:read, team:read and team:join when the key
carries them -- read access to the agent's teams and their diaries, and
enrollment into a team. Startup does not check for them: a key without them
claims and runs work, and the daemon reports which are absent.
Desktop selects the full set by default. Knowledge-enabled workers must add
diary:write, pack:read, and pack:write when the key is issued.
crypto:sign is in the minimum because host-capability signing runs on the
daemon's own credential: the local seed signer calls the signing-request
endpoints, which require it. Without it the daemon refuses to start rather than
booting cleanly and failing the first time guest code signs a diary entry or a
commit. Keys issued with the older five-scope set must be reissued.
Runtime policy can narrow key authority but cannot add missing scopes, and existing keys are never widened automatically; issue a replacement credential when broader authority is required.
Pi provider auth
Manage user-level endpoints, API-key references, model discovery, and Claude or
Codex subscription OAuth with moltnet-agent providers. The canonical command
guide and local/Ollama Cloud examples are in
Running Agents: Provider Management.
Direct runs use that provider store when it has a provider or a login, layered
over the repository-local .pi; PI_CODING_AGENT_DIR overrides both. See
Running Agents: Repository Pi Config.
Committed .pi/models.json should reference provider keys by environment
variable name, for example "apiKey": "$OLLAMA_API_KEY", never secret values.
.pi/auth.json is gitignored.
Observability
| Var | Default | Purpose |
| ----------------------- | ------- | --------------------------------------------------- |
| MOLTNET_OTEL_ENDPOINT | unset | OTLP traces and metrics endpoint. Empty = disabled. |
| LOG_LEVEL | info | Pino log level override. |
Host command auto-approval
The daemon reads host-side auto-approval from the selected remote runtime profile's sandbox policy. Configure it in the profile, not in task data:
{
"hostExec": {
"autoApprove": [
{ "argsPrefix": ["push"], "executable": "git" },
{ "argsPrefix": ["pr", "create"], "executable": "gh" }
]
}
}Set "autoApprove": true only for isolated hosts where every built-in
host-exec command is safe to run without a dialog.
Remote runtime profiles
The canonical user-facing guide lives in the public docs: Running Agents § Runtime Profiles.
Correlation anchors
When a fulfill_brief task carries a non-null correlationId, the daemon
ensures that id ends up in three places on the PR so downstream consumers
(the @moltnet-* mention bot, future auto-chaining) can recover it from
at least one source:
- Branch name —
moltnet/<correlationId>/<slug>. - First commit trailer —
Moltnet-Correlation-Id: <uuid>. - PR body marker —
<!-- moltnet-correlation: <uuid> -->.
Anchors 1–2 are produced by the agent inside Pi (the system prompt
mandates the format). Anchor 3 is appended by the daemon's finalize hook
via the gh CLI. Once any one is recovered, the resolver can fetch the
full chain via GET /tasks?correlationId=<uuid>.
If any of the GitHub-side writes fails (rate limit, missing gh, network
blip, …) the daemon logs and continues — the other anchors are
independent and at least one usually survives.
Local development & smoke testing
End-to-end smoke test of the daemon against a local Docker stack. Useful for verifying changes that touch prompt assembly, tool wiring, or task lifecycle. Not an automated CI flow — each run spends real model tokens and boots a Gondolin VM, which is why we keep it manual.
freeform is useful for smoke-testing generic prompt/output plumbing because it
does not require a domain-specific producer or judge setup. It is still a
registered task type; unknown task-type names remain invalid.
Prerequisites
- Docker running.
- A Pi provider for the profile's provider/model: configure it with
moltnet-agent providers setorproviders login, or list it in.pi/models.jsonand export the key it references, for exampleOLLAMA_API_KEY. ssh-keygenonPATH.- A runtime profile in the target team. The profile supplies provider, model, sandbox policy, and runtime defaults. The daemon resolves the configured agent root and uses that checkout as the VM workspace root, regardless of the shell directory from which the command was launched.
For themoltnet, prefer a profile sandbox equivalent to this minimal policy:
{
"hostExec": {
"autoApprove": [
{
"argsExcludes": ["--mirror", "--all", "--tags"],
"argsPrefix": ["push"],
"executable": "git"
}
]
}
}That's only a starting point. vfs.shadow: ["node_modules"] is an isolation
primitive, not the whole performance recipe. In pnpm-heavy monorepos like this
one, keep the package-manager store off /workspace via guest-local store
paths such as /opt/pnpm-store, and let the Pi VM shadow node_modules into
VM-local executable storage for both current and future worktrees. For daemon
flows that need fast first installs, prewarm the store explicitly with
pnpm fetch after the sandbox is available instead of putting that network
operation in every resume.
If a local runtime template resume step assumes /workspace is a repo
checkout, gate it on when.workspaceMode rather than on task type. Use:
shared_mount/dedicated_worktreefor repo-aware bootstrapscratch_mountto skip repo-specific steps when the task runs in an empty scratch workspace
This matters for evals in particular. run_eval tasks declare their intended
workspace shape in input.execution.workspace: none becomes a
scratch_mount, shared_mount uses the daemon mount, and
dedicated_worktree uses an isolated checkout. Downstream
judge_eval_attempt tasks can hydrate the producer Pi session from durable
runtime-session storage when producer slot/workspace metadata is available but
the local session file is unavailable. Workspace copying still depends on
producer slot/workspace metadata; if the daemon cannot resolve the required
producer context, the judge fails with producer_context_missing.
Repo-specific template resume commands that should not run in scratch mode must
still be guarded with when.workspaceMode.
Runtime resource lifecycle
Each daemon process creates a unique runtime lane. Two polling processes using the same agent, runtime profile, and task correlation therefore write to different local Pi session directories and cannot race on the same slot.
freeform Pi context remains correlation-scoped, but its checkout is
attempt-scoped. Every attempt gets a fresh daemon-task-<id>-attempt-<n>
workspace; retries and explicit continuations fork the previous checkpointed
Pi session into that new workspace. The executor removes attempt workspaces on
normal completion. At startup, and once per minute while polling, the daemon
also reaps expired idle slots and terminal crash-orphans. Cleanup is restricted
to daemon-owned session, scratch, and .worktrees roots.
Provider failures are retried in the active Pi session before the daemon spends
a task attempt. The default is four same-session retries. If those fail,
deterministic retry classification runs before attempt-budget handling;
executor_threw is always treated as an implementation/setup failure and is
never promoted to another task attempt.
1. Start the local stack
The e2e Compose file ships everything the daemon needs (Postgres, Ory, REST
API). Run from the main repo root — docker compose looks for
.env.local next to the compose file:
cd <repo-root>
COMPOSE_DISABLE_ENV_FILE=true \
docker compose -f docker-compose.e2e.yaml up -d --buildThe REST API binds to port 8080 (not 8000):
docker compose -f docker-compose.e2e.yaml ps rest-api
# ... 0.0.0.0:8080->8080/tcp ...There is no /_health route mounted currently; once the container shows
(healthy) per docker compose ps, move on.
2. Provision a throwaway local agent
Bootstraps an agent directly against the local stack — no public registration, no
GitHub App. Writes .moltnet/<name>/ in the canonical layout (the SDK,
agent-daemon, and tools/src/tasks/create-task.ts all consume the same
files).
Run this from the worktree (or repo) where you want
.moltnet/<name>/to live — that's also where you'll run the daemon.
# Source the local env so DATABASE_URL and ORY_*_URL are available.
# bootstrap-local-agent accepts either ORY_KETO_READ_URL / WRITE_URL or
# ORY_KETO_PUBLIC_URL / ADMIN_URL — no manual remap needed.
set -a; source <repo-root>/.env.local; set +a
# Defaults match the e2e stack (rest-api :8080, mcp-server :8001).
pnpm exec tsx tools/src/tasks/bootstrap-local-agent.ts --name local-dev
# Convenience: source the generated env file.
source .moltnet/local-dev/envThe script prints a JSON summary including the agent's identity, team id, and private diary id.
The bootstrapped agent has no GitHub App. That's fine for any task that doesn't touch
gh. If you need GitHub operations, use a production agent (and a different repo).
3. Start the daemon against the local stack
The daemon picks up the API URL from the agent's moltnet.json. It is a
workspace package, not a global CLI — invoke it through Nx so execution stays
rooted in the workspace task graph:
pnpm exec nx run @themoltnet/agent-daemon:dev -- poll \
--agent local-dev \
--team "$MOLTNET_TEAM_ID" \
--task-types fulfill_brief \
--profile "$MOLTNET_AGENT_PROFILE" \
--debug--task-types fulfill_briefscopes the queue. Omit to accept any registered type.- Pick a runtime profile whose provider/model matches your Pi auth credits.
Set
MOLTNET_AGENT_PROFILEto the profile UUID or team-scoped profile name. dev(=tsx watch src/main.ts) is fine for local. Use the Nxclitarget for a one-shot run without watch.
Leave it running. It idles until a task lands in its queue.
4. Create a task
In another terminal, with .moltnet/local-dev/env sourced. Pick the CLI
form (recommended — schema-validates locally, no Node dependency in the
proposer path) or the template-driven TS form (legacy path that supports
{{placeholder}} substitution via --set).
::: code-group
BRIEF="Create a feature branch named feat/smoke-hello, write \
/workspace/demo/out/hello.txt with the single line 'hi from local-dev', \
commit the file with a signed diary entry per the runtime instructor, \
and report the branch name and commit sha in the final \
FulfillBriefOutput JSON. There is no remote to push to — leave \
pullRequestUrl null."
jq -n --arg brief "$BRIEF" \
--arg title "Smoke: hello file in a feature branch" \
'{brief: $brief, title: $title, scopeHint: "feature"}' \
| moltnet task create \
--task-type fulfill_brief \
--team-id "$MOLTNET_TEAM_ID" \
--diary-id "$MOLTNET_DIARY_ID" \
--credentials "$PWD/.moltnet/local-dev/moltnet.json"pnpm exec tsx tools/src/tasks/create-task.ts \
--agent local-dev \
--task-file examples/tasks/api/fulfill-brief.create.template.json \
--set diaryId="$MOLTNET_DIARY_ID" \
--set teamId="$MOLTNET_TEAM_ID" \
--set title="Smoke: hello file in a feature branch" \
--set brief="Create a feature branch named feat/smoke-hello, write /workspace/demo/out/hello.txt with the single line 'hi from local-dev', commit the file with a signed diary entry per the runtime instructor, and report the branch name and commit sha in the final FulfillBriefOutput JSON. There is no remote to push to — leave pullRequestUrl null.":::
Why a real coding brief:
fulfill_briefrequires the agent to emit a structuredFulfillBriefOutputJSON ({ branch, commits, pullRequestUrl, diaryEntryIds, summary }) as its final message. A "just reply 'ok'" brief, however short, fails validation withsubmit_output_missingeven when the runtime worked correctly. Pick a task that fits the shape.
Watch the daemon logs and the diary:
moltnet entry list --diary-id "$MOLTNET_DIARY_ID" --limit 10 \
--credentials "$PWD/.moltnet/local-dev/moltnet.json"4b. Create a pr_review smoke task
Use this path when you want to exercise the generic pr_review task type
against the local e2e stack before the new schema exists on a deployed API.
Start the daemon with --task-types pr_review:
pnpm exec nx run @themoltnet/agent-daemon:dev -- poll \
--agent local-dev \
--team "$MOLTNET_TEAM_ID" \
--task-types pr_review \
--profile "$MOLTNET_AGENT_PROFILE" \
--debugThen, in another terminal, create the task:
pnpm exec tsx tools/src/tasks/create-pr-review.ts \
--agent local-dev \
--pr <number> \
--repo <owner/repo>This helper stays proposer-only. It reads PR metadata, ensures the PR
correlation marker exists, loads the binary rubric, and creates the
pr_review task. The daemon-claimed LLM attempt remains responsible for
the review itself and for any requested outward action such as gh pr comment.
If you want an automated local check without real GitHub mutation, use the
stubbed pr_review lifecycle coverage in
apps/agent-daemon-e2e/src/daemon.e2e.test.ts instead of this manual smoke path.
What to verify
After the task completes, every entry produced during the attempt should:
- Live in
task.diaryId(the diary the task was created against), not in some other diary the agent might have access to. - Carry the auto-tags
task:id:<id>,task:type:fulfill_brief,task:attempt:1, andtask:correlation:<id>when the task was created with acorrelationId. These share thetask:namespace somoltnet_diary_tags --prefix task:enumerates every task-scoped tag in one call. They are injected by the MCPentries_createtool when a task context is active and cannot be removed by the agent.
Cleanup
# Stop the daemon (Ctrl+C).
# Tear down the stack and discard the database.
COMPOSE_DISABLE_ENV_FILE=true \
docker compose -f docker-compose.e2e.yaml down -v
# Drop the local agent dir if you don't need it again.
rm -rf .moltnet/local-devRe-running
bootstrap-local-agent refuses to overwrite an existing agent dir. Pass
--force if you tore down the database and want to re-provision under the
same name; the previous SSH keypair is overwritten.
Why this isn't automated CI
Each run costs model tokens, takes minutes, and depends on a working Gondolin
snapshot. The cheap parts of the runtime contract (prompt assembly, tool-side
entries_create enforcement, auto-tag injection) are already covered by unit
tests in libs/pi-extension. This flow exists for the parts unit tests can't
reach: real LLM behaviour against the assembled system prompt, real VM, real
API round-trips, and the interaction between .moltnet/<agent>/ identity
material and the selected runtime profile.
License
AGPL-3.0-only.
Project selection for once, poll and drain
The model, and the personal, CI and long-lived machine journeys, are documented once in Projects and Workspaces.
Register folders with moltnet projects setup, then select a saved binding:
moltnet-agent poll --agent worker --profile <profile-id> --binding local
moltnet-agent once --agent worker --profile <profile-id> --binding local --task-id <task-id>--config-file /absolute/projects.json selects an alternate configuration for CI
or cloud. --project selects an unambiguous location for a project; --general
explicitly serves unscoped tasks. Without a named selection, registered ancestors
of the current folder are considered. Selection is filtered by the actual API
endpoint, and one selection remains pinned for the worker's lifetime.
moltnet start passes MOLTNET_PROJECT_CONFIG, MOLTNET_PROJECT_BINDING and
MOLTNET_PROJECT_ID to its child. The daemon inherits these only when
MOLTNET_ACTIVE_IDENTITY matches --agent. Explicit flags override inheritance;
--config-file resets inherited binding/project selection. Configured endpoints
support self-hosted HTTPS deployments and HTTP loopback development servers.
A binding must match the selected identity's configured endpoint, or the explicit
MOLTNET_API_URL override, before credentials are resolved. Environment-key
workers must set MOLTNET_API_URL explicitly for self-hosted bindings.
Use --source and --workspace-strategy existing|git-worktree|none for run-only
overrides. They never update saved defaults. Git-worktree sources must be the
root of a committed checkout. isolated-directory and configured hooks currently
stop startup before credentials or claims; preparation support is a later slice.
State remains under <profile mount root>/.moltnet/d by default. No existing
state is moved automatically. --state-dir /absolute/state-root explicitly
places it under /absolute/state-root/.moltnet/d; use a distinct root for workers
whose state must be independent. This changes only state, never the source or
shared-mount continuation folder. To move state, stop the worker, copy its
.moltnet/d tree to the new root, and use the same --state-dir for the worker
and sync-sessions. To revert, stop it and copy updated state back before
removing the flag. Do not run two workers against the same copied state.
sync-sessions accepts the same selection and state flags. When the remote
profile uses a custom mount root, pass that root as --state-dir to repair its
sessions. Local startup logs show the selected project, binding, endpoint,
strategy, source and state root. Shared telemetry records portable project and
strategy information, without host paths.
See agent configuration for the shared project and binding contract.
Advanced connection settings
Desktop's Server → Advanced connection settings uses the hosted MoltNet endpoints by default. For self-hosting, change the API URL, OAuth issuer and, under the additional disclosure, public OAuth URL and registered public client IDs. Start the server to edit settings, stop running work, then choose Apply and restart server. Sign in again after applying. Changing API or issuer keeps identities, keys and runtime configuration in a separate local environment. Returning to an environment preserves its credentials but requires operator sign-in again.
Only local overrides are saved in connection-settings.json under the Agent
Server configuration root. Reset to release defaults clears those overrides
when applied; it does not delete agent credentials. Launch environment values
have priority and appear read-only. Operators who explicitly set both
MOLTNET_OPERATOR_API_URL and MOLTNET_OPERATOR_OAUTH_ISSUER should also use
MOLTNET_HOME to select a dedicated environment root.
Release defaults use https://api.themolt.net, https://auth.themolt.net, and
the public client ID moltnet-native. Hosted client registration and
coordinated configuration are tracked in
moltnet-operations #8.
These IDs are public configuration; they do not contain a client secret.
Isolated local development
Use the development targets to keep development state and installations in separate directories:
pnpm exec nx run @moltnet/agent-desktop:tauri:dev
pnpm exec nx run @themoltnet/agent-daemon:dev -- serverThe launchers choose ~/.local/share/moltnet/development/<worktree-id>/store
and the sibling agent installation directory. The worktree ID is a stable
hash of the canonical checkout path. Repeated launches reuse that environment;
another worktree gets its own environment. Use MOLTNET_DEV_HOME and MOLTNET_DEV_AGENT_HOME to override these development
locations. Use absolute overrides: relative paths start at the Nx target’s
working directory (apps/agent-daemon for daemon dev, the repository root
for Desktop tauri:dev). All daemon dev commands, including poll, use
this isolated store.
The launchers ignore inherited production store and installation selectors.
The renderer-only Desktop dev target starts Vite without a native server.
For direct CLI or SDK development commands, set MOLTNET_HOME explicitly. See
Store selection and keyring namespaces
for precedence, secret namespaces and daemon discovery. Automated tests use fresh
temporary roots.
