@@ -171,12 +171,7 @@ func runAgent(ctx context.Context, agentName, fullsendDir, outputBase, targetRep
171171 }
172172
173173 // 1. Resolve and load harness.
174- harnessPath , err := resolveHarnessPath (absFullsendDir , agentName , printer )
175- if err != nil {
176- return err
177- }
178174 harnessStart := time .Now ()
179- printer .StepStart ("Loading harness: " + harnessPath )
180175
181176 forgePlatform , err := detectForgePlatform (forgeFlag )
182177 if err != nil {
@@ -188,50 +183,62 @@ func runAgent(ctx context.Context, agentName, fullsendDir, outputBase, targetRep
188183 policy .Offline = rFlags .offline
189184
190185 // Best-effort org config loading — provides the allowlist for base
191- // harness fetching. If the file is missing or unparseable we proceed
192- // without it; HasURLReferences will enforce its presence later if needed.
186+ // harness fetching and the agent registry for config-driven resolution.
187+ // If the file is missing or unparseable we proceed without it;
188+ // HasURLReferences will enforce its presence later if needed.
193189 orgConfigPath := filepath .Join (absFullsendDir , "config.yaml" )
194190 orgCfg := tryLoadOrgConfig (orgConfigPath , printer )
195191 var orgAllowlist []string
196192 if orgCfg != nil {
197193 orgAllowlist = orgCfg .AllowedRemoteResources
198194 }
199195
200- // If the harness has a URL base and org config failed to load,
201- // load it strictly now so LoadWithBase gets a proper error path
202- // rather than an unhelpful "URL base requires allowed_remote_resources".
203- if orgCfg == nil {
204- if rawH , rawErr := harness .LoadRaw (harnessPath ); rawErr == nil && rawH .Base != "" && harness .IsURL (rawH .Base ) {
205- var err error
206- orgCfg , err = requireOrgConfig (orgConfigPath , printer )
207- if err != nil {
208- return err
209- }
210- orgAllowlist = orgCfg .AllowedRemoteResources
211- }
212- }
213-
214196 var composeForgeClient forge.Client
215197 if rFlags .forgeClient != nil {
216198 composeForgeClient = rFlags .forgeClient
217199 } else if token , tokenErr := resolveToken (); tokenErr == nil {
218200 composeForgeClient = gh .New (token )
219201 }
220202
221- h , baseDeps , err := harness . LoadWithBase ( ctx , harnessPath , harness.ComposeOpts {
203+ composeOpts := harness.ComposeOpts {
222204 WorkspaceRoot : absFullsendDir ,
223205 FetchPolicy : policy ,
224206 AuditLogPath : filepath .Join (absFullsendDir , ".fullsend-cache" , "fetch-audit.jsonl" ),
225207 ForgePlatform : forgePlatform ,
226208 OrgAllowlist : orgAllowlist ,
227209 ForgeClient : composeForgeClient ,
228- })
210+ }
211+
212+ // Resolve agent source: config agents take precedence over disk harnesses.
213+ harnessPath , fetchDeps , err := resolveAgentSource (ctx , absFullsendDir , agentName , orgCfg , composeOpts , printer )
214+ if err != nil {
215+ return err
216+ }
217+
218+ printer .StepStart ("Loading harness: " + harnessPath )
219+
220+ // If the harness has a URL base and org config failed to load,
221+ // load it strictly now so LoadWithBase gets a proper error path
222+ // rather than an unhelpful "URL base requires allowed_remote_resources".
223+ if orgCfg == nil {
224+ if rawH , rawErr := harness .LoadRaw (harnessPath ); rawErr == nil && rawH .Base != "" && harness .IsURL (rawH .Base ) {
225+ var err error
226+ orgCfg , err = requireOrgConfig (orgConfigPath , printer )
227+ if err != nil {
228+ return err
229+ }
230+ composeOpts .OrgAllowlist = orgCfg .AllowedRemoteResources
231+ }
232+ }
233+
234+ h , baseDeps , err := harness .LoadWithBase (ctx , harnessPath , composeOpts )
229235 if err != nil {
230236 printer .StepFail ("Failed to load harness" )
231237 return fmt .Errorf ("loading harness: %w" , err )
232238 }
233239
234- for _ , dep := range baseDeps {
240+ allDeps := append (fetchDeps , baseDeps ... )
241+ for _ , dep := range allDeps {
235242 if dep .CacheHit {
236243 printer .StepInfo (fmt .Sprintf ("Base: %s (cache hit)" , dep .URL ))
237244 } else {
@@ -2344,3 +2351,62 @@ func validateRepoNames(repos []string) error {
23442351 }
23452352 return nil
23462353}
2354+
2355+ // resolveAgentSource resolves the harness path for an agent, checking
2356+ // config-registered agents before falling back to disk-based lookup.
2357+ // Returns the local filesystem path to the harness (cached for URL sources)
2358+ // and any fetch dependencies from URL-based agent resolution.
2359+ func resolveAgentSource (ctx context.Context , fullsendDir , agentName string , orgCfg * config.OrgConfig , composeOpts harness.ComposeOpts , printer * ui.Printer ) (string , []harness.Dependency , error ) {
2360+ if orgCfg == nil || len (orgCfg .Agents ) == 0 {
2361+ path , err := resolveHarnessPath (fullsendDir , agentName , printer )
2362+ return path , nil , err
2363+ }
2364+
2365+ if err := config .ValidateAgentEntries (orgCfg .Agents , orgCfg .AllowedRemoteResources ); err != nil {
2366+ return "" , nil , fmt .Errorf ("invalid agent config: %w" , err )
2367+ }
2368+
2369+ sha := commitSHA
2370+ if sha == "dev" {
2371+ sha = ""
2372+ }
2373+ scaffoldNames , snErr := scaffold .HarnessNames ()
2374+ if snErr != nil {
2375+ return "" , nil , fmt .Errorf ("listing scaffold harnesses: %w" , snErr )
2376+ }
2377+
2378+ merged , err := config .MergedAgents (scaffoldNames , sha , orgCfg .Agents , scaffold .HarnessBaseURLWithHash )
2379+ if err != nil {
2380+ return "" , nil , fmt .Errorf ("building merged agent set: %w" , err )
2381+ }
2382+
2383+ agent := config .LookupMergedAgent (merged , agentName )
2384+ if agent == nil || ! agent .IsConfig {
2385+ path , err := resolveHarnessPath (fullsendDir , agentName , printer )
2386+ return path , nil , err
2387+ }
2388+
2389+ if harness .IsURL (agent .Source ) {
2390+ printer .StepStart (fmt .Sprintf ("Fetching agent harness: %s" , agent .Name ))
2391+ localPath , dep , err := harness .FetchAgentHarness (ctx , agent .Source , composeOpts )
2392+ if err != nil {
2393+ printer .StepFail ("Failed to fetch agent harness" )
2394+ return "" , nil , fmt .Errorf ("resolving config agent %s: %w" , agent .Name , err )
2395+ }
2396+ printer .StepDone (fmt .Sprintf ("Agent %s resolved from config (URL)" , agent .Name ))
2397+ return localPath , []harness.Dependency {dep }, nil
2398+ }
2399+
2400+ if filepath .IsAbs (agent .Source ) {
2401+ return "" , nil , fmt .Errorf ("config agent %s: local path must be relative, not absolute" , agent .Name )
2402+ }
2403+ localPath := filepath .Clean (filepath .Join (fullsendDir , agent .Source ))
2404+ if rel , err := filepath .Rel (fullsendDir , localPath ); err != nil || strings .HasPrefix (rel , ".." ) {
2405+ return "" , nil , fmt .Errorf ("config agent %s: local path %q escapes fullsend directory" , agent .Name , agent .Source )
2406+ }
2407+ if _ , err := os .Stat (localPath ); err != nil {
2408+ return "" , nil , fmt .Errorf ("config agent %s: local path %s: %w" , agent .Name , agent .Source , err )
2409+ }
2410+ printer .StepDone (fmt .Sprintf ("Agent %s resolved from config (local path)" , agent .Name ))
2411+ return localPath , nil , nil
2412+ }
0 commit comments