@@ -49,8 +49,28 @@ const (
4949
5050 // metricsFile is the filename written to the run directory with behavioral metrics.
5151 metricsFile = "metrics.json"
52+
53+ // Default agents repository for runtime fallback when an agent is not
54+ // registered in config. The binary resolves the latest commit SHA from
55+ // this repo and fetches the harness dynamically.
56+ defaultAgentsRepoOwner = "fullsend-ai"
57+ defaultAgentsRepoName = "agents"
58+ defaultAgentsRepoBranch = "main"
59+ agentsRepoURLPrefix = "https://raw.githubusercontent.com/fullsend-ai/agents/"
5260)
5361
62+ // agentsRepoKnownAgents is the set of first-party agents available in the
63+ // fullsend-ai/agents repository. The fallback only attempts resolution for
64+ // agents in this set — custom agents are not tried against the agents repo.
65+ var agentsRepoKnownAgents = map [string ]bool {
66+ "triage" : true ,
67+ "code" : true ,
68+ "fix" : true ,
69+ "review" : true ,
70+ "retro" : true ,
71+ "prioritize" : true ,
72+ }
73+
5474// statusMintToken is the test seam for minting tokens. Shared by both
5575// setupStatusNotifier (status comment tokens) and mintAgentToken (agent
5676// runtime tokens). Tests that override it affect both paths.
@@ -224,7 +244,7 @@ func runAgent(ctx context.Context, agentName, fullsendDir, outputBase, targetRep
224244 }
225245
226246 // Resolve agent source: config agents take precedence over disk harnesses.
227- harnessPath , fetchDeps , err := resolveAgentSource (ctx , absFullsendDir , agentName , orgCfg , composeOpts , printer )
247+ harnessPath , fetchDeps , err := resolveAgentSource (ctx , absFullsendDir , agentName , composeGitToken , orgCfg , composeOpts , printer )
228248 if err != nil {
229249 return err
230250 }
@@ -2556,11 +2576,15 @@ func validateRepoNames(repos []string) error {
25562576}
25572577
25582578// resolveAgentSource resolves the harness path for an agent, checking
2559- // config-registered agents before falling back to disk-based lookup.
2579+ // config-registered agents first, then falling back to the agents repo
2580+ // (fullsend-ai/agents), then to disk-based lookup.
25602581// Returns the local filesystem path to the harness (cached for URL sources)
25612582// and any fetch dependencies from URL-based agent resolution.
2562- func resolveAgentSource (ctx context.Context , fullsendDir , agentName string , orgCfg * config.OrgConfig , composeOpts harness.ComposeOpts , printer * ui.Printer ) (string , []harness.Dependency , error ) {
2583+ func resolveAgentSource (ctx context.Context , fullsendDir , agentName , gitToken string , orgCfg * config.OrgConfig , composeOpts harness.ComposeOpts , printer * ui.Printer ) (string , []harness.Dependency , error ) {
25632584 if orgCfg == nil || len (orgCfg .Agents ) == 0 {
2585+ if path , deps , ok := tryAgentsRepoFallback (ctx , agentName , gitToken , composeOpts , printer ); ok {
2586+ return path , deps , nil
2587+ }
25642588 path , err := resolveHarnessPath (fullsendDir , agentName , printer )
25652589 return path , nil , err
25662590 }
@@ -2585,6 +2609,9 @@ func resolveAgentSource(ctx context.Context, fullsendDir, agentName string, orgC
25852609
25862610 agent := config .LookupMergedAgent (merged , agentName )
25872611 if agent == nil || ! agent .IsConfig {
2612+ if path , deps , ok := tryAgentsRepoFallback (ctx , agentName , gitToken , composeOpts , printer ); ok {
2613+ return path , deps , nil
2614+ }
25882615 path , err := resolveHarnessPath (fullsendDir , agentName , printer )
25892616 return path , nil , err
25902617 }
@@ -2611,6 +2638,61 @@ func resolveAgentSource(ctx context.Context, fullsendDir, agentName string, orgC
26112638 return contained , nil , nil
26122639}
26132640
2641+ // tryAgentsRepoFallback attempts to resolve an agent from the default agents
2642+ // repository (fullsend-ai/agents) by fetching the latest harness from the
2643+ // main branch. Returns (path, deps, true) on success, or ("", nil, false)
2644+ // if the fallback should be skipped (offline, no token, agent not found, etc.).
2645+ // All errors are non-fatal — the caller falls through to disk-based lookup.
2646+ func tryAgentsRepoFallback (ctx context.Context , agentName , gitToken string , composeOpts harness.ComposeOpts , printer * ui.Printer ) (string , []harness.Dependency , bool ) {
2647+ if ! agentsRepoKnownAgents [strings .ToLower (agentName )] {
2648+ return "" , nil , false
2649+ }
2650+ if composeOpts .FetchPolicy .Offline {
2651+ return "" , nil , false
2652+ }
2653+ if gitToken == "" {
2654+ return "" , nil , false
2655+ }
2656+
2657+ client := gh .New (gitToken )
2658+ branchSHA , err := client .GetBranchRef (ctx , defaultAgentsRepoOwner , defaultAgentsRepoName , defaultAgentsRepoBranch )
2659+ if err != nil {
2660+ printer .StepWarn (fmt .Sprintf ("Could not resolve %s/%s@%s: %v" , defaultAgentsRepoOwner , defaultAgentsRepoName , defaultAgentsRepoBranch , err ))
2661+ return "" , nil , false
2662+ }
2663+
2664+ rawURL := agentsRepoURLPrefix + branchSHA + "/harness/" + agentName + ".yaml"
2665+
2666+ content , err := fetch .FetchURL (ctx , rawURL , composeOpts .FetchPolicy )
2667+ if err != nil {
2668+ return "" , nil , false
2669+ }
2670+
2671+ hash := fetch .ComputeSHA256 (content )
2672+ pinnedURL := rawURL + "#sha256=" + hash
2673+
2674+ allowlist := composeOpts .OrgAllowlist
2675+ if len (allowlist ) == 0 {
2676+ allowlist = config .DefaultAllowedRemoteResources ()
2677+ }
2678+
2679+ printer .StepStart (fmt .Sprintf ("Fetching agent %s from %s/%s@%s" , agentName , defaultAgentsRepoOwner , defaultAgentsRepoName , branchSHA [:12 ]))
2680+ localPath , dep , err := harness .FetchAgentHarness (ctx , pinnedURL , harness.ComposeOpts {
2681+ WorkspaceRoot : composeOpts .WorkspaceRoot ,
2682+ FetchPolicy : composeOpts .FetchPolicy ,
2683+ AuditLogPath : composeOpts .AuditLogPath ,
2684+ OrgAllowlist : allowlist ,
2685+ TreeFetcher : composeOpts .TreeFetcher ,
2686+ GitToken : composeOpts .GitToken ,
2687+ })
2688+ if err != nil {
2689+ printer .StepWarn (fmt .Sprintf ("Agents repo fallback failed for %s: %v" , agentName , err ))
2690+ return "" , nil , false
2691+ }
2692+ printer .StepDone (fmt .Sprintf ("Agent %s resolved from %s/%s (latest)" , agentName , defaultAgentsRepoOwner , defaultAgentsRepoName ))
2693+ return localPath , []harness.Dependency {dep }, true
2694+ }
2695+
26142696// containedLocalPath resolves a relative source path against baseDir and
26152697// verifies the result stays within baseDir. Returns an error for absolute
26162698// paths or paths that escape via traversal.
0 commit comments