fix(skills): mount skills on a runtime the library does not know (#1634) - #1834
Merged
Conversation
This was referenced Sep 10, 2026
lex00
approved these changes
Sep 10, 2026
jhgaylor
force-pushed
the
stack/1634-2-runtime-command
branch
from
September 11, 2026 01:19
9f0ac07 to
c7bdd83
Compare
jhgaylor
force-pushed
the
stack/1634-3-skills
branch
from
September 11, 2026 01:19
ad112c9 to
8e7b6f0
Compare
) Every runtime Fountain has is an LLM coding agent. A deterministic program that wants a warm machine, a vault, a thread, a schedule and a human who can read what happened has nowhere to run: `Managoat.Runtimes` is a closed map of four CLIs, each with a pinned adapter, a provider and an inference credential. This adds the runtime module and the host dispatch that resolves it, and nothing else. `"acp"` is not yet a value `Agent.changeset/2` accepts and the `runtime_command` column it reads does not exist, so no request reaches this code until the next PR in the stack opens the door. - `Fountain.CommandRuntime` implements `Managoat.Runtimes` for a command the agent names. It writes no config, prepares no sandbox and builds no legacy command, so `Provisioning`'s `function_exported?` guards skip it. Its `default_env/2` ignores the credentials it is handed and exports one variable, `FOUNTAIN_SKILLS_DIR`. - `Fountain.RuntimeDispatch` answers `acp` itself: `for_agent/1`, `acp_enabled?/1` and an `install/3` that has nothing to install. It gains `acp_model/2` (always nil for `acp`, so the pin path that reports `model`/`failed` is unreachable there), and the two predicates the schema needs next, `model_required?/1` and `command_required?/1`. - `command/1` becomes `command/2`. The argv of this runtime is a property of the agent rather than of the runtime name, so the agent has to be in hand; every other clause ignores it. `TurnMachine.command/7` passes it through. Stack for #1634 (merge top down): 1. **this PR** — the runtime module and the dispatch 2. `agents.runtime_command`, and `acp` becomes a runtime an agent can name 3. skills mount on the acp runtime 4. a turn on the acp runtime, end to end 5. refuse a turn whose agent no longer carries a command 6. the console form 7. the conformance fixture, a real OS process through a whole turn 8. docs, CHANGELOG and the SDK release Refs #1634 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01A2iMoNvSvrkQQfnP4RzVi2
…an name (#1634) Opens the door the previous PR built behind. `acp` joins the runtime enum, a new `runtime_command` column carries the command it launches, and `model` becomes optional for that one runtime. - **The column.** `agents.runtime_command` is text: a value may be a whole shell line ("cd /srv/app && exec bin/agent acp"), and a length rule belongs in the changeset if one is ever wanted. `agents.model` loses its NOT NULL in the same migration, because whether a model is required is a rule about the pair of columns and belongs where both are in hand. - **Both refusals are 422s naming the field.** A missing `runtime_command` on `acp`, or one carried by any other runtime, which resolves its own executable from a pinned table. A `runtime_command` that is stored and never run reads as configuration and is not. - **A model is still checked when it is given.** Optional on `acp` and inert there, but it still has to parse and name a known provider, because a value that is stored and ignored is worse than one that is refused. - **`model` is nullable on the wire**, in the response and in both request bodies, so an agent converted to `acp` can clear the model it no longer uses with `{"model": null}`. CastAndValidate runs before the changeset, so a non-nullable string there would reject the null with a 400 and leave the stale value on the row forever. `AgentRequest` drops `model` from `required` and the changeset enforces it instead, so a missing model is a 422 rather than a 400. - Model suggestions return nothing for `acp`, and the export carries the field so an account export stays a restorable copy. **The Swift SDK had to widen with the wire.** `FountainKit.Agent` declared `public var model: String` on a `Decodable` struct, so an explicit null threw `valueNotFound` — and because a page is decoded whole, one acp agent in the account broke `agents.list()` for every Swift caller, not just a fetch of that agent. `model` is now `String?`, `Agent` and `AgentInput` carry `runtimeCommand`, and `Runtime.acp` joins the other four. The contract checker does not compare a hand-written Swift model's optionality against a nullable wire field, so nothing caught this: the two new decode tests do, and both fail with the original `valueNotFound` when the widening is backed out. `fountain apply` needed no change — the manifest's allowed spec keys derive from `Agent.cast_fields/0` — but nothing pinned that, and the docs in the last PR of this stack show a manifest using the field. Two tests in `manifest_test.exs` now cover it, and the first fails if `runtime_command` leaves `cast_fields/0`. `sdk-no-release`: `contract.json`, the generated TypeScript types and the Swift model move here; the TypeScript version bump is the last PR in the stack, and the Swift entry goes under its existing `Unreleased` heading. Refs #1634 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01A2iMoNvSvrkQQfnP4RzVi2
`SandboxSkills.mount/3` forwarded a runtime **string** to
`Managoat.Runtimes.Skills.install/3`, which resolves it through
`Managoat.Runtimes.for_runtime/1` — a closed map of the four packaged CLIs
that knows neither `acp` nor the deployed ACP fixture. It answered
`{:error, "unsupported runtime: acp"}`, and the sandbox came up with no skills
at all, including the two bundled ones, with nothing said about it anywhere.
- A string is resolved through `Fountain.RuntimeDispatch`, the host dispatch
that already knows every runtime Fountain has. A module is passed straight
through as before.
- A runtime nothing implements is logged and returned rather than swallowed. A
missing skill is a degraded agent and not a broken one, which is the trade
`Managoat.Runtimes.Skills` already makes for a single skill that will not
install; this extends it to a runtime that cannot be resolved, without
extending it to silence.
The deployed fixture was reached by this too, and its own changeset refuses an
agent carrying any skills, so nothing observable changes there.
Stack for #1634 (merge top down):
1. `Fountain.CommandRuntime`, the acp runtime's module
2. `agents.runtime_command`, and `acp` becomes nameable
3. **this PR** — skills mount on the acp runtime
4. a turn on the acp runtime, end to end
5. refuse a turn whose agent no longer carries a command
6. the console form
7. the conformance fixture, a real OS process through a whole turn
8. docs, CHANGELOG and the SDK release
Refs #1634
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01A2iMoNvSvrkQQfnP4RzVi2
jhgaylor
force-pushed
the
stack/1634-2-runtime-command
branch
from
September 11, 2026 01:41
c7bdd83 to
d577e1f
Compare
jhgaylor
force-pushed
the
stack/1634-3-skills
branch
from
September 11, 2026 01:41
8e7b6f0 to
5d02705
Compare
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.
SandboxSkills.mount/3forwarded a runtime string toManagoat.Runtimes.Skills.install/3, which resolves it throughManagoat.Runtimes.for_runtime/1— a closed map of the four packaged CLIsthat knows neither
acpnor the deployed ACP fixture. It answered{:error, "unsupported runtime: acp"}, and the sandbox came up with no skillsat all, including the two bundled ones, with nothing said about it anywhere.
Fountain.RuntimeDispatch, the host dispatchthat already knows every runtime Fountain has. A module is passed straight
through as before.
missing skill is a degraded agent and not a broken one, which is the trade
Managoat.Runtimes.Skillsalready makes for a single skill that will notinstall; this extends it to a runtime that cannot be resolved, without
extending it to silence.
conversation_server.exis line-pinned and only shrinks (#1369), so the noteabout the mount being best effort folds into the comment already there rather
than adding a line to it. The pin stays at 2774.
The deployed fixture was reached by this too, and its own changeset refuses an
agent carrying any skills, so nothing observable changes there.
Stack for #1634 (merge top down; each PR is based on the one below it in the list):
Fountain.CommandRuntimeand the host dispatch foracpagents.runtime_command, andacpbecomes a runtime you can nameReplaces #1677, which was the same feature as one 1,760-line branch.
🤖 Generated with Claude Code
https://claude.ai/code/session_01A2iMoNvSvrkQQfnP4RzVi2