Files
agent-radar 2ed3415fb7
Publish Claude Code Binaries / Stage and publish binaries (push) Canceled after 0s
Publish Tutti App Runtime / Build darwin-arm64 (push) Canceled after 0s
Publish Tutti App Runtime / Build darwin-amd64 (push) Canceled after 0s
Publish Tutti App Runtime / Build linux-amd64 (push) Canceled after 0s
Publish Tutti App Runtime / Build windows-amd64 (push) Canceled after 0s
Sync Tutti Agent Skills / sync (push) Canceled after 0s
Windows Agent Adapters / Test Windows agent adapters (push) Canceled after 0s
Windows Daemon Adapters / Test Windows daemon adapters (push) Canceled after 0s
Publish Tutti App Runtime / Publish runtime catalog (push) Canceled after 0s
源码快照 @ 2026-09-01 (source-only, 剔除非源码素材)
2026-09-01 20:17:27 +08:00
..

Agent Runtime Preparation

packages/agent/runtimeprep is the reusable, provider-local preparation layer for Tutti agent sessions. A host calls it on the same machine where the agent provider will run. That machine is the desktop host for tutti and the managed Linux VM for VM-backed products such as tsh.

The module owns the canonical system-prompt and skill content, capability-pack resolution, provider home/plugin materialization, per-session environment overlays, manifests, and cleanup. Hosts keep transport, workspace path projection, environment trust filtering, account bootstrap, and deployment capability selection.

Host Wiring

preparer := runtimeprep.NewDefaultPreparer(stateDir)
preparer.Profile = runtimeprep.StandardProfile()
preparer.CommandCatalog = commandCatalogAdapter
preparer.ComputerUseAvailable = computerReadinessCheck
preparer.SkillSources = []runtimeprep.SkillSource{pluginSkillSource}

prepared, err := preparer.Prepare(ctx, runtimeprep.PrepareInput{
    WorkspaceID:    workspaceID,
    AgentSessionID: sessionID,
    Provider:       "claude-code",
    Cwd:            cwd,
    BrowserUse:     true,
    ExtraSkills:    sessionSkills,
})

PreparedRuntime.Env is an overlay for the provider launch, not a complete process environment. Cleanup removes only paths recorded in the session manifest or the session-scoped runtime root.

Agent Extension hosts may pass a signed, validated PrepareInput.ExtensionRuntimePrep overlay for ACP providers that need provider-owned state projected into the session. The overlay is declarative and provider-neutral: it may write the instructions file, create a per-session home, copy declared opaque files from a user-home source, expose that home through one validated environment variable, materialize Tutti-managed skills into declared extension skill roots, and merge those roots into supported YAML config keys. It may also project explicitly declared mutable directories such as verified helper-binary caches from the stable provider source home into each isolated session home. If a newly introduced stable directory is empty, manifest-owned same-provider legacy session homes seed it before projection; existing stable entries are never overwritten. Deleting one session removes only its projection, not the shared provider-owned directory. Runtimeprep must not add provider-ID branches for third-party extensions. YAML config projection must use the shared parser-backed merge helpers and fail closed on invalid or incompatible config instead of maintaining provider-specific line parsers.

Codex preparation keeps session state isolated under the run-scoped CODEX_HOME. Hosts whose provider state does not live under the operating system user Home pass its absolute provider-native root through PrepareInput.ProviderStateHome; runtimeprep uses that root for auth, config, models cache, plugins, Skills, and imported rollout validation. An empty value preserves the native ~/.codex default for existing embedders, including its existing symlink behavior and the legacy tolerance for an unavailable user Home. Strict path and filesystem-shape validation applies only to an explicit value. VM-backed hosts must pass the explicit stable root and must not create a compatibility facade under the Linux login Home.

Desktop composition also supplies the provider user's stable personal Skill root and materializes it with the provider user's ownership before preparation. Runtime preparation requires that root to exist and exposes it directly as $CODEX_HOME/skills (a symlink on POSIX and a directory junction on Windows), so Codex's native skill-creator keeps its ordinary personal-by-default behavior without copying or promoting content from historical Session homes. Tutti-owned Session Skills remain under the run root and are registered through skills/extraRoots/set; they are never written into the personal root. Hosts that do not explicitly supply a personal root keep the isolated layout.

