Add claude-code harness runtime — agents into running processes#11
Merged
Conversation
Wires declared agents into running processes. For each services.forge.agents.<name> with harness = "claude-code", the new modules/harnesses/claude-code.nix generates: - a wrapper script forge-agent-<name> that sets up the agent's state dir, writes CLAUDE.md from the declared role, plumbs the discord bot token from the secret file (if discordBot is set), and execs claude through a shared tmux server (-L forge) for PTY + attachability - a systemd service running as the runAs user with restart-on-failure - claude-code, tmux, and the wrappers in environment.systemPackages Adds claude-code-nix (github:sadjow/claude-code-nix) as a flake input and passes it to modules via specialArgs. Decisions baked into this PR (open questions from PR #9): - linger NOT used; system services with User=<runAs> autostart natively via wantedBy = [ "multi-user.target" ] - state dir at ~/.local/state/forge/agents/<name>/ (XDG, per-user) - Restart=on-failure, RestartSec=5s, StartLimitBurst=3 - tmux always on (claude-code needs a PTY); shared "forge" socket - harness lives in modules/harnesses/<harness>.nix so codex and future custom processes swap in cleanly without touching the schema Updates deployments/agicash-team-forge/configuration.nix with a commented-out example agent paired with the sops bot-token declaration, ready to uncomment after the secrets bootstrap. Commits the first flake.lock for reproducibility. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
gudnuf
reviewed
May 27, 2026
| export DISCORD_STATE_DIR="$STATE_DIR" | ||
| ''} | ||
|
|
||
| export CLAUDE_CODE_MAX_THINKING_TOKENS=-1 |
Contributor
There was a problem hiding this comment.
I'm not sure that this does anything anymore. Can you verify that?
Verified against claude-code 2.1.150 unwrapped binary: only CLAUDE_CODE_EFFORT_LEVEL and CLAUDE_CODE_SIMPLE are read. CLAUDE_CODE_MAX_THINKING_TOKENS is not referenced anywhere in the binary; the setting was carried over from v1's launch-agent but no longer does anything. Replace with `--effort max` on the claude invocation, matching v1's default effort level. Per-agent override via an `effort` agent option is a follow-up if/when we want it. Addresses gudnuf's review comment on PR #11. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
gudnuf
approved these changes
May 27, 2026
orveth
added a commit
that referenced
this pull request
May 27, 2026
Verified: --bare with HOME set but no ANTHROPIC_API_KEY reports "Not logged in" — confirms --bare is mutually exclusive with subscription OAuth (which is the working auth model on turtle and the desired flow for laptops). Per gudnuf: "when we auth now, we use OAuth to log into our subscription account." Drop --bare from the default recipe. systemd User= sets $HOME, and the wrapper only changes CWD (not HOME), so claude-code reads ~/.claude/.credentials.json automatically. ONE LOGIN PER LINUX USER, ALL AGENTS INHERIT — for free, no env-var plumbing or apiKeyHelper. Tradeoff documented: ~12 binary-bundled skills appear in system prompt regardless of declared whitelist. Acceptable noise; the bounded-context property is "mostly achieved" without --bare given gudnuf's setup (no user-level skills, per-agent CWD isolates MCPs, --setting-sources project blocks settings leak). Added an Authentication section explaining the one-login-per-user property and prerequisite (`claude login` once per Linux user). Recipe also adds explicit --effort max (matches v1 default, ties into the dead CLAUDE_CODE_MAX_THINKING_TOKENS fix from PR #11). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
8 tasks
orveth
added a commit
that referenced
this pull request
May 27, 2026
…s) (#12) * Spec: per-agent environment isolation (skills + MCPs + plugins + tools) Designs how forge v2 makes the trust gradient enforceable and bounded context concrete at the agent boundary — each agent runs claude-code under --bare with explicit per-agent skills/MCPs/plugins/permissions, drawn declaratively from a shared library. Uses existing claude-code flags (--bare, --strict-mcp-config, --plugin-dir, --add-dir, --settings, --allowedTools) — no new abstractions. Submodule-merging pattern mirrors modules/discord.nix. Includes nix develop .#agent.<name> as a debug affordance, validation criteria, and seven open questions for review. This is a spec PR (markdown only). Implementation lands in a follow-up after the runtime PR (#11) merges. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * Rewrite spec to ground in v1 launch-agent reality Three corrections after auditing /home/gudnuf/forge/scripts/launch-agent and /srv/forge/.claude/skills: 1. Skills are discovered from CWD/.claude/skills/, not user-level. v1 launch-agent already cd's into /srv/forge so /srv/forge/.claude /skills/ resolves as project-level. The v2 seam is per-agent CWD, not --add-dir or --bare. Drop --bare entirely. 2. Skills, MCP servers, and plugins are three distinct mechanisms in claude-code. Spec now separates them: skills via CWD symlink farm, MCPs via --strict-mcp-config + .mcp.json, plugins via --plugin-dir. v1 used --channels for plugins; v2 prefers --plugin-dir pointing at Nix-managed sources, but the open question of whether the official discord plugin loads identically under --plugin-dir is flagged. 3. Added an explicit "How v1 already does this" section grounding the four seams (per-agent state dir, CWD discovery, --mcp-config, --channels) the design extends. v2 is a declarative reorganization of mechanisms that already work, not new abstractions. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * Synthesize review findings into spec amendments Technical review (claude-code 2.1.150) refuted three load-bearing claims; design review surfaced critical deferrals. Major changes: - --bare + add-back flags, NOT no-bare. Without --bare, 12+ binary-bundled skills + auto-MEMORY.md + user settings leak through regardless of other flags. --bare is the only way to actually achieve bounded context. Add --add-dir, --setting-sources project, --append-system-prompt-file to restore what we want. - Drop --strict-mcp-config. It silently strips plugin-bundled MCPs (discord, mercury, pikachat, nwc), breaking those plugins entirely. --bare already gates user-level MCPs; strict is redundant + harmful. - --setting-sources project required to prevent user settings.json merging in. - Skill discoverability resolved via generated skill-catalog.md appended to system prompt — narrow context + discoverable invocation. - --dangerously-skip-permissions stays default per gudnuf's call. Scoped-down agents need their own design pass. - Defer: per-agent Linux user, plugin/comms split, hooks family, library namespacing, harness portability. All named as known gaps. - source type discipline: always Nix path. - Implementation order rewritten with trimmed demo minimum (steps 1+2+3+4+6). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * Distinguish marketplace plugins from local MCP servers Empirical verification of v1 plugin sources on turtle revealed v1's two "channel" mechanisms load structurally different things: - --channels plugin:discord@claude-plugins-official loads a REAL plugin with .claude-plugin/plugin.json + bundled .mcp.json. Maps cleanly to v2's services.forge.plugins.<name> + --plugin-dir. - --dangerously-load-development-channels server:mercury loads a Bun MCP server with NO plugin manifest (just server.ts + package.json). Maps to services.forge.mcpServers.<name> + entry in agent's .mcp.json. The spec's prior example wrongly put Mercury under plugins. Corrected: Mercury / pikachat / nwc all belong under mcpServers. Real plugins (discord from the marketplace, future custom claude-code plugins) under plugins. Both v1 flags are undocumented in claude-code 2.1.150 --help — on borrowed time. The v2 mapping uses stable documented flags (--plugin-dir, --mcp-config) for both. Added allowedTools example showing the two distinct tool-name prefixes: mcp__plugin_<plugin>_<server>__* (from plugin-loaded MCPs) vs mcp__<server>__* (from .mcp.json-loaded MCPs). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * Drop --bare from default recipe to preserve OAuth auth Verified: --bare with HOME set but no ANTHROPIC_API_KEY reports "Not logged in" — confirms --bare is mutually exclusive with subscription OAuth (which is the working auth model on turtle and the desired flow for laptops). Per gudnuf: "when we auth now, we use OAuth to log into our subscription account." Drop --bare from the default recipe. systemd User= sets $HOME, and the wrapper only changes CWD (not HOME), so claude-code reads ~/.claude/.credentials.json automatically. ONE LOGIN PER LINUX USER, ALL AGENTS INHERIT — for free, no env-var plumbing or apiKeyHelper. Tradeoff documented: ~12 binary-bundled skills appear in system prompt regardless of declared whitelist. Acceptable noise; the bounded-context property is "mostly achieved" without --bare given gudnuf's setup (no user-level skills, per-agent CWD isolates MCPs, --setting-sources project blocks settings leak). Added an Authentication section explaining the one-login-per-user property and prerequisite (`claude login` once per Linux user). Recipe also adds explicit --effort max (matches v1 default, ties into the dead CLAUDE_CODE_MAX_THINKING_TOKENS fix from PR #11). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Wires declared agents into actual running processes — closing the loop from #7's schema and #9's design to a deployable system.
What's in
modules/harnesses/claude-code.nix(new) — per-harness module that, for eachservices.forge.agents.<name>withharness = "claude-code":forge-agent-<name>(sets up state dir, writes CLAUDE.md from the declared role, plumbs the discord bot token from the configured secret file, execs claude through a shared tmux server)runAsuser, with restart-on-failure and a start-rate limitflake.nix—claude-codeinput (github:sadjow/claude-code-nix); package passed to modules viaspecialArgsmodules/default.nix— imports the new harnessdeployments/agicash-team-forge/configuration.nix— commented-out example agent paired with the sops bot-token declaration, ready to uncomment after the secrets bootstrapflake.lock(new) — first lockfile committed; pins all current inputsRuntime model
tmux -L forge new-session -A -s agent-<name>— same socket for every agent on a user, sotmux -L forge lsenumerates them.User = agent.runAs,wantedBy = multi-user.target. Native autostart, no linger gymnastics. Logs via standardjournalctl -u forge-agent-<name>.~/.local/state/forge/agents/<name>/(XDG, per-user, no privilege elevation).on-failure,RestartSec=5s,StartLimitBurst=3/StartLimitIntervalSec=60.Decisions on PR #9 open questions
User=autostart natively~/.local/state/forge/agents/<name>/systemPackages;nix runform is a follow-up)Demo flow
After bootstrap (per
docs/secrets-bootstrap.md):secrets.yamlwith the bot tokenconfiguration.nixnix run .#deploy.agicash-team-forgeforge-agent-coordinatoron boot; the agent joins Discord and responds to @mentionssudo -u gudnuf tmux -L forge attach -t agent-coordinatorto read alongWhat's NOT in (deferred)
nix run .#agent.<name>form — flake-app form for manual invocation. Wrappers are insystemPackagessoforge-agent-<name>works on PATH; flake-app form is cosmetic and can land in a follow-up.modules/harnesses/<name>.nixlater, no changes to the agent schema.forgeCLI — punt per the runtime design doc until pain shows up.Validation
nix eval .#nixosConfigurations.agicash-team-forge.config.system.build.toplevel.drvPathevaluates cleanly to a valid derivation (with a placeholderdeploy-config.nixfor testing only — gitignored, not committed).