When an embedder supplies both ProviderStateHome and a distinct Codex PersonalSkillRoot, the provider-native skills/ directory remains an explicit app-server Skill root while the personal root is the writable CODEX_HOME/skills projection. Claude Code can likewise receive a distinct PersonalSkillRoot; runtimeprep exposes it through a session-scoped SDK additional directory whose .claude/skills entry points at the stable root. Empty personal roots preserve the native Host behavior for both providers.

TuttiAgentPreparer.ResolveAuthSource lets a host expose one explicit absolute credential authority into the session-scoped TUTTI_AGENT_HOME. When omitted, the Tutti desktop keeps its existing ~/.tutti-agent/auth.json behavior. An explicit source may be a dangling symlink target so a host-controlled login can materialize it atomically later; runtimeprep never follows or deletes that target during session cleanup. If a configured resolver returns no path, runtimeprep leaves auth unprojected and does not fall back to the VM user's ~/.tutti-agent. Tutti Agent preparation also materializes the same resolved native Skills used by the other supported providers.

The desktop injects MutagenAuthFileProjector for this credential projection. It first attempts a file symlink. When Windows denies that operation, it copies the current stable auth into each run. If Mutagen is already available, the projector starts an official two-way-safe session with the default real-time watcher. Cleanup flushes the session and refuses to delete the run home if Mutagen reports a conflict; otherwise it terminates the session before runtime deletion. If Mutagen is unavailable, cleanup copies a valid changed run auth back atomically only when the stable auth still matches the original baseline; concurrent changes preserve both files for recovery. .refresh.lock is always a symlink or hard link to the stable lock so both homes coordinate through one OS file object. Mutagen resolution prefers TUTTI_MUTAGEN_BIN, then PATH. Packaged Windows amd64 Desktop builds inject the verified v0.18.1 executable bundled at build time, while unpackaged hosts use the guarded copy fallback without a runtime download. Other bundled platforms remain to be confirmed.

When TuttiAgentPreparer.StableSkillBundleRoot is configured, Tutti-managed Skills are content-addressed under <root>/v1/<sha256>/skills instead of the run-scoped home. Preparation emits the validated roots through the internal TUTTI_AGENT_EXTRA_SKILL_ROOTS_JSON launch handoff. The app-server adapter removes that handoff from the child environment and calls skills/extraRoots/set after initialized and before thread/start or thread/resume. A zero-value root preserves the legacy run-scoped layout. Active Connector routes may contribute their verified, content-addressed skills/ directories to the same handoff. Runtime preparation validates that each contributed path is absolute, exists as a directory, and is not a symlink; duplicate roots are removed. Connector processes use separate disposable execution snapshots, so restarting a process does not invalidate the Skill paths held by an Agent session. Stable bundles are immutable rebuildable cache entries and are not removed by single-session cleanup. When StableSystemSkillBundleRoot is also configured, runtimeprep passes that store as internal launch metadata. After the provider has installed its embedded .system Skills during initialization, the Adapter snapshots their actual provider-owned content into <root>/v1/<sha256>/.system, atomically replaces the run-scoped directory with a symlink to that immutable target, and only then refreshes Skills through the extra-roots RPC. This preserves provider ownership and version updates while making the earliest rendered Skill paths stable across Sessions. Both internal handoffs are stripped before child launch.

Capability Packs

A deployment capability resolves once into its policy, skills, and environment contribution:

runtimeprep.CapabilityPack{
    Name: "example",
    Resolve: func(ctx context.Context, input runtimeprep.PrepareInput) (
        runtimeprep.CapabilityContribution,
        error,
    ) {
        return runtimeprep.CapabilityContribution{
            Enabled: true,
            Skills: []runtimeprep.SkillSpec{{
                ID: "example/tool",
                Name: "example-tool",
                Files: map[string]string{"SKILL.md": "# Example\n"},
            }},
            PolicySections: []runtimeprep.PolicySection{{
                Anchor: runtimeprep.PolicyAnchorTools,
                Key: "usage",
                Body: "Use `$example-tool` for example work.",
            }},
            EnvOverlay: []string{"EXAMPLE_ENABLED=1"},
        }, nil
    },
}

Policy sections sort by anchor, order, and pack-qualified key. Duplicate pack names, unknown anchors, duplicate skill IDs, and unsafe skill file paths fail resolution rather than silently overriding content. Sections apply to both provider runtime policy and dynamic skill bundles by default; set PolicySection.Delivery when a section is valid for only one delivery path.

StandardProfile includes CoreSkillsPack, TuttiDesktopHostPack, browser use, and computer use. CoreSkillsPack includes the provider-neutral Tutti workflow skills plus tutti-model-allocation, whose C0-C3 policy combines the current Tutti Mode effect/speed preferences with live composer model catalogs and derives speed's bounded 1-4 parallel planning target. Allocation compares joint Agent/model candidates across every plausible target without favoring the planning Agent, its current model, or provider defaults. A non-desktop deployment should compose its own profile from CoreSkillsPack and deployment-owned packs instead of copying the desktop-host policy. Add VerifiedEndpointOutputPack when the provider should report verified, user-reachable local server endpoints as Markdown links without inheriting desktop execution, media, or filesystem rules:

profile := runtimeprep.DeploymentProfile{
    Name:  "managed-vm",
    Title: "Managed VM Runtime",
    Intro: "This session runs inside the managed VM.",
    HostFacts: runtimeprep.HostFacts{
        TurnResources:  runtimeprep.AgentTurnResourcesReadPath,
        WorkspaceScope: runtimeprep.AgentWorkspaceScopeRoom,
        TargetContinuation: runtimeprep.AgentTargetContinuationProfile{
            Mode: runtimeprep.AgentTargetContinuationExceptPrefixes,
            UnsupportedTargetIDPrefixes: []string{"shared-agent:"},
        },
    },
    Packs: []runtimeprep.CapabilityPack{
        runtimeprep.CoreSkillsPack(),
        runtimeprep.VerifiedEndpointOutputPack(),
        vmEnvironmentPack,
    },
}

Every preparation requires CommandCatalog. Runtimeprep reads it once and builds one immutable, agent-facing command snapshot shared by Skills, policy, and command-guide.md. The host must remove trusted bindings such as room or workspace inputs before returning the catalog. Runtimeprep filters non-public/integration commands, preserves output metadata, and adds wrapper-owned invocation inputs such as wait timeouts. It does not synthesize a fallback catalog.

Skill and policy Markdown use Go text/template. Runtimeprep exposes has, hasAll, hasFamily, hasInput, inputValues, path, args, and command. command resolves paths and flags from the snapshot, appends --json only when output metadata advertises JSON, and fails rendering for an unknown command, input, enum value, or malformed schema.

{{if has "issue-manager.issue.task.create-batch"}}
Persist tasks with
`{{command "issue-manager.issue.task.create-batch"
    (args "tasks-json" "'[{\"title\":\"<title>\"}]'")}}`.
{{else if has "issue-manager.issue.task.create"}}
Create tasks in order with
`{{command "issue-manager.issue.task.create" (args "title" "<title>")}}`.
{{end}}

DeploymentProfile.HostFacts contains only behavior that cannot be derived from command metadata: turn-resource path semantics, workspace scoping, and target continuation exceptions. Omitted fields use Tutti's local-path, workspace-environment, and continue-all defaults. Command presence, command paths, inputs, cancellation scope, response support, and conversation reads belong only in the command snapshot.

Skill Injection

Skills resolve in this order:

  1. skills from the deployment profile's enabled capability packs;
  2. host SkillSource results;
  3. per-session PrepareInput.ExtraSkills.

Skill IDs are stable logical identities. Materialized directory slugs may gain a suffix to avoid overwriting an existing user skill. If an injected ability also needs policy or environment changes, inject a capability pack instead of an isolated extra skill.

Prepare and RenderSkillBundle use the same resolver, so provider files and the skill-bundle API cannot drift.

The canonical Tutti CLI skill treats the daemon and CLI as the only supported execution control plane. Provider Agents must not inspect or modify Tutti's backing SQLite databases to infer runtime state or bypass a rejected command. When the source Agent has the Tutti execution snapshot capability, the skill also renders the checkpoint/action matrix and a bounded recovery protocol: refresh an outdated fence once, correct a documented mutation schema once, and report a repeated rejection with its stable reason and hint.

Product Boundaries

The module must not import services/tuttid/*. Product adapters translate their command catalog and readiness types into the narrow interfaces here. Tutti Account HTTP and local credential paths remain in services/tuttid/service/tuttiagent. The host-neutral issue/login/verify and cleanup ordering lives in packages/agent/daemon/tuttiagentauth; only provider home/config/Skills preparation lives in this module.

Provider-specific runtime protocol and session control belong to packages/agent/daemon, not this module.